查看原文
其他

MLSys 2020 | 支撑亿级终端设备,阿里淘系端上开源推理引擎MNN解读

机器之心 2021-01-29
机器之心发布

机器之心编辑部

阿里巴巴有诸多的复杂端智能业务场景,对于深度学习引擎的性能、包大小、普适性都要很高的要求。在这样的背景下,阿里巴巴自研的端上推理引擎 MNN 应运而生。

目前,它已经大规模应用在阿里巴巴移动端及 IOT 设备上,支撑了亿级的终端设备以及数十种不同类型的应用场景。在 2019 年的双 11,更是作为底层的技术设施,承担了数千亿级的计算。

MNN 在推理引擎设计与优化方面提出了一系列创新性的思想,论文近期在 MLSys 2020 上发表,并进行了远程 Oral 演讲。


论文地址:https://proceedings.mlsys.org/static/paper_files/mlsys/2020/7-Paper.pdf
MNN 开源地址:https://github.com/alibaba/MNN

注:受 Conv-19 疫情影响,今年包括 AAAI 等会议在内的诸多受邀进行的 Oral 演讲都通过远程的方式进行。

具体而言,MNN 提出了三大核心创新:

  • 运行时半自动搜索架构

  • 卷积算法优化创新

  • 异构设备混合调度


MNN 的整体框架如下图:


运行时半自动搜索架构

半自动搜索,是在模型结构已知的情况下,在已有的高性能计算模块中,按照一定规则,搜索、组合出最适应该模型的计算方案。它是介于以 TVM 为代表的的全自动搜索(i.e. 自动调优)和以 NCNN 为代表的全手动搜索(i.e. 手工实现每个 case)之间一种新颖的设计思想。它的核心洞察在于,TVM 的自动编译优化,难以匹敌针对硬件特性和算子的手写汇编;同时,模型算子、参数的 case 组合无穷多,无法针对每个 case 进行优化。在最后的「数据论证」部分,我们会用实验数据展示 MNN 相对于全自动搜索(TVM)和全手动搜索(NCNN)的优势。

为了支撑运行时半自动搜索的能力,MNN 提出了一个特殊的处理过程,称为「预推理」。预推理过程中,会提前进行算子的计算策略选择和资源分配。

一般情况下深度学习的应用输入尺寸变动的频率比较小或者可以经过特定的预处理阶段变成相对归一的尺寸。而在输入尺寸确定的情况下,我们可以对模型中每个 Op,计算出它的输出大小以及不同计算策略的消耗以及资源需求量,并以此为依据决定每个 Op 的计算策略,提前进行资源的分配。

计算策略选择

算子的计算策略,包含算法与运行后端的选择。每种算子可以有多种计算策略,不同计算策略适用于不同的输入尺寸,MNN 采用 Cost 计算的方式,去决定计算策略的选取。算法的 Cost 与运行后端的调度 Cost 共同决定了一种计算策略的 Cost。公式如下:

 
运行后端的调度 Cost,我们通过在大量实际的机器上测试获得。而需要重点关注的是不同算法实现的 Cost。

以卷积为例,假定输入尺寸为,输出尺寸为,卷积核大小为,则有两种计算策略:滑窗/矩阵乘 与 Winograd 算法。

滑窗/矩阵乘的 Cost 为:


Winograd 算法需要先把图像划分为个小块(,自行指定),然后针对这些小块执行四个步骤:


其中可以预先计算,实际计算过程为后三个步骤,这三步的计算量分别是所以为:



n 增大时,前后变换耗时增加,中间的乘法数减少,因此先降后升,需要找最小值。

相比,取最小,即为卷积的计算策略 Cost:



具体的滑窗/矩阵乘、Winograd 算法的选择流程,可参考下图:


资源预分配

大多数推理框架都会提到「内存复用」,即在网络运行前离线或在线算好每个 feature map 的大小,然后根据数据依赖关系计算一个最大所需内存,申请并把各个 feature map 的指针计算出来。这样做存在一些不易扩展的问题:

  1. 没有考虑算子内部需要缓存的问题,在不同的计算策略下,算子自身可能需要或不需要缓存(比如卷积用滑窗不需要缓存,用 Winograd 算法需要),这时候算子内部的缓存无法复用或预先分配,会增加内存占用或者影响性能。

  2. Feature map 对于异构设备而言(主要是 GPU),所需要的资源往往不仅是一块连续内存,因此异构端无法用同一套内存复用机制,往往需要另外写。

  3. 网络中有可能需要做多路并行,比如 CPU、GPU 各一路,这样之前按整个网络算出来的内存地址就无效了。


MNN 采用一种灵活而简单的策略,解决了资源预分配与复用的问题:

1.计算一遍数据依赖,为每个 Tensor 计算引用计数

2.针对每个算子,把资源管理与计算的代码分开,其中资源管理的代码包括:

(1)计算输出大小
(2)依据输出大小申请 Tensor 资源
(3)算子做 resize 准备:计算策略选择-申请并回收缓存
(4)回收引用计数清零后的输入 Tensor 的资源,回收之后可供下次分配使用

3.在预推理过程中,仅执行资源管理的代码。在推理过程中,仅执行计算的代码。

对于 CPU 而言,资源管理就是内存的申请与释放,资源预分配分别在 MI6 和 P10 上降低了 6.5% 和 7.6% 的推理延时。而在使用 Vulkan 时,资源管理包括显存的申请与释放、Pipeline 资源的创建、Command Buffer 的创建等,这时资源预分配的收益十分可观,分别在 MI6 和 P10 上降低了 75.2 % 和 49.5 % 的推理延时。

卷积算法优化创新

NC4HW4 格式

在推理应用中,大部分算子的输入输出是 NCHW 四维,算子一般可在 N 和 C 方向天然并行,且 N 一般为 1。因此,MNN 采用 NC4HW4 格式,便于算子的 SIMD 优化。下图以向量乘 VMUL 指令为例,说明为什么 NC4HW4 对于性能优化更加合适:

  1. 端上硬件支持以 4 为单元的向量运算。CPU SIMD 与 GPU SIMT+SIMD 均是如此。

  2. 深度学习领域中大部分算子具有通道可拆分特性


 
基于以上两点原因,MNN 大范围地采用了 NC4HW4 的格式。

Winograd 算法创新

前文所提的 Winograd 算法,在最理想情况下,可以把 卷积的复杂度降到 的复杂度。一般情况下也有 2-4 倍的加速,但它的应用受到如下限制:

  1. Winograd 每次需要计算更多的像素,对于尺寸比较小的 feature map 会有冗余,影响性能

  2. 在一些情况下,Winograd 需要前处理/后处理,有可能抵消掉其减少的乘法数收益

  3. 针对每一种卷积核大小与计算数的组合,都需要实现一份代码,优化的工程成本高昂


因此其他框架实现 Winograd,仅对用得最多的 的卷积作多套实现,比如 等。

MNN 提出一种新的实现方案,可以较低的代码编写成本来对所有类型的卷积达到足够的加速:

1.内置 Winograd 因子生成器,可根据需要的 生成 Winograd 变换所需要的 A, B, G 矩阵,矩阵生成需要初始化一个多项式:

 
业界常见的方案是设,但这样 较大时会导致 G 矩阵中的数用浮点表示的误差变大,MNN 为了避免太大的误差,设置。

2.将 Winograd 的算法实现改写为 源变换——重排——矩阵乘——重排——目标变换 的流程,使核心运算由多个点乘转换为更高效的矩阵乘法:
 


3.权重进行预计算,使用 A , B 矩阵进行源变换和目标变换的计算

这样只需要写一份代码,就可以实现任意 F(N, K) 的 Winograd 优化,尽管相对于完全手写的版本性能会差一些,结合前面的半自动搜索的机制,仍然能起到比较明显的加速效果。

基于任意卷积的 Winograd 优化算法,MNN 可以优化 stride > 1 的反卷积实现,参考:
https://github.com/alibaba/MNN/blob/master/source/backend/cpu/compute/DeconvolutionWithStride.cpp

Strassen 算法创新

对于大矩阵乘 C=AB 的计算,学界很早就有 Strassen 算法,其思路是把 A, B 等分拆成 4 个小块,进行一系列的加减计算后,进行 7 次小块矩阵乘,再经过一系列加减计算组装成 C。这样,原本需要 8 次小矩阵乘,现在只需要 7 次,就减少了 1 次矩阵乘。

但这个算法在通常的计算优化中难以应用,原因在于:

  1. 需要递归,影响效率
  2. 在矩阵不是足够大时,不如通常的矩阵乘有效率
  3. 计算过程中需要申请-释放内存,影响运行效率。

而 MNN 通过预推理机制,解决了 Strassen 算法的落地问题:

  1. 把递归申请-"执行"-释放的放到预推理中完成,"执行"步骤仅产生 lambda 函数,供推理时调用
  2. 用内存读写的大小去估 Strassen 算法的收益,判断是否进一步递归,对 的矩阵乘法,判断公式为 
  3. 在推理过程中:按顺序调用预推理产生的 lambda 函数数组即可,无需递归或者申请/释放资源。

代码详见:
https://github.com/alibaba/MNN/blob/master/source/backend/cpu/compute/StrassenMatmulComputor.cpp
https://github.com/alibaba/MNN/blob/master/source/backend/cpu/compute/Convolution1x1Strassen.cpp

异构设备混合调度

与其他深度学习框架的后端 API 不同,MNN 后端 API 的设计理念的独特性在于两点:

  1. MNN 后端 API 帮助实现异构设备的「混合调度」:TFLite 这样的后端 Delegate,会在遇到不支持的算子的时候,回退到算子的 CPU 实现。可以说,TFLite 后端设计是「被动式」的。与 TFLite 这样的后端 Delegate 不同,MNN 的异构调度是「主动式」的,我们称之为「混合调度」:MNN 在创建推理会话时,可以针对算子配置后端,且配置多于一个后端时,会根据后端实现动态选择对性能最优的后端。同时,会话负责衔接后端间的数据拷贝,单一后端仅需实现到数据缓存区域的读写,而无需感知其他后端的存在。这样,就可以在单会话内或多会话间实现后端的自由组合。在后端不支持或性能不适合实现特定算子时,就可以借助其他后端的实现,完成整个推理过程。
  2. MNN 后端 API 的为算子抽象级别,而非例如 TFLite 的子图抽象级别。也就是说,在 MNN 的后端实现中,每个算子是单独实现的,后端实现不需要考虑子图的拓扑结构与优化。这一点,与前文所提的「半自动搜索」有关:在预处理过程中,整个计算图的图优化已经统一提前完成,所以不需要在后端子图实现。另外,算子级别的后端抽象,也极大的提高了后端实现的可调试性:可以将实现有误的后端算子快速定位。

MNN 后端 API 设计如下图所示:


借助于统一的后端 API,MNN 在通用性上取得了很大的成果,支持了目前最广泛的计算设备,包括 CPU(ArmV7, ARM64, ARM64e),Vulkan,OpenCL,OpenGL,Metal 以及 NPU 等。

数据论证

备注:所有的数据均测试于各框架提供的 benchmark 标准下,时间为 2019.9 月论文提交截稿前。

通用性能测试

我们在移动端上最常见的模型和设备上将 MNN 和其他框架进行了对比。模型层面包含 MobileNet V1, SqueezeNet v1.1 以及 ResNet-18。而设备则采用了业界较为普及的 iPhone 8,iPhone X,小米 6(处理器:骁龙 835)以及 华为 Mate 20(处理器:麒麟 980)。性能实验考虑了 CPU 和 GPU 两种类型计算设备,在 CPU 推理性能评测中,我们分别评测了 2 线程及 4 线程的情况;而在 GPU 推理性能评测部分我们则分别对 MNN 支持的所有计算后端都进行了对比。
 


1. 从上述实验中不难看出,在绝大多数模型、设备、计算后端上,MNN 比其他现有的推理引擎在性能上快了大约 20% - 40%。

2. 对于 CPU 上的推理来说,由于 MNN 自身实现了高性能的线程池等底层优化,在 4 线程的并行推理实验中,iOS 平台上的表现相对其他推理引擎快了 30%,在 Android 平台上相对其他框架快了 34%。

3. 而对于利用 GPU 做推理:

(1)在 iOS 平台上,MNN 比起 TF-lite 有较大幅度的性能优势;而跟苹果自身的 CoreML 相比则有这略微的劣势,这一方面经过我们的一些探索,发现和苹果自身的特殊优化息息相关。

(2)而在 Android 平台部分,不同的框架都或多或少在支持不同计算后端时上有一些性能缺陷的情况。比如在 小米 6 之类的设备上,NCNN 运行 Vulkan 后端会有明显的性能差距;而 TF-Lite 的 OpenGL 后端在运行 ResNet-18 表现不尽如人意。而 MNN 则由于自身半自动搜索架构的设计,可以在不同的计算后端中都取得较为优秀的性能表现。

4. MNN 因为采用了比较合理的性能优化手段,在部分高端移动设备上,多线程下的 CPU 推理性能可以和 GPU 相媲美。

半自动搜索性能优势对比

为了更充分的证明 MNN 运行时半自动搜索的合理性,我们提出了两个对照实验。

第一个对照实验是针对那些存在比较边角的算子属性的情况下,各个框架的表现情况。
 


因为 Inception-V3 的模型存在诸如 1×7 and 7×1 卷积这样的边界条件,所以采用传统手动汇编逐一优化的方式下,会存在很多性能盲点。因此在结果中可以明显看出 NCNN 在这种情形下性能会大幅下降。而 MNN 虽然没有针对这样的算子属性进行针对性的优化,但是运行时半自动搜索架构的适应性可以较好的应对这种情况,取得不错的运行性能。

第二个对照实验是 MNN 与 TVM 的性能对比。
 


可以看出,在没有针对任何模型特定的优化的情况下,MNN 相比于 TVM 有可观的性能优势。另外考虑到 TVM 还有模型编译、调优的过程耗时,MNN 更加适合移动端智能的应用。

总结与展望

MNN 通过半自动搜索,卷积算法优化创新和异构设备的混合调度,达到了在绝大多数情况下,领先于业界的性能。我们意识到性能是端侧智能应用非常重要的一环,会在未来持续投入更多创新性的性能优化方案,比如把半自动搜索应用于 MNN 正在建设的动态图训练能力中,让动态搭建的计算图可以选择出最适合当前参数的算子实现。另外,我们还看到端智能应用场景正在往 NLP 和 IOT 方向飞速发展。由于 NLP 的模型普遍较大,IOT 设备相比于移动端算力受限,这些都对模型压缩提出了更高的要求。所以,MNN 在未来除了投资性能优化以外,还会致力于研究更大压缩比、更好性能提升的模型压缩算法,让 MNN 成为端侧推理性能、压缩能力最好的深度学习引擎。


本文为机器之心发布,转载请联系本公众号获得授权。
✄------------------------------------------------
加入机器之心(全职记者 / 实习生):hr@jiqizhixin.com
投稿或寻求报道:content@jiqizhixin.com
广告 & 商务合作:bd@jiqizhixin.com

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存