3. 数据流与计算模式:NPU上的数据流模型

大家好,我是你们的老朋友。今天我们来聊聊NPU上最核心的话题之一——数据流与计算模式。

说实话,很多做AI加速的工程师,一开始都容易忽略数据流的重要性。大家总觉得,把算力堆上去就行了。但我在项目中踩过不少坑,最后发现:数据怎么搬、怎么存、怎么复用,往往决定了你芯片的真实性能。你想想看,算力再高,数据喂不进去,那也是白搭。

3.1 三种经典的数据流模型

NPU里的数据流模型,说白了就是决定“谁待在原地不动,谁来回移动”。我习惯把这个问题想成是工厂流水线:你是让工人(计算单元)不动,物料(数据)送过来?还是让物料不动,工人走过去?

目前业界公认的三种模型是:Input Stationary(输入驻留)、Weight Stationary(权重驻留)、Output Stationary(输出驻留)。咱们一个一个来看。

3.1.1 Input Stationary(输入驻留)

这种模式下,输入特征图(Input Feature Map)被固定在计算单元里。权重数据流过来,跟输入做乘加运算。我个人比较喜欢这种模式,因为输入数据往往比较大,搬来搬去太费带宽。

核心思想:输入数据尽量留在本地,权重流式加载。

我在做某个图像分类模型时,发现输入特征图的复用次数很高。用Input Stationary,可以把输入数据一次性加载到本地缓存,然后反复使用。这样,权重虽然要频繁加载,但权重本身数据量小,带宽压力不大。

优点:

  • 输入数据复用率高,减少DRAM访问
  • 适合输入通道数多的卷积层

缺点:

  • 权重需要频繁加载,对权重压缩有要求
  • 输出部分累加结果需要临时存储

3.1.2 Weight Stationary(权重驻留)

这个模式正好反过来。权重数据固定在计算单元里,输入特征图流式加载。嗯,这里要注意:权重的复用次数通常等于batch size。如果你的batch size很大,这种模式就很香。

我曾经在部署一个BERT模型时,batch size设到了64。用Weight Stationary,权重只加载一次,然后所有batch的输入数据依次流过。效果非常好,带宽利用率直接拉满。

我的建议:如果你的模型权重比较大(比如全连接层),而且batch size足够大,优先考虑Weight Stationary。

3.1.3 Output Stationary(输出驻留)

这种模式下,输出结果(Partial Sum)被固定在计算单元里。输入和权重都流式加载,计算结果不断累加到本地。

说实话,这种模式我用的不多。但它有一个独特的优势:减少输出数据的写回次数。如果你的输出特征图很大,写回DRAM会成为瓶颈,那Output Stationary就派上用场了。

三种模型对比:

模型 驻留数据 流式数据 适用场景
Input Stationary 输入特征图 权重 输入通道多、输入复用率高
Weight Stationary 权重 输入特征图 batch size大、权重复用率高
Output Stationary 输出部分和 输入+权重 输出特征图大、写回带宽受限

3.2 数据复用策略

数据复用,说白了就是让同一份数据被尽可能多的计算单元使用。你想想看,从DRAM搬一次数据要花多少能量?比一次乘加运算多两个数量级!所以,复用策略做得好,能效直接翻倍。

我总结了几种常见的复用策略:

  • 空间复用:同一份数据广播给多个PE(处理单元)。比如权重广播,所有PE同时用同一份权重计算不同的输入。
  • 时间复用:同一份数据在不同时间被同一个PE使用。比如输入数据在本地缓存里待着,多个时钟周期反复使用。
  • 混合复用:空间+时间一起上。这是最常用的策略,也是NPU设计的难点。
避坑指南:我曾经在一个项目里,为了追求极致复用,把数据广播范围做得太大。结果呢?路由拥堵,时序跑不过。复用不是越多越好,要平衡好带宽和布线资源。

3.3 脉动阵列(Systolic Array)的工作原理

脉动阵列,这个名字听起来很玄乎。其实说白了,就是让数据像心跳一样,有节奏地在计算单元之间流动。每个PE只做一件事:接收数据,做乘加,然后把结果传给下一个PE。

Google的TPU就是脉动阵列的典型代表。我记得第一次看TPU论文时,被它的简洁设计震撼到了。没有复杂的控制逻辑,数据流就像流水一样自然。

脉动阵列的核心特点:

  • 规则化:所有PE结构相同,布局规整
  • 局部通信:数据只跟相邻PE交换,没有全局连线
  • 流水线:数据像流水一样,每个时钟周期向前推进

下面我画了一张图,帮你理解脉动阵列的数据流动过程:

脉动阵列数据流示意图(3x3) PE00 PE01 PE02 PE10 PE11 PE12 PE20 PE21 PE22 W0 W1 W2 I0 I1 I2 Psum 权重流动方向 输入数据流动方向 部分和累加方向 每个PE执行: Psum += I × W 结果传给下一个PE

你看这张图,权重从左往右流,输入数据从上往下流。每个PE收到数据后,做一次乘加,然后把部分和(Psum)往下传。最终,右下角的PE22输出完整的计算结果。

这种设计的好处是什么?局部性极强。每个PE只跟邻居通信,没有全局连线。时钟频率可以跑得很高,功耗也低。我当年第一次在FPGA上实现脉动阵列时,看到数据真的像心跳一样有节奏地流动,那种感觉,嗯,很爽。

关键点:脉动阵列的本质是用空间换时间。通过大量PE并行计算,把计算时间压到极致。但代价是PE利用率可能不高——如果矩阵维度不是PE阵列的整数倍,会有空闲PE。

3.4 实际项目中的选择

说了这么多,到底怎么选?我个人的经验是:没有银弹。

我在做某个视频分析芯片时,试过好几种方案。最后发现,对于卷积层,Input Stationary配合脉动阵列效果最好。但对于全连接层,Weight Stationary更合适。所以,现代NPU基本都是混合架构——不同层用不同的数据流模型。

嗯,这里还要提醒一点:编译器要跟硬件配合。你硬件支持了三种数据流模型,编译器得能自动选择最优的映射方案。否则,再好的硬件也发挥不出性能。

好了,数据流和计算模式就聊到这里。记住一句话:数据怎么流动,决定了你的NPU能跑多快。下一章我们聊聊更具体的缓存优化策略,到时候见。

想要更多资料,联系可可