第三章:NPU微架构概览
好,咱们今天聊聊NPU的微架构。说白了,就是NPU内部到底长什么样,数据怎么流,计算怎么干,控制怎么管,存储怎么存。我当年刚接触NPU时,看着架构图一头雾水,后来亲手撸了几个模块才明白——其实核心就三块:计算单元、控制单元、存储系统。
3.1 NPU整体数据流
先看数据怎么走。NPU的典型数据流,我习惯把它分成三个阶段:
- 输入阶段:特征图(Feature Map)和权重(Weight)从外部DDR加载到片上SRAM。这里有个坑——带宽往往不够用。我曾经在一个项目里,DDR带宽被CPU和NPU抢得厉害,最后不得不做优先级仲裁。
- 计算阶段:数据从SRAM喂给MAC阵列,做乘加运算。中间结果可能暂存在累加器或中间缓冲区。
- 输出阶段:计算结果写回SRAM,再通过DMA搬回DDR,或者直接送给后处理单元。
你想想看,整个数据流就像一条流水线。哪个环节慢了,整个系统就得等。我个人习惯在设计初期先画数据流图,标清楚每个阶段的带宽和延迟,这样后面不容易翻车。
关键点:NPU的数据流设计,核心是「搬数据」和「算数据」的平衡。搬得快算得慢,或者算得快搬得慢,都会浪费硬件资源。
3.2 计算单元:MAC阵列
MAC阵列是NPU的心脏。说白了,就是一堆乘法器和加法器排成阵列,同时做一堆乘加运算。
3.2.1 MAC阵列的结构
常见的MAC阵列有两种组织方式:
- 脉动阵列(Systolic Array):数据像波浪一样在阵列里流动。每个MAC只和邻居通信,布局布线很规整。Google的TPU就是典型例子。
- 广播阵列(Broadcast Array):一个输入数据广播给所有MAC,权重本地存储。这种结构控制简单,但布线压力大。
我记得有一次做16x16的脉动阵列,时序怎么都收敛不了。后来发现是数据路径太长,中间插了两级流水寄存器才搞定。嗯,这里要注意——脉动阵列的流水级数设计很关键,多了延迟大,少了时序差。
3.2.2 MAC的位宽选择
MAC的位宽直接影响精度和面积。常见的配置有:
| 位宽 | 适用场景 | 面积开销 |
|---|---|---|
| INT8 | 推理,精度要求不高 | 小 |
| INT16 | 中等精度推理 | 中 |
| FP16 | 训练或高精度推理 | 大 |
| BF16 | 训练,动态范围大 | 较大 |
我个人建议,如果做推理芯片,优先考虑INT8。面积小、功耗低,精度损失可以通过量化补偿。我曾经在一个项目里硬上FP16,结果芯片面积超标,最后不得不降频——教训啊。
小技巧:MAC阵列的利用率很关键。设计时尽量让数据连续输入,避免MAC空转。我一般会在控制逻辑里加一个「空转计数器」,用来监控利用率。
3.3 控制单元
控制单元是NPU的大脑。它负责调度数据、控制计算、处理异常。说白了,就是告诉其他模块「什么时候该干什么」。
3.3.1 指令集架构
NPU的控制通常基于指令。常见的指令类型包括:
- 计算指令:比如CONV、POOL、ACTIVATE。指定做什么运算。
- 数据搬移指令:比如LOAD、STORE、DMA。指定数据从哪搬到哪。
- 控制指令:比如JUMP、LOOP、SYNC。控制程序流程。
我见过有些团队把指令设计得特别复杂,结果解码器占了很大面积。其实NPU的指令集应该尽量精简——你想想看,NPU主要就跑那几种算子,搞那么复杂干嘛?
3.3.2 流水线控制
控制单元通常采用多级流水线:取指、译码、发射、执行、写回。这里有个经典问题——数据冒险。比如前一条指令刚把数据写进寄存器,后一条指令就要读。我曾经在调试时发现计算结果总是不对,查了两天才发现是写回阶段没处理好,数据还没写稳就被读走了。
避坑指南:我曾经在控制单元里忘了加写后读(RAW)的旁路逻辑,结果仿真时数据总是差一拍。后来加了个简单的旁路,问题就解决了。记住——NPU的控制单元一定要处理好数据冒险,否则计算结果会莫名其妙地出错。
3.4 存储系统
存储系统是NPU的血管。数据能不能及时送到MAC阵列,全靠它。
3.4.1 存储层次
典型的NPU存储层次如下:
- L1缓存:紧贴MAC阵列,容量小(几KB到几十KB),速度极快。通常存当前计算需要的权重和特征图。
- L2缓存:片上SRAM,容量大(几百KB到几MB),速度较快。存整个网络层的中间数据。
- 外部DDR:容量最大(几GB),速度慢。存整个模型和所有输入输出。
我习惯把L1缓存设计成双缓冲(Double Buffer)。一个缓冲区在计算,另一个在加载数据。这样计算和搬移可以重叠,利用率能提高不少。
3.4.2 数据复用策略
存储系统的核心问题是怎么减少数据搬移。常见的复用策略有:
- 权重复用:同一个权重被多个输入特征图共享。比如卷积核在特征图上滑动时,权重不变。
- 输入复用:同一个输入特征图被多个权重共享。比如多个卷积核同时处理同一个输入。
- 输出复用:多个MAC的计算结果累加到同一个输出位置。
我记得有个项目,权重复用做得不好,导致L1缓存频繁换入换出,功耗直接飙了30%。后来改了数据排布方式,把权重按通道连续存放,问题就解决了。
核心思想:存储系统的设计目标就是「让数据尽量待在离计算单元近的地方」。能放L1就别放L2,能放L2就别去DDR。每一次数据搬移,都是功耗和延迟的代价。
3.5 小结
嗯,NPU微架构其实不复杂。记住三个核心:
- 计算单元:MAC阵列,决定了算力上限。
- 控制单元:指令调度,决定了效率高低。
- 存储系统:数据搬移,决定了实际性能。
这三块设计好了,NPU基本就稳了。下一章咱们会深入MAC阵列的RTL实现,到时候手把手写代码。今天就先到这儿,有问题随时问我。
课后思考:如果你来设计一个NPU,你会优先优化计算单元还是存储系统?为什么?我个人会先优化存储——因为很多时候,瓶颈不在算力,而在数据喂不进去。