3、卷积神经网络(CNN)在NPU上的映射:卷积运算的硬件实现,im2col与Winograd算法在NPU中的加速原理,如何将一层卷积映射到MAC阵列上。

好,咱们直接切入正题。卷积运算,说白了就是神经网络里最“吃”算力的那部分。你在PC上跑个模型,CPU吭哧吭哧算半天,但到了NPU上,就得让硬件来干这活。我这些年调过不少NPU,发现很多新人一上来就盯着算法调,却忽略了硬件到底是怎么“看”卷积的。今天咱们就聊聊,卷积在NPU里到底是怎么被“消化”掉的。

3.1 卷积运算的硬件实现:从数学公式到MAC阵列

先看最基本的。一个3x3的卷积,数学上就是乘加求和。但在硬件里,这就是MAC(乘累加)单元的活。一个MAC单元一次做一个乘法和一个加法。你想想看,一个卷积层可能有几十万个这样的操作,所以NPU里会摆上一大堆MAC单元,排成阵列。

我个人习惯把MAC阵列想象成一个“算力网格”。输入特征图的数据流进来,权重也流进来,在网格里完成计算。这里有个关键点:数据复用。我在项目中遇到过,有人把卷积映射得特别“直白”,结果带宽被撑爆了。为什么?因为没利用好数据的局部性。

核心要点:卷积的硬件实现,本质就是“数据搬运 + 乘加计算”。搬运的效率,决定了MAC阵列的利用率。

举个例子,一个3x3的卷积核,在输入特征图上滑动。每次滑动,卷积核的9个权重是不变的,但输入数据在变。好的映射策略,会让权重“驻留”在MAC阵列的本地寄存器里,只让输入数据流动。这样就能省掉大量重复的权重搬运。

3.2 im2col算法:把卷积“拍扁”成矩阵乘法

im2col,名字听着挺唬人,其实就是“image to column”。它把卷积操作里那些滑动的窗口,全部展开成矩阵的列。你想想看,一个3x3的卷积核,在输入上滑动,每次滑动都产生一个9维的向量。把这些向量排成一列,就成了一个巨大的矩阵。

这样做的好处是什么?矩阵乘法是硬件最擅长的。MAC阵列本质上就是个矩阵乘法器。im2col把卷积变成了矩阵乘法,就能直接用上高度优化的矩阵乘库,比如GEMM。

但代价也很明显:内存爆炸。我记得有一次调一个MobileNet,im2col展开后,中间矩阵的大小比原始特征图大了快10倍。这直接导致DDR带宽成了瓶颈。所以,im2col适合那些卷积核尺寸小、特征图尺寸也小的层。对于大尺寸的输入,你得小心。

我的经验:在NPU上,im2col通常不是直接展开整个特征图,而是“分块”展开。一次只展开一个tile,算完就扔掉。这样能控制内存开销。

3.3 Winograd算法:用“巧劲”减少乘法次数

Winograd算法,说白了就是用更多的加法,来换更少的乘法。为什么?因为加法在硬件里比乘法便宜得多。一个MAC单元里,乘法器的面积和功耗都比加法器大。所以,如果能减少乘法次数,哪怕增加一些加法,整体上也是划算的。

Winograd的核心思想,是把卷积运算变换到另一个空间里。在这个空间里,乘法次数变少了。比如,对于一个3x3的卷积,用F(2,3)的Winograd,可以把每个输出点的乘法次数从9次降到4次。省了一半还多。

但Winograd也有坑。我踩过最大的坑是数值精度。Winograd的变换矩阵里有很多分数,比如1/2、1/4。在定点数计算里,这些分数会导致精度损失。我曾经在int8的NPU上试过Winograd,结果精度掉了1个点,最后不得不换回im2col。

避坑指南:Winograd对数据精度敏感。如果你的NPU是低精度(如int4、int8),建议先做一轮精度仿真。另外,Winograd只对特定尺寸的卷积核有效(如3x3、5x5),对1x1卷积没用。

3.4 如何将一层卷积映射到MAC阵列上

好,现在咱们把前面讲的串起来。假设你有一层卷积:输入是112x112x64,卷积核是3x3x64x128,输出是112x112x128。怎么把它映射到MAC阵列上?

我一般分三步走:

  1. 确定分块策略:MAC阵列有固定大小,比如16x16。你不能一次算完整个输出,得切成小块。比如,一次算输出通道的16个点,或者一次算空间位置的4x4个点。
  2. 选择数据流:是让权重固定、输入流动(权重固定流),还是让输入固定、权重流动(输入固定流)?我个人习惯用权重固定流,因为权重可以提前加载到MAC阵列的本地buffer里,减少重复搬运。
  3. 决定算法:用im2col还是Winograd?如果卷积核是3x3,且精度允许,我会优先用Winograd。如果卷积核是1x1,或者特征图尺寸很大,我会用im2col的分块版本。

下面这张图,是我自己总结的映射决策流程,你可以参考一下:

卷积层输入 卷积核尺寸? 3x3 / 5x5 / 1x1 3x3 Winograd F(2,3) 乘法减少55% 1x1 im2col + GEMM 直接矩阵乘法 5x5 分块im2col 控制内存开销 映射到MAC阵列 确定分块大小 + 数据流

嗯,这里要注意,映射到MAC阵列后,还有个关键步骤:循环分块。你得把卷积的循环(比如输出通道循环、输入通道循环、空间位置循环)拆成硬件能一次处理的大小。我一般会先确定MAC阵列的物理尺寸,然后反推循环分块的大小。

举个例子,如果MAC阵列是16x16,那我一次就处理16个输出通道和16个输入通道的乘加。剩下的通道,就靠循环来累加。这样,每个MAC单元都能满负荷工作,利用率能到90%以上。

总结一下:卷积映射到NPU,核心就是“算法选型 + 数据流设计 + 循环分块”。im2col适合小卷积核和大特征图,Winograd适合3x3卷积但要注意精度。选对了,MAC阵列利用率就上去了;选错了,带宽和精度都会出问题。

好了,这一章的内容就这些。下一章咱们聊聊池化层和全连接层在NPU上的映射,那又是另一套玩法了。

专注资料整理