2. DNN加速器基础:CNN基本运算与存储瓶颈
各位同学,今天我们来聊聊DNN加速器的基础。说白了,就是先搞清楚CNN到底在算什么,然后看看为什么通用处理器搞不定,最后引出专用加速器的必要性。
我个人习惯,每次讲架构之前,先回到最朴素的运算本身。你想想看,如果连卷积、池化、全连接这些基本操作都没吃透,后面谈什么数据流、脉动阵列都是空中楼阁。
2.1 卷积运算:DNN的主力
卷积是CNN里最核心、也是最耗时的操作。我当年第一次做硬件加速时,发现90%以上的计算时间都花在卷积上,一点都不夸张。
一个典型的2D卷积,输入是H×W×C的特征图,卷积核是K×K×C,输出是R×S×M。每次卷积,其实就是做一个乘累加(MAC)操作:
// 伪代码:单次卷积计算
for (r = 0; r < R; r++)
for (s = 0; s < S; s++)
for (m = 0; m < M; m++)
for (c = 0; c < C; c++)
for (i = 0; i < K; i++)
for (j = 0; j < K; j++)
output[r][s][m] +=
input[r*stride+i][s*stride+j][c] * weight[i][j][c][m];
看到这个六层循环了吗?这就是计算量的来源。一个典型的AlexNet,卷积层需要做约7亿次MAC操作。嗯,这里要注意,每次MAC包含一次乘法和一次加法,所以实际浮点操作数是14亿次。
关键数据:一次卷积的MAC次数 = R × S × M × C × K × K。以AlexNet第一层为例:R=55, S=55, M=96, C=3, K=11 → 约1.05亿次MAC。
2.2 池化运算:降维与特征筛选
池化相对简单,但同样重要。我见过不少新手,觉得池化就是取个最大值或平均值,没什么好优化的。其实不然。
池化层的作用有两个:一是降低特征图的空间维度,减少后续计算量;二是提供一定的平移不变性。常见的池化有两种:
| 池化类型 | 计算方式 | 特点 | 典型应用 |
|---|---|---|---|
| 最大池化 | 取窗口内最大值 | 保留最显著特征,对纹理敏感 | 图像分类网络 |
| 平均池化 | 取窗口内平均值 | 保留整体分布,对背景敏感 | 全连接前降维 |
池化的计算量虽然不大,但它的访存模式很特殊——非连续、跨步长访问。我在项目中遇到过,有些加速器在池化层上反而因为访存冲突导致性能下降。所以别小看它。
2.3 全连接运算:参数大户
全连接层,说白了就是一个矩阵向量乘法。输入向量x,权重矩阵W,输出向量y:
y = W × x + b
全连接层的计算量其实不算特别大,但它的参数数量惊人。举个例子,VGG-16的第一个全连接层,输入是25088维,输出是4096维,光这一层就有25088×4096 ≈ 1.03亿个参数。
避坑指南:我曾经在设计一个边缘端加速器时,忽略了全连接层的参数存储需求。结果发现片上SRAM根本放不下,不得不频繁访问DDR,功耗直接翻倍。全连接层的参数占比,往往比计算量占比更值得关注。
2.4 计算与存储瓶颈分析
好了,现在我们把三种基本运算都过了一遍。接下来,我们来分析一下,为什么通用处理器搞不定这些计算。
我画了一张图,把CNN的计算与存储瓶颈梳理了一下:
从这张图可以看得很清楚。三个瓶颈互相交织,最终指向一个结论:通用处理器搞不定。
为什么会这样?我简单算一笔账:
- 计算方面:一个典型的CNN推理,需要做数亿到数十亿次MAC操作。CPU的ALU数量有限,即使有SIMD指令集,并行度也远远不够。GPU虽然并行度高,但功耗太大,不适合嵌入式场景。
- 存储方面:权重参数动辄几十MB到几百MB,片上SRAM根本放不下。每次从片外DRAM读取数据,功耗是片上访问的100倍以上。
- 带宽方面:数据重用率低导致频繁的DRAM访问。我实测过一个MobileNet在树莓派上的推理,超过60%的时间花在等待数据上。
我的经验:判断一个加速器设计好不好,我通常会先看它的数据重用策略。如果能把数据重用率提高10倍,带宽需求就能降低一个数量级。这是Eyeriss架构的核心思想之一,后面我们会详细讲。
2.5 专用加速器的必要性
好了,现在我们来回答一个核心问题:为什么需要专用加速器?
说白了,通用处理器(CPU/GPU)的设计目标是通用性,它要能跑各种程序。但DNN计算有非常特殊的模式:
- 计算模式固定:卷积、池化、全连接,就这三种基本操作,循环结构高度规律。
- 数据流模式固定:特征图、权重、偏置,数据流动方向可预测。
- 精度要求灵活:推理场景下,8位整数甚至更低精度就够用,不需要64位浮点。
- 并行度极高:每个输出像素的计算相互独立,天然适合大规模并行。
专用加速器可以针对这些特点做极致优化:
| 优化方向 | 通用处理器 | 专用加速器 |
|---|---|---|
| 计算单元 | 少量通用ALU | 大量MAC阵列(如256个) |
| 存储层次 | 通用缓存 | 专用片上存储(权重/特征图分离) |
| 数据流 | 指令驱动,不可预测 | 静态调度,可预测 |
| 精度 | FP32/FP64 | INT8/INT4,甚至二值化 |
| 能效比 | ~10 TOPS/W | ~100 TOPS/W(Eyeriss级别) |
我当年参与的第一个加速器项目,目标是在1W功耗内实现1TOPS的算力。用CPU?不可能。用GPU?功耗至少10W起步。最后我们选择了专用架构,才勉强达标。
核心观点:专用DNN加速器的本质,是用硬件资源换取能效比。把固定的计算模式固化在硬件中,去掉指令取指、译码、调度等开销,把每一焦耳的能量都用在刀刃上。