Flash Attention:少搬几趟,比算得更快重要

Flash Attention:少搬几趟,比算得更快重要

GPU 的算力每代翻倍,但显存带宽十年没怎么涨。

所以 GPU 大部分时间不是在算,是在等数据搬过来。

Flash Attention 做的事情只有一件:少搬


标准做法:算一步,搬一趟

Standard vs Flash: 一个 Cycle 级视角 不看结构,看过程 —— 数据在 HBM 与 SRAM 之间到底怎么跑 Standard Attention Cycle ALU 状态 HBM 读写 1 等待 Q[i] READ Q[i] 2 计算 S[i,:] 3 等待写回 S WRITE S NxN 4 等待读回 S READ S NxN 5 Softmax(P) 6 等待写回 P WRITE P NxN 7 等待读回 P+V READ P+V 8 O = P * V WRITE O [读Q] --> [算S] --> [写S回HBM] --> [读S回来] --> [Softmax] --> [写P回HBM] --> [读P+V] --> [算O] 8 步中 5 步在等 IO,Stall 率约 62% 5 / 8 cycles 在等搬运 中间矩阵 S, P 各写回+读回一次 = O(N^2) 带宽 Flash Attention Cycle SRAM 内操作 HBM 读写 1 加载 Tile READ Tile 2 S = Q * K^T 3 Online Softmax 4 O += P * V 5 加载下一 Tile READ Tile 6-8 S+Softmax+O 9 写回 O WRITE O [读Tile] --> [S+Softmax+O 全在SRAM] --> [读下个Tile] --> ... --> [写回O] 中间结果不落地,仅首尾各一次 HBM 读写 仅 2 / 9 cycles 涉及 IO S, P 永远不写回 HBM = O(N) 带宽

左边的 Standard,每算一个中间结果都要写回显存再读回来。8 个 cycle 里 5 个在等。

这不是算得慢,是搬得慢。

Flash 做法:搬一次,算到底

右边。切成 Tile,一次搬进 SRAM,在 SRAM 里把 S、Softmax、O 全算完,再搬下一块。中间结果不落地

搬运量从 O(N²) 降到 O(N):

Memory Access: O(N^2) vs O(N) 数据实证 —— 随序列长度增长,搬运量的天壤之别 Memory Access (GB) 0 10 20 30 40 50 Sequence Length N 1K 2K 4K 6K 8K 0.7 2.8 11.3 25.3 45 GB 0.25 1.0 2.0 GB 22.5x Standard: O(N^2) Flash: O(N)

N=8K 时,45 GB vs 2 GB。22.5 倍,仅仅是少搬了几趟。


但分块之后,Softmax 怎么算?

传统 Softmax 需要看到整行才能算分母。分块之后只能看到局部。

Online Softmax 的解法:维护两个局部变量,增量更新。

  • row_m:到目前为止的最大值
  • row_l:到目前为止的累加和

每来一个 Tile,用旧的 m 和 l 算出新的 m 和 l。不需要全局视野,局部状态正确演进就能到达全局正确。

Online Softmax 逐步推演:m 和 l 的增量进化 不看公式,看过程 —— row_m 和 row_l 在每个 Tile 到来后到底怎么变 Step Tile 数据 row_m (当前最大值) row_l (累加和) O (累加输出) Init m = -inf l = 0 O = [0, 0] Tile 0 S = [2.0, 1.0] m = 2.0 max(-inf, 2.0) l = 1.37 0 + e^0 + e^-1 O = V0 * attn0 m 驱动 l Tile 1 S = [3.0, 0.5] m = 3.0 max(2.0, 3.0) l = 1.88 1.37*e^-1 + e^0 + e^-2.5 O *= exp(-1); O += V1 * attn1 m 变大 -> 旧 O 必须缩放 核心洞察:当 m 增大时,旧的 softmax 分母变小了,所以旧的 l 和 O 必须乘以 exp(m_prev - m_new) 来"修正" 这就是为什么 row_m 和 row_l 必须同时维护 —— m 变化时 l 必须联动修正 Tile 2 S = [1.5, 2.5] m = 3.0 max(3.0, 2.5) 不变 l = 3.03 1.88 + e^-1.5 + e^-0.5 O += V2 * attn2 m 不变,无需缩放 Final m = 3.0 l = 3.03 O = O / l 最终除以 l 归一化 Online Softmax 伪代码(对应 forward_kernel) m = -inf, l = 0, O = 0 for each Tile j: S_j = Q_i * K_j^T // SRAM 内计算 m_new = max(m, max(S_j)) // 局部最大值更新 l_new = l * exp(m - m_new) + sum(exp(S_j - m_new)) // 修正旧 l + 累加新 l O = O * exp(m - m_new) + exp(S_j - m_new) @ V_j // 修正旧 O + 累加新 O m = m_new, l = l_new // 提交更新

图里 Tile 1 那行是关键:m 从 2.0 变成 3.0,旧的 l 和 O 必须乘 exp(m_prev - m_new) 修正。m 变了,l 就得跟着变,O 也得跟着缩放。 这条因果链是 Online Softmax 的全部秘密。

对应代码就是 row_m_prev → row_m_newrow_l_prev → row_l_new


反向传播:重新算,比搬回来便宜

Flash Attention 反向传播时不存中间矩阵 S,而是从 Q、K、V 重新算。

看似浪费,实则最优:A100 的 312 TFLOPS 算力是过剩的,2 TB/s 的 HBM 带宽是稀缺的。用富余换稀缺。


一句话总结

在受限系统里,管理"移动"比管理"计算"重要。 少搬一趟的收益,远大于算得快一点。

这个规律不只在 GPU:

  • CPU 优化 = 减少 Cache Miss
  • 数据库优化 = 减少磁盘 IO
  • 分布式优化 = 计算向数据靠拢

都是同一件事:尊重物理约束,让数据少跑路。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

relis

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值