4、NPU硬件架构基础:NPU计算单元、数据流与存储层次、对稀疏性的硬件支持
好,咱们进入正题。这一章聊的是NPU的“骨架”——硬件架构。你想想看,算法再好,如果硬件不支持,那也是白搭。我这些年调过不少模型,踩过最多的坑,就是对硬件底层理解不够。今天咱们就把NPU的计算单元、数据流和存储层次,以及稀疏性支持这几个核心点,掰开揉碎了讲清楚。
4.1 NPU计算单元:MAC阵列与SIMD的博弈
NPU最核心的计算单元,说白了就是乘累加器阵列,也就是MAC阵列。为什么是它?因为神经网络里90%以上的计算都是矩阵乘法,而矩阵乘法的本质就是一堆乘法和加法。
我习惯把MAC阵列想象成一个“算盘格子”。每个格子就是一个MAC单元,能同时做一次乘法和一次加法。这些格子排成二维阵列,比如16x16、32x32甚至更大。阵列越大,并行度越高,算力越强。
关键点:MAC阵列的规模直接决定了NPU的峰值算力。公式很简单:峰值算力 = MAC数量 × 频率 × 2(因为一次MAC包含一次乘法和一次加法)。
但光有MAC阵列还不够。你想想看,如果数据喂不进去,再大的阵列也是空转。所以NPU里通常还会搭配SIMD(单指令多数据)单元,用来处理一些非矩阵运算,比如激活函数、池化、归一化等。我在项目中遇到过,有些团队为了追求极致算力,把SIMD单元砍得太小,结果模型里一个简单的ReLU反而成了瓶颈。嗯,这里要注意,平衡很重要。
| 计算单元 | 主要用途 | 特点 |
|---|---|---|
| MAC阵列 | 矩阵乘法、卷积 | 高并行、高吞吐 |
| SIMD单元 | 激活、池化、归一化 | 灵活、支持多种运算 |
| 向量处理单元 | 向量运算、逐元素操作 | 介于MAC和SIMD之间 |
4.2 数据流:权重固定、输入固定还是输出固定?
有了计算单元,接下来就是数据怎么流的问题。NPU的数据流模式,我总结下来主要有三种:权重固定、输入固定和输出固定。你可能会问,这有什么区别?区别大了去了,直接决定了你的带宽利用率和功耗。
- 权重固定(Weight Stationary):把权重存在MAC阵列的本地寄存器里,反复使用。适合权重复用度高的场景,比如全连接层。我早期做的一个项目,就是用这种模式,结果发现卷积层里权重复用度其实没那么高,反而浪费了存储。
- 输入固定(Input Stationary):把输入特征图存在本地,权重从外部搬入。适合输入复用度高的场景,比如深度可分离卷积。
- 输出固定(Output Stationary):把部分和存在本地,不断累加。适合输出通道数多的场景。
我个人习惯,在实际芯片设计时,不会只用一种模式。而是根据层类型动态切换。比如卷积层用输入固定,全连接层用权重固定。你想想看,这样是不是更灵活?
避坑指南:我曾经在一个项目里,为了省事,所有层都用同一种数据流。结果模型跑起来,带宽利用率不到40%。后来改成动态切换,直接提升到75%以上。所以,别偷懒,数据流的选择一定要跟层特性匹配。
4.3 存储层次:从片外到片内,每一级都是钱
NPU的存储层次,说白了就是金字塔结构。从最底层的片外DDR,到片上的全局共享内存(Global Buffer),再到每个MAC阵列的本地寄存器。每一级存储,访问延迟和功耗都差一个数量级。
为什么会这样?因为芯片面积和功耗是有限的。你不可能把所有数据都放在离计算单元最近的地方。所以,存储层次的设计,本质上是一个“数据搬移”的优化问题。
| 存储层级 | 典型容量 | 访问延迟 | 相对功耗 |
|---|---|---|---|
| 片外DDR | 4-16 GB | ~100ns | 10x |
| 全局共享内存 | 1-8 MB | ~10ns | 3x |
| 本地寄存器 | 几十KB | ~1ns | 1x |
我记得有一次,我们在做模型剪枝后的部署。剪枝后模型变小了,但推理速度反而没提升多少。查了半天,发现是数据搬移成了瓶颈。剪枝后的稀疏权重,在存储层次里没有做优化,导致频繁访问片外DDR。后来我们重新设计了数据预取策略,把常用权重提前搬到全局共享内存里,速度才提上来。
注意:存储层次的设计,一定要考虑数据的局部性。如果数据复用度低,就别往本地寄存器里塞,否则搬来搬去的开销比计算还大。
4.4 对稀疏性的硬件支持:从“零”里榨出性能
好,终于到了稀疏性。这是NPU架构里最“性感”的部分。模型剪枝后,权重和激活值里会出现大量零。如果能跳过这些零的计算,理论上能省下一大笔算力和功耗。
但问题是,硬件怎么知道哪些是零?怎么跳过?
目前主流的方法有两种:
- 结构化稀疏:把零值组织成固定模式,比如N:M稀疏(每M个值里只有N个非零)。硬件可以提前知道模式,直接跳过零块。我建议,如果条件允许,优先用结构化稀疏,因为硬件实现简单,效率高。
- 非结构化稀疏:零值随机分布,没有固定模式。硬件需要额外的索引或位图来标记非零位置。这种灵活性高,但硬件开销大。我在项目中遇到过,非结构化稀疏的加速效果,往往被索引读取的开销抵消了。
核心思路:稀疏性支持的本质,是“用控制逻辑换计算资源”。硬件多花一些逻辑去识别和跳过零,就能省下大量的MAC运算。但前提是,稀疏度要足够高(通常>70%),否则得不偿失。
另外,存储层次也要配合稀疏性。比如,稀疏权重可以用压缩格式存储,只存非零值和对应的索引。这样既能省存储,又能减少带宽。我习惯用CSR(Compressed Sparse Row)格式,但要注意,CSR的解压逻辑会引入额外延迟,需要流水线设计来掩盖。
嗯,这一章的内容就到这里。总结一下:NPU的硬件架构,计算单元是核心,数据流是灵魂,存储层次是骨架,稀疏性支持是点睛之笔。下一章,咱们会深入讲剪枝算法,到时候你会看到,算法和硬件是怎么互相配合的。