想象你管理着全城最大的中央厨房,每天要按几十种配方批量生产营养餐。每种套餐的味道,都取决于“食材用量 × 配方系数”再求和——这个动作和神经网络里的计算如出一辙。为了让后厨以最高效率做完海量乘加,芯片设计师发明了一台特制灶台:NPU(神经网络处理器)里的专用矩阵单元。 这台灶台并不是一只大锅,而是一整片密密麻麻的“小灶”:成百上千个只会做“乘加”的工位排成方阵,食材沿传送带脉动流过,配方卡钉在每个工位前不动,一次就能把一整块矩阵乘法焐熟。为了兼顾速度与成本,它还准备了 INT8、FP16、BF16 等不同的“称量档位”,可按需切换。这篇教程从矩阵乘法的本质出发,带你拆解矩阵单元的阵列架构、脉动数据流、精度模式与数据流模式,最后用吞吐量和能效两张成绩单,把 NPU 设计的关键权衡一次讲透。
掌握矩阵单元的工作原理,是理解 AI 芯片算力与功耗故事的钥匙——无论做推理加速、模型训练还是芯片选型,这份知识都能帮你做出更专业的判断。
可视化演示
展示NPU专用矩阵单元核心:MAC阵列微架构(4x4乘加阵列A/W输入→O输出)、脉动数据流(权重固定数据波前传播部分和累积)和多精度计算模式(INT8/FP16/BF16/TF32吞吐量对比),三模式理解NPU矩阵计算原理。
本节配有交互动效(进入视口后自动加载,约 3KB)。
'+C1+'
矩阵乘法基础
中央厨房接到一个大单:给幼儿园做营养套餐。每种套餐要把 3 种食材按不同配比混合,再装进 4 种口味的餐盒。厨师长发现,只要把“食材量”和“配方系数”相乘,再加到一起,就是一道菜——这个“乘一下再加起来”的动作反复出现,成了后厨的命脉。
在深度学习中,全连接层和卷积层干的正是这件事。输入特征就是食材,权重矩阵就是配方,输出就是成菜。一个全连接层把 1024 个输入映射到 1024 个输出,就需要约 100 万次乘加。于是芯片设计师不再依赖 CPU 里那种什么都能干的通用计算单元,而是专门做一批只会“乘加”的小部件,这就是专用矩阵单元的由来。
矩阵乘法有严格的数学定义。给定输入矩阵 $A$(尺寸 $M \times K$)与权重矩阵 $B$(尺寸 $K \times N$),输出矩阵 $C$(尺寸 $M \times N$)中每个元素,都是 A 的一行与 B 的一列做点积:
这一节说明白了:矩阵乘法本质是一堆乘加的批发。后厨只要准备一台能快速做“食材 × 系数 + 已有成品”的灶台,就能撑起整条生产线。下面我们看看,把灶台铺开能快到什么程度。
- 输出坐标 $C[i][j]$第 i 行第 j 列的那个输出格子,好比“第 i 种食材配第 j 种口味”的成品。
- 求和符号 $\displaystyle\sum_{k=1}^{K}$从 1 数到 K,把每一项都加起来;其中 K 是输入维度,也就是串起来乘加的次数。
- 逐项乘加 $A[i][k] \times B[k][j]$输入第 i 行的第 k 个元素,乘以权重第 j 列的第 k 个元素,相当于“一种食材 × 一个配方系数”。
MAC阵列架构
一个灶台一次只能出一道菜。幼儿园有 1200 个孩子,每道菜要 100 次乘加,单枪匹马得忙到天黑。怎么办?答案是“灶台矩阵”:把 16 个灶台排成 4 行 4 列,每人负责一道菜的一部分,同一时刻 16 份套餐在同步制作。这个灶台矩阵在芯片里就叫 MAC 阵列。
MAC 是 Multiply-Accumulate(乘加)的缩写。一个 MAC 单元由三部分组成:一个乘法器、一个加法器、一个保存累加值的寄存器。它的行为像记账:在老账本上添一笔新账,账越记越厚,但永远只有“乘、加、存”三个动作。
阵列规模是设计的第一颗旋钮。常见 NPU 采用 16×16、32×32 甚至更大的 MAC 阵列,一个时钟周期内所有单元同时做一次乘加,所以 16×16 阵列一拍能做 256 次 MAC。权重驻留是另一项关键布局:每个 PE(处理单元)内嵌寄存器存放权重,计算时权重不动、输入流经,避免每算一步都去片外取一次配方。
有了灶台矩阵,16 道菜同时开火。但新问题来了:食材和配方怎么送到每个灶台手上?如果每道菜都跑一趟中央仓库,再快的灶台也得饿肚子。于是,聪明的后厨设计了传送带——这就是脉动数据流。
- 累加器 $D$存放中间结果的寄存器,初始为 0,每次乘加后更新一次,相当于灶台上那口越攒越满的锅。
- 乘法 $A \times W$输入数据与权重相乘,是“食材量 × 配方系数”的配比步骤。
- 累加与赋值 $\leftarrow$把“旧账 D 加上新乘出来的积”写回 D 本身,代表累加器不断滚动更新。
脉动数据流
传统厨房里,厨师每做一道菜都要去中央冰箱取料,取料路上的时间比炒菜还多。脉动厨房的解决办法是:把灶台连成传送带,食材从头上下来,每个灶台取一点,做完递给下一位,最后一位把成品装盘。每个灶台只跟左右邻居打交道,谁也不用跑远路。
这个思想在芯片里叫脉动阵列(Systolic Array):数据像脉搏一样,一个周期从相邻的计算单元“泵”到下一个单元。每个 PE 的输出就是下一个 PE 的输入,整片阵列像一条流动的河。权重驻留时,数据只沿一个方向流动,访问片外内存的次数大幅下降。
经典脉动调度可以这样描述:第 r 行 c 列的 PE,在第 t 个时钟周期收到左邻居和上邻居传来的数据:
由于所有搬运都发生在芯片内部相邻单元之间,不再反复访问片外 DRAM,整个阵列每周期处理一条“波前”对角线。当波前扫过全部 $2N-1$ 条对角线时,一次矩阵乘法完成,这就是可视化演示里那条斜向亮带的含义。
传送带让灶台永不待料,数据每周期向前推进一格,所有灶台保持满负荷。不过传送带再快,也要看食材用什么度量衡——下一节讲精度档位。
- PE 定位 $\text{PE}(r,c)$位于阵列第 r 行、第 c 列的处理单元,可以想象成传送带上的第 r 排第 c 个灶台。
- 输入接力 $a_{r,c}^{(t)} = a_{r,c-1}^{(t-1)}$当前时刻的输入数据,来自左边邻居上一时刻的数据,体现“只传给邻居”。
- 权重接力 $w_{r,c}^{(t)} = w_{r-1,c}^{(t-1)}$权重数据从上方邻居逐行流下来,与输入方向垂直,形成二维脉动。
精度模式
中央厨房里有几种称量工具:大中小容量的标准量杯、家庭电子秤,还有一种“量程很大但刻度粗”的工业大秤。同样是称食材,用大秤速度快、省电,但每勺分量可能偏差一点;用精细秤更准,却更慢更费电。芯片里的数值也有类似的“称量工具”,这就是精度模式。
FP32 用 32 位存一个浮点数:1 位符号 + 8 位指数 + 23 位尾数。FP16 砍到 16 位:1 + 5 + 10,尾数精细但指数只有 5 位,能表示的范围很小。BF16 也是 16 位,但格式是 1 + 8 + 7:指数位和 FP32 一样多,范围一样大,只是尾数只有 7 位,精度较粗。INT8 则直接映射到 8 位整数网格,速度最快、能效最高。
INT8 量化的核心是一个缩放因子 s,把浮点数映射到整数区间:
精度对硬件的影响非常直接:INT8 的乘加电路面积约为 FP16 的四分之一,功耗更低,所以同一块芯片的 INT8 峰值算力往往是 FP16 的两倍以上。BF16 保留了大动态范围,训练大模型时梯度不容易溢出,成为 AI 加速器的新宠。
量杯、家用秤、工业大秤各有各的用武之地。NPU 把几种精度电路做进同一个矩阵单元,运行时可切换,就像后厨按订单类型换工具。工具选对了,剩下的问题是:怎么安排人手和物料?这就引出数据流模式。
- 真实值 $x_{\text{real}}$原始的浮点数值,比如 0.37,代表真实的食材分量。
- 整数码 $q_{\text{int8}}$量化后的 8 位整数,比如 4,只占一个字节,运算飞快。
- 缩放因子 $s$把整数网格映射回真实范围的步长,比如 0.1,于是 4 × 0.1 ≈ 0.4,接近原值 0.37。
数据流模式
厨房有两种物料:食材(输入)和配方(权重)。想让矩阵阵列出菜最快,得决定“谁站着不动、谁来回跑”。三种经典分工:配方卡钉在灶台上不动,食材流过台面;食材桶摆在一旁,配方卡和成品锅轮流过来取;成品锅钉在灶台上,食材和配方都流过来加料。
这三种分工在芯片术语中叫 Weight Stationary(WS,权重固定)、Input Stationary(IS,输入固定)、Output Stationary(OS,输出固定)。它们决定了谁被反复利用、谁被不断搬动,直接影响访存次数和能效。用集合语言表示:
实际产品中常见混合数据流:在阵列的行方向用 WS、列方向用 OS,两边优势都要。推理场景权重固定不变,一批批输入数据反复到来,WS 让权重永不挪窝;训练场景反向传播需要频繁累加部分和,OS 让累加器待着不动,能省下大量搬运。
定好分工,就该算产能了:一个阵列一个周期能做多少次乘加?一秒能做多少次?这就是吞吐量。
- WS 模式 $\text{WSfixed } W$:权重留驻在 PE 里一动不动,输入数据流经阵列,最省权重搬运。
- IS 模式 $\text{ISfixed } A$:输入数据钉在本地,权重和输出部分和流动,适合输入激活复用的场景。
- OS 模式 $\text{OSfixed } C$:部分和留在累加器里不断更新,输入和权重都从外面流进来,适合训练时的梯度累积。
吞吐量分析
园长问:这套灶台一小时能出多少份套餐?厨师长掏出公式:灶台数 × 每道菜的动作数 × 灶台开火的频率。芯片里对应三个量:阵列中 MAC 单元总数、每次 MAC 包含的乘与加两个动作、芯片运行的主频。
峰值吞吐量是矩阵单元最重要的性能指标之一,它只由硬件结构决定,与软件无关:
如果矩阵的形状和阵列不匹配,部分 MAC 单元会空转,实际吞吐低于峰值,这个比值叫利用率。比如矩阵维度是 10×10,硬塞进 16×16 阵列,只有约 39% 的灶台在干活。计算密度则把面积成本纳入考量:同样的面积能塞下更多 MAC、跑出更高频率,密度越高,单位面积的算力越值钱。
吞吐量这张成绩单告诉我们:灶台越多、开火越快,出菜就越快。但灶台多了,电费单也会变厚——下一节算算能效这笔账。
- MAC 总数 $N_{\text{MAC}}$阵列里计算单元的总个数,16×16 阵列就是 256 个灶台。
- 因子 2一次乘加运算其实包含“乘”和“加”两步操作,行业统计吞吐时按两步算。
- 频率 $f$芯片时钟每秒跳动的次数,单位是赫兹(Hz),好比灶台一秒能开几次火。
能效权衡
菜做得好,账也得算。后厨的电费单按“每道菜耗多少度电”来考核。芯片界有个对应的金标准:TOPS/W——每瓦特功耗能完成多少万亿次操作。能效是 AI 芯片最重要的指标之一,尤其在手机、汽车等功耗受限的场景里,它甚至比峰值算力更关键。
能效与三个因素强相关:精度、时钟频率和电压。INT8 的乘加电路面积小、翻转少,同样阵列下能效可达 FP16 的 4 到 8 倍;电压从 1V 降到 0.7V,动态功耗约降一半,所以许多 NPU 用“低电压 + 高并行”来换能效。公式如下:
除了精度和电压,利用率也直接决定能效:空转的 MAC 单元虽然没干活,却依然在漏电、在翻转,白白消耗功率。因此数据流模式的选择、批处理的大小、算子与阵列的匹配度,都会影响真实能效。设计矩阵单元时,阵列规模、频率、精度、数据流四个旋钮相互牵制,最终要落在“应用最需要的那个平衡点”上。
至此,中央厨房的完整图景浮现出来:矩阵乘法是菜谱,MAC 阵列是灶台,脉动传送带与三种分工是流水线,精度档位是称量工具,而吞吐和能效是两张成绩单。NPU 矩阵单元的微架构设计,就是在这张成绩单上不断逼近最优点。
- 计算力 $\text{TOPS}$芯片每秒能完成的万亿次操作数,代表“出菜速度”。
- 功耗 $P_{\text{power}}$芯片实际消耗的电功率,单位瓦特,代表“电费成本”。
- 能效 $\eta$两者相除,得到每瓦换来的算力,是衡量“划算不划算”的核心标尺。
AI 应用场景
云端推理:INT8 加速 Transformer
阶段:模型部署与优化
解决的问题:大模型推理延迟高、功耗大,传统 GPU 跑 FP16 的一次自回归生成要几百毫秒。把权重量化到 INT8、映射到矩阵单元后,同一芯片能效可提升数倍,吞吐翻倍。
具体案例:某云厂商把 BERT 模型量化到 INT8,部署在自研推理芯片的 16×16 矩阵单元上,配合权重驻留的脉动数据流,在线推理吞吐提升约 3 倍,单次请求时延下降 60%。
边缘设备训练:BF16 混合精度
阶段:模型训练
解决的问题:边缘芯片内存带宽有限,FP32 训练占显存太大;BF16 与 FP32 指数位相同,梯度动态范围大,不易溢出,训练稳定性接近 FP32。
具体案例:某手机 SoC 的 NPU 支持 BF16 模式,端侧微调视觉大模型时,激活和梯度均以 BF16 存储,显存占用减半,训练吞吐比 FP16 模式更稳。
大模型训练:脉动阵列的高吞吐 GEMM
阶段:大规模分布式训练
解决的问题:大模型训练中矩阵乘法(GEMM)耗时占比超过 70%,需要极高的峰值吞吐和稳定的数据供给。
具体案例:某训练集群用 32×32 脉动阵列跑千亿参数 LLM 的 GEMM 核心,配合 OS 输出固定模式做梯度累加,单卡算力利用率达到 46%,千卡规模下仍保持线性扩展。
Python 演示
import struct def bf16_truncate(value): """把 float32 截断为 bf16 再还原为 float32, 保留高位 16 位""" bits = struct.unpack('>I', struct.pack('>f', value))[0] return struct.unpack('>f', struct.pack('>I', bits & 0xFFFF0000))[0] def fp16_truncate(value): """把 float32 转成 fp16 再还原, 用 struct 的 e 格式""" return struct.unpack('>e', struct.pack('>e', value))[0] class MatrixUnit: """模拟 NPU 专用矩阵单元: MAC 阵列 + 可切换精度""" def __init__(self, rows, cols, precision='bf16'): self.rows = rows self.cols = cols self.precision = precision self.weights = None self.mac_count = 0 self.cycle_count = 0 def load_weights(self, weights): self.weights = weights def quantize(self, value): if self.precision == 'int8': q = int(round(value / 0.1)) q = max(-128, min(127, q)) return q * 0.1 if self.precision == 'fp16': return fp16_truncate(value) if self.precision == 'bf16': return bf16_truncate(value) return value def multiply(self, A, B): """三重循环模拟 MAC 阵列逐层累加, 统计乘加次数""" K = len(B) rows, cols = len(A), len(B[0]) C = [[0.0 for _ in range(cols)] for _ in range(rows)] for i in range(rows): for j in range(cols): for k in range(K): term = self.quantize(A[i][k] * self.weights[k][j]) C[i][j] = self.quantize(C[i][j] + term) self.mac_count += 1 self.cycle_count += 1 return C def main(): A = [[0.1, 0.2, 0.3], [0.4, 0.5, 0.6]] B = [[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]] for precision in ['fp32', 'bf16', 'fp16', 'int8']: unit = MatrixUnit(rows=2, cols=3, precision=precision) unit.load_weights(B) C = unit.multiply(A, B) print(precision, 'C =', C, 'MACs =', unit.mac_count) if __name__ == '__main__': main()
class MatrixUnit用 Python 类模拟专用矩阵单元,rows/cols对应阵列规模,precision对应精度档位。def quantize(self, value)按精度模式对数值做量化:INT8 走缩放取整,FP16/BF16 走截断模拟。bits & 0xFFFF0000把 float32 的低 16 位尾数清掉,等价于 BF16 的舍入方式。term = self.quantize(A[i][k] * self.weights[k][j])模拟 MAC 单元的“乘”:食材 × 配方系数后立即量化。C[i][j] = self.quantize(C[i][j] + term)模拟 MAC 单元的“加”:累加时再次量化,体现反复截断的累积误差。self.mac_count += 1每执行一次乘加就计数,对应吞吐公式里的 $N_{\text{MAC}}$ 可累计次数。main()` 中遍历 `['fp32', 'bf16', 'fp16', 'int8']对比四种精度的输出矩阵,观察 INT8 偏差最大、BF16 范围稳。
常见误区
误区阵列从 16×16 扩到 32×32,峰值算力翻四倍,芯片性能一定也翻四倍。既然面积预算够,往大了堆 MAC 就对了。
正解峰值吞吐确实翻倍,但实际吞吐还取决于利用率。矩阵维度小的时候,大片 MAC 单元空转,就像灶台增加了但食材传送带送不过来,空转的灶台只会浪费面积和电费。阵列规模必须和算子形状、内存带宽匹配,否则“大马拉小车”。
为何常见:厂商宣传单上永远写峰值 TOPS,小白很容易把“峰值”当成“实际”,忽略了喂料带宽这个隐形的天花板。
误区BF16 只有 7 位尾数,FP16 有 10 位,小数位少了三分之一,用 BF16 训练模型肯定掉点。
正解训练效果不光看尾数精度,更看动态范围。BF16 指数占 8 位,与 FP32 相同,能覆盖从极小到极大的数值范围;FP16 指数只有 5 位,大梯度直接溢出成 inf。在大模型训练中,范围不足比精度不足更致命,这也是 BF16 成为主流混合精度格式的原因。
为何常见:人们直觉上认为“小数点后位数越多越准”,但浮点数里“范围”和“精度”是两种独立维度,BF16 牺牲的是后者,保住了前者。
误区流水线就是上一级的输出给下一级,脉动阵列不过把一维流水线画成了二维,原理一样。
正解普通流水线各阶段做不同的任务(如取指、译码、执行),数据单向流动;脉动阵列所有 PE 干同样的乘加活,数据以固定节拍在阵列内循环流动、反复复用,核心收益是大幅减少访存次数,而不是任务并行。
为何常见:两者都强调“数据按拍传递”,脉动这个词又自带节奏感,且示意图里都有箭头,初学者很容易把概念混为一谈。
误区把 float 强转成 int,丢掉小数位就是量化,反正值差不多。
正解真正的 INT8 量化需要一个缩放因子 s 把浮点范围映射到 [-128, 127] 的整数网格,还要做 clamp 防止溢出,并用校准数据集统计 s 的取值。直接截断会让数值整体偏移严重,模型精度大幅下降。
为何常见:很多教程写 q = int(x) 这种伪代码,让人误以为量化等于取整,忽略了 scale 和校准这两个灵魂步骤。
面试题
Q1. NPU 矩阵单元中最基本的运算单元 MAC 执行的动作是
- A.读取内存中的权重值
- B.乘加运算并累加结果← 正确答案
- C.对激活函数求反向梯度
- D.把浮点数转换整型
答案:B。MAC 即 Multiply-Accumulate,核心动作是“乘法和加法并累加”。A 是访存操作,C 是反向传播的一部分,D 是量化操作,都不是 MAC 的本质。
Q2. 矩阵乘法 C=A×B 中,若 A 是 2×3、B 是 3×4,则单个 C[i][j] 需要多少次乘加
- A.2 次乘加运算
- B.3 次乘加运算← 正确答案
- C.4 次乘加运算
- D.12 次乘加运算
答案:B。单个输出元素需要对 A 的第 i 行和 B 的第 j 列做点积,维数是 K=3,因此需要 3 次乘加。12 次是整个 C 矩阵(2×4)的总乘加次数,不是单个元素。
Q3. 脉动阵列中,数据从一个计算单元传到下一个单元依靠的是
- A.全局总线的广播信号
- B.片外 DRAM 的读写中转
- C.相邻单元的物理接力← 正确答案
- D.软件配置的寄存器跳转
答案:C。脉动阵列的灵魂是“数据只传给邻居”,通过相邻 PE 之间的物理连线逐拍传递,避免全局广播和片外访存。A、B、D 描述的都不是脉动特点。
Q4. 关于 BF16 与 FP16 的差异,下列说法正确的是
- A.BF16 指数位更多、数值范围更大← 正确答案
- B.FP16 指数位更多、数值范围更大
- C.两者指数位和尾数位完全相同
- D.BF16 尾数位更多、精度更高
答案:A。BF16 是 1+8+7,FP16 是 1+5+10。BF16 指数位 8 位,和 FP32 相同,范围大;FP16 指数仅 5 位,范围小。C 显然错误,D 说反了。
Q5. 权重固定(WS)数据流模式的特征是
- A.权重留在阵列中不动、输入数据流经← 正确答案
- B.输入数据不动、权重流入阵列
- C.输出部分和不动、权重和输入流动
- D.所有权重和输入数据都驻留在本地
答案:A。WS 模式把权重预装载进 PE 的寄存器并保持不动,让输入数据流经阵列完成乘加,适合推理场景。B 是 IS,C 是 OS,D 不现实。
Q6. INT8 量化公式 x ≈ q×s 中,s 代表的含义是
- A.量化编码 q 的二进制位数
- B.浮点范围到整数网格的缩放因子← 正确答案
- C.矩阵乘法中的稀疏度参数
- D.累加器寄存器的位宽大小
答案:B。s 是缩放因子(scale),通过校准数据集统计得到,负责把浮点分布映射到 [-128,127] 的整数网格。A、C、D 都与 s 无关。
Q7. 峰值吞吐公式 TH = N_MAC × 2 × f 中,因子 2 代表
- A.MAC 阵列排成两行结构
- B.一次乘加含乘法和加法两步← 正确答案
- C.两种精度模式同时切换运行
- D.数据沿两个方向同时流动
答案:B。行业统计 OPS 时,把一次乘加算作两次操作(乘一次、加一次),因此公式里乘 2。它和阵列排布、精度切换、数据流方向无关。
Q8. 影响矩阵单元能效的主要因素包括
- A.使用的数值精度模式← 正确答案
- B.阵列规模与实际利用率← 正确答案
- C.芯片封装的外形尺寸大小
- D.数据流模式的选择策略← 正确答案
答案:ABD。精度决定单次乘加的电路功耗,阵列规模与利用率决定有效工作占比,数据流模式决定访存能耗,三者直接左右 TOPS/W。封装尺寸是间接因素,不是主要能效因素。
Q9. 脉动阵列相比传统片上总线结构的优势包括
- A.大幅减少对片外存储的访问← 正确答案
- B.每个单元只需连接相邻邻居← 正确答案
- C.可以支持任意尺寸的矩阵运算
- D.以数据流动换取高并行度← 正确答案
答案:ABD。脉动阵列让数据在 PE 间接力流动,访存次数大减;物理上只需邻接连线,布线简单;用流动的数据实现高并行。C 错了,矩阵尺寸超过阵列时需要切块和 padding,并不能任意支持。
Q10. 关于 BF16 浮点格式,下列说法正确的有
- A.与 FP32 拥有相同的指数位宽← 正确答案
- B.尾数位宽比 FP16 还要更小← 正确答案
- C.尾数与 FP32 保持完全一致
- D.比 FP16 更抗大数值梯度溢出← 正确答案
答案:ABD。BF16 是 1+8+7,指数位 8 位与 FP32 相同,尾数 7 位小于 FP16 的 10 位,所以 A、B 正确,C 错误。因为指数范围与 FP32 相同,大梯度不易溢出成 inf,D 正确。