故事背景:笔者开发了一个端侧的模型,是在mobilnetV3-small-0.75(Flops:52M)的基础上魔改出来的,其Flops比mbv3-large-1.0(Flops:58M)是低的,但是其在端侧手机GPU上面的初始化时间却高很多,推理时间甚至是后者的两倍,带着这个问题,以及需要优化模型性能的目标,分析并调研了以下的内容。
在学术界中,往往使用Flops来评估模型算力,其优化也是针对这一方面来优化的
所以,理论上如果我们对于当前的模型,如果设计为skip-ghost-oct-mobilnet-sod的形式(融合怪哈哈哈),其Flops可以进一步降低;
但是此时笔者想到了另外两个问题:
1、之前兄弟团队基于NAS优化mbv3-large-1.0,其搜索出来的模型Flops优化了50%,性能优化却只有8%~20%之间,功耗优化只有1~2mA(测试平台为三星9815、sm8350,功耗不排除测试误差);
2、学术界mbv3性能和效果都优于mbv2,但是工业界却普遍反馈并非如此,为了验证,我们在一个回归任务上,使用训练集训练mbv2和mbv3(两个都经过imagenet预训练),v2误差比v3低2%,同时v2的性能确实比v3要高。
据此分析,Flops和性能功耗成正相关,但是并不是严格正比的关系;
基于高通8450平台,批跑一批图片,测试结果如下:
mobilnet 模型大小,Flops,op种类,层数等对于初始化以及推理性能的影响
模型类别 |
当前模型 |
mbv3-small-1.0 |
mbv2-1.0 |
mbv2-0.5 |
mbv3-large-1.0 |
---|---|---|---|---|---|
Flops | 52M | 58M | 314M | 103M | 224M |
模型大小(float 32,output1~35之间) | 4.85M | 5.93M | 8.46M | 2.62M | 16M |
初始化(无缓存) | 1.66s | 1.02s | 0.656s | 0.645s | 1.16s |
初始化(有缓存) | 170~210ms | 110~160ms | 100~140ms | 108~146ms | 110~170ms |
推理时间 | 24~28ms | 8~11ms | 7~8ms | 6~8ms | 10~12ms |
params | 1.14M | 1.55M | 2.22M | 0.69M | 4.25M |
可以观察到以下几个现象:
经过查询资料以及与负责端侧算法框架的同事沟通后,了解到:
带着这三个观点,结合下面的模型结构图,再看一下上面的现象:(最左边为当前模型)
1、当前模型, 其深度、op种类,都高于mbv3-small-1.0或者mbv3-large-1.0,所以其初始化时间最长;
2、mbv2-1.0, 其深度,op种类都低于mbv3-small-1.0,所以其初始化时间最短;
初始化的时间差异,到这里就明白了。
那么为什么mbv3-small-1.0相对于mbv2-1.0 Flops更低,但是推理时间却更长了呢,Flops和访存量是怎么影响推理时间的呢?
推荐这一篇文章,讲的很详细(下面内容是对这篇文章中一些重点的摘抄):深度学习模型大小与模型推理速度的探讨 – POLARAI.CNhttps://polarai.cn/540.html
先解释几个概念:
Byte
内存交换到底用于进行多少次浮点运算。单位是FLOPs/Byte
。FLOPS。
带宽:也即计算平台的带宽上限,指的是一个计算平台倾尽全力每秒所能完成的内存交换量。单位是Byte/s。
FLOPs/Byte
。RoofLine 模型是一个用于评估程序在硬件上能达到的性能上界的模型,可用下图表示:
当程序的计算密度I较小时,程序访存多而计算少,性能受内存带宽限制,称为访存密集型程序,即图中橙色区域。在此区域的程序性能上界=计算密度×内存带宽,表现为图中的斜线,其中斜率为内存带宽的大小。计算密度越大,程序所能达到的速度上界越高,但使用的内存带宽始终为最大值。
反之如果计算密度I较大,程序性能受硬件最大计算峰值(下文简称为算力)限制,称为计算密集型程序,即图中蓝色区域。此时性能上界=硬件算力,表现为图中的横线。此时计算速度不受计算密度影响,但计算密度越大,所需内存带宽就越少。
在两条线的交点处,计算速度和内存带宽同时到达最大值。
在不同设备上,同一个程序的性质可能发生变化。例如上图中的程序2,在算力稍弱的设备2上属于计算密集型程序,而在算力较强的设备1上就属于计算密集型程序了。如果想要充分发挥设备1的性能,应当适当加大程序的计算密度(比如到程序3的位置)。
网络中的算子可以根据计算密度进行分类。一般来讲,Conv、FC、Deconv 算子属于计算密集型算子;ReLU、EltWise Add、Concat 等属于访存密集型算子。
同一个算子也会因参数的不同而导致计算密度变化,甚至改变性质,比如在其他参数不变的前提下,增大 Conv 的 group,或者减小 Conv 的 input channel 都会减小计算密度。
举个例子,对于不同参数的卷积,计算密度如下:
可以看到,不同参数下卷积算子的计算密度有很大的差异。第 4 个算子 Depthwise Conv 计算密度仅有 2.346,在当下的很多设备上都属于访存密集型算子。
算子的计算密度越大,越有可能提升硬件的计算效率,充分发挥硬件性能。我们以一个 Intel X86 服务器平台为例(10980 XE)。该平台 CPU 频率为 4.5 GHz,我们以 16 核为例,其理论 FP32 算力为 4.608 TFLOPs/s,内存带宽理论值为 96 GB/s。在此平台上的 RoofLine 模型为:
该平台“拐点”的计算密度为 48,计算较为密集的 OP1 和 OP2 处在计算密集区,能够达到平台的算力峰值;而 OP3 和 OP4 处在访存密集区,受内存带宽限制不能到达算力峰值,尤其是 OP4,由于计算访存比过低,计算效率仅有可怜的 4.9%,计算效率并不高。
3. 推理时间
这里涉及到一个 gap,很多部署的同学们更喜欢谈“计算效率”,而实际上算法同学真正关心的点是“推理时间”,导致两者在对接的时候经常会出现一些 misleading。因此我这里单独开一节来探讨一下“推理时间”的评估方法。
其实也很简单,按照 RoofLine 模型,我们很容易就能得到算子实际的执行时间:
这是一个分段函数,拆开来可得:
一句话总结:对于访存密集型算子,推理时间跟访存量呈线性关系,而对于计算密集型算子,推理时间跟计算量呈线性关系。
按照 RoofLine 模型,在计算密集区,计算量越小,确实推理时间越小。但是在访存密集区,计算量与推理时间没关系,真正起作用的是访存量,访存量越小,推理的时间才越快。在全局上,计算量和推理时间并非具有线性关系。
OP4 虽然计算效率很低,但由于访存量也很低,因此其实推理速度还是快于其他几个 OP 的。但是我们可以观察到,其计算量虽然只有 OP1 的 1/130,但是推理时间仅降低到了 1/6,两者并非是线性关系(也是当年我把模型减到 1/10 计算量,但其实没快多少的原因)。
再举两个例子强化一下,首先看这两个卷积,他们的计算量差不多,但是因为都在访存密集区,OP3 的访存量远低于 OP5,其推理也更快:
下面这个栗子更明显,OP5 和 OP6 的区别仅仅是一个是 DepthWise Conv,一个是普通 Conv,其他参数没有变化。按照我们之前的直观感受,Conv 换成 DepthWise Conv 应该会更快,但实际上两者的推理时间是差不多的
有了上面的认知,再结合我们实际项目中使用的平台,分析以下:
下图显示的是两个不同的平台的算力和带宽情况
这里的half就是fp16,float就是fp32,带个4的表示测试的是向量性能,否则是标量性能,GPU用的是宽度为4的向量,half4就是4个fp16数一起运算;
可以看到,现在的AI推理,算力往往不是瓶颈,很多时候被访存性能卡住的,移动端的访存带宽其实并不高,按照8450 GPU,3T的fp16算力,mobilnet才0.5G,计算部分0.16ms的事情,但实际要3-5ms;还有CPU和GPU之间的数据拷贝,那个性能更慢,10G/s附近,折算成ms,是10M/ms,在实际相机中,GPU通常会被各个模块占用,实际给模型能用的就更少了。
还有一个值得注意的地方是,OP的访存量也需要考虑到数据的复用次数,数据从DRAM 加载到CPU或者GPU的寄存器后,实际会复用多次(假设次数为N),而不用每次重复从DRAM中读取,另外还有Cache的影响。
N的次数会随着OP的不同而有所不同,比如Fully Connected和Conv1x1,N会相差很大,这个是由OP的特点和计算策略来决定的。
分析结论:
对于优化的指导:
参考内容:
深度学习模型大小与模型推理速度的探讨 – POLARAI.CN