深度学习模型部署性能分析,Intel和ARM CPU上CNN计算速度差距分析。
前言:一般的深度学习项目,训练时为了加快速度,会使用多GPU分布式训练。但在部署推理时,为了降低成本,往往使用单个GPU机器甚至嵌入式平台(比如 NVIDIA Jetson)进行部署,部署端也要有与训练时相同的深度学习环境,如caffe,TensorFlow等。由于训练的网络模型可能会很大(比如,inception,resnet等),参数很多,而且部署端的机器性能存在差异,就会导致推理速度慢,延迟高。这对于那些高实时性的应用场合是致命的,比如自动驾驶要求实时目标检测,目标追踪等。基本做法都是基于现有的经典模型提出一种新的模型结构,然后用这些改造过的模型重新训练,再重新部署。
一、 模型部署CPU性能分析
我们的一个业务是在Intel CPU的电脑上,进行深度学习模型算法的开发。模型有用到OpenCV,Pytorch,还有QT界面,大多数使用的是图像处理和CNN特征提取和分类的功能,处理的是摄像头输入视频,图片这两类数据。考虑到要求实时处理,我们设计的各个功能模块,能够20fps运行和显示,以及在minutes时间内,输出后处理分析的结果,就能达到预定效果。随后采用docker,部署整个环境到ARM CPU的服务器上。
问题出现在这里:
- 使用Intel CPU在本地开发,模型速度在纯CPU上,能够达到20fps,50-100ms处理完一个图像;
- 而部署到ARM CPU的服务器上,模型速度1fps,也就是说1000ms左右,加上后处理,20s处理一次数据。而且加载深度学习模型,需要耗时几十分钟,整体性能下降100x。
本文就针对深度学习模型的不同CPU架构下,如何高效部署,以及部署的注意事项,和性能差的关键因素,进行分析。提升大家对不同架构部署的性能考虑和定量认识。 大家在ARM ARM64架构,ARM v8指令集,intel core cpu,xeon cpu,包括ARM aarch64的M1芯片计算部署,都会遇到类似问题。可以查看下面的分析,对关键点进行处理。
1.1 开发阶段CPU—Intel X86架构
我们在开发阶段,采用的intel的CPU,core-i7-10875H @GHz 2.30GHz。8核16线程,内存16G,采用NVIDIA-GTX2060GPU。下图是我们采用的Intel CPU,我们在这个电脑上部署测试了性能。Intel CPU有OpenMP+MKL-DNN这些深度学习加速库,速度相对没有指令集优化的有很大提升。
基频2.3GHz,睿频5.1GHz。由于intel x86架构的CPU,单核能够超频处理,所以在测试的时候,其单核性能也许会跑到5GHz的处理速度。当我们的运算代码,测试代码,没有做多线程优化的时候,跑出来的结果,很有可能是单核计算的(大多数Intel CPU服务器,能跑最大性能,给CPU尽量跑满)。因此,intel CPU跑出的性能比ARM 架构服务器CPU跑出的性能,快差不多2倍。ARM CPU总是跑不满。
我们在开发过程中,实际测试的每一个功能,协同CPU和GPU,都能达到需要的速度。也就是20fps处理基本的图像,后处理加分析,也能在几百个ms给出结果。因此,决定部署到服务器上,进行测试。
下面是我们的开发阶段的统计图。每一个功能都有性能记录。
我们也统计了CPU和GPU的性能占用率等信息。不同功能消耗的CPU和内存的利用率统计如下。目前的开发平台配置是8核16线程。内存16G,GTX2060 GPU。
1.2 测试阶段CPU—ARM架构
在测试阶段,我们采用的CPU是飞腾的ARM架构的CPU,使用的arm-cortex a53指令集。FT-2000+/64 is a 64 core ARM server SoC designed by Phytium and introduced in 2019. Fabricated on TSMC’s 16 nm process, the chip operates at up 2.3 GHz with a TDP of 96 W. This chip is designed for server, communication, and infrastructure applications.
ARM 架构的CPU相对X86架构的CPU,能效比,功耗,耗电量,更低。但是,单核的计算力,弱于Intel CPU。所以,在服务器端,基于电量,服务的数量,能效比等多个因素,多采用能耗更低,性价比更高的ARM版本的CPU。单说服务器,其多采用64核,128核等,多核心来应对各种需求,弥补单核算力不足的问题,用核心堆出来的算力。ARM 架构的服务器,用的是定频,不能超频,这款飞腾的CPU采用的2.3GHz。下图是我们使用的ARM架构CPU的相应信息。采用ARM v8.0指令集。单核性能,其实和我们的桌面级CPU差不多。
1.3 模型出现性能偏差的分析
Intel的Core 酷睿CPU,单核性能,比服务器端Xeon或者飞腾ARM CPU所采用的ARM架构core的性能要强。而从总体性能上看,飞腾的ARM CPU 和intel的i7 CPU,性能差不多。不可能造成实际部署后,单张人脸检测和单个表情识别性能上,intel CPU比ARM CPU快20倍;微表情处理速度上,ARM服务器端就慢更多,速度差30倍。
因此,我们可以分析,大概是以下几块出的问题:
-
架构上的差别。 Intel的CPU是x86架构,飞腾CPU是ARM架构,且ARM的指令集比较老,性能较弱。导致了算力相当的CPU核心,在实际计算过程中,差距明显。且intel下是Windows系统,指令集丰富,指令集优化和高效的kernel更多。相比而言,ARM下的指令集,本身较弱,且服务器采用的麒麟操作系统,是麒麟基于Linux开发的一套国产系统。因此,在算术逻辑上的优化,高效计算kernel的设计方面,指令集上的差别。纯ARM端没有做推理优化。而英伟达的Jetson NX2平台,能够做到100fps,是对他的ARM做了优化,且结合tensorRT做了GPU下的推理优化,整体性能当然有质的提升。
(补充一句:NVIDIA的Jetson低功耗计算卡,也是ARM CPU加自家的GPU,之所以很快,是英伟达专门对pytorch,TensorFlow等这些库,进行了重新编译和优化,针对他们的Jetson平台,利用TensorRT,在ARM端做了优化,加上结合Jetson 的GPU,速度能够达到50fps等。
TensorRT是一个高性能的深度学习推理(Inference)优化器,可以为深度学习应用提供低延迟、高吞吐率的部署推理。TensorRT可用于对超大规模数据中心、嵌入式平台或自动驾驶平台进行推理加速。TensorRT现已能支持TensorFlow、Caffe、Mxnet、Pytorch等几乎所有的深度学习框架,将TensorRT和NVIDIA的GPU结合起来,能在几乎所有的框架中进行快速和高效的部署推理。
TensorRT 是一个C++库,从 TensorRT 3 开始提供C++ API和Python API,主要用来针对 NVIDIA GPU进行 高性能推理(Inference)加速。现在最新版TensorRT是4.0版本。) -
代码优化。 服务器天然拥有更多的核心,多核并行处理,多线程下,更占优势。因此,重构我们的代码,在计算流程上,更加流水线,任务多并行。使用多个线程,处理不同的业务。解耦上一个模块和下一个计算单元的耦合。将代码多线程化,从人脸检测,关键点检测,表情分类,微表情分类,都做成不同的线程和任务。且中间变量的优化处理,尤其重要。关键点检测,依赖人脸检测的结果。后面的表情分类和微表情分析,都依赖先前检测到的人脸,因此,没必要每个阶段都重新进行,人脸检测。中间变量缓存,防止重计算。
虽然人脸检测,表情分类和微表情分析,是多个业务,但是也避免需要特征(数据)的重复计算。计算一次,多个业务共用。这里就扯出来第三个问题。 -
中间数据存放数据库或内存中,禁止写到磁盘。 在数据的存储,变量的转移中,从内存摘取到磁盘,SSD,硬盘等,是最慢的部分,需要避免中间数据的磁盘写入。因为人脸检测的结果,需要在表情分类,微表情分析中用到截取的人脸。我们的代码,在实现过程中,为了防止内存爆炸,且开发的CPU内存是16G,视频序列一长,就会内存爆炸。因此,我们是将内存的人脸,写入到磁盘,在后面需要人脸的相关信息时候,再读取。
有两个解决方案,1)中间数据存数据库,害怕内存放不下,但是读写入磁盘,非常慢,这时,用数据库来保存中间变量,我们是非结构的无序数据,可以用MongoDB,数据量再大,可以上Hadoop。这个数据库类型是方便快速访问大量数据。这种数据库有自己的一套机制,来存储和索引相应的数据。而且,数据如何保存,存储,容量大了如何操作管理,有自己的一套机制的。因此不用担心内存不足和访存读写慢。(估计有自己的一套读写方案,来避免直接存储jpg图像到磁盘。我们采用的就是存储为jpg图像)
2)加大内存,内存是DDR4,速度还是比单独写入并读取到磁盘上,快很多。为了避免摘取到磁盘,我们增大内存,16G变为64G或者128G甚至是1T的DDR4。这样就不用担心,存不下中间变量了。这样在后续表情分类,微表情分析上,速度快很多。这样需要考虑,我们的部署应用场景,是长时间一直运行,还是有一个上限,例如,3-5小时,30FPS的图像采集速度。那么大概计算,32G的内存,能够存下3、5小时所产生的的中间变量。
-
指令集/架构优化。 在编译过程中,指定编译平台,架构,指令集,或者可以platform-oriented编译。能够使得编译中,尽量靠近目标架构,CPU的特性。面向CPU及其指令集的优化编译,生成的binary文件,比通用指令编译出来的算法,性能要高,计算过程中,由于是定制化的模型和计算架构,因此效率更高。
除此之外,我们还分析了,也有可能,是pytorch,TensorFlow,未对ARM cortex架构,做优化。最底层的计算图,计算单元,在Intel CPU下,全面优化,计算更快,使用了MKL-DNN,OpenMP等深度学习加速推理库,速度相对没有指令集优化的ARM CPU有很大提升。而针对ARM 的CPU,则没有特殊的指令集优化,导致了相同的计算过程,本身ARM 就要慢上几倍。所以库文件,依赖库,也是有关系的。
二、Intel v.s. ARM CPU各项性能测试实验
我们在不同的CPU,不同的服务器上,测试了LSTM和ResNet单独计算的时间。时间也是长短不一。初步怀疑,是pytorch的版本或者指令集优化问题,导致了,相同的代码,有的算得快,有的算的慢。
包括了:
- i5-10400 CPU@2.90GHz。 12 CPU
- XEON -E5 2690 CPU v4@2.6GHz。32 CPU
- i7-9700 CPU@3.6GHz。 8 CPU
- XEON -E5 2680 CPU v4@2.4GHz。56 CPU
- mac m1芯片
下面给出每一台服务器和电脑的CPU配置和跑测试代码的时间记录。
- i5-10400
- XEON -E5 2690
- i7-9700
- XEON -E5 2680
2.1 多核多个intel CPU测试
CPU 型号 | 支持加速指令 | 1 core 测试时间 | 2 core测试时间 | 4核测试时间 | 8核心测试时间 |
---|---|---|---|---|---|
i5-10400 CPU @2.90GHz 12核 睿频4.3GHz Ubuntu 18.04, x86-64,Linux 5.4.0,本地 mkl-threads 6 |
OpenMP+MKL-DNN | 27.63s | 17.63s | 9.64s | 8.0s |
与上同3080ti服务器 | OpenMP 4.5, 没有MKL-DNN | 36.44s | 24.10s | 16.00s | 13.25s |
i7-9700k CPU @3.60Hz 8核 睿频4.9GHz torch 1.5 linux 5.4 x86-64, | OpenMP+MKL-DNN | 28.46s | 15.40s | 11.1s, | 6.75s |
Intel Xeon CPU E5-2680 v4 @2.40GHz 2 CPU 56核 睿频3.3 GHz centos linux 7,linux 3.10.0 | OpenMP+MKL-DNN | 32.23s | 18.20s | 10.72s | 6.94s |
Intel Xeon CPU E5-2690 v4@2.6GHz,32核,centos linux 7,linux3.10.0 mkl-max-threads=16 | OpenMP+MKL-DNN | 36.72s | 20.44s | 12.31s | 7.34s |
飞腾FT-2000+/64 CPU,64核,2.3GHz, 架构ARM 64 操作系统kylin 4.0,内核 linux 4.4 | OpenMP 4.5,无MKL-DNN | 501.35s 100%CPU |
303.8s 170%cpu |
210.7s 250%CPU |
112.0s 510% |
MacBook ARM CPU m1 3.2GHz, aarch64架构,ARMv8-A指令集架构,8核,mkl-max-threads=1 | 没有OpenMP,有MKL-DNN | 57.10s | 52.94s | 49.6s | 48.62s |
ps:注意事项!! 测试性能的时候,一定要保证,设置的线程数,小于空闲的线程数,而不是强制设置为最大线程数,一旦有其他进程,占用着一些CPU资源。此时强制设置多于可用空闲CPU的线程数时,速度降低非常大,耗时严重。因为程序将严格分配到多个CPU核上,而此时有一些核被占用,需要强制的将这个新的线程去一起抢占。导致速度降低。解释:In our case, we do not use hyper-threading since one thread has fully utilized its physical core resource and adding one more thread to the same physical core will normally decrease the performance due to the additional context switch. (ps:在Windows下面,CPU是看总体利用率的,是12个核都在跑。设置线程越多,分配的计算量越多。)
飞腾的ARM服务器有一个小问题,设置的核心数,都跑不满。2核 170%, 4核 250%,8核 510%。
结论与现象分析
结论与测试现象分析::ARM架构问题,导致了密集型计算非常慢。由于我们测试的是ResNet18,并行计算密集型。ARM架构下,pytorch对于ARM的优化不友好,没有做指令集优化,单核性能差距,没有对深度学习库做指令集优化,底层数学库,MKl,MKL-DNN,OpenMP等,综合导致了在看似性能不错的ARM CPU上(2.4GHz, 64核),跑出的速度,非常慢。
还有一个问题需要注意,查看你配置的docker,配置和部署,是否出问题。如果你的docker是网上下载的别人已经构建好的,只做移植,没有做面向目标架构的优化。特别是编译构建pytorch,OpenCV这些库文件中,在编译过程中,包括目标平台设置,指令集设置,优化参数设置。现有的docker,没有实际在ARM架构上,定制化编译的性能好。这个从docker模拟器中,docker内部测试,外部测试结果可以看出。因此,要保证你部署的docker也是正确的。我已经验证了我们做的docker 是正确的。
- 结论:有无mkl-DNN,影响是一部分,大于1.5-4倍的速度差距。 下面是对MKL-DNN对模型加速的影响测试数据。
-
下面是我对mkl-dnn,有无这个模块的计算分析。在robo intel core CPU这台服务器上,有两个环境,一个是有OpenMP+mkl-dnn的,一个是只有OpenMP的环境。
这个是有mkl-dnn 的pytorch环境。有mkl环境的pytorch,开启mkl 5.65s,未开启mkl 6.35。 -
下面这个是没有mkl-dnn,只有OpenMP的环境配置。没有mkl下,跑出成绩为9.63s。
-
下面是另外一个intel xeon CPU服务器的配置。在eval模式下,开了mkl-dnn比没有开mkl的,时间要少1s。速度快1s。3.27s mkldnn设置, 4.10s 未设置mkldnn。 占cpu 2800%。
-
下面是intel core i7 CPU服务器的配置。在eval模式下,开了mkl-dnn时间为7.25s左右,没有开mkl-dnn设置的,为9.42s。 速度快了2s。
-
下面是我们,真正部署的服务器,麒麟系统,飞腾芯片这款ARM CPU。
如图所示,没有mkl,没有mkl-dnn。总的影响是:单核cpu算力倍率 x 没有mkl-dnn倍率 x ARM pytorch计算性能差距与intel差别:
性能差距 = 服务器CPU与intel cpu单核性能差距(单核20.4/10.05=2~2.5) x (开启mkl/未开mkl =4.1/3.27=1.1~1.2 )x(有无mkl-dnn =9.63/6.35=1.5 ) x(ARM 架构下依赖库计算的性能差 46.8/11.45s =4 ~7 ) = 13.2~31.5倍。 因此,我们做实验统计的速度差的比值倍数,是合理的。
所以,综上所述:有没有OpenMP支持,速度影响一部分。在1-2s内的影响。所采用的pytorch版本是否支持mkl-dnn,影响一部分。在mac ARM m1芯片下,开启mkl-dnn,速度比没有开启快4s。44s 与 48s的差别。M1 的ARM平台只有mkl-dnn,没有OpenMP指令集,no avx指令集,速度也比较慢。我们的Intel CPU平台,都是支持mkl-dnn。单纯没有mkl-dnn,速度差距不是太明显,只是一部分。
速度差距这么大,是多方面因素造成的。包括了本身CPU性能,深度学习库的底层加速指令集是否支持和优化,综合造成的。
2.2 纯单核心CPU计算性能测试
我们设置了几个小的计算代码,包括计算log+cos的函数,和计算是否是素数的函数。for 循环 20000000次。测试的表格如下:其中,intel core cpu性能强于xeon服务器CPU。因为单核性能,core CPU强于xeon CPU,能超频。
i5 10400 CPU @2.90GHz | 8.63s+8.18s |
---|---|
9700k CPU @3.60Hz | 7.11s+6.85s |
Xeon CPU E5-2680 v4 @2.40GHz 南京 | 11.97s+10.05s |
Intel Xeon CPU E5-2690 v4@2.6GHz 牛总 | 12.8s+11.2s |
i7-4930K CPU @ 3.40GHz | 18.5s+12.40s |
ARM m1芯片 | 10.6s +9.8s |
飞腾ARM 芯片 服务器 | 22.8s+20.4s 跑满单核100% |
飞腾芯片,在单核计算性能上,就比intel CPU慢2+倍。然后加上上面所提到的:pytorch在ARM 上,优化不是很好。
再次验证了我们的速度差距的公式,也就是速度分解因子:
性能差距= 服务器CPU与intel cpu单核性能差距(单核20.4/10.05=2~2.5) * (开启mkl/未开mkl =4.1/3.27=1.1~1.2 )*(有无mkl-dnn =9.63/6.35=1.5 ) * (ARM 架构下依赖库计算的性能差 46.8/11.45s =4 ~7 ) = 13.2~31.5倍。
三、补充支撑材料
我们上述的实验分析,也从多个论文和官方文件给出的性能评测,找到了对应的支撑点。证明了我们所提出的原因,是正确的,和实际存在的。造成相同深度学习模型,在ARM CPU 和intel CPU 上部署,差距十几二十倍的效率,是多方面造成的。需要对症下药。
Optimizing CNN Model Inference on CPUs.
这篇论文,分析了CPU平台,做推理优化的各种方法,主要是两类:算子级别的算子融合,计算图级别的图优化方法。进行了operation-level和graph-level的优化。在对比实验中,可以看到,ARM CPU比intel和AMD的CPU,相同的模型,相同的框架下,mxnet和TensorFlow框架下,都慢5-10倍。基本上表明了,在ARM 的CPU上,进行推理和训练,优化pytorch,TensorFlow框架等问题,本身就要慢10倍左右。这是ARM 架构造成的。
第二个结论是,多线程的个数,当大于8个线程时,所获得的gain不大。当然,文章提出的方法,能够最大化的获得多线程的gain。按照传统的mxnet和TensorFlow框架下的性能看,当num threads在10的时候,已经是瓶颈了。上面这些对比实验和数据,也验证了我们所提出的问题点,bottleneck也是正确的。其中,mxnet,TensorFlow,利用了mkl-dnn,OpenMP,OpenBLAS,Eigen等多个优化加速库。和我们的验证对比点和配置环境,是一样的。
3.1 参考资料
This page details benchmark results comparing MXNet 1.3.0 with MKLDNN vs without MKLDNN (integration proposal). The results clearly shows that MKL-DNN boosts inference throughput between 6x to 37x, latency reduced between 2x to 41x, while accuracy is equivalent up to an epsilon of 1e-8.
https://cwiki.apache.org/confluence/display/MXNET/MXNet+with+Intel+MKL-DNN±+Performance+Benchmarking
Training-Deep-CNNs-w-Horovod-on-Intel-HPC-Architecture
这里面,讲了有intel mkl-dnn和无mkl-dnn的延迟和性能比较。
这六个模型,都比没有使用mkl-dnn的,快1.25x ~ 2.5x。
throughput performance of TensorFlow with MKL DNN for the four models are better than Tensorflow without Intel MKL-DNN (baseline).
Improving TensorFlow* Inference Performance on Intel® Xeon® Processors using MKL-DNN
3.2 测试小技巧
由于在测试过程中,有CPU是被root下的未知进程给占用的。例如我们的一台2080Ti的服务器,root下有一个未知的进程,总是占用600%的CPU。我们一共才8核,未知进程就占用6核。此时,各种测试速度,都不正确,时间异常的久。为了准确测试,我们采用cpulimit命令,来限制某个进程号pid的资源占用。
apt-get install cpulimit
cpulimit -p pid -l 20 (pid是进程号, -l 20是限制在20%。要根据核心cpu数来算。 有些情况20%是要分摊的。)
hostnamectl 查看Linux版本号
lscpu 查看cpu型号
有问题,欢迎评论,共同探讨。