- 来自阿里
- MNN有三个贡献点:
- 提出了预推理机制,在线计算推理成本和最优方案
- 优化了kernel
- 提出后端抽象实现混合调度
- MNN的架构:
- 分离线和在线两部分。
- 离线就是很传统的模型转换、优化、压缩、量化的那一套东西,这里mnn转出的模型文件,后缀是.mnn的。
- 设备上的在线推理有三个部分:
- 预推理、kernel优化和后端抽象。每个算子在预推理的时候,结合算子信息和后端信息,做成本评估,从方案池里面找一个最优方案出来。然后再对方案做kernel级别的优化,整点SIMD啥的。后端抽象了之后就可以支持各种各样的后端。
- 预推理是假设输入大小是固定的,那么计算过程中的内存大小和计算成本都可以预先确定,实现加速。分两个部分:
- 计算方案选择、准备-执行的解耦。
- 计算方案选择:成本评估机制:C(total)=C(算法)+C(后端) ,C表示成本。
- C(算法):以卷积为例,有滑窗和Winograd两种实现可选,总体思路是根据不同的卷积动态选最小化计算成本的方法:
- 如果k=1,那就是个矩阵乘法而已,直接用Strassen算法
- 如果k>1就用Winograd将卷积转换成矩阵乘法。
- 如果输出大小是1,那就要用滑窗,不然就继续用Winograd了
- C(后端):把不同后端的所有算子时间都加起来,然后选时间成本最小的,后端算子的成本计算:
- MUL就是乘法次数,FLOPS 和 t_schedule都是后端的确定常量。
- C(算法):以卷积为例,有滑窗和Winograd两种实现可选,总体思路是根据不同的卷积动态选最小化计算成本的方法:
- 准备-执行的解耦:
这个就比较暴力,就是预推理的时候,把网络跑一边,确定下来内存池,真正推理的时候就重用这个内存池,省去频繁的开辟和释放内存。
- kernel优化包含两部分,算法优化和调度优化,目标是用复杂度最低的算法,做最高效的调度。作者们主要做了两个:
- 一个是Winograd的优化
- 另一个是大矩阵乘的优化:就是前面说的那个Strassen算法加速矩阵运算,MNN是第一个用上这个来加速的推理引擎。Strassen是用加法替换乘法,需要递归调用来最大小性能。在MNN中,对于一个矩阵乘[n,k] X [k,m] ->[n,m] ,直接的乘法次数是mnk ,用Strassen的话只要7*m/2*n/2*k/2 次,但还额外要4次大小为[m/2,k/2] 的矩阵加、4次大小为[n/2,k/2] 的矩阵加、7次大小为 [m/2,n/2]的矩阵加法。所以Strassen算法递归执行的条件是:
mnk- 7*m/2*n/2*k/2 >4*m/2*k/2+4*n/2*k/2+7*m/2*n/2
- 后端抽象类的优点:
- 降低复杂度:这个Backend类统一管理资源加载和内存分配,做前端的可以专心搞算子,做后端的可以专心搞后端API
- 混合调度:MNN可以灵活组合不同后端的算子混合执行,例如卷积在CPU上跑,ReLU又能放到GPU上跑,就是玩
- 轻便:前后端结构,后端可抽离,例如Android不支持Metal就可以直接把Metal模块移除掉,MNN号称支持最全后端
- MNN没有局限于逐个算子优化,而是优化了算子更底层的依赖,例如矩阵乘,可以让新算子得到更简单的加速和优化。