我在 2017 年底加入了英特尔® 学生开发人员计划,当时我非常兴奋地试用了英特尔® 至强® 可扩展处理器。[1] 这是他们大约同一时间推出的英特尔® AI DevCloud 的一部分。为了确保所有人保持同步,我想先介绍英特尔至强可扩展处理器及其对计算的影响。接下来我将介绍最后一部分——优化深度学习 TensorFlow* [2] 代码。
与前一代英特尔® 至强™ 处理器 E5-2600 v4 产品家族(以前称为 Broadwell 微架构)相比,基于“Purley”平台的英特尔® 至强® 可扩展处理器产品家族是一种具有许多附加功能的新型架构。我认为,英特尔® 至强® 可扩展处理器的人工智能 (AI) 计算能力之所以很出色,主要原因在于英特尔®高级矢量扩展 512(英特尔® AVX-512)[3] 指令集。它提供超宽 512 位矢量运算能力,可以处理 TensorFlow *所需的大部分高性能计算工作。由于 TensorFlow 中最基本的计算单元涉及通过矢量处理单元并行运算的张量流。这些称为 单指令多数据 (SIMD) [4] 运算。举个例子,比如我们以自然的方式添加两个向量,我们将循环遍历维度并添加相应的单位。尽管支持矢量的中央处理单元 (CPU) 将通过单个加法运算来添加这两个矢量,从而减少向量维数因子的延迟,但是在我们的示例中,它最多可以执行 512 位。我们将获得比普通 CPU 高 3 到 4 倍的性能提升。
接下来我们来看看我进行的实验,它证明了我的观点。当我加入该计划时,我已经拥有 1500 行神经图像标题系统的代码库,可以试用以前仅在Google* Cloud 平台 [5]上运行的代码库。神经标题系统是通过编码器 - 解码器神经网络系统为图像生成标题的系统。就我而言,我对Vinyals 等人 [6] 的工作进行了轻微修改。我的图像编码系统是VGG16 模型 [7]。这是一个ILSVRC [8] 中的卷积神经网络,用于对象识别竞赛。事实证明,它可以作为一个很好的特征提取器,所以我删除了第 7 层完全连接层之后的所有内容,并使用了最终的 4096 长度矢量。这种方法在深度学习社区中称为“转移式学习”。我预先提取了 Microsoft COCO [9] 数据集所有图像的特性,然后对数据进行了主成分分析 (PCA),以便将其尺寸减小到 512。我对两个数据集(PCA 和非 PCA)进行了实验。这项工作正在进行中,如果您想查看,代码库就在我的GitHub库中。
在运行过程中,当我第一次尝试在英特尔至强处理器上运行时,性能毫无提升,反而有些下降。所以上个月我一直在研究这个问题,希望能找出原因。在此我想与您分享一些我发现的步骤,以帮助您在这款复杂而强大的处理器上实现性能提升。
- 当我们通过多个数据进行批量处理时,应尽可能避免任何类型的“磁盘读写”。在英特尔 AI DevCloud 中,我们的主文件夹——网络文件系统 (NFS) 在计算节点和登录节点之间共享。集群上的读取写入需要很长时间,因为它与家用 PC 相比距离更远。那么,我们如何去做呢?TensorFlow 通过其 数据集 API [10] 提供了一个基于简洁队列的操作。API 支持我们使用可重用的操作构建复杂的输入管道。它用您选择的预处理操作包装您的数据,并将它们分配在一起。这将大大缩短您的延迟,因为您的整个数据集将在所有限制和要求下进行缓存,并将所有操作嵌入到计算图中。
- Colfax research [11] 有一篇很好的文章,我想根据这篇文章写一篇对性能有直接影响的技巧。它能够基于 YOLO [12] 对一个对象检测网络进行优化,并建议调整某些从性能角度而言非常重要的关键变量。
- KMP_BLOCKTIME:这是诸多变量之一,用于控制 OpenMP * API 的行为。该 API 是一个并行编程接口,主要负责 Tensorflow API 内部的多线程操作。这个变量用于控制 OpenMP 线程在休眠之前的等待时间(以毫秒为单位)。较大的数值可确保数据经常访问,但也很容易造成其他线程资源的闲置,所以这个变量需要调整,以最好地满足我们的需求。在本示例中,我将其设为 30。
os.environ["KMP_BLOCKTIME"] = “30” - OMP_NUM_THREADS:这是指 TensorFlow 操作可以使用的并行线程的数量。TensorFlow 的推荐设置是将其设置为物理内核的数量。我试了一下 136,它很合适。
os.environ["OMP_NUM_THREADS"] = “136” - KMP_AFFINITY:这提供了对 OpenMP 线程放置到物理内核的抽象控制。TensorFlow 的推荐设置为‘fine, compact, 1, 0’。‘Fine’ 可阻止线程迁移,从而减少缓存未命中。‘Compact’ 将邻近的线程放在一起。‘1’ 将线程优先放置在不同的可用物理内核上,而不是在有超线程的相同内核上。这种行为与电子轨道填充原子的方式类似。‘0’ 是指索引核心映射。
os.environ["KMP_AFFINITY"] = "granularity=fine,compact,1,0" - 内部操作并行线程:这些是 TensorFlow 提供的变量,用于控制可以运行多少个并发操作以及每个操作可以运行多少个并行线程。在本例中,我将前者设为 2,并使后者等于 OMP_NUM_THREADS(按照建议)
tf.app.flags.DEFINE_integer(‘inter_op’,2,”””Inter op Parallelism Threads”””)
tf.app.flags.DEFINE_integer(‘inter_op’,136,”””Intra op Parallelism Threads”””)
调整上述所有变量后,性能提高了多达 4 倍,周期时长从 2.5 小时缩减为 30 分钟,从而大大缩短了延迟。正如之前介绍的,英特尔至强可扩展处理器非常强大,而我们在英特尔 AI DevCloud 中获得的性能是理论上承诺的 260 TFlops 性能。除非在适当位置放置了特定卡,否则无法直接实现这一性能。
参考
- 英特尔至强可扩展处理器
- arXiv:1603.04467: TensorFlow:异构分布式系统上的大规模机器学习
- 高级矢量扩展
- 单指令多数据 (SIMD)
- Google cloud Platform
- arXiv:1411.4555: 展示说明:神经图像标题生成器
- arXiv:1409.1556 用于大规模图像识别的非常深的卷积网络
- ImageNet 大规模视觉识别竞赛 (ILSVRC)
- arXiv:1405.0321v3 Microsoft COCO: 上下文中的通用对象
- TensorFlow 的数据集 API
- 英特尔至强可扩展处理器上实时对象检测的优化,Colfax
- arXiv:1506.02640: 只查看一次:统一的实时对象检测