MAC 乘加单元:a×b+c 一个周期如何成为 AI 芯片的原子运算
更新时间:2026-09-25。本文是「集成电路大类 · 芯片设计」**进阶层(intermediate)**数字逻辑专题之一。前面已经有整数乘法器、Wallace/Dadda 压缩树和浮点 IEEE 754三篇垫底,这一篇把「乘法 + 加法」焊在一起,看 AI 芯片里被复制成千上万次的那个原子部件。配套阅读:混合精度 FP16/BF16/FP8、INT8/INT4 量化校准、脉动阵列。
本文要回答的问题
- 为什么 AI 芯片的算力单位是 MAC/TOPS,而不是 MIPS?一个 MAC 在电路上由哪几级组成?
- 定点 MAC(INT8/INT16)凭什么又小又省电?乘法器、压缩树、累加器各占多少?
- 累加器位宽为什么不能和输入一样宽?连加多少次会溢出,guard bits 怎么算?
- 浮点 MAC(FP16/FP32)多出了对阶、规格化、舍入几级,代价大到什么程度?
- 位宽和面积、功耗、算力密度是什么数量关系?MAC 在 systolic 阵列里又是怎么连的?
一、MAC 是什么:a×b+c,一个周期出一个结果
MAC(Multiply-Accumulate,乘累加)做的事情写出来只有一行:
acc ← a × b + acc即一个时钟周期内完成一次乘法并把乘积叠加上去。它之所以是深度学习的「原子」,是因为神经网络的计算几乎全部能化成点积,而点积就是一串 MAC:
卷积输出一个像素: y = Σ(i=0..K²−1) w[i]·x[i] → K² 个 MAC
全连接一层: y[j] = Σ(i=0..N−1) w[j][i]·x[i] → N×M 个 MAC一个 1×1 的卷积、一次 attention 里的 QKᵀ 矩阵乘、LayerNorm 之前的投影,剥开层层循环后没有任何别的运算。剩下的非线性算子(ReLU、softmax、归一化)在总指令数里占比通常不到一成,硬件上要么在阵列出口顺手做掉,要么交给一个小向量单元,完全不影响「MAC 是主算力消费者」这个基本盘。所以芯片宣传页上的「624 TOPS INT8」意思是:每秒最多完成 624 万亿次 INT8 乘累加(有的厂商标法把一次 MAC 计作 2 个 OP,读规格书要先对口径)。
一个最朴素的定点 MAC 数据通路如下:操作数先打一拍寄存器喂给乘法器,双宽积送到加法器与累加寄存器里的旧值相加,结果写回。
图上有一个容易被新手忽略的细节:乘法器输出是 2n 位,累加器比它还宽(多出 g 位 guard bits),这条放宽的通路是后两节的主线。
延迟与吞吐要分开看。 图上这条通路在真实芯片里几乎都会插流水线寄存器:乘法器内部「部分积产生 → 压缩树 → 末级加法」可以切成 2~3 拍,累加反馈回加法器入口再切一拍。于是单个 MAC 从进操作数到出累加结果可能要 3~4 拍延迟,但喂饱流水线后每个周期都能吐出一个新的部分和。阵列场景里更极致:相邻周期的乘法互不相关(不同 PE、不同点积位置),流水线可以 100% 填满。评估 MAC 单元的时序时,设计者真正盯的是「压缩树最后一层到累加器进位」这一段能不能压进一个周期——它决定整个阵列能不能上目标主频。
二、定点 MAC:小乘法器 + 压缩树 + 加宽累加器
INT8 MAC 里两个 8 位有符号数相乘,硬件做三件事:
- 产生部分积:按 Booth 或直接与阵列产生若干行部分积(8×8 最多 64 个与门那一行);
- 压缩:用全加器/半加器搭成的 Wallace/Dadda 树把多行部分积压成两行,再走一个先行进位加法器收成 16 位积;
- 累加:16 位积送加法器与累加寄存器相加。
INT16 同理,只是规模全变:16×16 的部分积阵列是 8×8 的 4 倍面积量级。乘法器面积大致随操作数位宽平方增长,这一条决定了后面所有的密度对比。
两个实现细节值得展开。其一,有符号数不能直接摆与阵列:补码乘法若不处理符号位,负权值会产生错误的部分积,常见做法是 Booth 编码——基 4 Booth 一次扫描两位、直接产生带符号的倍数(0、±1、±2 倍被乘数),部分积行数直接减半,后面的压缩树也跟着少一半层级,是窄位宽 MAC 的标准打法。其二,点积有两种加法组织方式:串行累加(一个加法器反复使用,面积小但每个点积要 N 拍)和加法树归约(每拍产生的多个乘积用树形结构两两相加,N 个乘法器一次铺开,一个点积只需 log₂N 拍延迟),systolic 阵列里用的是前者在空间上的展开——每个 PE 串行累加自己那条线,多个 PE 在空间上并行,恰好把面积和延迟折中。
累加位宽与溢出算例。 两个 INT8 有符号数最坏情况是 (−128)×(−128) = 16384 = 2¹⁴,单个积就要 16 位存放。假设一个点积连续累加 N 次:
最大累加和 = N × 16384 = 2¹⁴ × N
需要的位数 ≈ 16 + ceil(log2 N) (含符号口径)- N = 256(典型 3×3、16 通道以内的卷积核规模):需要 16 + 8 = 24 位;
- N = 4096(大通道数的 1×1 卷积):需要 16 + 12 = 28 位。
工程上不做一堆定制宽度,直接统一用 32 位累加器,INT8×INT8 连加 2²⁴ 次都不会溢出,覆盖神经网络里所有实际核长。这就是为什么量化推理的算子定义几乎都是「INT8 输入、INT32 累加、再 requantize 回 INT8」——输入窄、累加宽,详见量化校准。位宽舍不得加,跑出来的特征图就会静默回绕(wrap-around),误差不是噪声而是灾难,而且仿真时未必次次撞上最坏值。
三、浮点 MAC:对阶、尾数乘、规格化,每一级都贵
浮点不能拿尾数直接相乘再相加,因为两个数的指数不同。一个 FMA(fused multiply-add)数据通路至少多三块东西:
流程对应 IEEE 754 那一篇讲的三步:乘积与加数先对阶(小指数向大指数看齐,尾数右移,需要大桶形移位器),宽加法器相加(中间保留 guard/round/sticky 三位防双重舍入误差),再经 LZC 前导零检测决定左移多少位重新规格化,最后修指数、就近偶舍入。FMA 把乘和加熔在一条通路里,中间只舍入一次,比「先乘后加」既快又准——但 LZC、两个大移位器、指数通路和特殊值(∞/NaN/非规格化)处理一个都省不掉。
还要说明 FMA 为什么敢把乘法和加法焊在一起而不是两个独立单元串起来。拆开的实现里乘法结果先舍入到目标格式(比如 FP16 的 11 位尾数),再取出来参与加法,等于丢了两轮精度;FMA 在内部把尾数乘积保持在双宽(FP32 下是 48 位量级的中间尾数),加完才唯一舍入一次。代价是中间加法器、移位器全都按这个更宽的中间规格做——面积又涨一截,但关键路径反而比「乘法器 + FADD 两个完整单元」更短,因为省掉了中间写回和重新取指。非规格化数是另一个性能黑洞:一旦中间结果落到最小正规格数以下,隐含前导 1 消失,正常通路算出来的是错的,高性能 MAC 普遍挂 FTZ(flush-to-zero)把它直接归零,换来可预测的单周期吞吐。
算例感受对阶损失。 FP16 尾数只有 10 位(隐含 1,共 11 位精度)。计算 1024 + 1:1024 = 1.0×2¹⁰,对阶时 1 的尾数要右移 10 位,直接全部移出尾数窗口,结果仍是 1024。这不是 bug 而是格式本身的精度天花板;训练里梯度累加遇到这种「大数吃小数」就会丢更新,所以混合精度训练普遍用 FP16/BF16 存参数、FP32 做累加。
四、位宽-面积-功耗-密度:一张表看清 INT8 为什么赢
乘法器面积 ≈ 位宽平方,再把浮点周边(对阶移位器、LZC、规格化、舍入、指数通路)算进去,单 MAC 的代价随格式急剧拉开。以下为同工艺、同主频下的相对数量级(不同微架构会浮动,但量级关系稳定):
| 格式 | 尾数/乘法器规模 | 单 MAC 相对面积 | 相对能耗/次 | 同面积算力密度 | 典型场景 |
|---|---|---|---|---|---|
| INT8 | 8×8 乘法 | 1× | 1× | 1×(基准) | 推理主力:检测、分类、端侧视觉 |
| INT16 | 16×16 乘法 | ~4× | ~3–4× | ~1/4 | 高精度音频、控制、部分雷达 DSP |
| FP16/BF16 | 11×11 尾数 + 浮点周边 | ~6–10× | ~5–8× | ~1/8–1/12 | 训练、推理的敏感层(softmax/归一化) |
| FP32 | 24×24 尾数 + 浮点周边 | ~15–25× | ~12–20× | ~1/16 | 科学计算、训练累加、梯度更新 |
读这张表的三个要点:
- 「INT8 密度约 FP32 的十几倍」:单看尾数乘法器,24×24 对 8×8 是 9 倍面积差;把对阶、LZC、规格化、舍入和更宽的寄存器堆一起算进去,同样一片硅片上塞下的 INT8 MAC 数量约为 FP32 MAC 的十几倍。再叠加「一个 INT8 MAC 每次搬运的数据只有 FP32 的四分之一、访存能耗同步下降」,端到端能效差常常比面积差还大。
- INT16 是最朴素的参照点:没有任何浮点周边,纯粹 4 倍面积/0.25 密度,干净地展示平方律本身。
- FP16 和 BF16 数据通路几乎一样大:两者都是 16 位、尾数乘法规模相近(11 位),差别只在指数位宽切分,硬件成本基本相同,选谁取决于动态范围需求而非成本。
这也解释了为什么一颗芯片要同时报多种算力:INT8 数字最好看拿去做峰值宣传,FP16/BF16 决定训练能力,FP32 留给那些量化后精度救不回来的薄层。
补一个能效视角的算账:先进工艺下,一次 8 位整数运算本身的能耗在飞焦(fJ)量级,而从片外 DRAM 读一个 32 位数据的能耗是它的上千倍,即便从片上 SRAM 读也要贵几十倍。这意味着 MAC 数据通路真正的能耗杠杆有两根——一是把数据位宽砍窄(FP32 变 INT8,同样的搬运量带走 4 倍的操作数),二是让每个读进来的数据被更多次 MAC 复用(正是下一节阵列做的事)。只比 MAC 内部能耗而忽略搬运,会严重低估混合精度系统的真实收益。
五、MAC 阵列:单个 MAC 不够,要几万个连起来
单个 MAC 一个周期只出一个部分和,而矩阵乘需要的是百万级 MAC/周期。现代 AI 芯片把 MAC 排成二维阵列,其中最有代表性的是 systolic(脉动)阵列:每个处理单元(PE)内部就是一个 MAC,权重在阵列里预先驻留,输入数据从左向右、部分和从上向下逐拍流动,每个 PE 每拍执行一次 acc += a·b 并把数据转发给邻居。
PE(0,0): a00·w00 ──psum──▶ PE(1,0): +a01·w10 ──psum──▶ PE(2,0) ...
▲ a 向右流 ▼
PE(0,1) ... 末端得到一个输出点积这种连法的精妙之处在于:每个数据从片上 SRAM 只读出一次,之后在 PE 之间靠寄存器转发被反复复用,省掉了海量访存(阵列的功耗大头从来不是 MAC 本身而是喂数据)。可以算一笔复用账:一个 K×K 的阵列,同一个输入值横向穿过 K 个 PE,相当于被 K 个不同权重复用;权重预先灌进阵列后驻留 K 拍以上,被 K 个不同输入复用。对比「每个 MAC 都临时从 SRAM 取两个操作数」的朴素组织,访存次数下降了约一个阵列边长的倍数,这正是阵列越大、单位算力功耗越好看的原因,也是阵列尺寸不能无限大的原因——边长超过矩阵规模后利用率反而塌缩。
从单个 MAC 的视角看,进阵列后它还会再瘦身:PE 里的加法器只做「积 + 本 PE 部分和」,不需要通用 ALU 的标志位、旁路和多操作数选择;控制信号全阵列共享,每个 PE 连译码都省了;部分和在列方向流动时通常保持更宽位宽(INT8 阵列的列底也是 INT32),到阵列出口才统一做偏置、激活和 requantize。换句话说,前面几节那些「舍不得」的面积(Booth 编码、guard bits、FTZ)在阵列语境下都会被成千上万倍地放大,所以 MAC 单元级别的每一个全加器都值得抠。一个 128×128 的阵列就是 16384 个定点 MAC 在同时跳动,阵列的完整时序、数据流分类(权重驻留/输出驻留/行驻留)和为什么选这个尺寸,在脉动阵列专题里展开。阵列设计给 MAC 单元提了两个硬约束:面积必须压到极小(不然阵列铺不开)、接口必须规则(输入、输出、部分和三根线),所以阵列里几乎只放定点窄位宽 MAC,FP32 通路只在阵列出口或独立向量单元里出现。
六、累加精度与溢出:工程上的三种处理姿势
最后把「加」这件事的坑收一下,实际芯片里就三种策略:
- 加宽定点累加器(最主流):INT8 配 INT32 累加,数学上保证任意实际核长不溢出,零额外操作,代价只是累加寄存器和加法器宽一点——而这部分在整个 MAC 面积里占比很小,性价比极高。
- 饱和(saturation):溢出时钳到最大正值/最小负值而不是回绕。饱和后的结果至少是个「合理方向」的大数,不会从正最大值跳到负最大值把整层特征搅乱,控制类 DSP 尤其依赖它。实现上只需在加法器出口检查「两个正数相加得负」之类的溢出标志,再用一个二选一多路器替换成钳位值,代价是几十门,却能把一类静默数据错误变成显式可见的限幅,通常还会顺手挂出一个饱和状态位供固件统计。
- 浮点/块浮点累加:训练场景用 FP32 累加保动态范围;有的推理芯片用 block floating point——一组数共享指数、尾数仍按定点累加,介于两者之间。
除了位宽,舍入方式也直接影响累加质量。INT8 推理在 requantize 那一步若只做简单截断,相当于系统性向下偏置,逐层累加成可见的精度损失;更好的做法是加零点的随机舍入(stochastic rounding),让舍入误差期望为零。训练侧小批量梯度更新尤其吃这个特性——很多更新量小于目标格式的末位精度,确定性舍入会把它们全部丢掉,训练直接不收敛。
需要警惕的是部分和重排:定点加法满足结合律,加法树怎么排结果都一样;浮点不满足,同一组乘积换个累加顺序结果就末位跳动,做参考模型对拍时这是最常见的「假 bug」来源,RTL 仿真和 GPU golden 值对不上时先查累加顺序,再怀疑电路。另外 INT4 等更窄格式的乘法器虽然更小,但单积动态范围太差,对累加器宽度和量化校准的要求反而更苛刻,并不是位宽砍半就白赚一倍密度。
这一篇在知识体系里的位置
- 前置基础:整数乘法器讲部分积与移位乘法、Wallace/Dadda 压缩树讲乘法器内部怎么压、浮点 IEEE 754讲对阶与规格化、高级加法器讲末尾进位怎么收。
- 同层配套:混合精度 FP16/BF16/FP8回答训练和推理各选什么格式,INT8/INT4 量化校准回答模型怎么变成窄位宽输入。
- 向上集成:AI 加速器脉动阵列讲 MAC 成千上万连片之后的数据流与架构权衡,是本篇的直接下篇。
总结
一句话总结:MAC 就是 acc ← a×b+acc 一个周期完成的乘累加,卷积和矩阵乘剥开后全是它,所以它是 AI 芯片的原子与算力单位;定点 MAC 由窄乘法器(部分积加 Wallace/Dadda 压缩树,面积随位宽平方增长)和一个刻意加宽的累加器组成——INT8 积是 16 位、累加 N 次需要 16+log₂N 位,工程上统一用 32 位保证 2²⁴ 次连加不溢出,溢出时用饱和避免回绕;浮点 MAC 在尾数乘法之外还要对阶移位器、宽加法器、LZC、规格化和舍入,FP16 还有「1024+1=1024」的对阶吃数问题,所以训练用 FP16 存参数、FP32 累加;综合算下来同片硅片上 INT8 MAC 的密度约为 FP32 的十几倍、能效差更大,INT16 则以约 4 倍面积干净展示平方律;在 systolic 阵列里每个 PE 就是一个 MAC,数据和部分和在邻居间逐拍转发以最大化复用,要求单元面积极小、接口极规则——理解了乘法器、累加位宽和阵列连接这三件事,就拿到了打开 AI 加速器数据通路的钥匙。