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 微架构的完整数据流

咱们把上面这些串起来,看看一个完整的数据流是什么样的:

  1. 加载阶段:权重从行缓冲加载到 PE 的输入寄存器。激活值从列缓冲加载到 PE 的输入寄存器。
  2. 计算阶段:MAC 单元执行乘加操作,结果存入累加器。
  3. 传递阶段:psum 从累加器传到输出寄存器,然后传递给下一个 PE。
  4. 清空阶段:当所有累加完成后,psum 从最右侧的 PE 写回全局缓冲区。

这个过程是流水线化的。也就是说,当 PE1 在做计算时,PE2 已经在加载数据了。这种设计能最大化吞吐量。

下面这张图展示了 PE 内部的结构和数据流向:

PE 微架构内部结构图 输入寄存器 (激活值 + 权重) MAC 单元 乘法器 (16-bit) 加法器 (20-bit) 累加器 (20-bit) 输出寄存器 本地控制逻辑 数据 psum psum 控制信号 邻居 PE 输入 数据传递 输出到邻居 说明: 数据流方向:输入寄存器 → MAC 单元 → 累加器 → 输出寄存器 → 邻居 PE 控制信号由本地控制逻辑统一调度,确保三种数据流(权重、激活值、psum)时序对齐

这张图里,我特意把数据流和控制流分开了。实线是数据流,虚线是控制流。你想想看,如果控制信号和数据信号混在一起,调试的时候会多痛苦——我当年就因为这个,在仿真器前熬了三个通宵。

4.5 稀疏计算对 PE 微架构的影响

Eyeriss 支持稀疏计算,这意味着 PE 需要能处理“跳过零值”的情况。具体来说:

  • 零值检测:在输入寄存器后面加一个零值检测器。如果检测到激活值或权重为 0,直接跳过 MAC 操作。
  • 门控时钟:当 MAC 单元被跳过时,关闭其时钟,节省动态功耗。
  • 数据压缩:稀疏数据在传递时,可以用游程编码(RLE)压缩,减少 PE 间的数据带宽。

实际效果:在 70% 稀疏率的场景下,PE 的功耗可以降低 50% 以上。但代价是控制逻辑变复杂了——零值检测和门控时钟的引入,让 PE 的面积增加了约 10%。

我个人习惯在 PE 设计初期就把稀疏支持考虑进去。如果后期再加,改动量会非常大——我曾经在一个项目里,因为前期没考虑稀疏,后期不得不重做整个 PE 的控制逻辑,浪费了两个月的时间。

好了,PE 微架构的核心内容就这些。记住:PE 设计的关键是平衡——面积、功耗、性能,三者不可兼得。你要根据具体的应用场景,找到最适合的平衡点。