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

签署人: Jing Xu PREETHI VENKATESHHung-Ju Tsai

已发布:01/25/2019   最后更新时间:05/18/2021

为了充分利用英特尔® 架构 (IA) 的功能获得高性能,可以用英特尔® oneAPI Deep Neural Network Library (oneDNN) 中英特尔高度优化的数学例程驱动 TensorFlow*。oneDNN 包括卷积、标准化、激活、内积和其他原语。

英特尔® Optimization for TensorFlow* 被作为英特尔® oneAPI AI Analytics Toolkit (AI Kit) 的一部分发布。AI Kit 统一提供整合的英特尔最新深度学习和机器学习优化软件包,便于轻松开发。除了面向 TensorFlow 的优化之外,AI Kit 还包括其他深度学习框架(例如 PyTorch)和高性能 Python 库(例如 Numpy 或 XGBoost)的英特尔优化版本,用于简化英特尔架构上的端对端数据科学和人工智能工作流程。

选择并下载最符合需求的 AI Kit 分发包,并按照入门指南中的安装后说明进行操作。

您还可以仅设置英特尔® Optimization for TensorFlow* 框架,如本安装指南所述。

最大吞吐量与实时推理

您可以使用两种不同的策略执行深度学习推理,每种策略有不同的性能度量和建议。第一种策略是最大吞吐量 (MxT),旨在每秒处理尽可能多的图像,以大小大于 1 的批次分批传递。对于最大吞吐量策略,可通过在插槽上运行所有物理内核来获得更高性能。通过此策略,您可以以并行、矢量化的方式在 CPU 中加载尽可能多的工作并处理尽可能多的图像。

另一种完全不同的策略是实时推理 (RTI),通常用于尽可能快速地处理单个图像。采用此策略的目标是避免因并发进程之间的过度线程启动和编排而受到惩罚。策略是快速限制和执行。这两种策略的已知最佳方法 (BKM) 有所不同。

TensorFlow 图形选项提升性能

图形的优化可将图形节点转换为只有推理相关节点,并移除所有训练节点,从而帮助缩短延迟和吞吐时间。

第一,使用 Freeze_graph

第一,冻结图形可以带来额外的性能优势。freeze_graph 可以在 GitHub 中使用,是 TensorFlow 的一部分,可将推理图上的所有变量运算转换为常量运算,并输出冻结的图形。若将生成的推理图中的所有权重全部冻结,可以缩短推理时间。这是一个访问 freeze_graph 工具的链接

第二,使用 Optimize_for_inference

将训练的模型仅用于推理时,在冻结图形后,额外的转换流程可以帮助优化推理图。GitHub 中的 TensorFlow 项目提供了一个易于使用的工具,通过对训练的模型输出进行这种转换,缩短了推理时间。输出将是经过推理优化的图形,可缩短推理时间。这是一个访问 optimize_for_inference 工具的链接

TensorFlow 运行时选项提升性能

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

  • intra_/inter_op_parallelism_threads
  • 数据布局

intra_/inter_op_parallelism_threads

推荐设置 (RTI):intra_op_parallelism = number of physical cores

推荐设置:inter_op_parallelism = 2

使用 TensorFlow 基准 tf_cnn_benchmarks usage (shell) 设置 inter_op_num_threads 和 intra_op_num_thread 的方法示例如下:

python tf_cnn_benchmarks.py --num_intra_threads=<number of physical 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 线程的线程池中同时运行它们。应将此变量设置为您希望代码运行的并行路径数。对于英特尔® Optimization for TensorFlow,我们建议从设置 '2' 开始,并在实证检验后进行调整。

数据布局

推荐设置 → data_format = NHWC

tf_cnn_benchmarks usage (shell)

python tf_cnn_benchmarks.py --num_intra_threads=<number of physical cores> --num_inter_threads=2 --data_format=NHWC

有效利用缓存和内存可大幅提升整体性能。适当的内存访问模式最大程度地降低了访问内存数据所需的额外成本,并提升了整体处理能力。数据布局指如何存储和访问数据,在实现这些适当内存访问模式的过程中发挥重要作用。数据布局描述了如何在内存地址空间中线性存储多维数组。

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

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

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

 

Data Formats for Deep Learning NHWC and NCHW

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

注:自 2.4 版本起,英特尔优化的 TensorFlow 支持 NCHW/NHWC 等纯数据格式和 oneDNN 分块数据格式。使用分块数据格式可能有助于进行矢量化,但可能会在 TensorFlow 中增加一些数据重新排序操作。

用户可以使用 TF_ENABLE_MKL_NATIVE_FORMAT 环境变量在 Tensorflow 中启用/禁用 oneDNN 分块数据格式。在导出 TF_ENABLE_MKL_NATIVE_FORMAT=0 后,TensorFlow 将使用 oneDNN 分块数据格式作为替代。

我们建议用户通过以下命令启用 NATIVE_FORMAT,以获得出色的开箱即用性能。
export TF_ENABLE_MKL_NATIVE_FORMAT=1(或 0)

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

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

用法 (shell)

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

非一致性内存访问 (NUMA) 是数据中心电脑所用的内存布局设计,旨在充分利用包含多个内存控制器和内存块的多插槽电脑中的内存局部性。在支持 NUMA 的电脑上运行需要注意一些特殊事项。将执行和内存使用限制在单个 NUMA 节点上时,英特尔® Optimization for 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

 

oneDNN 技术性能注意事项

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

为了确保稳健性能,英特尔开发了针对 oneDNN 优化的深度学习原语。除了矩阵乘法和卷积之外,还实施了以下构建块,以用于适合矢量化的数据布局:

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

oneDNN 利用以下环境变量调整英特尔® 优化 TensorFlow 的性能。因此,更改这些环境变量的值会影响框架的性能。这些环境变量将在以下部分中详细介绍。我们强烈建议用户针对特定的神经网络模型和平台调整这些值。

  • 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

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

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

KMP_AFFINITY=[,...][,][,]

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

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

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

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

KMP_AFFINITY=granularity=fine,verbose,compact(如果禁用了超线程)。

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

OpenMP 全局线程池 ID

图 2: 采用 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 运行时库环境变量的打印。

资源

有关英特尔® AI Kit 和 TensorFlow 优化的更多信息,请查看这些资源链接:

产品和性能信息

1

性能因用途、配置和其他因素而异。请访问 www.Intel.cn/PerformanceIndex 了解更多信息。