最大限度提升 CPU 上的 TensorFlow* 性能:推理工作负载的注意事项和建议

若要充分利用强大的英特尔® 架构 (IA) 实现高性能,可以通过英特尔针对深度学习任务进行了高度优化的数学例程为 TensorFlow* 提供支持。这个基元库被称为面向深度神经网络的英特尔® 数学核心函数库(英特尔® MKL-DNN)。英特尔 MKL-DNN 包括卷积、规范化、激活、内积和其他基元。

若要在系统上设置面向 TensorFlow* 的英特尔® 优化,请参见 安装指南

结论: 用户实现了 CPU 加速 TensorFlow 执行无需更改代码。

Tensorflow-MKL Inference and Training Performance on Intel CPU

图 1:英特尔® 处理器上的推理和训练性能(采用英特尔® MKL-DNN 和面向 TensorFlow* 的英特尔® 优化)

最大吞吐量与实时推理

深度学习推理可以通过两种不同的策略来完成,每种策略都有不同的性能测量和建议。第一种策略是最大吞吐量 (MxT),旨在每秒处理尽可能多的图像,以大小为 1 的批次分批传递。对于最大吞吐量,通过在插槽上运行所有物理内核来实现最佳性能。这个解决方案非常直观,因为我们只需要为 CPU 加载尽可能多的任务,并以并行和矢量化的方式处理尽可能多的图像。实时推理 (RTI) 是一种完全不同的机制,我们通常希望尽可能快速地处理单个图像。在这里,我们的目标是避免因并发进程之间的过度线程启动和编排而受到惩罚。策略是快速限制和执行。在 MxT RTI 方面,以下最佳方法 (BKM) 有所不同。

TensorFlow 运行时选项会影响性能

运行时选项严重影响 TensorFlow 性能。了解它们将有助于从面向 TensorFlow* 的英特尔® 优化中获得最佳性能。

  • intra_/inter_op_parallelism_threads
  • 数据布局

intra_/inter_op_parallelism_threads

推荐设置 (RTI)→ intra_op_parallelism = 物理内核数

推荐设置 → inter_op_parallelism = 2

tf_cnn_benchmarks usage (shell)

python tf_cnn_benchmarks.py --num_intra_threads=cores --num_inter_threads=2

intra_op_parallelism_threadsinter_op_parallelism_threads 是 TensorFlow 中定义的运行时变量。ConfigProto。ConfigProto 用于创建会话时的配置。这两个变量控制要使用的内核数。

  • intra_op_parallelism_threads

此运行时设置控制操作内的并行性。例如,如果要在多个线程中执行矩阵乘法或归约,则应设置此变量。TensorFlow 将在包含 intra_op_parallelism_threads 线程的线程池中调度任务。如后面的图 3 所示,OpenMP* 线程绑定到不同内核的线程上下文并尽可能靠近,建议将此环境变量设置为可用的物理内核数。

  • inter_op_parallelism_threads

注:此设置高度依赖于硬件和拓扑,因此最好根据经验确认最佳的工作负载设置。

此运行时设置控制独立操作之间的并行性。由于这些操作彼此无关,因此 TensorFlow 将尝试在包含 inter_op_parallelism_threads 线程的线程池中同时运行它们。建议将此变量设置为您希望代码运行的并行路径数。对于面向 TensorFlow 的英特尔® Optimization  ,我们建议从设置 2 开始,并在实证检验后进行调整。 

数据布局

推荐设置 → data_format = NCHW

tf_cnn_benchmarks usage (shell)

python tf_cnn_benchmarks.py --num_intra_threads=cores --num_inter_threads=2 data_format=NCHW

在现代英特尔架构中,缓存和内存使用效率会对整体性能产生显著影响。良好的内存访问模式可以最大限度地降低访问内存数据的额外成本,不会降低整体处理速度。若要实现这一目标,数据的存储和访问方式起着重要作用。这通常被称为 数据布局。它描述了多维数组如何在内存地址空间中线性存储。

在大多数情况下,数据布局由二维图像的 四个字母表示。

  • N:批次大小,表示一个批次中的图像数量。
  • C: 通道,表示一张图像中的通道数。
  • W: 宽度,表示图像水平维度的像素数。
  • H: 高度,表示图像垂直维度的像素数。

这四个字母的顺序表示像素数据如何存储在 1-d 内存空间中。例如,NCHW 表示像素数据首先以宽度存储,然后是高度、通道,最后是批量存储(如图 2 所示)。然后使用通道优先索引从左到右访问数据。NCHW 是使用英特尔 MKL-DNN 的推荐数据布局,因为这种格式是 CPU 的高效数据布局。TensorFlow 使用 NHWC 作为默认数据布局,但它也支持 NCHW。

Data Formats for Deep Learning NHWC and NCHW

图 2: 深度学习 NHWC 和 NCHW 的数据格式

影响性能的非一致性内存访问 (NUMA) 控件

推荐设置 → --cpunodebind=0 --membind=0

用法 (shell)

numactl --cpunodebind=0 --membind=0 python

在支持 NUMA 的电脑上运行需要注意一些特殊事项。非一致性内存访问 (NUMA) 是数据中心机器所用的内存布局设计,旨在充分利用包含多个内存控制器和块的多路机器中的内存位置。将执行和内存使用限制在单个 NUMA 节点上时,面向  TensorFlow 的英特尔优化可实现最佳性能。在支持 NUMA 的系统上运行时,应选择 intra_op_parallelism_threads 作为每个 NUMA 节点的本地内核数。

并发执行

您可以通过将工作负载分解为多个数据分片,然后在多个 NUMA 节点上并发运行来优化性能。在每个节点 (N) 上运行以下命令:

用法 (shell)

numactl --cpunodebind=N --membind=N python

例如,您可以使用“&”命令在多个 NUMA 节点上启动同步进程:

numactl --cpunodebind=0 --membind=0 python & numactl --cpunodebind=1 --membind=1 python

英特尔® MKL-DNN 技术性能考虑因素

英特尔 MKL-DNN 技术摘要:该库通过矢量化利用 SIMD 指令,并通过多线程利用多个内核。矢量化技术有效利用了现代 CPU 的缓存和计算能力,以及指令集的效用。单个计算最多可处理 16 个单精度(512 位长)数字。同时,最多可以在一个周期内完成两次乘法和加法(融合乘加 (FMA))运算。此外,多线程技术有助于同时执行多个独立运算。通过避免顺序执行,通常可以最好地执行深度学习任务的计算,因此获取并行运行的可用内核是加速深度学习任务的理想选择。英特尔 MKL-DNN 采用 OpenMP,以充分利用英特尔架构。

为了确保稳定性,英特尔在英特尔 MKL-DNN 中开发了许多优化的深度学习基元。除了矩阵乘法和卷积之外,还实施了以下构建块,以用于适合矢量化的数据布局:

  • 直接批量卷积
  • 内积
  • 池化:最大、最小、平均
  • 标准化:跨通道局部响应归一化 (LRN),批归一化
  • 激活:修正线性单元 (ReLU)
  • 数据操作:多维转置(转换)、合并、求和和缩放

英特尔 MKL-DNN 利用以下环境变量进行矢量化和多线程处理。因此,更改这些环境变量的值会影响框架的性能。这些环境变量将在以下部分中详细介绍。我们强烈建议用户针对特定的神经网络模型和平台调整这些值。

  • KMP_AFFINITY
  • KMP_BLOCKTIME
  • OMP_NUM_THREADS
  • KMP_SETTINGS

KMP_AFFINITY

推荐设置 → KMP_AFFINITY=granularity=fine,verbose,compact,1,0

用法 (shell)

export KMP_AFFINITY=granularity=fine,compact,1,0

tf_cnn_benchmarks usage (shell)

python tf_cnn_benchmarks.py --num_intra_threads=cores --num_inter_threads=2 data_format=NCHW --kmp_affinity=granularity=fine,compact,1,0

英特尔 MKL-DNN 能够将 OpenMP 线程绑定到物理处理单元。KMP_AFFINITY 用于充分利用这一功能。它将某些线程的执行限制为多处理器计算机中的物理处理单元的子集。

此环境变量的用法如下所示。

KMP_AFFINITY=[<modifier>,...]<type>[,<permute>][,<offset>]

修饰符是由关键字和说明符组成的字符串。type 是一个字符串,表示要使用的线程关联。permute 是一个正整数值,用于控制机器拓扑图排序时最重要的级别。该值强制映射,使指定数量的最重要的排序级别成为最不重要的级别,并颠倒重要性顺序。树的根节点不被视为排序操作的单独级别。offset 是一个正整数值,表示线程分配的起始位置。我们将以 KMP_AFFINITY 的推荐设置为例解释此环境变量的基本内容。

KMP_AFFINITY=granularity=fine,verbose,compact,1,0

修饰符为 granularity=fine,verbose.Fine 以将每个 OpenMP 线程绑定到单个线程上下文。Verbose 在运行时打印有关所支持关联的信息,这是可选的。这些消息包括有关包的数量、每个包中的内核数、每个内核的线程上下文数以及 OpenMP 线程绑定到物理线程上下文的信息。Compact 是 type 的值,将 OpenMP 线程 <n>+1 分配给一个自由线程上下文(尽可能接近放置了 <n> OpenMP 线程的线程上下文)。

注意 如果您的 电脑上禁用了超线程,则建议会更改。在这种情况下,建议如下:

KMP_AFFINITY=granularity=fine,verbose,compact          if hyperthreading is disabled.

图 3 显示了 KMP_AFFINITY设为这些值时的机器拓扑图。OpenMP 线程 <n>+1 被绑定到尽可能接近 OpenMP 线程 <n> 的线程上下文,但位于不同内核中。为每个内核分配一个 OpenMP 线程后,后续的 OpenMP 线程将以相同的顺序分配给可用内核,但它们在不同的线程上下文中分配。

OpenMP Global Thread Pool IDs

图 3.采用 KMP_AFFINITY=granularity=fine,compact,1,0 设置的机器拓扑图

此设置的优势在于连续的线程紧密绑定在一起,从而最大限度减少了通信开销、缓存行无效开销和页面抖动。假设应用还有许多未使用所有可用 OpenMP 线程的并行区域,希望避免将多个线程绑定到同一个内核且使其他内核处于闲置状态。

如欲获取关于 KMP_AFFINITY 的更详细说明,请参见 英特尔® C++ 开发人员指南

KMP_BLOCKTIME

CNN 的推荐设置→ KMP_BLOCKTIME=0

非 CNN 的推荐设置→ KMP_BLOCKTIME=1(用户应根据经验进行验证)

用法 (shell)

export KMP_BLOCKTIME=0 (or 1)

tf_cnn_benchmarks usage (shell)

python tf_cnn_benchmarks.py --num_intra_threads=cores --num_inter_threads=2 data_format=NCHW --kmp_affinity=granularity=fine,compact,1,0 --kmp_blocktime=0( or 1)

此环境变量用于设置一个线程在完成并行区域的执行之后,在休眠之前应等待的时间(以毫秒为单位)。默认值为 200 毫秒。

完成并行区域的执行后,线程等待新的并行工作变得可用。经过一段时间后,它们就会停止等待和休眠。休眠允许线程供可能在并行区域之间执行的非 OpenMP 线程代码使用,或供其他程序使用,直到更多并行工作变得可用。如果应用包含在并行区域之间执行的非 OpenMP 线程代码,则较小的KMP_BLOCKTIME值可以提供更好的整体性能。如果保留线程仅用于 OpenMP 执行,则较大的 KMP_BLOCKTIME 值可能更合适,但可能会惩罚其他同时运行的 OpenMP 或线程应用。建议将基于卷积神经网络 (CNN) 的模型的此值设为 0。

OMP_NUM_THREADS

CNN 的推荐设置→ OMP_NUM_THREADS = num physical cores

用法 (shell)

export OMP_NUM_THREADS=num physical cores

如果未在应用中指定其他值,则此环境变量会设置用于 OpenMP 并行区域的最大线程数。

该值可以是单个整数,在这种情况下,它指定所有并行区域的线程数。该值也可以是以逗号分隔的整数列表,在这种情况下,每个整数指定嵌套级别的并行区域的线程数。

列表中的第一个位置表示最外层的并行嵌套级别,第二个位置表示下一个内部并行嵌套级别,依此类推。在任何级别,整数都可以不在列表中。如果省略列表中的第一个整数,则意味着在最外层使用线程的正常默认值。如果整数被排除在任何其他级别之外,则该级别的线程数将从上一级继承。

默认值是对执行程序的操作系统可见的逻辑处理器数。建议将此值设置为物理内核数。

KMP_SETTINGS

用法 (shell)

export KMP_SETTINGS=TRUE

此环境变量在程序执行期间启用 (TRUE) 或禁用 (FALSE) OpenMP 运行时库环境变量的打印。

 

硬件

使用 FP32 批量大小 Caffe* GoogleNet v1 128  AlexNet 256 的推理。

推理吞吐量的配置

截至   2018 年 6 月 7 日的英特尔测试:平台:双路英特尔® 至强® 铂金 8180 处理器,拥有 2.50GHz / 28 个内核,超线程[WE1] :开启,睿频:开启,总内存 376.28GB(12 个插槽/32 GB/2666 MHz),4 个框架实例,CentOS*,Linux* 7.3,1611-Core,固态盘:sda RS3WC080,机械硬盘:744.1GB,sdb RS3WC080,机械硬盘:1.5TB,sdc RS3WC080,机械硬盘:5.5TB,深度学习框架:Caffe* 版本 a3d5b022fe026e9092fc7abc7654b1162ab9940d,拓扑:GoogLeNet v1,BIOS:SE5C620.86B.00.01.0004.071220170215,英特尔 MKL-DNN:版本:464c268e544bae26f9b85a2acb9122c766a4c396, 无数据层。测量对象:每秒 1449 张图像,对比截至 2018 年 6 月 15 日的英特尔测试 平台:双路英特尔® 至强® 处理器你 E5-2699 v3,拥有 2.30GHz / 18 个内核,超线程:开启,睿频:禁用,缩放调控器通过 intel_pstate 驱动程序设置为“性能”,64GB DDR4-2133 ECC RAM。BIOS:SE5C610.86B.01.01.0024.021320181901,CentOS Linux-7.5.1804(Core) kernel 3.10.0-862.3.2.el7.x86_64,SSD sdb INTEL SSDSC2BW24 SSD 223.6GB。伯克利视觉与学习中心 (BVLC) 框架 Caffe:https://github.com/BVLC/caffe,使用“caffe time”命令测量推理和训练。 对于“ConvNet” 拓扑,使用了虚拟数据集。对于其他拓扑,在训练之前,数据存储在本地存储中并在内存中缓存。BVLC Caffe (http://github.com/BVLC/caffe),修订版 2a1c552b66f026c7508d390b526f2495ed3be594。

训练吞吐量配置:

截至 2018 年 5 月 29 日的英特尔测试。平台:双路英特尔® 至强® 铂金 8180 处理器,拥有 2.50Ghz/28 个内核,超线程:开启,睿频:开启,总内存 376.28GB(12 个插槽/32 GB/2666 MHz),4 个框架实例,CentOS Linux-7.3.1611-Core,固态盘:sda RS3WC080,机械硬盘:744.1GB,sdb RS3WC080 HDD 1.5TB,sdc RS3WC080 HDD 5.5TB,深度学习框架 Caffe* 版本:a3d5b022fe026e9092fc7abc765b1162ab9940d,拓扑:AlexNet,BIOS:SE5C620.86B.00.01.0004.071220170215,英特尔 MKL-DNN:版本:464c268e544bae26f9b85a2acb9122c766a4c396,无数据层。测量对象:每秒 1257 张图像,对比截至 2018 年 6 月 15 日的英特尔测试 平台:双路英特尔® 至强® 处理器 E5-2699 v3 @ 2.30GHz(18 个内核),超线程:启用,睿频:禁用,缩放调控器通过 intel_pstate 驱动程序设置为“性能”,64GB DDR4-2133 ECC RAM。BIOS:SE5C610.86B.01.01.0024.021320181901。CentOS Linux-7.5.1804(Core) kernel 3.10.0-862.3.2.el7.x86_64,SSD sdb INTEL SSDSC2BW24 SSD 223.6GB。BLVC 框架 Caffe:https://github.com/BVLC/caffe,使用“caffe time”命令测量推理和训练。 对于“ConvNet” 拓扑,使用了虚拟数据集。对于其他拓扑,在训练之前,数据存储在本地存储中并在内存中缓存。BVLC Caffe (http://github.com/BVLC/caffe),修订版 2a1c552b66f026c7508d390b526f2495ed3be594

 

Para obter informações mais completas sobre otimizações do compilador, consulte nosso aviso de otimização.