MACE源码解析【GPU内存排布技巧】

前言

在移动端AI推理引擎中,除了一些计算上技巧外,内存排布对效率也是有一定影响的。本篇来浅析一下MACE的opencl模块在内存上有什么讲究,应用了哪些技巧。欢迎留言讨论。

权重的排布

MACE中权重是是由opencl中的image2D对象为存储介质(亦有buffer版的kernel),所以需要把4维的filter参数转换到2维上。一般情况下filter默认排布为OIHW,其中O为该层输出通道个数,I为该层输入通道个数,W和H则为filter kernel宽高。下表是从MACE官方文档中摘抄的关于filter参数排布的内容。

Tensor Buffer Image size [width, height] Explanation
Convolution Filter OIHW [I, (O+3)/4 * W * H] Convolution filter format,There is no difference compared to [H *W *I, (O+3)/4]
Depthwise Convlution Filter MIHW [H * W * M, (I+3)/4] Depthwise-Convolution filter format
Tensor type Pixel coordinate relationship Explanation
Convolution Filter P[m, n] = {E[o, i, h, w] | (o=[n/HW*4+k], i=m, h=T/W, w=T%W)} HW= H * W, T=n%HW, k=[0, 4)
Depthwise Convolution Filter P[m, n] = {E[0, i, h, w] | (i=[n*4+k], h=m/W, w=m%W)} only support multiplier == 1, k=[0, 4)

只看公式还是有些抽象。举例说明一下,假设现在有一组filter,输入为2通道,输出为4通道,kernel大小为2*2。记为wo,ikw^{k}_{o,i} ,o和i分别为输入和输出通道,k为kernel内的编号,此例范围为0到3。图1为im2col方式中,kernel的内存排列方式。每个输出通道占一行,一行内则顺序存储多个输入通道对应的kernel。默认kernel的访问为顺序访问。
im2col

图1

图2则展示了MACE上表所表示的内存排列。实际上,openCL的kernel中使用read_imagef(h)来读取元素,因其设置为RGBA格式,所以每次read的出来的是一个长度为4的向量。因此这里也是使用了以4为单位的存储形式,每个向量中权重的索引和输入通道索引都是相同的,一个向量内的输出通道是顺序排列的。
mace_kernel

图2

最后来看DepthWise情况的权重排布,dw下少了一个维度,上面公式中用M替换O的位置。M的意思应该是 multiplier ,就是在做DW的同时增加通道数,比如输入通道为16,输出通道为32,此时multiplier就为2。MACE文档中说只支持为1的情况。所以这个M对应的索引就一直是常量0了。图3列举的是kernel size为4,输入通道为4的情况。

在这里插入图片描述

图3

Featuremap的内存排布

特征图的内存分布相对好理解一些。假设尺寸为NxCxHxW。想象有NxC个大小为HxW的二维矩阵。按照顺序一字排开,其中每4张图做一个通道交织的操作,得到NxC/4张Hx4W大小的图。图4画出了这个过程。第一行为原始排列,例子中为8个通道的2x2 feature map。做完交织后变成第二行的样子,最终内存排布如第三行所示。假如做一个1x1卷积,取图中1、2、3、4位置的值,原始排布则每个元素的获取都要跨越一个imagePitch去访存,cache命中率偏低。做4通道交织后,则可以提高访存效率。其实就是根据做卷积时的访存规律去设计了内存排布。
在这里插入图片描述
具体数学表达式例子见图5
在这里插入图片描述

图4

小结

GPU内存排布有几个特点

  1. 因为要使用opencl的image2d 对象作为filter的存储对象,所以需要映射到一个2维空间。
  2. opencl的GPU编程中,SIMD是一个基本且重要的优化手段,不论是访存还是计算都有提速效果。所以image2d对象中的一个像素元素设置为RGBA。为了符合这一特点,filter的存储都是以4通道交织为基础。
  3. 在MACE的ARM的版本中filter内存排布为OIHW,即分plane排布。
  4. 在高通adreno系列GPU上使用image2d对象相比于buffer对象可以利用L1 cache。此外深度学习的模型内存可以做到很高的内存复用,比如第N层做完运算后后续没有再依赖第N层输入的层,就可以释放第N层的输入所占用的内存,如果用内存池则这块内存可以给后面的层去使用。在这样的内存池的情况下,会出现实际使用的内存和内存大小形状差距较大的情况。例如一个image2d对象大小为1000x1200,但后续层的特征图很小,实际利用为100x60。此时如果用buffer去做跨行的内存访问那cache命中率会差很多。image对象的内存实现有内部机制,对邻域操作较为友好。还有如MACE这样的4通道交织的方法也可以加宽图像宽度,使得内存更多的在宽度上去扩展,如上所述可以提高cache命中率。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章