数据复用策略:输入复用、权重复用、输出复用、混合复用策略详解

大家好,我是你们的AI芯片架构讲师。今天我们来聊聊张量加速核里一个非常核心的话题——数据复用策略。

说实话,我刚开始做芯片架构那会儿,觉得计算单元才是主角。后来踩了不少坑才明白:数据搬运才是真正的性能瓶颈。你想想看,算力再强,数据喂不进去,一切都是白搭。

为什么需要数据复用?

先问大家一个问题:一个典型的卷积运算,数据访问量和计算量是什么比例?

我给你们算笔账。假设输入是3x3的卷积核,特征图是64x64。每次计算一个输出点,需要读取9个输入数据和9个权重数据。但这两个数据集的复用次数完全不同——权重被所有输出位置共享,输入数据被多个卷积窗口共享。

如果不做复用,数据搬运的能耗会是计算的几十倍甚至上百倍。我在一个项目里测过,没有优化的数据搬运占了总能耗的70%以上。嗯,这就是我们研究复用策略的根本原因。

核心观点:数据复用本质上是用片上存储(SRAM)来减少对片外存储(DRAM)的访问次数。因为DRAM访问的能耗是SRAM的100倍以上。

四种复用策略详解

1. 输入复用

输入复用,说白了就是让同一个输入数据被多个计算单元共享。

举个例子:一个3x3的卷积核在特征图上滑动。相邻两个输出位置,它们的输入数据有6个是重叠的。如果我们能把这6个数据存下来重复使用,就能省下大量读取带宽。

我在项目中常用的做法是:

  • 行缓冲(Line Buffer):把特征图的一整行数据缓存下来。这样卷积核在行内滑动时,只需要读取新的一列数据。
  • 滑动窗口:配合行缓冲,维护一个3x3或5x5的窗口寄存器组。每次滑动只更新一列数据。
// 伪代码:行缓冲实现输入复用
for (row = 0; row < H; row++) {
    for (col = 0; col < W; col++) {
        // 从行缓冲读取已有数据
        // 只读取新的一列到行缓冲
        line_buffer[col % K] = input[row][col];
        
        // 从行缓冲构建滑动窗口
        window = build_window(line_buffer, col);
        
        // 执行卷积计算
        output[row][col] = conv(window, weight);
    }
}

我的经验:行缓冲的大小要仔细设计。太小了复用率低,太大了浪费面积。一般取卷积核高度+1到2行就够用。

2. 权重复用

权重复用是最直观的策略。一个卷积核的权重,会被所有输出通道共享。

你想想看,如果输出特征图是64x64,那么每个权重会被重复使用4096次。这复用率相当可观。

权重复用的典型实现方式:

  • 权重固定(Weight Stationary):把权重存在片上,输入数据流式送入。适合权重数量不大、输入数据量大的场景。
  • 权重广播:把同一个权重广播给多个处理单元(PE),每个PE处理不同的输入数据。

我记得有个项目,我们做的是大卷积核(7x7)的加速。当时我坚持用权重固定的方式,把所有权重存在片上。结果性能提升了3倍,因为省掉了大量重复读取权重的时间。

注意:权重复用虽然好,但权重数量不能太大。对于全连接层或者超大卷积核,片上放不下所有权重,就得考虑分块策略了。

3. 输出复用

输出复用可能大家接触得少一些。它的思路是:多个输入数据累加到同一个输出位置。

比如在卷积计算中,一个输出点是由多个输入通道累加得到的。如果我们把部分和(partial sum)存在片上,每次只读取新的输入数据做累加,就能减少对输出数据的读写。

输出复用的典型场景:

  • 多通道累加:多个输入通道的卷积结果累加到同一个输出点。
  • 残差连接:在ResNet等网络中,需要把输入和输出相加。

我曾经在一个边缘AI芯片上用过输出复用。因为边缘芯片的片上存储特别小,我们就把部分和存在寄存器里,每次只读一个通道的数据做累加。这样虽然计算单元利用率不高,但省掉了大量DRAM访问,整体能效反而更好。

4. 混合复用

实际工程中,很少只用一种策略。混合复用才是王道。

我给大家画个图,看看混合复用是怎么工作的:

混合复用策略数据流图 DRAM 输入复用 输入缓存 权重复用 权重缓存 PE阵列 16x16 MAC 混合计算 输出复用 输出缓存 写回 三种复用策略协同工作 输入复用: 行缓冲+滑动窗口,减少输入数据的重复读取 权重复用: 权重固定/广播,所有PE共享同一组权重 输出复用: 部分和累加,减少输出数据的读写次数 混合策略优势: 根据数据流特点动态切换,最大化整体复用率 实际案例: 我在某款NPU中采用输入+权重混合复用,能效比提升40%

混合复用的核心思路是:根据数据流的特点,动态选择最优的复用策略。

比如在卷积层的前几层,输入特征图很大,权重很小,这时候优先做权重复用。到了后面的层,特征图变小,权重变多,就切换到输入复用。

我做过一个实际的NPU设计,里面用了三级存储层次:

  • L1缓存:每个PE私有的寄存器文件,做输出复用
  • L2缓存:PE阵列共享的SRAM,做输入复用
  • L3缓存:全局共享的SRAM,做权重复用

这样三级配合,数据搬运效率提高了不少。

如何选择复用策略?

这个问题没有标准答案。我给大家一个参考框架:

场景 推荐策略 原因
输入特征图大,权重小 权重复用 权重可以全部放在片上,输入流式处理
输入特征图小,权重大 输入复用 输入可以全部放在片上,权重分块加载
输出通道数多 输出复用 部分和累加,减少输出写回次数
通用场景 混合复用 根据层特征动态调整,综合最优

我的建议:刚开始做设计时,先用混合复用策略搭一个通用框架。然后针对你的目标网络做profiling,找到瓶颈再优化。不要一开始就追求极致,容易走弯路。

避坑指南

最后分享几个我踩过的坑:

  • 过度复用:我曾经为了追求高复用率,把缓存做得特别大。结果面积超标,频率上不去。复用不是越高越好,要平衡面积和性能。
  • 忽略控制逻辑:复用策略需要复杂的地址生成和调度逻辑。我有个项目,数据通路设计得很漂亮,但控制逻辑成了瓶颈,时序收敛不了。
  • 边界情况:特征图的边界处理要特别小心。行缓冲在边界处会读到无效数据,需要加mask逻辑。

嗯,今天就讲到这里。数据复用是张量加速核设计的基石,希望大家能真正理解这四种策略的优劣和适用场景。下次课我们会深入讨论具体的硬件实现细节。

核心总结:没有最好的复用策略,只有最适合的复用策略。理解你的数据流特征,才能做出最优的设计决策。

想要更多资料,联系可可