面向英特尔® 架构优化的 Caffe*:使用现代代码技巧

提升深度学习框架的计算性能

PDF 版本

作者

英特尔公司 Vadim Karpusenko 博士,英特尔公司
Andres Rodriguez 博士,英特尔公司
Jacek Czaja,英特尔公司
Mariusz Moczala

摘要

本文将介绍一种特殊版本的深度学习框架 Caffe*(最初由伯克利愿景和学习中心 (Berkeley Vision and Learning Center,BVLC) 开发),该框架专门面向英特尔® 架构优化。 这一版本的 Caffe(即面向英特尔® 架构而优化的 Caffe)目前集成了最新版本的英特尔® 数学核心函数库 2017,专门面向英特尔® 高级矢量扩展指令集 2 优化,还将纳入英特尔高级矢量扩展指令集 512 指令。 该解决方案支持英特尔® 至强® 处理器和英特尔® 至强融核™ 处理器等。 本文列举了有关 CIFAR-10* 图像分类数据集的性能结果,并介绍了部分可提升 BVLC Caffe 代码和其他深度学习框架的计算性能的工具和代码修改。

简介

深度学习属于通用机器学习的一个子集。近年来,深度学习在图像与视频识别、语音识别、自然语言处理 (NLP) 和其他大数据与数据分析领域取得了突破性进展。 近期在计算、大型数据集和算法等方面所取得的进展已经成为深度学习获取成功的关键因素,其工作原理是将数据传递各个层,各层均可从中提取日益复杂的特征。

图 1. 深度网络的每一层通过培训,能够识别更为复杂的特征 — 上图显示了投影成像素空间(左侧的灰色图像)的深度网络的一小部分特性以及激活这些特性的相应图像(左侧的彩色图像)。
Zeiler, Matthew D. 和 Fergus, Rob. 纽约大学计算机科学系 《卷积网络的可视化与理解》, 2014 年。 https://www.cs.nyu.edu/~fergus/papers/zeilerECCV2014.pdf

监督式深度学习需要带有标记的数据集。 常见的 3 种监督式深度网络包括多层感知器 (MLP)、卷积神经网络 (CNN) 和循环神经网络 (RNN)。 在这些网络中,输入在通过每一层时,经过一系列线性和非线性转换,然后生成输出结果。 首先计算网络中权值与激活的成本梯度,并以迭代方式向后传递至下一层,然后计算错误以及相关错误成本。 最后根据计算的梯度更新权值或模型。

在 MLP 中,每层的输入数据(表示为矢量)首先乘以该层独有的密集矩阵。 在 RNN 中,每层(循环)的密集矩阵都是相同的,网络长度取决于输入信号的长度。 CNN 与 MLP 类似,但使用卷积层的稀疏矩阵。 该矩阵乘法变现为 2-D 权值表示卷积该层输入的 2-D 表示。 CNN 常用于图像识别,但也可用于语音识别和 NLP。 有关 CNN 的详细介绍,请参阅“面向视觉识别技术的 CS231n 卷积神经网络”: http://cs231n.github.io/convolutional-networks/

Caffe

Caffe 是伯克利愿景和学习中心 (Berkeley Vision and Learning Center, BVLC) 与社区撰稿者共同开发的深度学习框架。 本文将初始版 Caffe 称作“BVLC Caffe”。

相比之下,面向英特尔® 架构优化的 Caffe 为特定的优化型 BVLC Caffe 框架。 面向英特尔架构优化的 Caffe 目前集成了最新版英特尔® 数学核心函数库(英特尔® MKL) 2017,专门面向英特尔® 高级矢量扩展指令集 2(英特尔® AVX2)优化,还将纳入英特尔高级矢量扩展指令集 512(英特尔® AVX-512)指令,其支持英特尔® 至强® 处理器和英特尔® 至强融核™ 处理器等。 如欲了解关于编译、培训、调优、测试,以及可用工具的详细信息,请阅读“基于面向英特尔® 架构优化的 Caffe* 培训和部署深度学习网络”:https://software.intel.com/zh-cn/articles/training-and-deploying-deep-learning-networks-with-caffe-optimized-for-intel-architecture

英特尔非常感谢 Boris Ginsburg 在 OpenMP* 多线程化实施面向英特尔® 架构优化的 Caffe* 方面所提出的创意和做出的前期工作。

本文介绍了面向英特尔架构优化的 Caffe* 与基于英特尔架构运行的 BVLC Caffe 在性能方面的差异,并探讨了可用于提升 Caffe 框架的计算性能的工具和代码修改。 还展示了使用 CIFAR-10* 图像分类数据集(https://www.cs.toronto.edu/~kriz/cifar.html) 和 CIFAR-10 full-sigmoid 模型(由卷积层、最大池化与平均池化,以及批归一化 (batch normalization) 构成)的性能结果: (https://github.com/BVLC/caffe/blob/master/examples/cifar10/cifar10_full_sigmoid_train_test_bn.prototxt).

Example of CIFAR-10* dataset images
图 2. CIFAR-10* 数据集图像示例

如欲下载有关测试 Caffe 框架的源代码,请访问:

图像分类

CIFAR-10 数据集包含 60,000 张彩色图像,每张图像的尺寸为 32 × 32,平均划分并标记为以下 10 类:飞机、汽车、鸟、猫、鹿、狗、青蛙、马、船和卡车。 这些类别相互排斥;不同类型的汽车(比如轿车或运动型多用途车 [SUV])或卡车(仅包含大型卡车)之间不重叠 — 两组均不包含皮卡(见图 2)。

英特尔测试 Caffe 框架时,使用 CIFAR-10 full-sigmoid 模型 — 包含卷积、最大池化、批归一化、全连接和 softmax 在内的多层 CNN 模型。 关于层级描述请参阅借助 OpenMP* 实现代码并行化部分。

初始性能分析

评测面向英特尔架构优化的 Caffe 和 BVLC Caffe 性能的方法是使用时间命令计算各层的向前和向后传播时间。 该时间命令可用于测量各层所消耗的时间,并为不同模型提供相关执行时间:

./build/tools/caffe time \ 
    --model=examples/cifar10/cifar10_full_sigmoid_train_test_bn.prototxt \ 
    -iterations 1000

在这种环境下,迭代可定义为一批图像上方的向前和向后传递。 前一命令返回每层 1,000 次迭代以及整个网络每次迭代的平均执行时间。 图 3 显示了完整输出。

Output from the Caffe* time command
图 3. Caffe* 时间命令的输出

在测试过程中,我们使用搭载了一颗英特尔至强处理器 E5-2699 v3 的双路系统(每路 2.30 GHz,每 CPU 10 个内核),并禁用英特尔® 超线程技术(英特尔® HT 技术)。 该双路系统共有 36 个内核,因此测试中如无另行说明,OMP_NUM_THREADS 环境变量指定的默认 OpenMP* 线程数量为 36(请注意,我们建议让面向英特尔架构优化的 Caffe 自动指定 OpenMP 环境,而非手动设置)。 该系统还安装了 64 GB DDR4,其运行频率为 2,133 MHz。

本文根据这些数字验证英特尔工程师进行代码优化后所取得的性能成效。 我们在性能监控过程中使用了以下工具:

  • Valgrind* 工具链中的 Callgrind*
  • 英特尔® VTune™ Amplifier XE 2017 测试版

英特尔 VTune Amplifier XE 工具可提供以下信息:

  • 总体执行时间最长的函数(热点)
  • 系统调用(包括任务切换)
  • CPU 和高速缓存使用率
  • OpenMP 多线程负载平衡
  • 线程锁定
  • 内存占用率

我们通过性能分析查找优化目标,比如代码热点和长时间函数调用。 图 4 显示了英特尔 VTune Amplifier XE 2017 测试版汇总分析运行 100 次迭代时的重要数据点。 图 4 顶部的运行时间 (Elapsed Time) 为 37 秒。

该时间为测试系统中代码的执行时间。 运行时间下方的 CPU 时间 (CPU Time) 为 1,306 秒 — 稍微短于 37 秒与 36 个内核的乘积(1,332 秒)。 CPU 时间为所有用于执行的线程(或内核,因为测试中禁用超线程技术)的总时长。

Intel® VTune™ Amplifier XE 2017 beta analysis summary for BVLC Caffe* CIFAR-10* execution
图 4. 英特尔® VTune™ Amplifier XE 2017 测试版分析; BVLC Caffe* CIFAR-10* 执行汇总

图 4 底部的 CPU 使用率直方图 (CPU Usage Histogram) 显示了测试过程中特定数量的线程同时运行的频率。 大部分时间(14 秒,总时长 37 秒)仅运行单个线程(单个内核)。 其他时间的多线程运行效率非常低,执行的线程数量不到 20。

图 4 中部执行汇总的重要热点 (Top Hotspots) 部分显示了详细的运行信息。 它列举了函数调用及其相应的 CPU 时间。 kmp_fork_barrier 函数是面向隐式壁垒的内部 OpenMP 函数,用于同步线程执行。 kmp_fork_barrier 函数消耗了 1,130 秒 CPU 时间,说明在 87% 的 CPU 执行时间里,线程围绕该壁垒旋转,并未完成任何有用工作。

BVLC Caffe 文件包的源代码不包含 #pragma omp parallel 代码行。 BVLC Caffe 代码中没有明确用于多线程的 OpenMP 库。 然而,英特尔 MLK 中使用 OpenMP 线程对部分数学例程调用进行并行化处理。 为确保这种并行化,我们可以查看自下而上选项卡视图(见图 5,查看在有效利用时间内的函数调用[顶部]以及线程时间线[底部])

图 5显示了 CIFAR-10 数据集上有关 BVLC Caffe 的函数调用热点。

Timeline visualization and function-call hotspots for BVLC Caffe* CIFAR-10* dataset training
图 5. 关于 BVLC Caffe* CIFAR-10* 数据集培训的时间线可视化和函数调用热点

gemm_omp_driver_v2 function — libmkl_intel_thread.so 的一部分 — 是英特尔 MKL 的一种通用矩阵 (GEMM) 乘法实施。 该函数在后台使用 OpenMP 多线程。 优化后的英特尔 MKL 矩阵乘法是用于向前和向后传播的主要函数,即用于权值计算、预测和调整。 英特尔 MKL 初始化 OpenMP 多线程,通常会缩短 GEMM 操作的计算时间。 然而,在这种特殊情况下 — 卷积 32 × 32 图像 — 在单次 GEMM 操作中,该工作负载不足以高效利用 36 个内核上的全部 36 个 OpenMP 线程。 因此需采用一种不同的多线程-并行化方案,本文稍后将予以介绍。

为了说明 OpenMP 线程利用的开销,我们运行包含 OMP_NUM_THREADS=1 环境变量的代码,然后对比相同工作负载的执行时间: 31.1 秒,而非 37 秒(见图 4图 6 顶部的运行时间部分)。 通过使用这一环境变量,我们迫使 OpenMP 仅创建一个线程,并将其用于代码执行。 相比 BVLC Caffe 代码实施,这缩短的近 6 秒时间即为 OpenMP 线程初始化和同步化开销。

 OMP_NUM_THREADS=1
图 6. 英特尔® VTune™ Amplifier XE 2017 测试版关于 BVLC Caffe* CIFAR-10* 数据集执行单一线程 OMP_NUM_THREADS=1 的分析汇总

通过这种分析设置,我们确定了可在 BVLC Caffe 实施过程中实现性能优化的三个目标: im2col_cpu、col2im_cpuPoolingLayer::Forward_cpu 函数调用(见图 6 中部)。

代码优化

相比 BVLC Caffe 代码,面向英特尔架构优化的 Caffe 实施 CIFAR-10 数据集的时间缩短了将近 13.5 倍(向前-向后传播的时间分别为 20 毫秒和 270 毫秒)。 图 7 显示了 1,000 次迭代过程中关于向前-向后传播的平均结果。 左栏为 BVLC Caffe 的结果,右栏为面向英特尔架构优化的 Caffe 的结果。

Forward-backward propagation results
图 7. 向前-向后传播结果

如欲深入了解各层,请参阅下文的神经网络层优化结果部分。

如欲了解更多关于定义各层计算参数的信息,请访问 http://caffe.berkeleyvision.org/tutorial/layers.html

下文将介绍用于提升各层性能的优化方法。 我们所使用的技巧遵循英特尔® 现代代码开发人员代码指南,部分优化方法依赖于英特尔 MKL 2017 数据基元。 此处将展示用于面向英特尔架构优化的 Caffe 的优化和并行化技巧,以帮助您更好地了解如何实施代码,并支持代码开发人员将这些技巧用于其他机器学习和深度学习应用和框架。

标量和串行优化

代码矢量化

分析 BVLC Caffe 代码并识别热点(消耗最多 CPU 时间的函数调用)后,我们采用面向矢量化的优化方法, 包括:

  • 基本线性代数子程序 (BLAS) 库(从自动调优线性代数系统 [ATLAS*] 切换至英特尔 MKL) 
  • 在汇编过程中进行优化(Xbyak just-in-time [JIT] 汇编程序) 
  • GNU Compiler Collection* (GCC*) 和 OpenMP 代码矢量化

BVLC Caffe 可以选择采用英特尔 MKL BLAS 函数调用或其他实施方法。 例如,GEMM 函数经过优化,适用于矢量化、多线程化和提高缓存流量。 为提高矢量化效果,我们还使用了 Xbyak — 面向 x86 (IA-32) 和 x64(AMD64* 或 x86-64)的 JIT 汇编程序。 Xbyak 目前支持以下矢量指令集: MMX™ 技术、英特尔® 流式单指令多数据扩展(英特尔® SSE)、英特尔 SSE2、英特尔 SSE3、英特尔 SSE4、浮点单元、英特尔 AVX、英特尔 AVX2 和英特尔 AVX-512。

Xbyak 汇编程序是一种面向 C++ 的 x86/x64 JIT 汇编程序,专为高效开发代码而创建的库。 Xbyak 汇编程序以仅头文件代码的形式提供。 它还可动态汇编 x86 和 x64 助记符。 代码运行期间生成 JIT 二进制代码可支持多种优化方法,比如量化操作和多项式计算操作,前者用特定数组元素除以次要数组元素,后者根据 constant、variable x、add、sub、mul、div等创建行为。 由于支持英特尔 AVX 和英特尔 AVX2 矢量指令集,Xbyak 可帮助面向英特尔架构优化的 Caffe 在代码实施过程中提高矢量化率。 最新版 Xbyak 支持英特尔 AVX-512 矢量指令集,从而能够基于英特尔至强融核处理器 x200 产品家族显著提高计算性能。 矢量化率提高后有利于 Xbyak 使用单指令多数据 (SIMD) 指令同时处理更多数据,从而显著提高数据并行化的利用率。 我们使用 Xbyak 对这种操作进行矢量化处理,从而显著提高进程池化层的性能。 如果知道池化参数,我们可以生成汇编代码来处理面向特定池化窗口或算法的特定池化模型。 结果证明,平面汇编的效率远远高于 C++ 代码。

通用代码优化

其他串行优化方法包括:

  • 降低编程复杂性
  • 减少计算数量
  • 展开循环

通用代码删除是我们在代码优化过程中所采用的一种标量优化技巧。 使用该技巧的目的是为了预先确定在最内层 for-loop 的外部进行哪些计算。

例如,考虑下面的代码片段:

for (int h_col = 0; h_col < height_col; ++h_col) { 
  for (int w_col = 0; w_col < width_col; ++w_col) { 
    int h_im = h_col * stride_h - pad_h + h_offset; 
    int w_im = w_col * stride_w - pad_w + w_offset;

在该代码片段的第三行,关于 h_im 计算,我们不使用最内层循环的 w_col 索引。 但我们仍然在每次迭代最内层循环的过程中执行该计算。 我们还可以通过以下代码将该行移出最内层循环:

for (int h_col = 0; h_col < height_col; ++h_col) { 
  int h_im = h_col * stride_h - pad_h + h_offset; 
  for (int w_col = 0; w_col < width_col; ++w_col) { 
    int w_im = w_col * stride_w - pad_w + w_offset;

特定于 CPU、特定于系统和其他通用代码优化技巧

以下列举的其他通用优化方法可用于:

  • 改进 im2col_cpu/col2im_cpu 实施
  • 降低批归一化的复杂性
  • 特定于 CPU/系统的优化方法
  • 每个计算线程使用一个内核
  • 避免线程移动

英特尔 VTune Amplifier XE 2017 测试版将 im2col_cpu 函数确定为热点函数 — 使其成为性能优化的理想目标。 im2col_cpu 函数是一种常见步骤,能够以 GEMM 操作形式执行直接卷积,从而使用高度优化的 BLAS 库。 每个本地补丁都可扩展为独立矢量,而且整张图像可转化成大型(使用更多内存)矩阵,其中各行对应于使用过滤器的多个位置。

适用于 im2col_cpu 函数的一种优化技巧是减少索引计算。 BVLC Caffe 代码包含三个贯穿图像像素的嵌套循环:

for (int c_col = 0; c_col < channels_col; ++c_col) 
  for (int h_col = 0; h_col < height_col; ++h_col) 
    for (int w_col = 0; w_col < width_col; ++w_col)
      data_col[(c_col*height_col+h_col)*width_col+w_col] = // ...

在该代码片段中,BVLC Caffe 首先计算 data_col 数组元素的相应索引,尽管该数组的索引能够按照顺序简单处理。 因此,四次算术运算(两次加法和两次乘法)可替换为单次索引递增运算。 另外,条件校验的复杂性也可显著降低,原因如下:

/* Function 通过将整数转换成无符号类型,以对比参数 a 的值是否大于或等于零,还是小于参数 b 的值。参数 b 为带符号类型,通常为正值,因此它的值通常小于 0x800...其中转换负参数值可将其转换为大于 0x800 的值。 这种转换允许使用一个(而非两个)条件。 */ 
inline bool is_a_ge_zero_and_a_lt_b(int a, int b) { 
  return static_cast<unsigned>(a) < static_cast<unsigned>(b); 
}

在 BVLC Caffe 中,原始代码包含条件校验 if (x >= 0 && x < N),其中 xN 为带符号整数,N 通常为正值。 将这些整数类型转换成无符号整数,可改变比对间隔。 无需通过逻辑 AND 运行两次比对,进行类型转换后,只需进行一次比对:

if (((unsigned) x) < ((unsigned) N))

为避免操作系统移动线程,我们使用 OpenMP 相似性环境变量 KMP_AFFINITY=c ompact,granularity=fine。 紧凑放置相邻线程可提高 GEMM 操作性能,因为所有线程都可共享相同的末级高速缓存 (LLC),从而可将之前预取的缓存行重复用于数据。

如欲了解有关高速缓存封闭优化实施方法以及数据布局和矢量化,请参阅以下文章: http://arxiv.org/pdf/1602.06709v1.pdf

借助 OpenMP* 实现代码并行化

神经网络层优化结果

以下神经网络层也可通过采用 OpenMP 多线程并行化实现优化:

  • 卷积
  • 去卷积
  • 局部响应归一化 (LRN)
  • ReLU
  • Softmax
  • 级联
  • OpenBLAS* 优化实用程序 — 比如 vPowx - y[i] = x[i]β 操作、caffe_set、caffe_copycaffe_rng_bernoulli
  • 池化
  • Dropout
  • 批处理标准化
  • 数据
  • Eltwise

卷积层

卷积层,顾名思义,将包含一套权值或过滤器的输入进行卷积,在输出图中生成一张特征图。 这种优化方法可防止低效使用适用于一套输入特征图的硬件。

template <typename Dtype>
void ConvolutionLayer<Dtype>::Forward_cpu(const vector<Blob<Dtype>*>& \ 
      bottom, const vector<Blob<Dtype>*>& top) { 
  const Dtype* weight = this->blobs_[0]->cpu_data(); 
  // If we have more threads available than batches to be prcessed then 
  // we are wasting resources (lower batches than 36 on XeonE5) 
  // So we instruct MKL 
  for (int i = 0; i < bottom.size(); ++i) { 
    const Dtype* bottom_data = bottom[i]->cpu_data(); 
    Dtype* top_data = top[i]->mutable_cpu_data(); 
#ifdef _OPENMP 
    #pragma omp parallel for num_threads(this->num_of_threads_) 
#endif 
      for (int n = 0; n < this->num_; ++n) { 
        this->forward_cpu_gemm(bottom_data + n*this->bottom_dim_, 
                               weight, 
                               top_data + n*this->top_dim_); 
        if (this->bias_term_) { 
          const Dtype* bias = this->blobs_[1]->cpu_data(); 
          this->forward_cpu_bias(top_data + n * this->top_dim_, bias); 
        } 
      } 
   } 
}

我们处理 k = min(num_threads,batch_size)input_feature 图;例如,k im2col 操作并行执行,k 调用至英特尔 MKL。 英特尔 MKL 自动切换至单线程执行流程,其整体性能高于进行批处理的英特尔 MKL。 这种行为在源代码文件 src/caffe/layers/base_conv_layer.cpp 中定义。 这种实施方法通过 src/caffe/layers/conv_layer.cpp — 包含相应代码的文件位置,优化了 OpenMP 多线程

池化或二次采样

max-poolingaverage-poolingstochastic-pooling(尚未实施)是三种不同的下采样方法,其中 max-pooling 应用最为普遍。 池化层将前一层的结果划分成一套通常不相互重叠的矩形区块。 然后,该层为每个这种子区域输出最大值、算术平均值,或(将来)从通过激活各区块所形成的多项式分布中提取的随机值。

池化非常适用于 CNN,原因有三点:

  • 池化可缩减问题的影响区域,并降低上层的计算负载。
  • 池化下层可支持上层的卷积内核覆盖较大的输入数据区域,从而了解更多复杂特征;例如,下层内核通常学习如何识别小型边缘,而上层内核可能学习如何识别森林或海滩等大型场景。
  • max-pooling 提供换算偏差表。 在 8 个支持通过单一像素换算 2 × 2 区块(典型池化区块)的方位中,其中 3 个将返回同一最大值;对 3 × 3 窗口而言,其中 5 个将返回同一最大值。

池化适用于单张特征图,因此我们使用 Xbyak 高效完成汇编流程,以便创建适用于一张或多张输入特征图的平均-最大池化。 如果与 OpenMP 并行运行,该池化流程可用于一批输入特征图。

该池化层与 OpenMP 多线程并行执行;因为图像相互独立,因此多个线程可用于同时处理图像:

#ifdef _OPENMP 
  #pragma omp parallel for collapse(2) 
#endif 
  for (int image = 0; image < num_batches; ++image) 
    for (int channel = 0; channel < num_channels; ++channel) 
      generator_func(bottom_data, top_data, top_count, image, image+1, 
                        mask, channel, channel+1, this, use_top_mask); 
}

借助 collapse(2) 子句,OpenMP #pragma omp parallel 扩散到两个嵌套 for-loop、批量迭代图像和图像通道、将两个循环合成一个,并对该循环进行并行化处理。

Softmax 和损失层

损失(代价)函数是机器学习中的重要组件,可通过将预测输出与目标或标记进行比对,然后重新调整权值,以最大限度地降低计算梯度(与损失函数相关的权值的部分衍生品)的代价,进而指导网络培训流程。

softmax(归一化指数)函数是分类概率分布的梯度日志正规化子。 一般来说可用于计算可能呈现 K 种成效的随机事件的结果,每种成效的概率单独指定。 具体来说,在多项式逻辑回归(多类分类问题),该函数的输入为 K 个不同线性函数的结果,示例矢量 x 的第 j 类预测概率为:

应用于这些计算时,OpenMP 多线程化将作为一种并行化方法,通过使用主线程以划分任务的方法分解特定数量的从线程。 然后线程在分配至不同处理器的过程中并行运行。 例如,在下列代码中,包含独立数据访问的并行化算术操作除以在不同通道中计算的范数:

    // division 
#ifdef _OPENMP 
#pragma omp parallel for 
#endif 
    for (int j = 0; j < channels; j++) { 
      caffe_div(inner_num_, top_data + j*inner_num_, scale_data, 
              top_data + j*inner_num_); 
    }

修正线性单元 (ReLU) 和 Sigmoid - 激励/神经元层

ReLU 是深度学习算法中目前应用最为普遍的非线性函数。 激励/神经元层是逐元素运算符,提取底部 blob 并生成大小相同的顶部 blob。 (blob 为标准数组和适用于框架的统一内存接口。 随着数据和衍生品进入网络,Caffe 以 blob 的形式保存、交换并控制信息。)

ReLU 层提取输入值 x 并将输出计算为正值 x,然后通过 negative_slope 调整为负值:

negative_slope 的默认参数值为零,等于提取 max(x, 0) 的标准 ReLU 函数。 由于激励流程的数据独立性特征,每个 blob 都可并行处理,如下一页所示:

template <typename Dtype>
void ReLULayer<Dtype>::Forward_cpu(const vector<Blob<Dtype>*>& bottom, 
    const vector<Blob<Dtype>*>& top) { 
  const Dtype* bottom_data = bottom[0]->cpu_data(); 
  Dtype* top_data = top[0]->mutable_cpu_data(); 
  const int count = bottom[0]->count(); 
  Dtype negative_slope=this->layer_param_.relu_param().negative_slope(); 
#ifdef _OPENMP 
#pragma omp parallel for 
#endif 
  for (int i = 0; i < count; ++i) { 
    top_data[i] = std::max(bottom_data[i], Dtype(0)) 
        + negative_slope * std::min(bottom_data[i], Dtype(0)); 
  } 
}

类似的并行计算可用于向后传播,如下所示:

template <typename Dtype>
void ReLULayer<Dtype>::Backward_cpu(const vector<Blob<Dtype>*>& top, 
    const vector<bool>& propagate_down, 
    const vector<Blob<Dtype>*>& bottom) { 
  if (propagate_down[0]) { 
    const Dtype* bottom_data = bottom[0]->cpu_data(); 
    const Dtype* top_diff = top[0]->cpu_diff(); 
    Dtype* bottom_diff = bottom[0]->mutable_cpu_diff(); 
    const int count = bottom[0]->count(); 
    Dtype negative_slope=this->layer_param_.relu_param().negative_slope(); 
#ifdef _OPENMP 
#pragma omp parallel for 
#endif 
    for (int i = 0; i < count; ++i) { 
      bottom_diff[i] = top_diff[i] * ((bottom_data[i] > 0) 
          + negative_slope * (bottom_data[i] <= 0)); 
    } 
  } 
}

同样,通过以下方法可对 sigmoid 函数S(x) = 1 / (1 + exp(-x)) 进行并行化处理:

#ifdef _OPENMP 
  #pragma omp parallel for 
#endif 
  for (int i = 0; i < count; ++i) { 
    top_data[i] = sigmoid(bottom_data[i]); 
  }

由于英特尔 MKL 不提供数学基元来实施 ReLU,为添加这项功能,我们尝试借助汇编代码(通过 Xbyak)实施性能优化版 ReLU 层。 然而,我们发现英特尔至强处理器并没有实现明显的性能提升 — 也许是因为内存带宽有限。 对现有 C++ 代码进行并行化处理能够有效地提升整体性能。

结论

前文探讨了神经网络的不同组件与层级,以及各层中已处理数据的 blob 如何分布于可用 OpenMP 线程和英特尔 MKL 线程。 图 8 中的 CPU 使用率直方图显示了实施优化和并行化后,特定数量的线程以并行形式运行的频率。

借助面向英特尔架构优化的 Caffe,同时运行的线程数量显著增加。 借助面向英特尔架构优化的 Caffe,测试系统的执行时间从最初未修改的 37 秒缩短至 3.6 秒,将整体执行性能提高了超过 10 倍。

Intel® VTune™ Amplifier XE 2017 beta analysis summary of the Caffe* optimized for Intel® architecture implementation for CIFAR-10* training
图 8. 英特尔® VTune™ Amplifier XE 2017 测试版分析汇总面向 CIFAR-10* 培训实施面向英特尔® 架构优化的 Caffe

图 8 顶部的运行时间所示,运行执行过程中仍然存在部分旋转时间 (Spin Time)。 因此,执行性能无法随着线程数量的增加而实现线性增长(根据阿姆达尔定律)。 另外,未通过 OpenMP 多线程实现并行化的代码中仍然存在串行执行区域。 重新初始化 OpenMP 并行区域面向最新 OpenMP 库实施进行了显著优化,但仍然产生了不可忽略的性能开销。 将 OpenMP 并行区域移至主代码函数可提升性能,但要求大量的代码重构工作。

图 9 汇总了使用面向英特尔架构优化的 Caffe 时遵循的优化技巧和代码重新编写原则。

Step-by-step approach of Intel® Modern Code Developer Code
图 9. 英特尔® 现代代码开发人员代码详细步骤

在测试中,我们使用了英特尔 VTune Amplifier XE 2017 测试版查找热点 — 进行优化和并行化的理想代码目标。 我们实施了标量和串行优化,包括通用代码删除和减少/简化用于索引和条件计算的算术操作。 接下来我们按照“GCC 中的自动矢量化” (https://gcc.gnu. org/projects/tree-ssa/vectorization.html) 中介绍的一般原则优化了矢量化代码。 JIT 汇编程序 Xbyak 有助于我们更高效地使用 SIMD 操作。

我们还在神经网络层中对 OpenMP 库实施了多线程化,其中有关图像或通道的数据操作具有数据独立性特征。 英特尔现代代码开发人员代码方法的最后一步,是针对众核架构和多节点集群环境扩展单节点应用。 这是此次研究与实施的重点。 我们还采用了以重复利用内存(缓存)为目的的优化方法,以提升计算性能。 更多信息请访问: http://arxiv.org/pdf/1602.06709v1.pdf。 面向英特尔至强融核处理器 x200 产品家族的优化方法包括使用高带宽 MCDRAM 内存和象限 NUMA 模式。

面向英特尔架构优化的 Caffe 不仅能够提升计算性能,还可支持您从数据中提取日益复杂的特征。 本文介绍的优化方法、工具和修改将帮助您借助面向英特尔架构优化的 Caffe 实现顶级计算性能。

更多有关英特尔现代代码开发人员代码计划的信息,请参阅以下文章:

关于机器学习的更多信息,请参见:


在性能检测过程中涉及的软件及其性能只有在英特尔微处理器的架构下方能得到优化。 诸如 SYSmark* 和 MobileMark* 等测试均系基于特定计算机系统、硬件、软件、操作系统及功能。 上述任何要素的变动都有可能导致测试结果的变化。 请参考其他信息及性能测试(包括结合其他产品使用时的运行性能)以对目标产品进行全面评估。 如欲了解完整信息,请访问 intel.cn/content/www/cn/zh/benchmarks/intel-product-performance.html

优化声明: 英特尔的编译器针对非英特尔微处理器的优化程度可能与英特尔微处理器相同(或不同)。 这些优化包括 SSE2®、SSE3 和 SSSE3 指令集以及其它优化。 对于在非英特尔制造的微处理器上进行的优化,英特尔不对相应的可用性、功能或有效性提供担保。 此产品中依赖于处理器的优化仅适用于英特尔微处理器。 某些不是专门面向英特尔微体系结构的优化保留专供英特尔微处理器使用。 请参阅相应的产品用户和参考指南,以了解关于本通知涉及的特定指令集的更多信息。

通知版本编号 20110804

英特尔技术的特性和优势取决于系统配置,并需要兼容的硬件、软件或需要激活服务。 实际性能会因您使用的具体系统配置的不同而有所差异。 任何计算机系统都无法提供绝对的安全性。 请联系您的系统制造商或零售商,或访问 intel.com,了解更多信息。

英特尔技术可能要求激活支持的硬件、特定软件或服务。 请咨询您的系统制造商或零售商。

本文件不构成对任何知识产权的授权,包括明示的、暗示的,也无论是基于禁止反言的原则或其他。

英特尔明确拒绝所有明确或隐含的担保,包括但不限于对于适销性、特定用途适用性和不侵犯任何权利的隐含担保,以及任何对于履约习惯、交易习惯或贸易惯例的担保。

本文包含尚处于开发阶段的产品、服务和/或流程的信息。 此处提供的信息可随时改变而毋需通知。 联系您的英特尔代表,了解最新的预测、时间表、规格和路线图。

本文件所描述的产品和服务可能包含使其与宣称的规格不符的设计缺陷或失误。 这些缺陷或失误已收录于勘误表中,可索取获得。

索取本文件中提的、包含订单号的文件的复印件,可拨打1-800-548-4725,或登陆 intel.com/design/literature.htm

该示例源代码根据英特尔示例源代码许可协议发布。

英特尔、Intel 标识、Intel Xeon Phi、英特尔至强融核、VTune、Xeon 和至强是英特尔在美国和/或其他国家(地区)的商标。

*其他的名称和品牌可能是其他所有者的资产。

英特尔公司 © 2016 年版权所有。 所有权保留。

0816/VK/PRW/PDF 334759-001CN

有关编译器优化的更完整信息,请参阅优化通知
附件大小
PDF icon Caffe_optimized_for_IA.pdf1.88 MB