NPU 设计(一):从神经网络计算到数据通路

前言

NPU 最容易被一句话讲浅:它是用来做神经网络推理的矩阵乘法加速器。这句话没有错,但它省略了真正有工程价值的部分。

一个 NPU 不只是“能乘矩阵”。它要解决四个问题:

  1. 神经网络中的计算怎样变成规则的乘加。
  2. 数据怎样进入片上缓冲并被重复使用。
  3. 大量 PE 怎样同时工作而不是等数据。
  4. 主机怎样用命令驱动 LOAD -> MATMUL -> ACT -> POOL -> STORE 这条算子链。

这篇先建立整条数据通路的心智模型。后面几篇再分别拆 PE 阵列、Unified Buffer、控制状态机和仿真验证。

从矩阵乘法到 NPU 数据通路

神经网络为什么总绕不开乘加

神经网络推理里最常见的核心计算,可以压缩成一个公式:

1
C[i][j] = sum_k A[i][k] * B[k][j]

这就是矩阵乘法。全连接层天然是矩阵乘法;卷积可以通过数据展开变成矩阵乘法;attention 里的 QK、AV 也大量依赖矩阵乘法。

举一个极小例子:

1
2
3
4
5
6
7
A = [ 1  2 ]      B = [ 5  6 ]
[ 3 4 ] [ 7 8 ]

C[0][0] = 1*5 + 2*7 = 19
C[0][1] = 1*6 + 2*8 = 22
C[1][0] = 3*5 + 4*7 = 43
C[1][1] = 3*6 + 4*8 = 50

这里一共做了 8 次乘法和 4 组累加。矩阵规模变大以后,这种乘加会成千上万次重复出现。NPU 的机会就在这里:把通用 CPU 上的循环,变成硬件空间上的并行阵列。

CPU 和 NPU 的关注点不同

CPU 擅长处理复杂控制流:

  • 分支;
  • 函数调用;
  • 指针;
  • 异常;
  • 操作系统;
  • 各种不规则数据结构。

NPU 的目标更窄:让规则张量计算尽可能高吞吐、低能耗地完成。

所以 NPU 设计更关心这些问题:

问题 CPU 视角 NPU 视角
乘加 一条条指令执行 大量 PE 同时执行
数据 cache 尽量命中 片上 buffer 显式复用
控制 指令流驱动 命令状态机驱动
输出 寄存器/内存结果 流式结果或写回 buffer

换句话说,NPU 不是“更强的 CPU”,而是把某一类循环硬化成数据通路。

为什么常用 INT8 输入、INT32 累加

推理阶段经常使用量化后的 INT8 权重和激活。低位宽有三个直接好处:

  1. 乘法器面积更小。
  2. 片上 buffer 占用更小。
  3. 同样带宽能搬更多元素。

但累加结果不能也随便用 8 位。看一个最坏但很直观的估算:

1
2
3
4
int8 最大正数 = 127
单次乘法最大值 = 127 * 127 = 16129
如果累加 256 项:
16129 * 256 = 4,129,024

这个结果已经远远超过 8 位,也超过 16 位有符号范围。因此常见做法是:输入是 INT8,乘积扩展后进入 32 位累加器。

一个 PE 的计算可以写成:

1
2
3
4
// 这是简化后的 PE 语义,不是完整模块。
product = signed(a_int8) * signed(b_int8);
sum = y_in_int32 + sign_extend(product);
y_out = clear ? 0 : sum;

这里的关键不是语法,而是数据格式分工:

  • ab 用 INT8,降低存储和乘法成本;
  • product 需要按有符号数解释;
  • sum 用 INT32,容纳长链累加。

一条最小 NPU 数据通路

一个教学级 NPU 可以分成五个部件:

部件 作用
Control Unit 接收命令,调度状态机
Unified Buffer 存权重、激活、中间结果和输出
PE Array 执行并行乘加
Activation Unit 执行 ReLU、LeakyReLU 等非线性
Pooling Unit 执行 2x2 max/average pooling

它们串起来以后,典型工作流是:

1
2
3
4
5
6
LOAD_W    把权重写入片上 buffer
LOAD_A 把激活写入片上 buffer
MATMUL 启动 PE Array 做矩阵乘法
ACT 对结果做激活函数
POOL 对结果做池化
STORE 把最终结果输出

这条链路看起来像软件 API,实际上是硬件状态机的输入语言。

一个 NPU 命令长什么样

为了让主机驱动 NPU,命令可以被压缩成 32 位:

1
2
3
4
31          24 23          12 19    16 11           0
+-------------+--------------+--------+--------------+
| opcode | addr/base | extra | len/rows |
+-------------+--------------+--------+--------------+

其中:

opcode 含义
0x10 LOAD_W,加载权重
0x11 LOAD_A,加载激活
0x20 MATMUL,矩阵乘法
0x30 ACT,激活函数
0x40 POOL,池化
0x50 STORE,输出结果

这种命令格式很小,但已经能表达一个推理算子链。真实商业 NPU 会有更复杂的 descriptor、DMA、队列、中断和 runtime,但第一性原理仍然类似:软件发命令,硬件按状态机执行。

为什么 Hello 式文章讲不清 NPU

如果一篇文章只说“先运行某个命令,然后看到某个输出”,读者其实很难学到 NPU。因为读者真正缺的不是操作步骤,而是:

  1. 矩阵乘法怎样映射到 PE。
  2. 数据为什么要先进入片上 buffer。
  3. 激活和池化为什么是后处理链。
  4. 控制命令怎样变成硬件状态。

所以这个系列会尽量把公式、数据流、伪代码、寄存器表和波形观察点直接写在正文里。读者即使没有本地代码,也能顺着文章理解设计。

小结

NPU 设计的起点不是“做一个大乘法器”,而是构造一条稳定的数据通路:

1
2
3
4
5
主机命令
-> 片上数据搬运
-> PE Array 并行乘加
-> 激活/池化后处理
-> 输出结果

这一篇建立了总览。下一篇进入最核心的计算部件:PE 与阵列如何把矩阵乘法在硬件空间里展开。


NPU 设计(一):从神经网络计算到数据通路
http://blog.luliang.online/2026/04/28/npu-design-01-matrix-datapath/
作者
Luyoung
发布于
2026年4月28日
许可协议