卷积运算的硬件映射:从算法到电路
各位同学,今天我们来聊聊卷积加速器里最核心的一个话题——怎么把数学公式里的卷积运算,真正搬到硬件上跑起来。
说实话,我刚入行那会儿,觉得卷积不就是个乘加运算嘛,有什么难的?直到第一次做加速器项目,才发现这里面的门道多着呢。你想想看,一个3×3的卷积核,在输入特征图上滑动,每次都要取9个数据,还要考虑不同通道、不同batch……数据流稍微没规划好,硬件利用率直接掉到30%。
好,我们一个一个来看。
im2col算法原理:把卷积变成矩阵乘法
先说说im2col。这名字挺形象——image to column,把图像数据拉成列向量。
为什么要这么做?说白了,卷积运算本质上就是点积运算。你每次滑动窗口,取出一块数据,和卷积核做内积。那如果我们提前把这些窗口数据都排好,是不是就能直接用矩阵乘法来算了?
我给大家画个图就明白了:
看到没?原来需要4次滑动、每次取4个数据,现在变成了一次矩阵乘法。硬件上,矩阵乘法有成熟的加速方案——Systolic Array、脉动阵列,都是干这个的。
核心要点:im2col把卷积运算转换成了矩阵乘法,让硬件可以用成熟的矩阵加速器来处理。代价是数据被重复存储了——每个输入像素可能出现在多个窗口里,内存开销增加了。
我在项目中遇到过一个问题:im2col展开后,数据量暴增。比如输入是224×224×64,卷积核3×3,stride=1,展开后的矩阵有多大?算一下:输出尺寸是222×222,每个窗口9个数据,总共222×222×9 ≈ 44万个元素。而原始数据才224×224×64 ≈ 320万个。嗯,这里要注意,im2col不是免费的午餐。
滑动窗口的硬件实现
好,说完了im2col,我们来看看另一种思路——直接在硬件里做滑动窗口。
说白了,就是用一组寄存器把卷积窗口的数据缓存下来。每次来一个新像素,就把最旧的那个踢出去,新的加进来。这样每个时钟周期都能拿到一个完整的窗口数据。
我给大家画个3×3滑动窗口的硬件结构:
这个结构我特别喜欢,因为它很规整。3个行缓冲(Line Buffer)存3行数据,每个行缓冲里再取3个像素,组成3×3的窗口。每个时钟周期,所有数据向右移动一格,新像素从左边进来。
个人经验:行缓冲的深度要等于图像宽度。如果图像是416×416,那每个行缓冲就要416个寄存器。面积不小,但这是必须的。我曾经试过用BRAM实现行缓冲,结果带宽不够,后来还是老老实实用了寄存器。
多通道卷积的数据流分析
接下来聊聊多通道。实际应用中,输入特征图很少是单通道的——RGB图像有3通道,中间层的特征图可能有64、128甚至512通道。
多通道卷积怎么做?每个输出通道,是所有输入通道的卷积结果累加。举个例子:输入有64通道,输出有128通道,那就要做64×128 = 8192次卷积运算。
数据流怎么安排?我一般分两种策略:
- 输入通道优先(Input Stationary):先把一个输入通道的数据广播给所有输出通道的卷积核,算完再换下一个输入通道。好处是输入数据复用率高,适合输入带宽小的场景。
- 输出通道优先(Output Stationary):固定一个输出通道,遍历所有输入通道,累加出结果。好处是输出累加器可以本地化,适合输出带宽小的场景。
你想想看,如果输入数据很大(比如224×224×64),但卷积核很小(3×3),那输入通道优先就更合适——因为输入数据可以反复用。反过来,如果输出通道很多(比如1024个),那输出通道优先能减少输出数据的搬运次数。
注意:多通道卷积最怕的就是数据搬运。我曾经在一个项目里,发现70%的功耗都花在了DRAM和计算单元之间的数据搬移上。后来改用数据复用策略,功耗直接降了40%。
卷积核的并行化策略
最后说说并行化。卷积加速器的性能,说白了就是看你能同时算多少个乘加。
并行化可以从三个维度展开:
- 输出通道并行:同时计算多个输出通道。每个输出通道对应一组卷积核,互不依赖,天然可以并行。
- 输入通道并行:同时处理多个输入通道的卷积结果,然后累加。这需要多个乘加器同时工作。
- 空间并行:同时计算多个输出位置。比如一次算4个相邻输出像素,这需要更大的窗口寄存器阵列。
我给大家一个表格,看看不同并行策略的权衡:
| 并行策略 | 硬件开销 | 数据复用率 | 适用场景 |
|---|---|---|---|
| 输出通道并行 | 高(每组卷积核一套乘法器) | 高(输入数据广播) | 输出通道数多 |
| 输入通道并行 | 中(累加器多) | 中(输入数据需多次读取) | 输入通道数多 |
| 空间并行 | 中(窗口寄存器大) | 高(窗口数据复用) | 大尺寸特征图 |
实际项目中,我一般会混合使用。比如同时算4个输出通道,每个输出通道同时处理8个输入通道,再加上2×2的空间并行。这样总的并行度就是4×8×4 = 128个乘加器同时工作。
关键点:并行度不是越高越好。并行度上去了,数据带宽也要跟上。我曾经算过一笔账:如果每个时钟周期要喂128个数据,那数据带宽至少要128×8bit = 1024bit/cycle。在500MHz下,就是512Gbps。这个带宽,一般的DDR4都扛不住。
所以,并行策略一定要和存储带宽匹配。说白了,就是计算和存储要平衡。计算太快,数据喂不饱,那就是空转;存储太快,计算跟不上,那就是浪费。
好了,这一章的内容就到这里。卷积运算的硬件映射,核心就是这三件事:怎么组织数据(im2col)、怎么缓存窗口(滑动窗口)、怎么安排并行(并行策略)。把这些想清楚了,加速器的骨架就有了。
我的建议:刚开始做卷积加速器设计时,先别急着上高并行度。先用一个简单的滑动窗口结构跑通功能,再逐步增加并行度。我见过太多人一上来就搞128路并行,结果调试了三个月还没跑通。
可可精品资料库