Optimizing CNN Model Inference on CPUs翻译

摘要

卷积神经网络(CNN)模型的普及以及CPU的普遍存在意味着CNN模型在CPU上推理的更好性能可以为大量用户带来可观的收益。为了提高CNN推理在CPU上的性能,当前的方法(例如MXNet和Intel OpenVINO)通常将模型视为图形,并使用诸如Intel MKL-DNN的高性能库来实现图形的操作。尽管可以通过现成的库在单个操作上实现合理的性能,但由于预定义了本地操作级别的优化,因此该解决方案使在图级别进行优化变得不那么灵活。因此,它是限制性的,并且错过了整体上优化端到端推理管道的机会。本文介绍NeoCPU,这是一种采用完整堆栈和系统优化方案的CPU上CNN模型推理的综合方法。 NeoCPU无需依赖第三方库即可将操作优化为模板,从而可以通过操作和图形级联合优化进一步提高性能。实验表明,与当前在各种流行CPU上的最新实现相比,NeoCPU的CNN模型推理延迟最多降低3.45。

1 引言

卷积神经网络(CNN)模型在计算机视觉应用中的越来越多的使用使该模型体系结构自然成为性能优化工作的重点。同样,CPU在服务器,客户端和边缘设备中的广泛部署使该硬件平台成为有吸引力的目标。因此,对许多用户而言,在CPU上高效执行CNN模型推断至关重要。

CNN模型在CPU上的推断性能有很大的改进空间。执行CNN模型*相等贡献推理实质上是执行由操作组成的计算图。实际上,人们通常使用高性能内核库(例如Intel MKL-DNN [27]和OpenBlas [51])来获得CNN操作的高性能。尽管这些库针对具有正常输入数据形状的常见操作(例如2D卷积)进行了非常仔细的调整,但它们仅专注于(主要是卷积)操作,却错过了在图级别进一步优化端到端模型推断的机会。图级优化通常由深度学习框架来处理,例如TensorFlow [5]和MXNet [8]。

但是,框架可以执行的图形级优化(例如操作融合和数据布局规划)受到限制,因为操作实现已在第三方库中预定义。因此,框架中的优化无法与内核库中的优化协同工作,从而在实践中无法实现显着的性能提升。此外,不同的CPU体系结构依赖于不同的高性能库,并且将库集成到深度学习框架中需要容易出错且耗时的工程工作。最后,尽管这些库经过高度优化,但它们以第三方插件的形式出现,这可能会引入框架中其他库的争用问题。例如,TensorFlow最初使用Eigen库[4]处理CPU上的计算。后来,还引入了MKL-DNN。结果,在运行时MKL-DNN线程与本征线程共存,导致资源争用。总而言之,这种针对CPU的CNN模型推断的特定于框架的方法是不灵活,麻烦且次优的。

由于框架施加的约束,许多深度学习从业者显然对不涉及框架(即与框架无关的方法)的端到端CNN模型推理性能进行优化。最近,英特尔推出了一种通用的CNN模型推理引擎,称为OpenVINO Toolkit [16]。该工具包在英特尔处理器(主要是x86 CPU)上的计算机视觉领域中优化了CNN模型,并声称可以实现更好的性能性能要比深度学习框架本身高。但是,OpenVINO只能提供有限的图形级优化(例如,在ngraph [15]中实现的操作融合),因为它仍然依赖MKL-DNN为精心调整的操作提供性能提升。因此,对于大多数CNN模型,OpenVINO所做的优化仍然不够。

基于先前的观察,我们认为,为了进一步提高CNN模型在CPU上的推理性能,关键是要能够进行灵活的端到端优化。在本文中,我们提出了NeoCPU,这是一种优化CNN模型以在CPU上进行有效推理的综合方法。 NeoCPU是全栈系统的,包括操作级别和图形级别的联合优化,并且不依赖任何第三方高性能库。在操作级别上,我们遵循研究透彻的技术来优化模板中最复杂的计算操作,例如卷积(CONV),这适用于多种CPU架构上的不同工作负载,并使我们能够进行灵活的图形级优化。在图级别,除了常用的技术(例如操作融合和推理简化)之外,我们还通过操纵流经整个模型的数据布局来协调各个操作的优化,以实现最佳的端到端性能。总而言之,NeoCPU以灵活和自动的方式进行端到端优化,而现有作品依赖于第三方库并且缺乏全面的性能调整。

NeoCPU建立在名为TVM [9]的深度学习编译器堆栈上,具有许多增强功能。 TVM允许使用自己的操作级别优化而不是第三方高性能库的可能性,这使得可以灵活地应用我们的操作和图形级别的联合优化。但是,在我们进行工作之前,原始TVM堆栈中只有一种针对特定数据形状的卷积的ARM CPU上只有一个定制的操作级优化,而没有操作级和图形级的联合优化。此外,还存在其他深度学习编译器,例如Tensor Comprehensions [46]和Glow [40]。不幸的是,它们要么没有针对CPU,要么没有很好地优化CPU性能,例如根据论文描述和我们自己的实验,Glow仅优化了CPU的单核性能。因此,我们不将这些作品作为基准。表1总结了NeoCPU与其他CPU相比的功能。据我们所知,NeoCPU在各种流行的CPU上都具有CNN模型推断的竞争性能。

具体而言,本文做出了以下贡献:

  • 提供操作和图形级联合优化方案,以在包括Intel,AMD和ARM在内的各种流行CPU上获得较高的CNN模型推理性能,其性能优于当前的最新实现;

  • 构造一个模板以实现良好的卷积性能,该模板可以灵活地应用于多种CPU架构(x86和ARM)上的各种卷积工作负载,而无需依赖高性能内核库;

  • 设计一种全局方案,以在CNN模型的不同操作中寻找最佳的布局组合,从而在保持各个操作的高性能的同时,最大程度地减少了操作之间的数据布局转换开销。

表1:NeoCPU与现有关于CNN模型推断的工作的并排比较

值得注意的是,本文主要涉及直接卷积计算,而NeoCPU与其他对计算密集型内核的优化工作兼容。通过Winograd [7,29]或FFT [52]进行转换。

我们在具有x86和ARM架构的CPU上评估了NeoCPU。通常,NeoCPU可以为Intel Skylake CPU的15种流行网络中的13种,AMD EYPC CPU的15种中的14种以及ARM Cortex A72 CPU的所有15种型号提供最佳性能。值得注意的是,芯片供应商(Intel MKL-DNN)更仔细地调整了x86 CPU的基准,但ARM CPU的优化程度较低。尽管所选的特定于框架的解决方案(MXNet和TensorFlow)和与框架无关的解决方案(OpenVINO)在一种情况下可能会表现良好,而在另一种情况下则表现不佳,但NeoCPU在不同架构的模型之间高效运行。

另外,NeoCPU产生了一个独立模块,其最小尺寸不依赖于框架或高性能内核库,从而可以轻松部署到多个平台。 NeoCPU在Amazon SageMaker Neo Service 1中使用,使模型开发人员可以优化以推断云中基于CPU的服务器和边缘设备。使用此服务,许多应用程序开发人员已部署了针对在几种类型的平台上进行生产推断而优化的CNN模型。所有源代码已发布到开源TVM项目2中。

本文的其余部分安排如下:第2节回顾了现代CPU的背景以及典型的CNN模型。 第3节详细介绍了我们提出的优化想法以及如何实现它们,然后在第4节中进行了评估。在第5节中列出了相关工作,在第6节中总结了本文。

 

2 背景

2.1 现代CPU

尽管GPU和TPU之类的加速器在深度学习工作负载上表现出出色的性能,但实际上,由于高可用性,在通用CPU上仍存在大量的深度学习计算,尤其是模型推断。当前,大多数安装在PC和服务器上的CPU由x86架构的Intel或AMD制造[1],而具有ARM架构的ARM CPU占据了嵌入式/移动设备市场的大部分[2]。

现代晶体管通过多核[21]使用线程级并行机制来提高整体处理器性能,因为晶体管预算的增加正在逐渐减少,以构建更大,更复杂的单处理器。至关重要的是,要避免在同一处理器上运行的线程之间产生干扰,并最大程度地降低其同步开销,以便在多核处理器上具有良好的可伸缩性。在处理器内,单个物理内核通过SIMD(单指令多数据)技术实现了最高性能。 SIMD将多个值加载到宽向量寄存器中以一起处理。例如,英特尔推出了512位高级矢量扩展指令集(AVX-512),该指令集每个CPU周期最多可以处理16个32位单精度浮点数(总共512位)。不太先进的AVX2处理256位寄存器中的数据。此外,这些指令集利用融合乘加(FMA)技术执行一个矢量化乘法,然后在同一CPU周期内将结果累加到另一个矢量寄存器。类似的SIMD技术在ARM CPU中体现为NEON [3]。如实验所示,我们提出的解决方案可在x86和ARM体系结构上使用。

另外,值得注意的是,现代服务器端CPU通常通过同时多线程(SMT)技术支持超线程[37],在该技术中,系统可以将两个虚拟核(即,两个线程)分配给一个物理核,以实现目标。在提高系统吞吐量方面。但是,超线程的性能改进取决于应用程序[35]。在我们的情况下,我们不使用超线程,因为一个线程已充分利用了其物理核心资源,并且由于附加的上下文切换,在同一个物理核心上再添加一个线程通常会降低性能。我们还使用共享内存编程模型将优化限制在处理器内,因为这是CNN模型推断的典型系统设置。在同一主板上的多个处理器的情况下发生的非统一内存访问(NUMA)模式超出了本文的范围。

2.2 卷积神经网络

卷积神经网络(CNN)通常用于计算机视觉工作负载[23、26、33、36、41-43]。 CNN模型通常抽象为计算图,本质上是有向无环图(DAG),其中一个节点表示一个操作,从节点X指向Y的有向边表示操作X的输出用作( )操作Y的输入(即,Y不能在X之前执行)。执行模型推断实际上是使输入数据流经图形以获得输出。在图形上进行优化(例如修剪不必要的节点和边,独立于输入数据的预计算值)可能会提高模型推理性能。

CNN模型中的大多数计算将归因于卷积(CONV)。这些运算本质上是一系列乘法和累加,通过设计可以充分利用现代CPU的并行化,矢量化和FMA功能。现有工作[19、24、27]已经证明,通过以架构友好的方式安排数据布局并因此进行计算,可以在CPU上实现高性能的卷积运算。剩下的挑战是如何有效地管理流经这些操作的数据布局,以从端到端CNN模型推论中获得高性能。

其余的CNN工作负载主要是与CONV相关的内存绑定操作(例如批处理规范化,池化,激活,逐元素添加等)。常规做法[9]是将它们融合到CONV,以增加工作负载的整体算术强度,从而提高性能。

CNN模型训练的计算图与推理没有本质区别,只是更大(增加了向后操作)和更多计算上不重要的操作(例如损失函数)。因此,针对CNN模型推断所做的优化工作也适用于训练。

 

3 优化

本节详细介绍了我们的优化思路和实现。本文提出的解决方案是进行CNN模型推断的端对端。我们将在评估中显示,我们提出的解决方案具有足够的通用性,可用于各种常见的CNN模型。我们方法的基本思想是将优化视为端到端问题,并寻求全球最佳的优化。也就是说,我们不像许多以前的工作那样偏向于单个操作的局部性能最优。为了实现这一目标,我们首先介绍如何使用可配置模板(第3.1节)在底层优化计算密集型卷积运算。这使它可以灵活地在特定CPU架构上搜索特定卷积工作量的最佳实现,并通过在操作之间选择适当的数据布局来消除不必要的数据布局转换开销(在3.2和3.3节中介绍),从而优化整个计算图。 。

通过在编译过程,操作调度和运行时组件中添加许多新功能,我们基于TVM堆栈[9]实现了优化。原始的TVM堆栈已经完成了一些通用的图形级优化,包括操作融合,预计算,简化批处理范式和辍学的推论[9],这些也继承了这项工作,但本文将不介绍。

3.1 操作优化

优化卷积操作对于CNN工作负载的整体性能至关重要,因为它需要进行大量计算。这是一个经过充分研究的问题,但先前的工作通常会深入到汇编代码级别以实现高性能[24,27]。在本小节中,我们将展示如何利用最新的CPU功能(SIMD,FMA,并行化等)来优化单个CONV,而无需使用繁琐的汇编代码或C ++内部函数。通过高层管理实现,可以轻松地将优化从单个操作扩展到整个计算图。

3.1.1 单线程优化

我们从优化一个线程内的CONV开始。 CONV是计算密集型的,它多次遍历其操作数以进行计算。因此,至关重要的是管理送入CONV的数据的布局,以减少内存访问开销。我们首先重新讨论CONV的计算,以说明我们的内存管理方案。 CNN中的2D CONV使用3D特征图(高度宽度通道)和多个3D卷积核(通常较小的高度和宽度,但通道数量相同)进行卷积以输出另一个3D张量。计算如图1所示,其中包含6个维度的循环:in_channel,kernel_height,kernel_width,out_channel,out_height和out_width。每个内核都沿高度和宽度尺寸在输入要素图上滑动,进行逐元素乘积并累加值以在输出要素图中生成相应的元素,这自然可以利用FMA。内核数量构成out_channel。注意,三个维度(in_channel,kernel_height和kernel_width)是归约轴,不能令人尴尬地并行化。

我们使用常规符号NCHW来描述默认的数据布局,这意味着输入和输出为4-D张量,其批量大小为N,通道数C,特征图高度H,特征图宽度W,其中N为最外层, W是数据的最内部维度。内核的相关布局是KCRS,其中K,C,R,S代表输出通道,输入通道,内核高度和内核宽度。

图1:以CONV为例,并以AVX-512指令的有效实现为例。深蓝色,绿色和浅粉红色描绘了三个内核。为了执行高效的FMA,将多个内核值打包到一个ZMM寄存器中,然后重新使用以与不同的输入值相乘,并累加为不同的ZMM寄存器中的输出值。

按照惯例[27,45],我们将特征图布局组织为NCHW [x] c,以获得更好的内存访问模式(即,更好的缓存局部性),其中c是通道C在超维上的拆分子维,其数量为x表示子维度的分割大小(即#channels = sizeo f(C)sizeo f(c),其中sizeo f(c)= x)。输出具有与输入相同的布局NCHW [y] c,而分离因子可以不同。相应地,卷积内核以KCRS [x] c [y] k进行组织,其中具有分割大小x的c和具有分割大小y的k分别是输入通道C和输出通道K的子维度。值得注意的是,要获得所需的布局,需要付出大量的数据转换开销。

除了重新排序尺寸外,为了更好地利用最新的矢量化指令(例如AVX-512,AVX2,NEON等),我们使用reg_n因子将out_width拆分为ow_outer和ow_inner,并在内部将ow_inner的循环移动以进行寄存器阻塞。例如,在具有AVX-512的CPU上,我们可以如下使用其32 512位宽度的寄存器ZMM0􀀀ZMM31[28]。我们维护循环层次结构,以使用一个ZMM寄存器存储内核数据,而使用其他ZMM寄存器存储功能图。存储在一个ZMM寄存器中的内核值(在float32中,最多512位,也就是16个输出通道)用于与通过AVX-512F指令[28]连续存储在DRAM中的多个输入特征图值相乘。然后将其累加到存储输出值的其他ZMM寄存器中。图1说明了这个想法。对于其他矢量化指令,同样的想法适用,但是out_width(即reg_n)的分割因子可能会更改。

算法1总结了我们在单线程中对CONV的优化,与先前的工作一样,该优化实质上是关于1)友好内存局部性的维排序和2)寄存器块化以实现良好的矢量化指令利用率。但是,与其他语言不同,我们将其作为高级语言的模板,可以轻松配置块大小(x,y),使用的寄存器数(reg_n)和循环展开策略(unroll_ker)。因此,可以根据不同的CPU体系结构(缓存大小,注册的矢量宽度等)以及不同的工作负载(功能图大小,卷积内核大小等)来调整计算逻辑。这很灵活,可以实现图级优化,我们将在后面讨论。

 

3.1.2 线程级并行化

通常将CONV分成不相交的部分,以在现代CPU的多个内核之间并行化。像英特尔MKL-DNN这样的内核库通常使用现成的多线程解决方案,例如OpenMP。但是,我们发现现成的并行化解决方案的可伸缩性是不可取的(第4.2.4节)。

因此,我们实现了定制的线程池,以有效处理这种令人尴尬的并行化。基本上,在具有N个物理核心的系统中,我们将操作的最外面的循环均匀地划分为N个片段,以分配给N个线程。然后,我们使用C ++ 11原子来协调fork-join期间的线程,并使用调度程序和每个工作线程之间的单生产者-单消费者无锁队列来分配任务。通过线程绑定确保活动线程在不相交的物理核心上运行,以最大程度地减少硬件争用,并且不像2.1节中讨论的那样使用超线程。对于由多个线程(例如无锁队列)访问的全局数据结构,我们根据需要插入了缓存行填充,以避免线程之间的错误共享。总而言之,根据我们的评估,此定制的线程池采用了有意的机制来防止资源争用并减少线程启动开销,这使其优于OpenMP。

3.2 消除布局转换

在本小节中,我们将优化范围从单个操作扩展到CNN模型的整个计算图。这里的主要思想是在图级别上提出通用解决方案,以最大程度减少第3.1节中的优化所引入的数据布局转换。以前专注于单个操作优化的工作[19,24,27]通常不考虑高度优化操作之间的数据布局转换开销。

由于NCHW [x] c对于占用大部分CNN模型计算的CONV是有效的,因此我们应确保在此布局中执行每个CONV。但是,CONV之间的其他操作可能仅与默认布局兼容,这使得每个CONV在计算之前将输入数据布局从默认(NCHW或NHWC)转换为NCHW [x] c,并在最后将其转换回去。这种转换会带来大量开销。

幸运的是,从图级别的角度来看,我们可以将CONV之外的布局转换作为一个独立的节点,并仅在必要时插入它。也就是说,我们消除了CONV操作中发生的变换,并尽可能地保持了变换后的布局在整个图形中的流动。

为了确定是否需要进行数据转换,我们首先根据操作与数据布局的交互方式将操作分为三类:

  1. 布局无关的操作。这些操作在不知道其布局的情况下处理数据,即它可以处理任何布局的数据。一元运算(例如ReLU,Softmax等)属于此类。

  2. 允许布局的操作。这些操作需要知道要处理的数据布局,但是可以处理许多布局选项。例如,在我们的案例中,CONV可以处理NCHW,NHWC和NCHW [x] c布局。其他操作(如Batch_Norm,Pooling等)也属于此类别。

  3. 与布局有关的操作。这些操作仅以一种特定的布局处理数据,也就是说,它们不容许任何数据转换。因此,必须先将布局转换为某种格式,然后再进行依赖于布局的操作。诸如Flatten,Reshape等的转换操作属于此类。

图2:简单的CNN模型的布局优化。边缘上的符号表示通过此边缘的数据的布局。左侧显示了具有默认数据布局的网络。粉色的每个CONV节点都需要付出额外的开销,才能将数据转换为合适的布局以实现良好的性能,然后再转换回默认值。右侧的网络在图形级别进行了优化,以最小化运行时的数据布局转换。绿色的CONV节点在计算之前和之后不需要转换任何数据。

典型CNN模型中CONV之间的操作要么是布局无关的(例如ReLU,SoftMax,Concat和ElemwiseAdd),要么是布局允许的(例如Batch_Norm,Pooling),从而可以在整个卷积中将数据布局保持为NCHW [x] c层。从NCHW到NCHW [x] c的布局转换发生在第一个CONV之前。 CONV之间的数据布局可以保持不变(即NCHW [x] c共享相同的x值),而无需进行转换。仅当进行与布局有关的操作时,例如展平后,数据布局从NCHW [x] c转换回NCHW。

实际上,我们首先遍历计算图以推断每个节点的数据布局,如图2左侧所示,然后我们将CONV的布局从默认更改为NCHW [x] c,以获得更好的性能。请注意,为防止进一步的变换,我们在所有CONV上将x设为常数(例如16)。但是,此值在不同的CONV之间可能会有所不同,以便获得最佳性能,这需要进行布局转换。我们将在3.3节中对此进行详细说明。最后,将LayoutTransform节点相应地插入到图形中。因此,我们仍然具有网络的NCHW输入和输出,但是CONV层之间的内部布局处于优化的NCHW [x] c中,如图2的右侧所示。值得注意的是,模型的布局诸如卷积核权重以及Batch_Norm的均值和方差之类的参数是不变的,因此可以在编译期间进行预转换。我们还在图2的右边部分对此进行了说明。

我们通过向TVM堆栈引入多个图级优化过程来实现这些想法。通过使转换后的数据布局在CONV层之间尽可能保持不变,并在编译时对卷积核权重的布局进行预转换,我们可以进一步提高CNN模型推断的端到端性能。

3.3 优化方案搜索

我们基于对硬件的理解,例如上面提到的优化方案,提出了上述优化方案,特别是如何布置数据。高速缓存大小,矢量化单元宽度,内存访问模式等。但是,手工耗尽所有可能的最佳情况既繁琐又不切实际。作为权衡,第3.2节假设通道的分割因子(即NCHW [x] c中的x)在整个网络中保持不变,而在不同的CONV中具有不同的x值可能会带来更好的性能。另外,输出宽度的分割因子,即reg_n,也需要针对不同的矢量化指令集进行调整。

因此,需要自动搜索最佳方案以进一步提高性能。基本上,我们应该构建一个系统,以使领域专家能够为机器构建搜索空间,以探索最佳方案,从而缩短执行时间。搜索分为两个阶段,首先在本地进行查找,以找到各个计算密集型操作的优化方案候选者,然后进行全局选择和组合各个方案,以获得最佳的端到端结果。给定第3.1节中描述的优化模板,进行这种搜索是可行的。

3.3.1 本地搜索

第一步是找到每个计算密集型操作(即CNN模型中的CONV)的最佳计划。我们使用元组(ic_bn,oc_bn,reg_n,unroll_ker)表示卷积计划,其项被选择为涵盖不同的CPU架构以及针对不同卷积工作负载的代。前两项ic_bn和oc_bn代表输入和输出通道的分割因子(即NCHW [x] c表示法中的x),与特定CPU的缓存大小有关。第三项reg_n是要在内部循环中使用的SIMD寄存器的数量,该数量在不同的CPU体系结构和不同代之间有所不同。此外,我们观察到在单个线程中使用所有SIMD寄存器并不总是能返回最佳性能。最后一项unroll_ker是一个布尔值,它决定是否展开涉及卷积内核计算的for循环(算法1的第12行),因为在某些情况下,展开此循环可能会通过减少分支惩罚等来提高性能。本地搜索使用3.1.1中讨论的模板来找到这些值的最佳组合,以最大程度地减少CONV执行时间,类似于[31]中的内核优化步骤。

具体来说,本地搜索的工作方式如下:

  1. 定义ic_bn和oc_bn的候选列表。为了穷尽所有可能的情况,我们将所有因素都包括在内。例如,如果频道数为64,则将[32、16、8、4、2、1]列为候选。

  2. 定义reg_n的候选列表。实际上,我们从[32、16、8、4、2]中选择reg_n值。

  3. 将unroll_ker的候选列表定义为[True,False]。

  4. 遍历定义的空间以测量所有组合的执行时间,每个组合将多次运行以求平均,以抵消源自操作系统和/或其他进程的意外干扰而导致的可能差异。最终会生成按执行时间升序排列的组合列表。

值得注意的是,我们以可配置的方式设计了上述元组,这意味着我们可以随时根据需要修改元组(例如,添加或删除项目,修改项目的候选值)。

根据经验,使用一台机器进行CNN模型的本地搜索需要花费几个小时,这是一次性的工作,因此可以接受。例如,在18核Intel Skylake处理器上搜索ResNet-50的20种不同的CONV工作负载大约花费了6个小时。此外,我们可以维护一个数据库来存储每种CPU类型上每种卷积工作量(由特征图和卷积内核大小定义)的结果,以防止在不同模型中重复搜索同一卷积。

本地搜索对于每个单独的操作都很好,并且确实找到了比我们的手动工作更好的优化方案。但是,贪婪地采用每个操作的局部最优可能不会导致全局最优。考虑两个连续的CONV操作conv_0和conv_1,如果conv_0的输出分割因子(oc_bn)与conv_1的输入分割因子(ic_bn)不同,则需要将3.2.3.2节中讨论的LayoutTransform节点插入到图中。这种转换开销可能太昂贵,无法利用局部最优带来的好处,尤其是在网络数据量很大时。另一方面,如果我们在整个网络中保持相同的分裂因子(如我们在3.2节中所做的那样),我们可能会错过优化某些CONV的机会。因此,应该使用全局搜索进行权衡。

3.3.2 全局搜索

在本小节中,我们将优化搜索扩展到整个计算图。想法是允许每个CONV自由选择分割因子x(即ic_bn和oc_bn),并考虑相应的数据布局转换时间。根据3.2节,CONV之间的操作可以忽略布局,也可以允许布局,因此它们可以使用由CONV操作确定的x。

我们在图3中提取了一个典型CNN模型的代码片段来说明这一想法。从图中我们可以看到,每个CONV都有许多由不同的(ic_bn和oc_bn)对指定的候选方案。在本地搜索步骤中,可以获得每对实现的最短执行时间。由于ic_bn和oc_bn通常都具有小于10的选择,因此对的数量限制为100。选择不同的方案将引入不同的数据转换开销(在CONV之间用虚线框表示)或不进行转换(如果CONV的oc_bn等于ic_bn其后继)。为简单起见,在图中,我们省略了不影响全局搜索决策的操作,例如两个CONV之间的ReLU,Batch_Norm。但是,不能忽略诸如Elementwise_ Add之类的操作,因为它要求其两个输入操作数(图中的CONVj和CONVk的输出)的布局相同。

天真地讲,如果CNN模型由n个CONV组成,每个CONV具有ki个候选方案,则全局方案的选项总数将为Õni= 1 ki,随着层数n的增加,很容易变得难以处理。幸运的是,在实践中,我们可以使用动态编程(DP)算法来有效解决此问题。请注意,在为CONV选择方案时,我们只需要考虑其及其前任CONV的数据布局,而无需考虑任何其他祖先CONV,只要迄今为止的全局最优方案一直到该前任被记住。

 

因此,在算法2中构造了一种简单的算法。实际上,许多CNN模型的结构都像列表一样简单,其中每个CONV仅具有一个前任[33,41]。在这种情况下,完成CONV后,可以安全地删除为其前身存储的中间状态。对于具有更复杂结构的网络,例如使用Elementwise_Add添加两个CONV输出以馈送到下一个CONV [23],这比较棘手,因为可能需要保存CONV的方案以备将来使用(例如,在图3中,CONV1需要通过Elementwise_Add的CONVj方案)。

图3:全局搜索CNN模型推论。可以根据全局决策调用或不调用LayoutTransform。如果调用,则需要支付以黄色表示的数据转换的额外开销。

但是,如果模型结构变得过于复杂,CONV之间存在许多数据依赖关系链接,那么直接的DP算法也可能变得棘手。例如,在对象检测模型SSD [36]中,由于许多级联块的出现,状态的数量可以达到数万亿个数量级。在这种情况下,我们介绍了一种近似解决方案以加快搜索速度。特别是,我们将全局搜索问题简化为规范的编译器域中的寄存器分配问题,具体方法如下所示。寄存器分配问题被建模为图形表示,其中每个节点(变量)都有一个包含所有可能的寄存器选项的候选列表,并且每个边都与指示两个节点之间寄存器的可用性的成本矩阵相关联[20]。类似地,在我们的全局搜索中,每个CONV都有一个候选方案列表,每个边缘都与两个CONV的方案列表生成的布局转换成本矩阵相关联。对于需要在同一布局中的所有输入的其他非CONV节点(例如Elementwise_Add),我们固定了一个输入的布局并将所有其他输入的布局转换为该布局。因此,我们将非CONV节点的候选列表定义为与第一个输入CONV相同,并且将这两个节点之间的边缘上的成本矩阵定义为相同,因为所有对角元素均为0,所有其他元素均为无穷大。对于此非CONV节点和其他输入节点之间的边缘,从第一输入节点和其他输入节点生成成本矩阵。进行此类修改后,图中的所有节点和边都具有寄存器分配建模所需的有效属性。这使我们能够将基于分区布尔二次规划(PBQP)的启发式求解器应用于我们的问题,因为它是在寄存器分配中完成的[20]。

为了验证该近似算法的结果,我们将其与DP易于处理的一些简单网络上的DP结果(最佳保证)进行了比较。事实证明,近似算法至少可获得最佳可用结果的88%。根据经验,对于大多数CNN模型,典型的DP搜索将在1分钟内完成。实际上,如果DP在5分钟内未完成,我们将切换为近似算法。近似算法可以快速完成,例如在10秒内。对于我们在第4节中评估的15个流行网络,仅大约完成了SSD。

 

4 评估

本节通过回答以下问题来评估我们提出的解决方案NeoCPU的性能:

  1. 与各种CPU上的最新替代产品相比,NeoCPU的总体性能如何?

  2. 我们提出的每个优化构想的个人贡献是什么?

所有实验均在Amazon EC2实例上完成。我们在以下三种CPU上评估了NeoCPU:Intel Skylake(C5.9xlarge,18个物理内核,AVX-512具有功能),AMD EPYC(M5a.12xlarge,24个物理内核,AVX2具有功能)和ARM Cortex A72(A1.4xlarge) ,16个物理内核(NEON提供)。尽管在云上进行了测试,但由于具有相同的架构,我们的ARM CPU结果适用于Raspberry Pi和Amazon Echo Dot等边缘设备上的结果。所有内核均具有统一的内存访问权限。

NeoCPU建立在TVM堆栈0.4.0的代码库之上。对于具有x86架构的CPU,我们选择了两个特定于框架的解决方案和一个与框架无关的解决方案作为比较的基准。对于特定于框架的解决方案,我们研究了多种选择,并发现与其他(例如Intel Caffe)相比,启用了Intel MKL-DNN v0.15的MXNet 1.3.1具有最广泛的模型覆盖范围和最佳的推理性能。此外,由于其受欢迎程度,我们选择了具有ngraph v0.12.0-rc0集成的TensorFlow 1.12.0(经验证明在CPU上优于TensorFlow XLA)。与其他流行的深度学习框架PyTorch [14]相比,TensorFlow在CPU上具有更好的性能。最新的Intel OpenVINO Toolkit 2018 R5.445作为与框架无关的解决方案。我们使用官方的图像分类样本3和对象检测-ssd样本4进行基准测试。对于ARM CPU,我们选择了带有OpenBlas 0.2.18的MXNet 1.3.1和带有Eigen fd68453 5的TensorFlow 1.12.0。没有执行与框架无关的比较,因为在ARM CPU上没有OpenVINO与x86 CPU对应。此外,在GCC 7.3中实现的OpenMP 4.5与我们自己的线程池进行了比较,以实现多线程可伸缩性。注意,所有实现都使用直接卷积。结合高级卷积算法以进一步提高性能仍然需要将来的工作。

我们对许多流行的CNN模型进行了模型推断,包括使用ResNet-50作为基础网络的ResNet [23],VGG [41],DenseNet [26],Inception-v3 [43]和SSD [36]。 MXNet和OpenVINO使用的模型来自Gluon Model Zoo6。TensorFlow使用的模型主要是从TF-SLim 7获得的,对于某些缺失的模型(例如ResNet-34,DenseNet-169),我们手动创建了它们。不同格式的相同模型在语义上是相同的。从TVM堆栈继承而来,NeoCPU与Gluon和TF-slim格式兼容,并且在评估中我们使用了前者。遵循流行的惯例,除了Inception Net(299 299)和SSD(512 512)外,模型推断的输入数据为224 224个图像。由于模型推断的最重要性能标准是等待时间,因此我们以批次大小1进行了所有实验,即每次仅将一张图像馈入模型以测量推断时间。因此,我们将NCHW [x] c中的值N固定为1。NeoCPU也适用于较大的批处理大小,在这种情况下,我们只需要将N值添加到配置元组中即可。

由于我们的优化不会改变模型的语义,因此我们不希望模型输出发生任何变化。作为健全性检查,我们将NeoCPU生成的结果与其他基准(图像分类模型的预测准确性和对象检测模型的平均准确性预测)进行了比较,以验证正确性。

4.1 整体表现

首先,我们在表2中报告了15种流行CNN模型与不同CPU上的基线相比所获得的总体性能。结果是通过平均1000个样本的执行时间并一次进行一次推理得出的。通常,与不同的基准相比,NeoCPU在不同的CPU体系结构上的不同模型之间的效率要高得多(在不考虑可疑的OpenVINO异常值的情况下,最高可提高11倍的速度),这将在后面进行解释。与每种型号的最佳基准结果相比,NeoCPU在Intel Skylake CPU上的性能为0.94-1.15,在AMD EYPC CPU上的性能为0.92-1.72,在ARM Cortex A72 CPU上的性能为2.05-3.45。

作为特定于框架的解决方案,MXNet和TensorFlow对于CPU上的CNN推理次优,因为它缺乏执行足够的图形级优化的灵活性(例如,灵活的数据布局管理)。 MXNet具有Intel的MKL-DNN主动支持,因此在具有x86架构的CPU上表现出色。由于可扩展性问题(图4c中所示),MXNet在ARM上的性能比TensorFlow差。 TensorFlow在SSD上的表现要差得多,因为它向该模型引入了分支,这需要在运行时做出动态决策。相比之下,OpenVINO提供的与框架无关的解决方案试图通过消除框架限制来进一步提高性能。但是,OpenVINO的性能在各个模型中都不稳定。尽管OpenVINO在某些情况下会产生吸引人的结果,但由于某些未知原因,OpenVINO有时在某些型号上的表现极其缓慢(例如,对于AMD上的ResNet-152,它的速度比我们慢45)。在总结加速结果时,我们不包括这些异常值。还值得注意的是,OpenVINO可以在不考虑大量操作(包括多盒检测)的情况下测量SSD的执行时间。由于OpenVINO不是开源的,因此我们无法对其进行修改以在SSD型号上进行苹果对苹果的比较。 OpenVINO不适用于ARM CPU,因为它依赖MKL-DNN,后者仅针对具有x86架构的CPU进行优化。 NeoCPU优于基准的主要原因是我们在第3节中介绍了先进的优化技术。此外,所有基准在很大程度上都依赖第三方库(MKL-DNN,OpenBlas,Eigen)来获得良好的性能。另一方面,NeoCPU独立于那些高性能库,这为我们提供了更多空间来整体上优化模型推断。

表2:NeoCPU的整体性能和所选基准。每个条目均包含1000次运行的平均值和相应的标准误差。每种型号的最佳性能以粗体显示。 (* Intel和AMD CPU上的OpenVINO不能衡量整个SSD执行时间)

4.2 优化的含义

本小节通过研究我们在第3节中描述的每种单独的优化技术的性能提升,来打破NeoCPU的端到端性能提升。为了节省空间,在每次比较中,我们分别只从一个网络家族中选择一个网络。 。 同一家族中的其他网络共享相似的利益。 我们仅在第4.2.1-4.2.3节中报告有关Intel CPU的性能结果。 优化效果也适用于AMD和ARM CPU。 基本上,第4.2.1节是操作级别的优化,而第4.2.2和4.2.3节则涵盖操作级别和图级别的联合优化。

4.2.1 CONV的布局优化

首先,我们比较表3第二行中CONV操作在有存储器访问和无向量化指令使用友好布局(NCHW {x} c)中组织数据的性能。比较第4.1节中比较基准通常应用的操作级别优化。我们使用TVM调度方案将其复制为模板,而无需使用汇编代码或内部函数,从而可以针对不同CPU架构上的各种CNN模型进行后续优化。从表3的第2行中可以看到,与默认数据布局(NCHW)相比,性能得到了显着改善,默认性能为基线1。原始TVM堆栈,例如运算融合,预计算,推理简化等。

表3:与NCHW基准相比,我们的优化带来的个人提速。第n行的加速是通过应用优化技术直到这一行来实现的。

4.2.2 消除布局转换

其次,我们评估了消除3.2节中讨论的数据布局转换开销所带来的性能提升。结果总结在表3的第三行中。与CONV的布局优化(表3的第二行)相比,消除布局转换进一步将执行时间缩短了1:1􀀀1:5。 NeoCPU通过推断整个计算图中的数据布局并仅在需要时才插入布局转换节点来消除不必要的数据布局转换,这在其他工作中是看不到的。

4.2.3 优化方案搜索

接下来,我们比较了由搜索算法产生的优化方案和由我们精心挑选的优化方案之间的性能。通过比较表3的第三行和第四行,我们的算法(在3.3节中进行了介绍)能够找到(大约)最佳的数据布局组合,其性能比人工选择的结果高1:1􀀀1:5。 ResNet-50(及其变体)从全局搜索中获得了更快的速度,因为网络结构更加复杂,因此留出了更多的优化空间。相反,由于该模型的结构相对简单,因此VGG-19(及其变体)的收益较少。 SSD利用近似算法并获得了显着的加速。结果还证明,通过自动搜索,我们可以通过产生更好的结果来摆脱繁琐的手工参数选择。据我们所知,NeoCPU是唯一进行此优化级别的产品。

4.2.4 多线程并行化

最后,我们使用第3.1.2节中描述的由我们自己的线程池支持的多线程实现以及在GCC编译器中实现的常用OpenMP API进行了强大的可伸缩性实验。我们还包括了使用Intel MKL-DNN,OpenBlas或Eigen(均通过OpenMP实现多线程)的MXNet,TensorFlow和OpenVINO的结果进行比较。我们通过环境变量配置了OpenMP,以确保作业是静态分区的,并且每个线程都在不相交的核心上运行,这类似于我们的线程池的行为,以便进行苹果之间的比较。图4总结了一个模型可以每秒一幅推断图像的数量(即批大小= 1),这是该模型推断使用的线程数量的函数。为了节省空间,我们演示了一种CPU类型的一种结果。该图显示,在NeoCPU以及基准中,我们的线程池具有比OpenMP更好的可伸缩性。尽管任务可以令人尴尬地并行化,但是每个模型推断都由许多并行化区域组成。 OpenMP在一个区域之前和之后启动和抑制线程的开销大于我们的线程池,这归因于OpenMP的可伸缩性较差。此外,有时我们观察到,在添加线程时,OpenMP抖动或什至降低了性能。此外,OpenMP的性能可能在不同的实现中有所不同。总而言之,我们的评估表明,在我们的用例中,最好有一个具有完全控制权的自定义线程池。

图4:不同多线程实现之间的可伸缩性比较。标准误差(<0:4)太小而无法在图中看到。

 

5 相关工作

随着深度学习在现实世界中的应用中显示出越来越强大的功能,人们正在做出大量的努力来加速从CPU [24、27、44、53],GPU [ 11、13],FPGA [18、22、49]到专用加速器[12、32]。现代深度学习框架通常利用这些优化的实现来对相应的硬件目标进行深度学习训练和推理。还有专为推理而设计的工作,以解决特定于推理的要求,例如不同硬件目标(例如GPU [38],ASIC [22])上的低延迟和较小的二进制大小。 NeoCPU更加灵活,并且智能地结合了操作和图形级别的优化。尽管本文关注于CPU,但是这些思想也适用于其他硬件目标。

NeoCPU基于TVM堆栈[9],这是受Halide [39]启发的端到端框架,该框架将深度学习模型表达为中间表示(IR)并编译为机器代码。还有其他几种类似的深度学习编译器,例如TensorFlow XLA [34],Tensor Comprehensions [46],Glow [40]和DLVM [47]。但是,到目前为止,他们都没有报告过与我们所做的结果相同的CPU推断结果(例如,Glow仅优化了CPU的单核性能)。我们认为,我们提出的解决方案可以成为这些框架的组成部分。

我们遵循在其他高性能库[27,51]中实现的经过充分研究的思想,以优化计算密集型CONV操作。除了这些库之外,还针对英特尔CPU上的卷积和矩阵乘法进行了高度定制的优化工作[19,24]。这些工作主要是针对单个操作级别的优化,这些优化不考虑通过整个网络维护数据布局。具体来说,他们仔细研究卷积的计算性质以及可用的CPU资源以微调操作。这种优化能够最大化目标CPU上的卷积性能,但扩展到其他平台并进行联合优化不是很灵活。与其他方法不同,我们将优化作为可配置的模板,以便可以灵活地适应不同的CPU体系结构,并有机会通过操作和图形级联合优化来超越手动调整的性能。

我们的工作利用自动搜索来寻找最佳解决方案。在其他作品中也使用了类似的自动调整思想[10,46,48]。但是,他们都专注于单个操作的性能调整,而我们的研究范围则扩展到了整个CNN模型,以在全球范围内寻找最佳解决方案。最近,我们还观察到了在图级别优化DNN工作负载的其他工作[30]。这项工作尝试使用宽松的图形替换来获得更好的全局性能,这可能会损害一些操作中的局部性能。它的非贪婪搜索想法在概念上与我们的想法相似,并且可能适用于我们的解决方案。 PBQP在寄存器分配问题中的应用启发了我们用于对结构复杂的模型(例如SSD)进行全局搜索的近似算法。本文利用了以前的想法,并通过较小的修改将其应用于新域。

 

6 结论

在本文中,我们提出了一种端到端解决方案来编译和优化卷积神经网络,以在现代CPU上进行有效的模型推理。 实验表明,与当前状态下的性能相比,我们可以在各种CPU(Intel Skylake,AMD EPYC和ARM Cortex A72)上的15种流行的CNN模型上实现高达3:45的加速。 艺术解决方案。 未来的工作包括扩展到其他卷积计算算法(例如Winograd和FFT),处理量化值中的模型推断(例如INT8)以及扩展我们的操作和图形级联合优化思想以在其他硬件平台上工作(例如,与TensorRT相比的NVidia GPU) 。 在动态形状(例如RNN [25,50])中支持优化的模型推断是另一个值得探索的方向。

 

致谢

我们要感谢我们的牧羊人彼得·皮策赫(Peter Pietzuch)和USENIX ATC计划委员会的匿名审稿人的宝贵意见,这些意见极大地改善了本文。 我们也感谢Tianqi Chen和Animesh Jain的有益讨论和建设性建议。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值