























(1)本文讲解使用的例子
以如下的卷积为例,进行昇腾 Im2Col 卷积过程:
从图上可以轻易看出相关信息。

现在想起来,光是遇到你这个家伙,就感觉自己赚到了。
------ 大家好啊 我是 暮冬 Z 羡慕
(2)矩阵乘运算单元
昇腾达芬奇架构设计了 16*16 的矩阵乘运算单元,能够提供强大的并行乘加计算能力,可以以一条指令实现两个 16*16 的矩阵相乘的运算。所以昇腾 Im2Col 卷积的目的就是让卷积能够高效地利用 “矩阵乘运算单元” 进行计算。

感兴趣的可以阅读昇腾架构介绍书籍。
矩阵计算单元可以⽤⼀条指令完成两个 16×16 矩阵的相乘运算(标记为 163,也是 Cube 这⼀名称的来历),等同于在极短时间内进⾏了 163=4096 个乘加运算,并且可以实现 FP16 的运算精度。如图 3-7 所⽰,矩阵计算单元在完成 C=A×B 的矩阵运算时,会事先将矩阵 A 按⾏存放在输⼊缓冲区中,同时将矩阵 B 按列存放在输⼊缓冲区中,通过矩阵计算单元计算后得到的结果矩阵 C 按⾏存放在输出缓冲区中。在矩阵相乘运算中,矩阵 C 的第⼀元素由矩阵 A 的第⼀⾏的 16 个元素和矩阵 B 的第⼀列的 16 个元素由矩阵计算单元⼦电路进⾏ 16 次乘法和 15 次加法运算得出。矩阵计算单元中共有 256 个矩阵计算⼦电路,可以由⼀条指令并⾏完成矩阵 C 的 256 个元素计算。 摘自《昇腾 AI 处理器架构与编程》
昇腾 Im2Col 五维卷积加速算法 基本流程:
输入为 nhwc 输出为 nhwc
权重维度变化: 权重的维度变化离线进行,不消耗神经网络推理时间。(神经网络推理大致分为 模型转换 量化 推理三个步骤,权重的维度转换可以在模型转换时进行,不占用推理的时间)。下面是权重变换的分步流程,代码实现可以一步完成,也可以分多步完成(因为不影响推理时间。)

上方的变换如果比较抽象的话,可以结合后面的流程来理解。


上图是 Kernel 2D 的数据排布方式,维度为【2*3*3*16,34】,为了简便,跳过昇腾 5D 结构,直接从 4D 转到 2D。下面介绍 4D 数据和 2D 数据的一一对应关系。
通过上述对应关系,我们不难得到维度为【2*3*3*16,34】的卷积核 2D 形式。由于昇腾卷积算法的 AI 计算核心是 16*16 的矩阵乘运算单元,同时为了取数方便,还需要将卷积核 2D 转换为大 Z 小 N 排布方式。

第一步,将 2D【2*3*3*16,34】中 34 补零为 16 的倍数,即 48,得到【2*3*3*16,48】。
第二步,将其按照 16*16 的方格进行划分,得到【2*3*3,3】个【16,16】的小块。(图中画成了 4 个小块,实际应该是 3 个,示意图,见谅)
第三步,将这些小块按照大 Z 小 N 的顺序进行排布。大 Z 指的是外部按照行优先,将按照 Cube1 到 Cube8 这种 “Z” 字形排布;小 N 指的是内部按照列优先,即每个 16*16 的 Cube,先排第一列,然后是第二列... 详见最右边的彩色表示。
多说一句,之所以专门按照 “小 N” 排布,是因为在矩阵运算中,权重作为矩阵乘的第二个参数,数据是按列取的。这就意味着在实际内存中要跳着取数(内存中都是按照行优先排序),自然效率低。提前将其按照列优先的方式进行排布,那么在矩阵乘运算中可以连续取数。至此,我们得到了 【2*3*3,3,16*16】的权重大 Z 小 N 排布形式,这种形式使得能够一次性取出 256 个数参与计算,效率很高。
下面的代码一次性完成了 权重 4D nhwc 到权重大 Z 小 N 排布,仅供参考。还是那句话,权重的变换离线进行,不占用宝贵的推理时间,所以无须关心转换的效率。
void WeightTrans_A(const float* filters, const TensorDim weight_dim, Ascend5Dim we_5D_dim, float* we_tran5D, | |
AscendTransform5Dim we_tran5D_dim, int CUBE_row, int CUBE_col){ | |
int lastdim4 = we_tran5D_dim.move * we_tran5D_dim.channel * we_tran5D_dim.LW * we_tran5D_dim.cube; | |
int lastdim3 = we_tran5D_dim.channel * we_tran5D_dim.LW * we_tran5D_dim.cube; | |
int lastdim2 = we_tran5D_dim.LW * we_tran5D_dim.cube; | |
int single_filter_num = weight_dim.c * weight_dim.h * weight_dim.w; | |
int single_filter_channel = weight_dim.h * weight_dim.w; | |
for(int ch_cube=0; ch_cube<we_tran5D_dim.batch; ch_cube++){ | |
int index_1 = ch_cube * lastdim4; | |
for(int hk=0; hk<we_tran5D_dim.move; hk++){ | |
int index_2 = index_1 + hk * lastdim3; | |
for(int wk=0; wk<we_tran5D_dim.channel; wk++){ | |
int index_3 = index_2 + wk * lastdim2; | |
for(int cout_cube=0; cout_cube<we_tran5D_dim.LW; cout_cube++){ | |
int index_4 = index_3 + cout_cube*we_tran5D_dim.cube; | |
for(int cube_row=0; cube_row<CUBE_row; cube_row++){ | |
for(int cube_col=0; cube_col<CUBE_col; cube_col++){ | |
int index = index_4 + cube_row*CUBE_col + cube_col; | |
if((cout_cube*CUBE_col+cube_row)>=weight_dim.n || (ch_cube*CUBE_col+cube_col)>=weight_dim.c){ | |
we_tran5D[index] = 0; | |
}else{ | |
int index_from = (cout_cube*CUBE_col+cube_row)*single_filter_num + (ch_cube*CUBE_col+cube_col)*single_filter_channel + hk*weight_dim.w+ wk; | |
we_tran5D[index] = filters[index_from]; | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
} |
输入 tensor 的内存排布为 nhwc 输出为 nhwc
昇腾算法的维度详细变换如图下图所示。这里展示了输入 input 从 4D 维度转换到 昇腾 5D 结构,然后再转换到 2D 结构,最后转换到大 Z 小 Z 维度。写这么详细只是为了方便读者理解,而在实际操作中,由于 Input 的变换是在线进行,消耗宝贵的推理时间,所以如华为昇腾书中所说:input 先是从 4D 维度 通过软件算法转换为 昇腾 5D 维度(在模型推理过程中这一步可能不需要,因为中间层的 tensor 已经处于昇腾 5D 维度了),之后从昇腾 5D 维度通过 硬件直接转换到大 Z 小 Z 排布(模型推理过程肯定是边转换变计算,所以不会将整个 tensor 转换为大 Z 小 Z 之后,才进行矩阵运算阶段的。本博客为方便,将整个 tensor 完全转换到大 Z 小 Z,再进行后面计算。)
说完这些,就可以介绍一下昇腾算法极致高效的输入的排布转换过程了!

还是再强调一下,昇腾可以做到整个模型的中间层的 tensor 均保持昇腾 5D 的维度,所以思考一下,可能只有最初输入到模型的 tensor 需要 从 Input 4D 转 到 Input 5D,或者再数据预处理的时候就将数据处理为 5D 排布。

此处数据搬运较为简单。
昇腾通过专门设计的硬件,将 input 从 5D 格式直接搬到 大 Z 小 Z 排布。想要知道怎么搬以及为什么这么搬,还真不得不把其 2D 排布讲明白。 《昇腾 AI 处理器架构与编程》这本书中直接跳过了 2D 排布,导致晦涩难懂。
所以我们直接看 Input2D 与 Weight 2D 的对应情况,如下图所示。

假如直接计算 Input2D 矩阵乘 Weight2D,卷积计算就得到最终结果啦!这就是普通的 Im2Col 算法,不清楚的小伙伴们还可以去读一下 Im2Col 算法 NCHW 和 Im2Col 算法 NHWC。
从 2D 的角度来看,算法是不是很简单啊。
不要高兴的太早,还没完呢。

接下来是将 Input2D 转换到大 Z 小 Z 排布
第一步,将 Input2D【25*25,2*3*3*16】中 25*25 补零为 16 的倍数,即 640,得到【640,2*3*3*16】 ,如图 L。
第二步,将其按照 16*16 的方格进行划分,即得到【40,18】个【16,16】的小块,如图 M。
第三步,将这些小块按照大 Z 小 Z 的顺序进行排布。大 Z 指的是外部按照行优先,将按照 Cube1 到 Cube720 这些块按照 “Z” 字形排布;像 N 图上方排成一行;小 Z 指的是内部也按照行优先,即每个 16*16 的 Cube,先排第一行,然后是第二行... 详见 N 图中的颜色表示。

上图来自《昇腾 AI 处理器架构与编程》,矩阵 A 的排布为大 Z 小 Z,矩阵 B 的排布为大 Z 小 N,大家可以再理解一下。
至此,Input 的大 Z 小 Z 排布已经实现,接下来就是 16*16 的矩阵乘了。

前两小节介绍了 Input5D 变换到 Input 2D,再变换到 大 Z 小 Z 的过程。而在昇腾芯片中,从 Input5D 到 Input2D 由硬件一步实现。
如果前面两小节已经看明白了的话,那么搬运的秘密就呼之欲出了。

当然,我猜测昇腾应该是设计了 16 个 DMA 组成的 DAM 队列,来实现一次 256 个数据的搬运。真的是相当高效了!
文章好长啊!画了好多图!
本博客目前以及可预期的将来都不会支持评论功能。各位大侠如若有指教和问题,可以在我的 github 项目 或随便一个项目下提出 issue,并指明哪一篇博客,我看到一定及时回复!
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。