4、计算单元设计:PE内部结构与MAC运算
好,咱们进入正题。PE(Processing Element)是整个Eyeriss架构的“心脏”。你想想看,数据从全局缓存一路流过来,最终就是在PE里完成真正的计算。这块设计得好不好,直接决定了芯片的能效和吞吐。
我个人习惯把PE看作一个“微型处理器”。它有自己的本地存储、控制逻辑,当然还有最核心的运算单元。今天咱们就把PE的里里外外拆开来看。
4.1 PE内部结构总览
一个典型的Eyeriss PE,内部大致包含这几个部分:
- 输入寄存器(Input Registers):暂存从行缓存或列缓存送来的激活值和权重。
- MAC运算阵列:执行乘加运算的核心部件。
- 累加器(Accumulator):将多个部分和累加,得到最终输出。
- 输出FIFO:暂存计算结果,等待写回全局缓冲区。
- 本地控制逻辑:处理握手信号、数据选通、清零等操作。
我画了一张结构图,帮你快速建立印象:
嗯,这张图基本把PE的骨架画出来了。接下来咱们逐个模块深入。
4.2 MAC运算单元设计
MAC(Multiply-Accumulate)是PE里最“累”的模块。它要在一个时钟周期内完成一次乘法和一次加法。我见过不少新手设计MAC时只关注乘法器,忽略了加法树的优化,结果时序跑不上去。
一个标准的MAC运算单元包含:
- 乘法器:输入两个N位数据,输出2N位乘积。
- 加法树:将多个乘法结果累加,通常采用Wallace树或CSA(进位保存加法器)结构。
- 累加寄存器:保存中间结果,支持多次累加。
举个例子,假设我们要实现一个支持INT8的MAC:
// 简化的MAC Verilog描述
module mac_unit #(
parameter DATA_WIDTH = 8,
parameter ACC_WIDTH = 32
)(
input wire clk,
input wire rst_n,
input wire [DATA_WIDTH-1:0] activation,
input wire [DATA_WIDTH-1:0] weight,
input wire acc_en,
output reg [ACC_WIDTH-1:0] result
);
wire [2*DATA_WIDTH-1:0] product;
reg [ACC_WIDTH-1:0] accumulator;
// 乘法器
assign product = activation * weight;
// 累加逻辑
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
accumulator <= 0;
else if (acc_en)
accumulator <= accumulator + product;
end
assign result = accumulator;
endmodule
这段代码看着简单,但实际流片时要注意:乘法器的面积和功耗会随位宽指数增长。我在项目中遇到过,把INT8改成INT4后,MAC面积直接缩小了60%,功耗降了40%。
4.3 位宽与精度配置
位宽配置是PE设计的“灵魂”。选大了,芯片面积和功耗爆炸;选小了,模型精度掉得没法看。咱们得在两者之间找平衡。
Eyeriss架构支持以下几种典型配置:
| 精度模式 | 激活值位宽 | 权值位宽 | 累加器位宽 | 典型应用场景 |
|---|---|---|---|---|
| INT32 | 32 | 32 | 64 | 训练、高精度推理 |
| INT16 | 16 | 16 | 32 | 通用推理 |
| INT8 | 8 | 8 | 32 | 边缘推理(主流) |
| INT4 | 4 | 4 | 16 | 超低功耗场景 |
| 混合精度 | 8 | 4 | 32 | 激活敏感、权值不敏感 |
我个人建议,做边缘端部署时,优先考虑INT8。为什么?因为INT8的硬件开销只有INT32的1/4左右,但精度损失通常控制在1%以内。我曾经在一个图像分类项目里试过INT4,精度掉了3个点,客户直接拒收。后来换成INT8,问题就解决了。
4.4 PE阵列的拓扑与数据流
单个PE设计好了,还得把它们组织成阵列。Eyeriss采用2D Mesh结构,每个PE只与相邻的PE通信。这样做的好处是:布线短、功耗低、扩展性好。
数据流方面,我习惯用“行广播、列累加”的方式:
- 行方向:同一行的PE共享同一个激活值输入。
- 列方向:同一列的PE共享同一个权值输入。
- 局部累加:每个PE的累加结果在列方向上逐级传递,最终在列底部得到完整输出。
这种数据流的好处是:数据复用率高,减少了对全局缓存的访问次数。我算过一笔账:在16×16的PE阵列中,采用行广播列累加,数据搬运量比全连接方式减少了约70%。