4. NPU硬件架构:脉动阵列、数据流与片上存储
做AI芯片这些年,我越来越觉得NPU的硬件架构就像一套精密的“数据舞蹈”编排。你想想看,神经网络计算本质就是大量乘加,怎么让数据在芯片上跑得又快又省电?核心就三个词:脉动阵列、数据流架构、存储层次。这一章咱们就把它们拆开揉碎聊透。
4.1 脉动阵列(Systolic Array)—— 计算的心脏
我第一次在芯片版图里看到脉动阵列时,心里就一个感觉:整齐得让人舒适。说白了,它就是一个由PE(处理单元)组成的二维网格。每个PE只和相邻的PE通信,数据像心跳一样有节奏地“泵”过整个阵列。
举个例子,一个8×8的脉动阵列,有64个PE。每个PE里有一个乘法器和一个累加器。权重从左边进来,激活值从上边进来,部分和在内部向下累加。整个过程没有复杂的全局互联,延迟极低。
实际芯片里,Google TPU v1用的是256×256的脉动阵列,也就是65536个PE。我当年第一次跑仿真时,看到数据像潮水一样流过阵列,吞吐量直接拉满,真的被震撼到了。
4.2 数据流架构:Weight Stationary vs Output Stationary
脉动阵列是骨架,数据流就是血液流动的方式。不同的数据流策略,直接影响功耗和带宽。我习惯把数据流分成三类,但最常用的是两种:权重固定(Weight Stationary) 和 输出固定(Output Stationary)。
4.2.1 Weight Stationary (WS)
权重预先加载到PE里,然后激活值流过来。每个PE里的权重一直不动,直到算完一整块。这样做的好处是权重复用率极高,适合权重密集的卷积层。
我记得在某个边缘芯片项目中,我们用WS数据流,权重从DRAM搬到PE一次,就能被用几十次,带宽压力小了很多。但缺点也明显——激活值要广播或者逐行扫描,如果激活值太大,中间缓存容易爆。
4.2.2 Output Stationary (OS)
反过来,输出部分和固定在PE里,权重和激活都流进来。每个PE负责计算输出特征图的一个像素。这种架构对输出通道数大的层很友好,因为部分和不用频繁写回。
你想想看,如果输出是32通道,每个PE只算一个输出点,累加都在本地完成。我做过对比,OS在深度可分离卷积里比WS省电约20%。
| 数据流类型 | 固定数据 | 流动数据 | 适合场景 | 典型芯片 |
|---|---|---|---|---|
| Weight Stationary | 权重 | 激活值、部分和 | 普通卷积、大kernel | Google TPU v1 |
| Output Stationary | 部分和 | 权重、激活值 | 深度可分离卷积、1×1卷积 | 某些NPU加速器 |
| Row Stationary (混合) | 行数据 | 权重、激活、部分和 | 平衡带宽与复用 | Eyeriss (MIT) |
4.3 片上存储层次 —— 数据搬移的艺术
做异构计算,有一句话我特别认同:“计算是免费的,搬数据才要钱。” NPU的存储层次就是为了让数据尽量待在离PE近的地方。
典型的NPU存储层次长这样:
- 寄存器文件 (RF) —— 每个PE内部,几个cycle的延迟,容量极小(几十字节)。
- 本地SRAM (Local Buffer) —— 每个PE或每行PE共享,几十KB到几百KB,延迟几个cycle。
- 全局共享SRAM (Global Buffer) —— 整个阵列共用,几MB,延迟几十cycle。比如TPU v1有24MB的 Unified Buffer。
- 片外DRAM (HBM / LPDDR) —— 容量大(GB级),但延迟几百cycle,带宽金贵。
我个人的习惯是:把权重放在Global Buffer里,激活值按tile切分后放到Local Buffer,部分和尽量留在RF里累加。这样每层计算的数据搬移量能减少5~10倍。
4.4 三者如何协同?
脉动阵列、数据流、存储层次不是孤立的。我画了一张图帮你理解它们怎么配合:
实际项目中,我通常先用性能模型估算不同数据流下的DRAM访问量,再选存储层次配置。比如ResNet-50,用WS+256KB Local Buffer,DRAM访问能比朴素实现少4倍。
嗯,这一章的内容差不多就是这些。脉动阵列、数据流、存储层次,这三板斧是理解NPU硬件的基石。下次你看到一款NPU的die photo,试着找找脉动阵列在哪,猜猜它用的什么数据流——会很有意思。