您的位置:首页 > 其它

MEC —— 优化内存与速度的卷积计算

2017-08-20 13:01 489 查看
本次介绍一种内存利用率高且速度较快的卷积计算方法。 来自ICML2017, 《MEC: Memory-efficient Convolution for Deep Neural Network》

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的对比,同时都是多线程操作,不知道单线程效率如何(估计会差一点)。 多线程的实现对编程能力要求还挺高的,先坐等大牛复现。(自己以后有应用需求也会考虑复现。)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: