2. NPU基础架构
NPU(Neural Processing Unit,神经网络处理单元)并非通用处理器的简单加速版,而是针对神经网络计算中“海量并行乘加”、“数据复用”、“有限控制流”三大特征进行极致优化的专用处理器。理解NPU的硬件架构,是设计高效指令集的前提。本章将拆解NPU的硬件组成、计算单元、存储层次与数据通路,并阐明它们与指令集设计之间的耦合关系。只有理解了硬件如何“物理地”执行指令,才能设计出既高效又可控的指令编码。
上图展示了NPU内部的核心模块及其连接关系。可以看到,NPU并非一个单核处理器,而是一个由标量单元(负责控制)、计算阵列(负责矩阵运算)、向量单元(负责逐元素运算)以及多级存储构成的异构计算系统。指令集设计必须覆盖这些模块的协同工作,才能发挥硬件峰值性能。
2.1 NPU硬件组成:三大子系统的分工与协作
核心原理:从“控制”与“计算”分离说起
与CPU强调分支预测和乱序执行不同,NPU的硬件设计遵循“弱控制、强数据流”原则。其硬件组成通常包含三大子系统:
- 控制子系统(标量单元 + 指令控制单元):负责指令的取指、译码、循环计数、地址生成和同步。它通常是一个简单的RISC-V或ARM核,或者是一个专用的微码引擎。
- 计算子系统(矩阵单元 + 向量单元):矩阵单元(MAC Array)专门处理卷积和全连接层中的乘加运算;向量单元处理激活函数、BatchNorm、池化等逐元素或逐通道操作。
- 存储子系统(片上Buffer + 外部DDR):片上SRAM用于暂存输入特征图、权重和输出特征图,以降低对外部带宽的依赖。
判断方法:如何快速识别一个NPU的架构风格?
在阅读NPU数据手册或汇编代码时,可通过以下特征判断其硬件组织方式:
- 看指令宽度:若指令长度固定为64bit或128bit,通常为VLIW(超长指令字)风格,一条指令同时控制MAC阵列和向量单元。
- 看寄存器文件:若存在大量标量寄存器(如R0-R31),说明控制逻辑较复杂;若只有少量配置寄存器,则可能采用“流式”指令集。
- 看Buffer分区:若输入、权重、输出Buffer物理分离,则指令中必须显式指定“从哪个Buffer读,写到哪个Buffer”。
案例拆解:以某款端侧NPU为例
假设某NPU的硬件规格如下:
| 模块 | 规格 | 指令集影响 |
|---|---|---|
| MAC阵列 | 8x8 脉动阵列,支持INT8 | 指令需包含8x8的矩阵尺寸描述,且要求数据按行主序排列 |
| 向量单元 | 32路并行,支持ReLU/Add | 向量指令需指定向量长度(最大32) |
| 片上Buffer | 输入Buffer 64KB,权重Buffer 64KB,输出Buffer 32KB | LOAD指令需指定目标Buffer编号(0/1/2) |
| 外部接口 | DDR4,带宽12.8GB/s | DMA指令需支持突发传输长度配置 |
在该硬件上执行一个3x3卷积,编译器生成的指令序列为:LOAD buf0, addr_in, size → LOAD buf1, addr_w, size → CONV buf0, buf1, buf2, stride=1, pad=1 → STORE addr_out, buf2, size。可以看到,指令集直接暴露了硬件Buffer的物理结构。
2.2 计算单元:MAC阵列与向量单元的指令级协同
核心原理:乘加运算的并行化
NPU的计算核心是乘加单元(MAC)。一个MAC执行 D = A * B + C 操作。为了提升吞吐,硬件将多个MAC排列成脉动阵列(Systolic Array)或SIMD阵列。指令集设计的关键在于如何描述“数据如何流入阵列”。
- 脉动阵列:数据从左侧和上方流入,每个MAC只与相邻MAC通信。指令只需指定“启动”和“尺寸”,数据流由硬件自动控制。
- SIMD阵列:所有MAC共享同一个控制信号,执行相同的操作。指令需指定“向量长度”和“操作码”。
操作流程:一条CONV指令的硬件执行过程
- 译码:指令控制单元解析出操作码(CONV)、源Buffer地址、目标Buffer地址、卷积核尺寸(KH、KW)、通道数(C)。
- 配置:将卷积参数写入MAC阵列的配置寄存器,如“权重加载模式”、“数据复用模式”。
- 数据加载:输入特征图从输入Buffer按行读取,权重从权重Buffer按列读取,二者在MAC阵列中“碰撞”并完成乘加。
- 累加与写出:部分和通过累加器暂存,最终结果写回输出Buffer。
CONV指令不应只包含“计算”信息,还应包含“数据布局”信息。例如,是否启用权重预加载(权重常驻在MAC阵列内部的寄存器中,避免重复搬运),这直接影响指令的执行周期数。
案例拆解:矩阵乘法指令的两种编码风格
假设需要计算 C[4][4] = A[4][4] * B[4][4]:
// 风格一:显式地址编码(类似CPU)
LOAD R0, A[0][0]
LOAD R1, B[0][0]
MAC R2, R0, R1
// 需要16条MAC指令,效率低
// 风格二:矩阵指令编码(NPU风格)
MATMUL A_buf, B_buf, C_buf, M=4, N=4, K=4
// 一条指令完成16次乘加,硬件自动调度数据流
显然,风格二才是NPU指令集的设计方向。但风格二要求硬件必须支持“二维数据块”的读取,这又反过来约束了片上Buffer的地址生成逻辑必须支持“二维步长(Stride)”寻址。
2.3 存储层次:指令集如何管理数据搬运
核心原理:存储墙问题与片上Buffer
NPU的性能瓶颈往往不在计算,而在数据搬运。因此,现代NPU普遍采用“多级存储”架构:寄存器文件(RF)→ 片上Buffer(SRAM)→ 外部DDR。指令集必须提供显式的数据搬运指令(如DMA指令),或者通过“缓存一致性”协议隐式管理。
| 存储层级 | 容量 | 访问延迟 | 指令集管理方式 |
|---|---|---|---|
| 寄存器文件 | 几KB | 1周期 | 由标量指令直接读写 |
| 片上Buffer | 几MB | 10-20周期 | 通过LOAD/STORE指令或DMA指令 |
| 外部DDR | 几GB | 200-400周期 | 通过异步DMA指令,需同步等待 |
判断方法:何时使用同步指令,何时使用异步指令?
- 同步LOAD:当后续计算指令立即需要该数据时,使用同步LOAD,硬件会阻塞流水线直到数据就绪。
- 异步DMA:当数据可以提前预取时(如双缓冲场景),使用异步DMA指令,并配合
WAIT指令进行同步。
案例拆解:双缓冲(Ping-Pong)调度
假设输入Buffer分为两个区域:Buf_A 和 Buf_B。调度流程如下:
DMA_LOAD Buf_A, DDR_addr_0, size(异步)DMA_LOAD Buf_B, DDR_addr_1, size(异步)WAIT Buf_A_ready(等待第一次搬运完成)CONV Buf_A, weight, out(计算第一块)WAIT Buf_B_ready(等待第二次搬运完成)CONV Buf_B, weight, out(计算第二块)
通过这种方式,DMA搬运时间被计算时间掩盖,硬件利用率可接近100%。指令集必须提供DMA_LOAD、WAIT、CONV三类指令,且WAIT指令需要能指定“等待哪个事件”。
2.4 数据通路与指令集:从硬件连线到指令编码
核心原理:数据通路决定指令字段
数据通路(Data Path)是指数据从存储单元到计算单元再到存储单元的物理路径。指令集的编码格式必须与数据通路的宽度和方向严格匹配。例如:
- 若数据总线宽度为128bit(一次可传输16个INT8数据),则LOAD指令的“字节数”字段必须是16的倍数。
- 若MAC阵列支持“权重固定”模式(权重在阵列中复用),则指令需包含“权重保持”标志位。
操作流程:指令译码如何控制数据通路
// 假设一条32位NPU指令编码如下:
// [31:28] 操作码 (OPCODE)
// [27:24] 目标Buffer编号 (DST_BUF)
// [23:16] 源Buffer编号 (SRC_BUF)
// [15:0] 立即数或地址偏移 (IMM)
// 硬件译码逻辑(简化):
if (OPCODE == 0b0001) { // CONV
MAC_CTRL.enable = 1;
MAC_CTRL.src = SRC_BUF;
MAC_CTRL.dst = DST_BUF;
MAC_CTRL.size = IMM;
}
上述代码展示了指令字段如何直接映射到硬件控制信号。如果硬件数据通路中,MAC阵列只能从Buffer0读取输入,那么指令编码中“源Buffer”字段就只有1bit(0或1),而不是2bit。
总结复盘:硬件与指令集的协同设计要点
| 硬件模块 | 指令集设计要点 | 常见错误 |
|---|---|---|
| MAC阵列 | 支持矩阵尺寸参数、权重预加载标志 | 未暴露阵列尺寸,导致编译器无法分块 |
| 向量单元 | 支持向量长度、操作类型(ReLU/Add/Mul) | 向量长度固定,无法适应不同通道数 |
| 片上Buffer | 支持Buffer编号、二维寻址(行/列步长) | 仅支持一维线性寻址,导致数据重排开销大 |
| DMA控制器 | 支持异步搬运、事件ID、突发长度 | 仅支持同步搬运,计算与搬运无法重叠 |