MEC —— 优化内存与速度的卷积计算
2017-08-20 13:01
489 查看
本次介绍一种内存利用率高且速度较快的卷积计算方法。 来自ICML2017, 《MEC: Memory-efficient Convolution for Deep Neural Network》
目前,卷积的计算大多采用间接计算的方式,主要有以下三种实现方式:
im2col + GEMM。 caffe等很多框架中都使用了这种计算方式,原因是将问题转化为矩阵乘法后可以方便的使用很多矩阵运算库(如MKL、openblas、Eigen等)。
FFT变换。 时域卷积等于频域相乘,因此可将问题转化为简单的乘法问题。
Winograd。 这种不太熟悉,据说在GPU上效率更高。 NNPACK就是FFT和Winograd方法的结合。
上面三种方法执行效率都还不错,但对内存占用比较高,因为需要存储中间结果或者临时辅助变量。
本文的方法主要改进了im2col + GEMM的策略,目的主要是减少内存消耗的同时顺便提升点速度。由于同样可以利用目前成熟的矩阵运算库,因此算法的实现难度并不大。
下图分别是直接计算卷积以及im2col + GEMM的实现方式。 可见后者需要比较多的内存存储一个临时矩阵(用于保存im2col的结果)。
![](https://img-blog.csdn.net/20170819235048657?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc2h1emZhbg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
![](https://img-blog.csdn.net/20170819234602600?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc2h1emZhbg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
下面给出算法流程以及一个示例(7x7输入,3x3卷积核,步长为1):
![](https://img-blog.csdn.net/20170820000248531?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc2h1emZhbg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
![](https://img-blog.csdn.net/20170819235539196?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc2h1emZhbg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
上面的算法伪代码可能太复杂了,我来个中文版本的示例解说:
1. 由于是3x3卷积核,且步长为1。因此,循环取出A、B、C、D、E这5个子矩阵, 每个矩阵的维度都是: 输入高度x3。
2. 将A、B、C、D、E按照行优先展开并拼成一个大的中间矩阵L, L的维度则为: 5x21。
3. 从L中循环取出P、Q、R、S、T这5个子矩阵,并计算5次矩阵乘法,就得到了最终的结果。
从上面的示例中我们不难看出,MEC的解决思路在于将im2col这一过程分成了Height和Width两部分,于是需要存储的中间矩阵也大大减小了。 可能带来的问题就是,原来的一次矩阵乘法,现在会变成多次小矩阵乘法。虽然有利于并行计算,但也失去了BLAS库计算大矩阵乘法的优势。
照例先给出算法伪代码和示例图:
![](https://img-blog.csdn.net/20170820111146188?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc2h1emZhbg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
![](https://img-blog.csdn.net/20170820111242291?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc2h1emZhbg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
Solution A:
Algorithm2中的9-19行。 9-13行和Algorithm1方法一致,14-19行对临时结果做排列转换。
Solution B:
Algorithm2中的21-25行。 循环每次处理一个样本,无需再做排列变换。
上面两种解决方案的浮点乘法计算量是一致的。但是实际操作中,子矩阵的数量对性能的影响很大,特别是在GPU设备上。 所以,在Algorithm2的第8行采用了一个参数T来控制是使用A还是B。 T是一个和平台有关的参数,文章发现对于目前的GPU来说100是一个不错的选择。
![](https://img-blog.csdn.net/20170820121605250?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc2h1emZhbg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
![](https://img-blog.csdn.net/20170820121517386?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc2h1emZhbg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
![](https://img-blog.csdn.net/20170820122517815?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc2h1emZhbg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
![](https://img-blog.csdn.net/20170820122901151?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc2h1emZhbg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
![](https://img-blog.csdn.net/20170820124107037?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc2h1emZhbg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
总结: 无论从内存占用还是计算速度上来看提升都不错,比较有复现价值。 不过缺少了和NNPACK以及CUDNN的对比,同时都是多线程操作,不知道单线程效率如何(估计会差一点)。 多线程的实现对编程能力要求还挺高的,先坐等大牛复现。(自己以后有应用需求也会考虑复现。)
1. 背景工作
目前的CNN模型中,全连接层往往在最后一层才会使用。 意思也就是说,网络的主体是由卷积层构成的。 因此,加快卷积层的计算对于整个网络的性能至关重要。目前,卷积的计算大多采用间接计算的方式,主要有以下三种实现方式:
im2col + GEMM。 caffe等很多框架中都使用了这种计算方式,原因是将问题转化为矩阵乘法后可以方便的使用很多矩阵运算库(如MKL、openblas、Eigen等)。
FFT变换。 时域卷积等于频域相乘,因此可将问题转化为简单的乘法问题。
Winograd。 这种不太熟悉,据说在GPU上效率更高。 NNPACK就是FFT和Winograd方法的结合。
上面三种方法执行效率都还不错,但对内存占用比较高,因为需要存储中间结果或者临时辅助变量。
本文的方法主要改进了im2col + GEMM的策略,目的主要是减少内存消耗的同时顺便提升点速度。由于同样可以利用目前成熟的矩阵运算库,因此算法的实现难度并不大。
下图分别是直接计算卷积以及im2col + GEMM的实现方式。 可见后者需要比较多的内存存储一个临时矩阵(用于保存im2col的结果)。
2. 方法介绍
首先规定一下符号:2.1 初级版本
这里先介绍一下简单情况下的MEC的实现方式,我将其称之为“初级版本”,因为里面不考虑batchsize和channel这两个维度。 虽然是“初级版本”,但想要理解还是需要熟悉上面Figure1中im2col+GEMM的计算方式(可参考知乎上的一个高票回答,图示很清楚, https://www.zhihu.com/question/28385679?sort=created)。下面给出算法流程以及一个示例(7x7输入,3x3卷积核,步长为1):
上面的算法伪代码可能太复杂了,我来个中文版本的示例解说:
1. 由于是3x3卷积核,且步长为1。因此,循环取出A、B、C、D、E这5个子矩阵, 每个矩阵的维度都是: 输入高度x3。
2. 将A、B、C、D、E按照行优先展开并拼成一个大的中间矩阵L, L的维度则为: 5x21。
3. 从L中循环取出P、Q、R、S、T这5个子矩阵,并计算5次矩阵乘法,就得到了最终的结果。
从上面的示例中我们不难看出,MEC的解决思路在于将im2col这一过程分成了Height和Width两部分,于是需要存储的中间矩阵也大大减小了。 可能带来的问题就是,原来的一次矩阵乘法,现在会变成多次小矩阵乘法。虽然有利于并行计算,但也失去了BLAS库计算大矩阵乘法的优势。
2.2 高级版本
相比初级版本, 高级版本进而考虑了batchsize和channel维度。照例先给出算法伪代码和示例图:
Solution A:
Algorithm2中的9-19行。 9-13行和Algorithm1方法一致,14-19行对临时结果做排列转换。
Solution B:
Algorithm2中的21-25行。 循环每次处理一个样本,无需再做排列变换。
上面两种解决方案的浮点乘法计算量是一致的。但是实际操作中,子矩阵的数量对性能的影响很大,特别是在GPU设备上。 所以,在Algorithm2的第8行采用了一个参数T来控制是使用A还是B。 T是一个和平台有关的参数,文章发现对于目前的GPU来说100是一个不错的选择。
3. 实验对比
理论的复杂度就不说了,意义不大。 下面直接上实际对比结果。3.1 配置说明以及benchmarks
C++实现CPU和GPU,矩阵运算库采用多线程OpenBLAS、OpenMP、cuBLAS,数据类型为32位浮点数。3.2 对比结果
总结: 无论从内存占用还是计算速度上来看提升都不错,比较有复现价值。 不过缺少了和NNPACK以及CUDNN的对比,同时都是多线程操作,不知道单线程效率如何(估计会差一点)。 多线程的实现对编程能力要求还挺高的,先坐等大牛复现。(自己以后有应用需求也会考虑复现。)
相关文章推荐
- Caffe中的卷积计算(矩阵优化加速)
- myeclipse 内存 速度 优化 总结 整理
- 利用VS的代码优化和openmp并行计算提高程序运行速度
- 优化内存中DataTable搜索速度,IP区域汇总
- Unity预计算全局光照的学习(速度优化,LightProbe,LPPV)
- Unity预计算全局光照的学习(速度优化,LightProbe,LPPV)
- Java IO速度和占用内存的极致优化方法,模板方法【从hdu 2602 说起】
- C# ASP.NET 优化程序性能、降低内存使用、提高程序运行速度
- 10个可以对Firefox内存占用及速度进行优化的方法
- AIX 与oracle优化(非计算内存与计算内存)
- <2012 12 24> 小技巧:关键计算代码建立内存索引表来增加算法速度
- [转]Android笔记: android APP 内存与速度的优化问题
- asp内存和速度优化,第一部分(英文版)
- Unity预计算全局光照的学习(速度优化,LightProbe,LPPV)
- 图像卷积回顾与计算优化(待续)
- Unity预计算全局光照的学习(速度优化,LightProbe,LPPV)
- 优化提高调试速度20倍, Move Temporary ASP.NET Files 到内存盘.
- 进一步优化计算的速度,调整次序。。。
- 优化myeclipse启动速度以及解决内存不足问题
- 如何利用VS的代码优化和openmp并行计算提高程序运行速度