NPU 设计(一):从神经网络计算到数据通路
前言
NPU 最容易被一句话讲浅:它是用来做神经网络推理的矩阵乘法加速器。这句话没有错,但它省略了真正有工程价值的部分。
一个 NPU 不只是“能乘矩阵”。它要解决四个问题:
- 神经网络中的计算怎样变成规则的乘加。
- 数据怎样进入片上缓冲并被重复使用。
- 大量 PE 怎样同时工作而不是等数据。
- 主机怎样用命令驱动
LOAD -> MATMUL -> ACT -> POOL -> STORE这条算子链。
这篇先建立整条数据通路的心智模型。后面几篇再分别拆 PE 阵列、Unified Buffer、控制状态机和仿真验证。
神经网络为什么总绕不开乘加
神经网络推理里最常见的核心计算,可以压缩成一个公式:
1 | |
这就是矩阵乘法。全连接层天然是矩阵乘法;卷积可以通过数据展开变成矩阵乘法;attention 里的 QK、AV 也大量依赖矩阵乘法。
举一个极小例子:
1 | |
这里一共做了 8 次乘法和 4 组累加。矩阵规模变大以后,这种乘加会成千上万次重复出现。NPU 的机会就在这里:把通用 CPU 上的循环,变成硬件空间上的并行阵列。
CPU 和 NPU 的关注点不同
CPU 擅长处理复杂控制流:
- 分支;
- 函数调用;
- 指针;
- 异常;
- 操作系统;
- 各种不规则数据结构。
NPU 的目标更窄:让规则张量计算尽可能高吞吐、低能耗地完成。
所以 NPU 设计更关心这些问题:
| 问题 | CPU 视角 | NPU 视角 |
|---|---|---|
| 乘加 | 一条条指令执行 | 大量 PE 同时执行 |
| 数据 | cache 尽量命中 | 片上 buffer 显式复用 |
| 控制 | 指令流驱动 | 命令状态机驱动 |
| 输出 | 寄存器/内存结果 | 流式结果或写回 buffer |
换句话说,NPU 不是“更强的 CPU”,而是把某一类循环硬化成数据通路。
为什么常用 INT8 输入、INT32 累加
推理阶段经常使用量化后的 INT8 权重和激活。低位宽有三个直接好处:
- 乘法器面积更小。
- 片上 buffer 占用更小。
- 同样带宽能搬更多元素。
但累加结果不能也随便用 8 位。看一个最坏但很直观的估算:
1 | |
这个结果已经远远超过 8 位,也超过 16 位有符号范围。因此常见做法是:输入是 INT8,乘积扩展后进入 32 位累加器。
一个 PE 的计算可以写成:
1 | |
这里的关键不是语法,而是数据格式分工:
a、b用 INT8,降低存储和乘法成本;product需要按有符号数解释;sum用 INT32,容纳长链累加。
一条最小 NPU 数据通路
一个教学级 NPU 可以分成五个部件:
| 部件 | 作用 |
|---|---|
| Control Unit | 接收命令,调度状态机 |
| Unified Buffer | 存权重、激活、中间结果和输出 |
| PE Array | 执行并行乘加 |
| Activation Unit | 执行 ReLU、LeakyReLU 等非线性 |
| Pooling Unit | 执行 2x2 max/average pooling |
它们串起来以后,典型工作流是:
1 | |
这条链路看起来像软件 API,实际上是硬件状态机的输入语言。
一个 NPU 命令长什么样
为了让主机驱动 NPU,命令可以被压缩成 32 位:
1 | |
其中:
| opcode | 含义 |
|---|---|
0x10 |
LOAD_W,加载权重 |
0x11 |
LOAD_A,加载激活 |
0x20 |
MATMUL,矩阵乘法 |
0x30 |
ACT,激活函数 |
0x40 |
POOL,池化 |
0x50 |
STORE,输出结果 |
这种命令格式很小,但已经能表达一个推理算子链。真实商业 NPU 会有更复杂的 descriptor、DMA、队列、中断和 runtime,但第一性原理仍然类似:软件发命令,硬件按状态机执行。
为什么 Hello 式文章讲不清 NPU
如果一篇文章只说“先运行某个命令,然后看到某个输出”,读者其实很难学到 NPU。因为读者真正缺的不是操作步骤,而是:
- 矩阵乘法怎样映射到 PE。
- 数据为什么要先进入片上 buffer。
- 激活和池化为什么是后处理链。
- 控制命令怎样变成硬件状态。
所以这个系列会尽量把公式、数据流、伪代码、寄存器表和波形观察点直接写在正文里。读者即使没有本地代码,也能顺着文章理解设计。
小结
NPU 设计的起点不是“做一个大乘法器”,而是构造一条稳定的数据通路:
1 | |
这一篇建立了总览。下一篇进入最核心的计算部件:PE 与阵列如何把矩阵乘法在硬件空间里展开。