4. 脉动阵列原理
脉动阵列(Systolic Array)之所以在 GEMM 加速中占据核心地位,根本原因在于它把“计算”从控制逻辑中解放出来,交给数据流动本身来驱动。传统处理器执行矩阵乘加时,需要反复取指、译码、访存、写回,控制开销往往超过乘加本身;而脉动阵列让权重、激活和部分和像心跳一样在 PE(Processing Element)之间同步推进,每个周期每个 PE 只做一次乘加并把结果传给邻居,控制路径极短,数据复用率极高。
一、数据流驱动计算:为什么“流动”比“调度”更高效
脉动阵列的基本思想是:把矩阵乘加拆成大量规则的小运算,让数据在二维网格中按固定方向逐拍移动。以 C = A × B 为例,A 的行元素从左向右流动,B 的列元素从上向下流动,每个 PE 在本地完成一次乘加,并把部分和沿对角线方向向下或向右传递。这样,同一个 A 元素可以被同一行所有 PE 复用,同一个 B 元素可以被同一列所有 PE 复用,而部分和不需要频繁写回全局内存。
这种结构的本质是“空间换时间”与“局部性换带宽”。PE 之间只有短距离的寄存器到寄存器连接,没有长线跨片通信;每个周期所有 PE 同步工作,形成类似心脏搏动的节律,因此得名“脉动”。对于 FPGA 而言,这种规则结构非常契合 DSP48 切片和 BRAM 的布局,便于做流水线平衡和时序收敛。
核心结论:脉动阵列的性能上限不取决于单个 PE 的算力,而取决于数据供给能否让每个 PE 在每个周期都保持忙碌。数据流设计的目标就是消除气泡、减少停顿、提高 PE 利用率。
二、PE 单元:脉动阵列的最小计算细胞
PE 单元通常由乘法器、累加器、若干寄存器以及局部互连组成。一个典型的 PE 在每个时钟周期执行:
// 简化 PE 行为模型
always @(posedge clk) begin
if (en) begin
acc <= acc + a_in * b_in; // 乘加
a_out <= a_in; // 激活向右传递
b_out <= b_in; // 权重向下传递
end
end
其中 a_in 和 b_in 分别来自左方和上方,a_out 和 b_out 分别送往右方和下方,acc 是本地部分和。根据权重是否驻留、部分和是否移动,PE 可以演化出多种变体。FPGA 实现时,通常把乘法映射到 DSP48E2,把累加和延时映射到寄存器,把权重缓存映射到分布式 RAM 或 BRAM。
三、典型脉动结构:权重固定、输出固定与行固定
根据“谁固定、谁流动”,脉动阵列可分为三类经典结构。它们没有绝对优劣,只有与算法形状、数据复用模式和硬件资源是否匹配。
| 结构类型 | 固定对象 | 流动对象 | 适用场景 | FPGA 实现特点 |
|---|---|---|---|---|
| 权重固定(Weight Stationary) | 权重 B | 激活 A、部分和 | 权重可长期驻留、批量推理 | 权重存 BRAM,PE 复用率高,适合大 K 维 |
| 输出固定(Output Stationary) | 部分和 C | 激活 A、权重 B | 输出块较小、需要频繁累加 | 累加器驻留 PE,减少部分和搬运 |
| 行固定(Row Stationary) | 一行权重与部分和 | 激活按行流动 | 卷积/GEMM 行复用明显 | 行内广播,行间流水,适合规则卷积 |
权重固定结构把 B 矩阵的每个元素预先加载到对应 PE 的本地寄存器中,A 矩阵从左向右流动,部分和沿对角线向下累加。它的优势是权重访问次数最少,适合 K 维很大、权重可复用的场景。输出固定结构则让每个 PE 持有一个输出部分和,A 和 B 同时流动,适合输出块较小但需要多次累加的情况。行固定结构介于两者之间,通常把一行权重固定在一行 PE 中,激活按行广播,部分和在行内累加后向下传递,在卷积神经网络中非常常见。
四、案例:8×8 权重固定脉动阵列做 8×8×8 GEMM
假设我们要在 FPGA 上实现一个 8×8×8 的 GEMM,即 C[8][8] = A[8][8] × B[8][8]。采用权重固定结构时,可以这样组织:
- 把 B 的 64 个元素按行列位置预加载到 8×8 个 PE 的本地权重寄存器中,每个 PE 持有 B[row][col]。
- A 的每一行从左侧输入,每个周期向右推进一格;A[row][k] 在第 k 个周期到达第 k 列 PE。
- 每个 PE 在收到 A 元素后,与本地权重相乘,并把乘积加到沿对角线向下传递的部分和中。
- 经过 8 个周期后,第一行部分和到达底部,形成 C 的一行结果;再经过若干流水周期,全部 64 个输出完成。
这个例子的关键参数是:PE 数量 64,权重加载周期 64,计算流水深度约 8+8=16 个周期,理论峰值算力为 64 次乘加/周期。若时钟为 200 MHz,则峰值约 12.8 GMAC/s。实际性能取决于 A 的供给带宽和部分和的读出带宽。
实操提示:在 FPGA 中实现权重固定脉动阵列时,优先把权重放在 BRAM 中按 PE 列组织,避免每个 PE 单独使用 LUTRAM 导致布线拥塞。A 的输入建议使用移位寄存器链,部分和则用 DSP48 的级联端口向下传递,减少通用布线资源占用。
五、误区与复盘:脉动阵列不是“越大越好”
初学者常认为 PE 阵列越大,算力越高。实际上,阵列规模受限于三个因素:第一,数据供给带宽。如果 A 和 B 的加载速度跟不上 PE 消耗速度,PE 就会空转。第二,部分和位宽与累加精度。K 维增大时,部分和位宽必须增加,否则会溢出或精度不足。第三,时序收敛。PE 之间的短连线虽然快,但大规模阵列的时钟树和复位网络会变得复杂,容易产生时序违例。
另一个常见误区是忽视“填充”和“边界”处理。当矩阵维度不是阵列尺寸的整数倍时,需要补零或做边界屏蔽,否则会引入错误结果。复盘时应重点检查:PE 利用率是否达到预期、权重加载是否与计算重叠、部分和读出是否成为瓶颈、以及是否存在气泡周期。
排障要点:如果综合后 PE 利用率低于 60%,优先检查 A/B 的输入 FIFO 是否频繁空满、权重加载是否阻塞计算、以及部分和是否在 PE 之间产生了反压。脉动阵列的调试应从数据流时序图入手,而不是只看最终结果。
总结来看,脉动阵列的精髓在于用规则的数据流动替代复杂的控制调度。权重固定、输出固定和行固定三种结构分别对应不同的复用策略和硬件约束。理解它们的差异,才能在 FPGA 上为具体 GEMM 形状选择最合适的阵列组织方式,并为后续的 tiling、双缓冲和流水线优化打下基础。