4. 数据流与访存模式:卷积层数据复用模式、矩阵乘法的数据流优化、Transformer注意力机制的访存特征

各位同学,今天我们聊一个硬核话题——数据流与访存模式。说白了,就是数据在芯片里怎么跑、怎么存、怎么搬。我做了十几年芯片设计,发现很多团队算法调得飞起,一上芯片就卡在带宽上。嗯,这里面的门道,我今天给你们掰开揉碎了讲。

4.1 卷积层数据复用模式

卷积层是CNN的算力大户,也是访存大户。你想想看,一个3x3的卷积核,在特征图上滑动,每次只算9个乘法,但数据要反复从DDR里搬进搬出。这效率能高吗?

我个人习惯把卷积的访存模式分成三类:

  • 输入特征图复用:同一个输入像素,会被多个卷积核用到。比如一个3x3的窗口,9个像素会被所有输出通道共享。
  • 权重复用:同一个卷积核,会在整个特征图上滑动。权重只加载一次,但要用很多次。
  • 输出累加复用:多个输入通道的卷积结果,要累加到同一个输出像素上。

我在项目中遇到过一个问题:某团队做边缘AI芯片,卷积加速器跑不快。一查,发现每次卷积核滑动时,都要从外部存储器重新读输入数据。这就是典型的「没利用好数据复用」。

核心原则:卷积加速器的设计目标,就是最大化数据在片上缓冲区的复用次数,减少对外部存储器的访问。

具体怎么做?我建议用循环分块(Loop Tiling)技术。把大的特征图切成小块,让小块数据在片上SRAM里完成所有计算,再写回外部存储器。

// 伪代码:卷积循环分块
for (oh = 0; oh < OH; oh += TH)      // 输出高度分块
  for (ow = 0; ow < OW; ow += TW)    // 输出宽度分块
    for (oc = 0; oc < OC; oc += TC)  // 输出通道分块
      // 加载输入块、权重块到片上SRAM
      load_input_block(oh, ow, TH, TW);
      load_weight_block(oc, TC);
      // 在片上完成所有计算
      for (kh = 0; kh < KH; kh++)
        for (kw = 0; kw < KW; kw++)
          for (ic = 0; ic < IC; ic++)
            output[oh][ow][oc] += 
              input[oh+kh][ow+kw][ic] * weight[kh][kw][ic][oc];
      // 写回结果
      store_output_block(oh, ow, oc, TH, TW, TC);

你看,这样每次加载的数据,在片上被反复用了很多次。我曾经在一个项目里,用这个技巧把DDR带宽占用从80%降到了30%。

避坑指南:分块大小不是越大越好。块太大,片上SRAM放不下;块太小,复用次数不够。我一般用「存储墙分析」来算最优块大小——先看SRAM容量,再看计算密度,最后定分块参数。

4.2 矩阵乘法的数据流优化

矩阵乘法是深度学习的基础操作。Transformer、全连接层、甚至卷积都能拆成矩阵乘法。但矩阵乘法的访存模式,和卷积不太一样。

矩阵乘法有三种经典的数据流:

数据流类型 描述 适用场景
内积数据流 一次计算一个输出元素,累加所有输入 小矩阵、低带宽场景
外积数据流 一次加载一列输入和一行权重,生成部分和 大矩阵、高带宽场景
分块数据流 把矩阵切成小块,在片上完成计算 通用场景,平衡带宽和计算

我个人最常用的是分块数据流。为什么呢?因为它能同时利用时间和空间局部性。你想想看,把矩阵A和B都切成小块,每次加载一个小块到片上,然后做矩阵乘法。这样每个数据在片上被用了很多次。

关键指标:计算与访存比(OPs/Byte)。这个比值越高,说明数据复用越好,带宽利用率越高。

举个例子,一个MxK的矩阵乘以KxN的矩阵,如果分块大小是BxK和KxB,那么计算量是2*B*B*K,访存量是2*B*K(输入)+ B*B(输出)。计算访存比大约是B。分块越大,比值越高。

我曾经在一个项目里,把矩阵乘法的分块从16x16改成64x64,计算访存比从16提升到了64。结果呢?同样的带宽下,吞吐量翻了近4倍。当然,前提是片上SRAM够大。

注意:分块数据流有个陷阱——部分和的管理。如果分块太小,部分和太多,写回外部存储器的开销会很大。我建议用「输出驻留」策略:让部分和一直留在片上,直到累加完成再写回。

4.3 Transformer注意力机制的访存特征

Transformer的注意力机制,访存模式跟卷积、矩阵乘法都不一样。它有个特点:计算量不大,但访存量巨大

为什么?你看注意力公式:

Attention(Q, K, V) = softmax(Q * K^T / sqrt(d)) * V

这里有个关键操作:Q * K^T 生成一个NxN的注意力矩阵(N是序列长度)。这个矩阵的大小是N^2。当N=1024时,注意力矩阵就有1M个元素。如果N=8192,那就是64M个元素。

我在项目中遇到过一个问题:某团队做Transformer加速器,发现推理速度上不去。一分析,发现大部分时间都花在读写注意力矩阵上。这就是典型的「计算稀疏、访存密集」场景。

Transformer的访存特征可以总结为三点:

  • 注意力矩阵的读写:NxN的矩阵,需要先写后读。这对片上缓冲区的容量要求很高。
  • KV Cache的访问:在自回归生成时,需要缓存所有历史K和V。序列越长,缓存越大。
  • softmax的归约操作:需要对每一行做求和和除法,这涉及大量的数据搬移。

核心挑战:注意力机制的访存带宽需求,随着序列长度平方增长。这是Transformer推理的主要瓶颈。

怎么优化?我建议从三个方向入手:

  1. Flash Attention:把注意力计算分块,让注意力矩阵的中间结果留在片上,不写回外部存储器。这能大幅减少访存量。
  2. KV Cache量化:把K和V从FP16量化到INT8甚至INT4。虽然精度有损失,但访存量减半甚至减到四分之一。
  3. 稀疏注意力:只计算部分位置的注意力权重,比如局部窗口或全局稀疏模式。这能直接减少N^2的访存开销。

我记得有一次,一个客户要做长文本生成(序列长度32K)。如果用标准注意力,光注意力矩阵就要1GB的带宽。后来我们用了Flash Attention + KV Cache量化,带宽需求降到了原来的十分之一。嗯,这才是能落地的方案。

避坑指南:Flash Attention虽然好,但实现起来有坑。比如分块大小怎么选?太小了计算效率低,太大了片上放不下。我一般用「计算访存比分析」来定——先算每块的计算量,再算访存量,找到最优分块。

4.4 三种访存模式的对比

好了,我们把三种模式放在一起看看:

模式 数据复用方式 访存瓶颈 优化方向
卷积层 输入/权重/输出复用 特征图反复加载 循环分块、数据重用
矩阵乘法 分块复用 部分和写回 输出驻留、计算访存比
注意力机制 注意力矩阵临时复用 N^2矩阵读写 Flash Attention、量化、稀疏

你发现没有?这三种模式的核心矛盾是一样的:计算越来越快,但访存越来越慢。说白了,就是「存储墙」问题。我做了这么多年芯片,最大的体会就是:算法设计要考虑访存,架构设计要匹配访存,系统设计要优化访存。

最后送大家一句话:带宽是芯片设计的硬约束,数据复用是突破约束的软技巧。希望今天的分享对你们有帮助。

AI芯片数据流与访存模式 卷积层 数据复用模式 • 输入特征图复用 • 权重复用 • 输出累加复用 优化:循环分块 矩阵乘法 数据流优化 • 内积数据流 • 外积数据流 • 分块数据流 优化:输出驻留 Transformer注意力 访存特征 • 注意力矩阵读写 • KV Cache访问 • softmax归约 优化:Flash Attention 核心矛盾:存储墙 计算越来越快,访存越来越慢 解决方案:数据复用 + 带宽优化 循环分块 | 输出驻留 | Flash Attention | 量化 | 稀疏 带宽是硬约束,数据复用是突破约束的软技巧