4. 处理单元(PE)微架构:PE内部结构、MAC单元设计、PE间的数据传递
好,咱们今天聊聊 PE 内部到底长什么样。很多同学看 Eyeriss 时,总觉得 PE 就是个黑盒子——数据进去,结果出来。但实际做芯片时,这个黑盒子里面的门道可不少。我当年第一次做加速器时,就因为在 PE 内部结构上偷了懒,结果后端布局布线时吃了大亏。
4.1 PE 内部结构:一个精简的“小处理器”
说白了,每个 PE 就是一个轻量级的计算核心。它不像 CPU 那样有复杂的控制逻辑,但该有的东西一样不少。
一个典型的 Eyeriss PE 内部包含以下几个关键模块:
- 输入寄存器(Input Registers):暂存从行缓冲或邻居 PE 传来的激活值和权重。
- MAC 单元(Multiply-Accumulate Unit):核心计算单元,做乘加操作。
- 累加器(Accumulator):保存部分和(psum),支持多周期累加。
- 输出寄存器(Output Registers):暂存计算完成的 psum,准备传递给下一个 PE 或全局缓冲区。
- 本地控制逻辑(Local Controller):处理简单的握手信号和数据选择。
关键点:PE 内部没有大容量的 SRAM,只有寄存器级别的存储。这是为了追求高吞吐和低功耗——你想想看,如果每个 PE 都配个 1KB 的缓存,那 168 个 PE 加起来就是 168KB,面积和功耗都扛不住。
我习惯把 PE 看作一个“三明治”结构:上面是数据输入层,中间是计算层,下面是数据输出层。这种分层方式在后端物理设计时特别好用,能有效减少信号串扰。
4.2 MAC 单元设计:乘法器与加法器的博弈
MAC 单元是 PE 的心脏。Eyeriss 用的是定点 MAC,不是浮点。为什么?因为定点在面积和功耗上优势太明显了。我在一个项目里做过对比:同样的 16 位定点 MAC 比 16 位浮点 MAC 面积小 40%,功耗低 60%。
MAC 单元的内部结构其实很简单:
// 伪代码:一个典型的 MAC 操作
psum_new = psum_old + (activation * weight)
但实现起来有几个坑:
- 乘法器位宽选择:Eyeriss 默认用 16 位乘法。我建议不要盲目减位宽——我曾经试过 8 位乘法,结果精度损失太大,模型精度掉了 2 个点,得不偿失。
- 加法器树结构:如果 PE 支持多个乘法器并行(比如 4 个乘法器),就需要加法器树来合并结果。这时候要注意加法器树的深度,深度越深,时序越紧张。
- 累加器溢出处理:psum 会不断累加,如果不做饱和截位,数据会溢出。Eyeriss 的做法是给累加器多留几位(比如 20 位),最后输出时再截断。
| 参数 | Eyeriss 默认值 | 我的建议 |
|---|---|---|
| 乘法器位宽 | 16 位 | 16 位(精度和面积的最佳平衡点) |
| 累加器位宽 | 20 位 | 20-24 位(取决于网络层数) |
| 乘法器数量 | 1 个 | 1-4 个(看面积预算) |
| 加法器树深度 | 1 级 | 2 级以内(超过 2 级时序容易崩) |
小技巧:如果你在做低功耗设计,可以考虑在乘法器输出加一个门控时钟。当输入为 0 时,直接跳过乘法操作。这在稀疏计算中特别有用——稀疏率越高,节省的功耗越多。
4.3 PE 间的数据传递:近邻通信的艺术
Eyeriss 的 PE 阵列是二维 mesh 结构。每个 PE 只和上下左右四个邻居通信。这种设计的好处是布线简单、扩展性好。但坏处是——如果数据需要跨越多跳,延迟会很高。
数据传递主要有三种模式:
- 权重广播(Weight Broadcast):同一行的 PE 共享同一组权重。权重从左侧进入,向右逐级传递。
- 激活值行传递(Activation Row Pass):激活值从上方进入,向下逐行传递。每个 PE 只保留自己需要的激活值。
- psum 累加传递(psum Accumulation Pass):psum 从左侧进入,向右累加。最终结果在最右侧的 PE 输出。
嗯,这里要注意:这三种模式是同时进行的。也就是说,一个 PE 在同一周期内可能同时接收权重、激活值和 psum。这对控制逻辑的要求很高——我曾经在仿真时发现数据冲突,就是因为没有处理好三种数据流的时序对齐。
避坑指南:我曾经在一个项目里,因为 PE 间的数据传递路径太长,导致关键路径时序不收敛。后来我加了一级流水寄存器,虽然多了一个时钟周期的延迟,但频率从 500MHz 提到了 800MHz。记住:在芯片设计里,频率比延迟更重要。
4.4 PE 微架构的完整数据流
咱们把上面这些串起来,看看一个完整的数据流是什么样的:
- 加载阶段:权重从行缓冲加载到 PE 的输入寄存器。激活值从列缓冲加载到 PE 的输入寄存器。
- 计算阶段:MAC 单元执行乘加操作,结果存入累加器。
- 传递阶段:psum 从累加器传到输出寄存器,然后传递给下一个 PE。
- 清空阶段:当所有累加完成后,psum 从最右侧的 PE 写回全局缓冲区。
这个过程是流水线化的。也就是说,当 PE1 在做计算时,PE2 已经在加载数据了。这种设计能最大化吞吐量。
下面这张图展示了 PE 内部的结构和数据流向:
这张图里,我特意把数据流和控制流分开了。实线是数据流,虚线是控制流。你想想看,如果控制信号和数据信号混在一起,调试的时候会多痛苦——我当年就因为这个,在仿真器前熬了三个通宵。
4.5 稀疏计算对 PE 微架构的影响
Eyeriss 支持稀疏计算,这意味着 PE 需要能处理“跳过零值”的情况。具体来说:
- 零值检测:在输入寄存器后面加一个零值检测器。如果检测到激活值或权重为 0,直接跳过 MAC 操作。
- 门控时钟:当 MAC 单元被跳过时,关闭其时钟,节省动态功耗。
- 数据压缩:稀疏数据在传递时,可以用游程编码(RLE)压缩,减少 PE 间的数据带宽。
实际效果:在 70% 稀疏率的场景下,PE 的功耗可以降低 50% 以上。但代价是控制逻辑变复杂了——零值检测和门控时钟的引入,让 PE 的面积增加了约 10%。
我个人习惯在 PE 设计初期就把稀疏支持考虑进去。如果后期再加,改动量会非常大——我曾经在一个项目里,因为前期没考虑稀疏,后期不得不重做整个 PE 的控制逻辑,浪费了两个月的时间。
好了,PE 微架构的核心内容就这些。记住:PE 设计的关键是平衡——面积、功耗、性能,三者不可兼得。你要根据具体的应用场景,找到最适合的平衡点。