Low-memory GEMM-based convolution algorithms for deep neural networks 深度神经网络中基于GEMM的低内存卷积算法

论文:Low-memory GEMM-based convolution algorithms for deep neural networks(深度神经网络中基于GEMM的低内存卷积算法)

作者:来自爱尔兰的都柏林圣三一大学Andrew Anderson,Aravind Vasudevan,Cormac Keane,David Gregg

论文链接:https://arxiv.org/abs/1709.03395

github链接:无

1 相关介绍

深度神经网络(DNNs)需要大量的计算来进行训练和推理。实现DNNs的一种常见方法是将计算开销最大的卷积操作重新转换为通用矩阵乘法(GEMM)。

DNN卷积的实现方法

目前,卷积的计算大多采用间接计算的方式,主要有以下三种实现方式:

  • im2col + GEMM。 caffe等很多框架中都使用了这种计算方式,原因是将问题转化为矩阵乘法后可以方便的使用很多矩阵运算库(如MKL、openblas、Eigen等)。
  • FFT变换。 时域卷积等于频域相乘,因此可将问题转化为简单的乘法问题。
  • Winograd。在GPU上效率更高。NNPACK就是FFT和Winograd方法的结合。

contributions

文中研究了使用BLAS GEMM程序实现DNN卷积的各种不同的方法。通过使用不同的数据布局将卷积重写为GEMM调用的不同的版本,从而可以在适合自动化的框架内权衡执行时间、内存需求和并行性。具体贡献如下:

  • 现存的最流行的patch-building的DNN卷积算法(例如im2col)要求 O ( K 2 C H W ) O(K^2CHW) O(K2CHW)的空间。尽管是基于GEMM的DNN卷积内存方面最高效的也要 O ( K C H W ) O(KCHW) O(KCHW)的空间。文中提出了两种新的基于GEMM的算法,分别只需要 M H W MHW MHW K W KW KW的空间, M M M表示卷积操作输出通道的数量。这两种算法减少了DNN中卷积的空间开销,从而使得DNN更适合于内存有限的一些嵌入式系统。
  • 实验证明了这两种低内存的算法尽管只需要一小部分额外的内存,但和最patch-building的方法一样快
  • 这两种低内存算法具有良好的数据局部性,这使它们在使用多核时比patch-building算法更有优势。因此,这两种低内存算法常常比使用多线程(或多核)的最佳的patch-building算法更快

2 DNN 卷积

DNNs的计算主要取决于卷积层,一个卷积层输入两个tensor然后输出一个tensor,用C语言代码表示tensor为:

float input[C][H][W];
float kernels[M][C][K][K];
float output[M][H][W];

一个卷积层包括四个部分

  • 输入:一个3D tensor, I ∈ R H × W × C \mathcal{I} \in \mathbb{R}^{H \times W \times C} IRH×W×C
  • kernels或filters集合:一个4D tensor, K ∈ R M × C × K × K \mathcal{K} \in \mathbb{R}^{M \times C \times K \times K} KRM×C×K×K
  • 一个卷积核的偏置项
  • 输出:一个3D tensor

一个Multiple Channel Multiple Kernel (MCMK)由 M M M个Multiple Channel Single Kernel (MCSK)组成,用循环嵌套的代码表示为:

3 使用 O ( K 2 C H W ) O(K^2CHW) O(K2CHW) 空间的patch matrix的卷积

DNN的卷积操作可以转换成矩阵乘法去高效的计算,例如使用GEMM库去计算。关于im2col方法的研究已经很多了,这种方法可以很好地把MCMK问题转化成GEMM问题。

考虑一个输入 I ∈ R H × W × C \mathcal{I} \in \mathbb{R}^{H \times W \times C} IRH×W×C和M个卷积核 K ∈ R M × C × K × K \mathcal{K} \in \mathbb{R}^{M \times C \times K \times K} KRM×C×K×K

  • 将输入转换成一个新的input-patch-matrix I ^ \hat \mathcal{I} I^的转换方法是从输入中复制patches并将它们展开成这个中间矩阵 I ^ \hat \mathcal{I} I^的每一列。这些patches和kernel的shape一致,即 K × K × C K × K × C K×K×C,然后可以用这些patches和kernel进行卷积运算
  • 同理,可以将 M M M K × K × C K × K × C K×K×C卷积核展开成 K ^ \hat \mathcal{K} K^的每一行,构建一个kernel-patch-matrix K ^ \hat \mathcal{K} K^。注意,如果一开始就以这种格式存储内核,则可以避免此步骤
  • 然后就可以执行 K ^ \hat \mathcal{K} K^ I ^ \hat \mathcal{I} I^的一个GEMM操作得到输出 O ^ ∈ R H × W × M \hat{\mathcal{O}} \in \mathbb{R}^{H \times W \times M} O^RH×W×M

3.1 Matrix layouts

DNN的卷积的输入和输出都是多维的tensor,因此就存在着一个维度的排序问题。例如,Caffe中的卷积实现输入数据的tensor的顺序是CHW格式的,构建的patch matrices的维度为CKKHW。调用GEMM操作时,将patch矩阵解释为维度CKK×HW的2D矩阵。

Fortran使用column-major格式,矩阵的同一列的元素在内存中是相邻的。C/C++使用 row-major格式,其中来自同一行的元素在内存中是相邻的。GEMM最初使用的是Fortran程序,即假定为以列布局的,但是现在许多DNN实现使用C/C++代码,因此这可能导致内存中数组格式的混乱。所以,将patch matrices称为patch-major or KKC-major的矩阵,这意味着同一个patch的元素在内存中是相邻的。另一种可能的布局是 HW-major 或 patch-minor的,其中patch矩阵的non-patch维度的元素在内存中相邻。

  • 图2展示了从输入张使用一个嵌套循环来创建一个patch矩阵:从input里复制元素到patch矩阵
  • 第2行的patch矩阵的维度为HWKKC,这可以视作一个五维的 H × W × K × K × C H × W × K × K × C H×W×K×K×C矩阵或者一个两维的 H W × K K C HW × KKC HW×KKC的矩阵
  • C/C++代码假定为 row-major 布局的,那么格式为 patch-major 或 KKC-major
  • 创建patch矩阵的关键步骤是从input矩阵中聚集元素。图2中的input的主要维度是 C C C
  • 最里面的循环将 C-major 的输入矩阵的相邻元素复制到patch矩阵的相邻元素
  • 图2中的代码具有非常好的空间局部性,因此非常快

3.2 Patch-minor layouts

其他的布局会导致不同的数据局部性。考虑一个HW-major而不是 patch-major的patch矩阵的例子。

  • 为了简化,令 C = 1 C=1 C=1
  • 用图3a的阴影部分在图3b中创建patch列,那么空间局部性会非常差

另一种策略可以产生很好的空间局部性:可以看到,patch矩阵中的每一行都包含了input中的一行。因此,如果输入格式兼容,甚至可以使用很好的空间局部性构造non-patch-major的patch矩阵。此外,即使input不适合直接将行复制到patch矩阵,也可以获得几乎同样好的空间局部性。

图3b的连续行包含重复的数据序列。一旦得到第一行包含的数据,那么就很容易地可以在patch矩阵中从前一行复制数据到下一行。这种策略对于有步长的DNN卷积很重要。图3c中可以看出,一个对于步长为 S S S的卷积的patch矩阵只有 H W / S HW/S HW/S个patch。因此,column-oriented的patch矩阵行中的值序列与原始矩阵布局不匹配。但是一旦得到第一行包含的一些特别的数据那么就能复制到后面的行中,并且序列一样。在row-major矩阵布局中计算column-oriented的patch矩阵的场景中,此文是第一次在行之间利用了空间局部性。

3.3 Patch-building Algoritms

im2col−scan

每个patch矩阵的KKC大小的列都是从输入矩阵按顺序构建的,这种方式在访问input时的空间局部性非常差,因为patch矩阵的每一列的值都来自于输入的不同行和不同列。

im2col−copy−self

im2col-copy-self方法是im2col-scan的改进。从图3b可以看到许多行共享相同值,因此可以只需要使用im2col−scan来构建少数行,然后就可以使用内存复制函数来更快地构建patch矩阵的其他行。

im2col−copy−long

从图3b可以看到patch矩阵的行是由input的连续部分构建的。使用im2col−copy−long的方法,可以通过将input的整个部分复制到patch矩阵中来更快地构建patch矩阵,并在需要的地方用0值替换。但是这种方法不适用于图3c这种带步长的patch矩阵。

im2col−copy−short

im2col−copy−short与im2col−copy−long非常相似。不同之处在于,虽然在图3b中没有显示,但从input复制到patch矩阵的大型连续区域中的一些值将被替换为零。在im2col-copy-long中,复制最长的部分,然后在需要的地方用0替换值。在im2col-copy-short中,复制两个0之间的非零元素,然后再赋值下一个部分。im2col-copy-short可以创建不同的带步长的patch矩阵。

3.4 Evaluation

  • 图4是一个盒须图(box and whiskers),展示了不同的patch构建算法和不通的数据布局之间的一个平衡
  • 后缀-ab-ki定义为使用的GEMM的类型
  • ab表示默认的GEMM,atb,abt,atbt
  • 最后一个部分表示矩阵乘法的顺序:ik表示Input矩阵×kernel矩阵;ki则相反
  • 方法的平均性能由方框中的小+表示
  • im2col−scan的变形比其他几种的性能都差

补充:盒须图
四分位数

  • 第一四分位数(Q1):一组数据由小到大排序,处于所有数据1/4位置的数字;
  • 第二四分位数(Q2):一组数据由小到大排序,处于所有数据1/2位置的数字;
  • 第三四分位数(Q3):一组数由小到大排序呢,处于所有数据3/4为主的数字;

盒须图六个参数

  • 上须:最大值
  • 下须:最小值
  • 上枢纽:Q3
  • 中位数:Q2
  • 下枢纽:Q1
  • 异常值:离散于整个数组,上面的盒须图中从左到右的第四个和第九个就有两个异常值

盒须图的意义:通过盒须图可以一眼看出一组数的离散程度,聚集区间(在整个数组中的四等分中那个区间数据比较集中)

4 使用 O ( K 2 M H W ) O(K^2MHW) O(K2MHW)空间的result matrix的卷积

前一部分中的卷积最大的问题就是patch matrix比是原始图片的 K 2 K^2 K2倍,因此这种增加的额外的空间会减少数据的局部性,增加内存堵塞,在嵌入式系统中还会造成内存不够的情况。

4.1 Kernel to Row (kn2row) and Kernel to Column (kn2col)

在Parallel Multi Channel Convolution using General Matrix Multiplication一文中已经介绍过这种方法了。用一个图表示:

  • kn2col和kn2row的性能都随着C和M的增大而增大,随着核的大小而减小。

5 使用空间大小 O ( K C H W ) O(KCHW) O(KCHW)patch matrix的基于GEMM的卷积

这部分提出了把第3部分中patch matrix O ( K 2 C H W ) O(K^2CHW) O(K2CHW)的大小变为 O ( K C H W ) O(KCHW) O(KCHW)

  • 图7a表示一个在内存中Row-major存储的二维11 × 4的矩阵

5.1 kn2row 和 kn2col的加速

两种加速方法

  • Accumulating Kernel to Row (kn2row − as)
  • Accumulating Kernel to Column (kn2col − as)

5.2 用GEMM加速

6 实验结果

猜你喜欢

转载自blog.csdn.net/yyl424525/article/details/102654797