2. 卷积神经网络基础:卷积层、池化层、全连接层的计算模式与数据复用特征
各位同学,咱们今天聊聊CNN的三大核心构件。说实话,我见过不少做AI加速器的同行,一上来就扎进数据流优化里,结果连卷积层到底怎么复用数据都没搞透。嗯,这就像盖楼不看图纸——迟早要返工。
我个人习惯,在讲Eyeriss之前,先把这三层掰开揉碎。你想想看,没有这个基础,后面那些花哨的数据流映射方案,你根本听不懂它在解决什么问题。
2.1 卷积层:计算模式与数据复用
卷积层是CNN的主力。我当年在做一个边缘端加速器项目时,发现90%的计算时间都耗在卷积上。所以,搞懂卷积,你就抓住了加速器设计的命门。
2.4.1 计算模式
说白了,卷积就是一个滑动窗口的乘累加操作。输入特征图和一个卷积核做点积,然后滑到下一个位置继续做。公式长这样:
输出特征图(o, i, h, w) = Σ(Σ(Σ(输入特征图(i, c, h+s, w+t) × 卷积核(o, c, s, t))))
这里o是输出通道,i是输入通道,h和w是空间位置,s和t是卷积核内的偏移。看着复杂?其实就是一个四重循环。
我在项目中遇到过一个问题:很多人以为卷积的计算量就是乘累加次数,其实不对。你得考虑数据搬运的成本。计算一次乘累加可能只要1个时钟周期,但把数据从DDR搬到片上SRAM,可能要几百个周期。这就是为什么数据复用如此重要。
2.1.2 数据复用特征
卷积层的数据复用,说白了就是同一个数据被多次使用。我总结了三种复用类型:
| 复用类型 | 描述 | 复用次数 |
|---|---|---|
| 输入特征图复用 | 同一个输入像素被多个卷积核使用 | 等于输出通道数 |
| 卷积核复用 | 同一个卷积核在空间位置上滑动 | 等于输出特征图的空间尺寸 |
| 部分和复用 | 同一个累加结果被多个输出通道共享 | 取决于数据流映射 |
关键洞察:Eyeriss的核心创新,就是通过Row-Stationary数据流,把这三类复用都利用到了极致。它不像传统加速器只盯着一种复用,而是让三种复用同时发生。
举个例子。假设输入特征图是32x32,卷积核是3x3,输出通道是64。那么每个输入像素会被64个卷积核各用一次,这就是输入复用。每个卷积核会在32x32个位置上滑动,这就是权重复用。你想想看,如果不利用这些复用,每次计算都从外面搬数据,那功耗得高到什么程度?
2.2 池化层:计算模式与数据复用
池化层相对简单,但别小看它。我见过不少加速器设计,在池化层上栽了跟头。
2.2.1 计算模式
池化层就是下采样。常见的有最大池化和平均池化。最大池化取窗口内的最大值,平均池化取平均值。公式很简单:
输出特征图(h, w) = max(输入特征图(h*stride + s, w*stride + t)) // 最大池化
输出特征图(h, w) = average(输入特征图(h*stride + s, w*stride + t)) // 平均池化
池化层没有可学习的参数,计算量也小。但它有一个特点:数据复用率极低。为什么?因为池化窗口不重叠(或者重叠很少),每个输入像素只被用一次。
避坑指南:我曾经在一个项目中,试图给池化层做数据复用优化,折腾了两周发现收益几乎为零。后来才明白,池化层的瓶颈不在计算,而在数据搬运。你只需要保证数据流顺畅就行,别在复用上浪费精力。
2.2.2 数据复用特征
池化层的数据复用,说白了就是几乎没有。每个输入像素只参与一次计算。所以加速器设计时,池化层通常走旁路,或者用专门的简单硬件处理。
但有一个细节要注意:池化层的输入来自卷积层的输出。如果你能把卷积层和池化层融合在一起,让池化操作直接在卷积的输出缓冲上完成,就能省掉一次数据搬运。这个技巧我在多个项目中都用过,效果不错。
2.3 全连接层:计算模式与数据复用
全连接层,说白了就是一个大矩阵乘法。输入向量和权重矩阵相乘,加上偏置,再经过激活函数。
2.3.1 计算模式
公式长这样:
输出(j) = Σ(输入(i) × 权重(i, j)) + 偏置(j)
全连接层的计算量取决于输入维度和输出维度。假设输入是4096维,输出是1000维,那么一次前向传播就要做4096×1000=409.6万次乘累加。这个量不小。
我记得有一次做语音识别芯片,全连接层占了模型参数量的80%以上。但计算量其实还好,因为全连接层的计算密度高,容易把硬件利用率做上去。
2.3.2 数据复用特征
全连接层的数据复用,和卷积层完全不同。它的特点是:
- 权重复用:同一个权重只被用一次(因为每个输入只和每个权重乘一次)
- 输入复用:同一个输入被所有输出神经元使用,复用次数等于输出维度
- 部分和复用:没有部分和复用,因为每个输出独立计算
你发现没有?全连接层的复用模式很单一,主要就是输入复用。所以加速器设计时,通常把权重放在片上,输入流式进来,输出累加后写回。
个人经验:全连接层的加速,关键在于权重压缩。我做过一个实验,用剪枝把全连接层的权重稀疏度做到90%以上,计算量直接降了一个数量级。但代价是硬件要支持非规则访存,这个取舍要看你具体的应用场景。
2.4 三层对比:一张图看懂
说了这么多,咱们用一张图总结一下。这张图是我做课程时画的,把三层的计算模式和复用特征放在一起对比,一目了然。
2.5 对Eyeriss的启示
好了,基础讲完了。咱们回到Eyeriss。你想想看,Eyeriss为什么选择Row-Stationary数据流?
原因很简单:卷积层有三重复用,但传统加速器只利用了一两种。Eyeriss通过把计算任务按行拆分,让输入、权重和部分和三种复用同时发生。池化层虽然复用少,但Eyeriss通过融合处理避免了额外开销。全连接层虽然复用单一,但Eyeriss的架构也能高效支持。