想象你是餐厅大厨,要为一百桌客人做一道招牌菜。菜谱很简单:把三种原料按顺序混合、加热、出锅。但你的厨房有个奇怪规定——所有食材都存放在离灶台很远的超大冷库里(HBM),而灶台旁的备菜台只有书本大小(SRAM)。每做一步,你都得跑一趟冷库把半成品端回来,做完再端回去存好。一百桌客人的分量全堆在冷库里,三个步骤来回搬运几百次,客人饿得直拍桌子。 后来你想明白了:与其每次跑冷库存半成品,不如把原料一小份一小份搬到备菜台上,在灶台边完成“打分—归一—拌菜”全部动作,最后只把成品端回去。搬动次数从“按桌数的平方”骤降到“按桌数”。 这就是 Flash Attention V2 的绝活。注意力计算——AI 判断“哪些词该互相注意”的核心步骤——本来要来回读写三个巨大的中间矩阵,显存带宽被撑爆。V2 把计算分块锁在 GPU 的 SRAM 里一次搞定,IO 复杂度从 O(N²) 降到 O(N),长上下文训练直接提速 2-4 倍,而且结果和原来一模一样,没有一丁点近似。
对 AI 从业者:Flash Attention 是当前大模型训练与推理的事实标准算子。理解它的分块与 IO 优化思路,是做长上下文调优、推理服务压测和手写高性能算子的必备底子。
目录
可视化演示
展示Flash Attention V2三阶段:标准与Flash Attention的HBM读写IO对比、QKV分块融合在SRAM内完成计算、不同序列长度下加速比对比,三模式递进呈现从IO瓶颈分析到分块优化到性能提升。
本节配有交互动效(进入视口后自动加载,约 3KB)。
标准vs Flash Attention IO对比 STD: 标准方式需6次HBM读写(Q/K/V/S/P/O),中间矩阵S和P尺寸N×N FLA: Flash方式仅4次HBM读写,S和P在SRAM内融合计算不落盘 IO: O(N²) → O(N²·d/M),M=SRAM大小 IO降低33%,无近似精确计算
1. 注意力三连乘:QK^T、Softmax、PV
大厨的招牌菜有三个固定步骤:先给每桌客人的口味打分,再把分数折算成“谁该多吃一口”的权重,最后按权重把食材拌成一盘菜。每一步做完,大厨都习惯把半成品端回冷库保存——毕竟备菜台太小,放不下全部东西。
注意力机制正是这三步。Query 与 Key 做内积,得到“两两相关”的分数矩阵 S;Softmax 把每行的分数变成和为 1 的概率矩阵 P;最后用 P 对 Value 做加权求和,得到输出 O。标准实现中,S、P、O 都是 N×N 的大矩阵,每一步都要写入 HBM 再读回来,等于大厨每做一步都跑一趟冷库。
这盘菜本身并不难做,难的是每一步都要跑冷库:S 写完存回去、读完算 P、P 写完存回去、读完算 O。三步三趟,一百桌客人就这么被冷库的路程拖垮了。
- 相关性打分$QK^\top$ — 把序列里每个 query 与所有 key 做内积,生成 N×N 的分数矩阵 S,标记每对 token 的关联强度。
- 缩放稳定$\displaystyle\frac{1}{\sqrt{d}}$ — 除以维度 d 的平方根,防止内积数值过大导致后续 softmax 梯度消失。
- 按行归一$\text{softmax}(\cdot)$ — 将每行分数转成和为 1 的概率分布,回答“当前 token 该把注意力分给谁”。
- 加权输出$V$ — 用概率矩阵 P 对 Value 矩阵做加权求和,得到最终输出 O。
2. 存储金字塔:SRAM 与 HBM
冷库又大又远,备菜台又小又近。大厨真正慢下来的时候,往往不是切菜本身,而是往返冷库的腿。GPU 的存储世界也是这样一座金字塔:HBM 容量以 GB 计,但走到它面前要花几十上百个时钟周期;SRAM 只有几十 KB,却几乎可以瞬间访问。
更关键的是带宽的增速赶不上算力的增速。GPU 算力每年翻倍,HBM 带宽却只涨四成,于是越来越多的算子从“计算密集”变成“访存密集”——GPU 算得再快,也在干等数据搬进来。注意力就是最典型的例子。
冷库再大,腿脚不够快也没用。注意力计算恰恰是“搬货”远多于“干活”,所以优化它的关键不在灶台上的刀工,而在减少往返冷库的趟数。
- 分子 FLOPs$\text{FLOPs}$ — 一次操作需要的浮点运算次数,代表 GPU 真正“干活”的量。
- 分母 Bytes$\text{Bytes}$ — 从 HBM 读入或写出的字节数,代表“搬货”的量。
- 商 I$I$ — 算术强度,即每搬 1 字节数据能支撑多少次计算。I 越大,越不容易被带宽卡住。
3. IO 复杂度:从 O(N^2) 到 O(N)
桌数翻倍,大厨来回搬半成品的趟数会变成四倍——因为每一桌都要跟另外每桌做一次配菜组合,这就是二次方增长。而分块上菜的做法,每桌只搬一次原料和成品,翻倍之后只是两倍。
IO 复杂度衡量的就是读写 HBM 的次数。标准注意力里,S 和 P 都是 N×N 的矩阵,写一遍、读一遍,光这两个中间矩阵就贡献了 O(N²) 的流量。Flash Attention 把 S 和 P 留在 SRAM 上用完即弃,Q、K、V、O 各自流式读写一次,中间矩阵完全不落地。
从“每桌跑三趟”变成“全场按桌数跑一趟”,IO 少了一个量级,墙钟时间自然跟着缩水。这就是 Flash Attention 能在长上下文训练中拿到 2-4 倍加速的根源。
- N 的含义$N$ — 输入序列长度,比如一段文本的 token 数,也代表客人桌数。
- 渐近记号$\Theta(\cdot)$ — 表示复杂度的紧致界,描述“随 N 增大,读写量按什么速度膨胀”。
- 平方与线性$N^2$ 与 $N$ — 标准实现因为搬运 N×N 的中间矩阵而呈平方增长,Flash 只搬运 Q/K/V/O,呈线性增长。
4. 分块策略:一次只做一盘菜
备菜台每次只能放下一桌的食材,所以大厨把一百桌的原料分成小批,每批在灶台上完成“打分—归一—拌菜”全套流程,再把成品端回冷库。一批做完再做下一批,绝不中途回冷库。
Flash Attention 的外层循环遍历 Query 块,内层循环遍历 Key/Value 块。对每个 Q 块,依次扫过所有 K/V 块,在 SRAM 里算出局部分数、局部 softmax,再累加进输出。关键是块大小由 SRAM 容量决定,不能拍脑袋随便设。
灶台有多大,每份菜就只能做多大。分块之后还有个小问题:每个块算出的 softmax 只是局部的,怎么拼起来才是完整的 softmax?这就要靠下一节的在线技巧了。
- SRAM 容量$M_{\text{SRAM}}$ — GPU 片上可用的 SRAM 总空间,相当于备菜台的总面积。
- 四份开销$4$ — 一次迭代要同时容纳 Q 块、K 块、V 块和输出 O 块,共四个矩阵。
- 向量维度$d$ — 每个 token 的特征维度,决定单块体积。
- 平均分配$\displaystyle\frac{M_{\text{SRAM}}}{4d}$ — 四个块平分灶台,算出每个块最多能放多少行。
- 向下取整$\left\lfloor\cdot\right\rfloor$ — 留出余量,防止数据超出 SRAM,宁可少放不可溢出。
5. 在线 Softmax:边做边校正温度计
大厨没法等一百桌的打分全部齐了再统一调咸淡,他选择边炒边尝。每尝一口,就看看温度计上的最高温度;如果这口更热,就把之前调好的味道乘以一个缩放系数,整体校正到新刻度上。最后出锅时,味道跟“先算完再一起调”一模一样。
Softmax 需要整行的最大值 m 和总和 l。分块后 Flash 看不到整行,于是维护两个“运行中的统计量”:当前见过的最大值 m,和当前总和 l。每来一个新块,若新块出现更大的最大值,就把旧统计按比例缩小到新刻度,再累加新块的贡献。
这套“边算边校准”不是近似,而是精确的数学恒等变换。温度计虽然一直在动,但最后读出的温度,和一步到位是同一个数字。
- 更新最大值$m^{\text{new}}=\max(m,\text{rowmax}(S_j))$ — 取旧的最大值和新块的最大值中更大的那个,作为校正基准。
- 校正旧总和$l\cdot e^{m-m^{\text{new}}}$ — 旧总和里的每一项都在旧刻度下,乘以指数因子缩放到新刻度。
- 加入新块$+\ e^{\text{rowmax}(S_j)-m^{\text{new}}}$ — 新块的最大值也缩放到新刻度,计入总和 l。
- 合并输出$O^{\text{new}}$ — 旧输出按新旧总和比例衰减,新块加权求和后合并,每一块都对最终结果做出贡献。
6. 重计算与内核融合:反向的省钱之道
大厨做菜时不再写详细笔记。等到要复盘“这道菜到底怎么做的”时,他宁可把关键步骤重新做一遍,也不愿随身带一本占地方的厚笔记本。反向传播也是同样的道理。
训练时反向要算 S 和 P 的梯度。传统做法正向时把 S、P 存到 HBM,反向再读回来;Flash Attention V2 干脆不存,反向需要时重新算一遍正向。多花一次前向的浮点运算,却省下 O(N²) 的显存读写。在访存瓶颈面前,这笔买卖非常划算。
除了重算,V2 还把多个 CUDA kernel 融合成一个:原本“写入→读回→再计算”的割裂流程,变成一次启动内连续完成。好比大厨把洗菜、切菜、下锅的多个动作合并成一套流水,少弯腰,少转身。
- 前向浮点量$\text{FLOPs}_{\text{forward}}$ — 正向一次 Attention 所需的浮点运算。
- 相等关系$\text{FLOPs}_{\text{recompute}}=\text{FLOPs}_{\text{forward}}$ — 反向重算让总计算量翻倍,但换来的是更少等待。
- 节省的显存$\text{memory}_{\text{saved}}=\Theta(N^2)$ — 省掉的是 S 和 P 两个 N×N 矩阵,序列越长省得越多。
7. V2 工程优化:主厨的流程革命
V1 大厨已经学会在灶台上做菜,但他频繁停下来看火、翻勺,真正切菜的时间只占七成。V2 大厨重新设计了站位和流程:把更多时间留给切菜(矩阵乘法),减少看火翻勺(非矩阵运算),还同时开四个灶头按行并行。
V2 相对 V1 有三个关键改进:不再使用 split-K 带来的原子操作,改为按行并行,减少非矩阵乘法的占比;把缩放因子融合进计算,少一次独立乘法;在 A100 上精细调优块大小和循环顺序,让矩阵乘法的时间占比从约 75% 提升到 90% 以上。算法没变,流程变了,速度就上来了。
同样的菜谱,换一种更顺手的方式上菜,客人等的时间直接砍半。Flash Attention 的进化路线也告诉我们:到了带宽瓶颈时代,优化往往不靠发明新算法,而靠少搬一次货。
---
- 耗时比$\displaystyle\frac{T_{\text{V1}}}{T_{\text{V2}}}$ — V1 的墙钟时间除以 V2 的墙钟时间,得到相对加速比。
- 近似结果$\approx 2\times$ — 论文报告在 A100 上 V2 比 V1 快约 2 倍。
AI 应用场景
场景一:大模型长上下文训练
阶段:模型训练与调优
解决的问题:预训练时序列长度从 2048 拉到 128k,标准注意力的 S/P 中间矩阵是 N×N,128k 时单是中间矩阵就要约 64GB,远超单卡显存。Flash Attention V2 让中间矩阵不落地,长上下文训练不再被显存卡死,同时 IO 降低带来 2-4 倍训练加速。
具体案例:某团队微调 7B 模型,序列长度 32k,标准实现需要 8 张 A100 且频繁重试;换上 Flash Attention V2 后 4 张卡即可训练,每轮耗时缩短约 3 倍,效果完全不下降。
场景二:长文本在线推理服务
阶段:推理部署与性能优化
解决的问题:在线问答要处理几十页文档,每次生成 token 都要做注意力计算。KV cache 在 HBM 里反复读写,成为吞吐瓶颈。FlashAttention 系列(含 Decoding 变体)减少中间读写,让长文档摘要的首 token 延迟和每秒生成数都明显改善。
具体案例:一个文档摘要服务,输入 10k token,优化前单请求延迟 8 秒,引入 FlashAttention 后降到 2.5 秒,服务端吞吐提升 2 倍以上。
场景三:推荐系统序列特征建模
阶段:特征工程与模型训练
解决的问题:用户行为序列就像一段“行为文本”,注意力机制要给每个历史行为打分。序列从几十条增长到上千条时,标准注意力同样面临中间矩阵膨胀。用 FlashAttention V2 处理行为序列,可以支撑更长的行为历史,提升 CTR 预估精度。
具体案例:某电商推荐团队把用户最近 512 个行为作为序列特征,用注意力建模。此前序列长度 256 时训练一天;用 FlashAttention 后序列翻倍到 512,训练时间反而缩短 40%。
Python 演示
import math def standard_attention(Q, K, V): n = len(Q) d = len(Q[0]) S = [[0.0] * n for _ in range(n)] P = [[0.0] * n for _ in range(n)] O = [[0.0] * d for _ in range(n)] for i in range(n): for j in range(n): S[i][j] = sum(Q[i][t] * K[j][t] for t in range(d)) S[i][j] /= math.sqrt(d) for i in range(n): m = max(S[i]) total = 0.0 for j in range(n): P[i][j] = math.exp(S[i][j] - m) total += P[i][j] for j in range(n): P[i][j] /= total for j in range(n): for t in range(d): O[i][t] += P[i][j] * V[j][t] return O, 4 * n * n def flash_attention(Q, K, V, B): n = len(Q) d = len(Q[0]) O = [[0.0] * d for _ in range(n)] L = [0.0] * n M = [-float('inf')] * n for ib in range(0, n, B): qb = Q[ib:ib + B] for jb in range(0, n, B): kb = K[jb:jb + B] vb = V[jb:jb + B] for li, q in enumerate(qb): i = ib + li scores = [sum(q[t] * kb[j][t] for t in range(d)) / math.sqrt(d) for j in range(B)] row_max = max(scores) row_exps = [math.exp(s - row_max) for s in scores] row_sum = sum(row_exps) new_m = max(M[i], row_max) old_scale = math.exp(M[i] - new_m) new_scale = math.exp(row_max - new_m) new_l = L[i] * old_scale + row_sum * new_scale for t in range(d): local = sum(row_exps[j] / row_sum * vb[j][t] for j in range(B)) O[i][t] = (O[i][t] * L[i] * old_scale / new_l + local * row_sum * new_scale / new_l) L[i] = new_l M[i] = new_m return O, 0 if __name__ == '__main__': n, d, B = 8, 4, 2 Q = [[(i + 1) * 0.5 + t for t in range(d)] for i in range(n)] K = [[(i + 1) * 0.3 - t for t in range(d)] for i in range(n)] V = [[(i + 1) * 0.2 + t * 0.1 for t in range(d)] for i in range(n)] O_std, io_std = standard_attention(Q, K, V) O_fa, io_fa = flash_attention(Q, K, V, B) diff = max(abs(O_std[i][t] - O_fa[i][t]) for i in range(n) for t in range(d)) print('standard S/P HBM elements:', io_std) print('flash S/P HBM elements:', io_fa) print('max abs diff:', diff) assert diff < 1e-9
S[i][j] = sum(Q[i][t] * K[j][t] for t in range(d))计算 QK^T 打分,对应注意力公式的第一步。P[i][j] = math.exp(S[i][j] - m)减去行最大值再指数,保证数值稳定。scores = [sum(q[t] * kb[j][t] for t in range(d)) / math.sqrt(d) for j in range(B)]分块内只算当前 K 块的小分数矩阵,不触碰其他行。row_exps = [math.exp(s - row_max) for s in scores]在块内先算指数,等价于局部 softmax 分子。new_m = max(M[i], row_max)在线 softmax 的第一步:更新运行中的最大值。old_scale = math.exp(M[i] - new_m)把旧的统计量缩放到新最大值刻度。O[i][t] = O[i][t] * L[i] * old_scale / new_l + local * row_sum * new_scale / new_l合并输出:旧输出衰减,新块加权累加。return O, 0Flash 版本把 S/P 留在"SRAM",因此 HBM 中间矩阵流量为 0。
常见误区
误区听说 Flash Attention 是“分块近似计算”,长上下文训练质量会打折扣,所以不敢用在关键模型上。
正解它和标准 softmax 在数学上完全等价。在线 softmax 每次用全局最大值校正旧统计量,最终结果就是同一个 softmax 的精确值,差异仅来自浮点舍入(通常 <1e-6)。不是牺牲精度换速度,而是省掉了多余搬运。
为何常见:稀疏注意力、线性注意力等加速方案确实是近似的,很多人把“加速注意力”统统归为近似算法。
误区既然 IO 复杂度从 O(N²) 降到 O(N),那 Flash Attention 应该让计算也变成线性,序列越长越“划算”。
正解FLOPs 完全没变,仍是 O(N²d)。O(N) 只是 HBM 读写的量级。GPU 上很多算子卡在访存而不是计算,省 IO 能直接反映为加速 2-4 倍,但数学计算量依旧是那么多。
为何常见:“IO 复杂度”是个较新的概念,初学者容易把它和一般的时间复杂度混为一谈。
误区既然分块是为了减少冷库往返,那把块设置得越大,岂不是越快?
正解块大小受 SRAM 容量硬限制,超了直接放不下。且过大的块会挤占寄存器、降低并行度,反而拖慢速度。真实部署要按 SRAM 容量、维度 d、GPU 架构综合调优,论文给出了经验公式。
为何常见:搬一次大包裹确实比搬十次小包裹省力,但忘记灶台只有那么大。
误区反向传播重算 S/P 会让训练计算量翻倍,感觉很不划算,还是把中间矩阵存下来更稳妥。
正解GPU 最贵的是等待,而不是算力。Flash Attention V2 用翻倍的浮点运算换掉 O(N²) 的 HBM 访问,在访存瓶颈下总时间反而下降。长序列场景中,省显存还能支持更大 batch,收益更明显。
为何常见:CPU 时代计算通常比内存贵,很多人沿用“宁可多存也别重算”的旧直觉。
误区既然中间矩阵不落地,显存占用变成 O(N),那显存焦虑应该彻底消失了。
正解它省的是 S/P 这类 N×N 中间矩阵,但 Q/K/V/O 和 KV cache 仍在 HBM 里。序列极长时 KV cache 照样可能爆显存,还需要序列并行、稀疏注意力、KV cache 量化等手段配合。
为何常见:宣传常说“显存节省 O(N²)”,被夸大成了“总显存也降到 O(N)”。
面试题
Q1. Flash Attention 主要减少的是什么开销?
- A.矩阵乘法的计算量
- B.HBM 的中间读写← 正确答案
- C.Softmax 的运算量
- D.参数的存储空间
答案:B。Flash Attention 不动浮点计算量,而是把 S/P 中间矩阵留在 SRAM,避免 HBM 反复搬运,IO 复杂度从 O(N²) 降到 O(N)。
Q2. 在线 Softmax 的核心目的是什么?
- A.让 softmax 数值更平滑
- B.看不到整行也能精确归一← 正确答案
- C.避免 fp16 精度不足
- D.减少矩阵乘法次数
答案:B。分块后拿不到整行的最大值与总和,在线 Softmax 维护运行中的 m 和 l,每块校正,数学上与全局 softmax 完全等价,不是平滑或近似。
Q3. 下列哪些是 Flash Attention V2 相对 V1 的改进?
- A.提高矩阵乘法时间占比← 正确答案
- B.去掉 split-K 原子操作← 正确答案
- C.使用稀疏近似注意力
- D.反向重算不存 S/P← 正确答案
答案:ABD。V2 按行并行去掉原子操作、减少非矩阵乘占比、反向重算不缓存 S/P;它没有引入稀疏或近似,仍是精确注意力。
Q4. HBM 与 SRAM 的关系,最适合类比厨房里的什么?
- A.灶台与装菜的盘子
- B.冷库与备菜台← 正确答案
- C.铁锅与菜刀
- D.大厨与服务员
答案:B。HBM 容量大但访问慢,如冷库;SRAM 容量小但速度极快,如备菜台。两者的速度与容量差异正好对应 GPU 的两级存储。
Q5. 标准 Attention 的 HBM 访问复杂度是?
- A.O(N)
- B.O(N log N)
- C.O(N^2)← 正确答案
- D.O(N^2 d)
答案:C。S 和 P 都是 N×N 矩阵,写一次、读一次就贡献 O(N²) 流量,因此标准实现呈平方级 IO。
Q6. Flash Attention 中,下面哪些矩阵不需要从 HBM 读回?
- A.S = QK^T← 正确答案
- B.P = softmax(S)← 正确答案
- C.Q 矩阵分块
- D.V 矩阵分块
答案:AB。S 和 P 在 SRAM 中算完立刻用于后续,不写回 HBM;Q/K/V 仍需分块从 HBM 读取,O 最后写回。
Q7. 若 SRAM 容量为 M,特征维度为 d,Flash Attention 的 Query 块大小通常取?
- A.M / d
- B.M / (4d)← 正确答案
- C.4M / d
- D.M / d^2
答案:B。一次迭代要同时容纳 Q、K、V、O 四个块,平均每个块约 M/(4d),再向下取整留余量。
Q8. FlashAttention V2 反向传播不保存 S/P,原因是什么?
- A.省显存换取重算← 正确答案
- B.梯度不需要它们
- C.S/P 永远不计算
- D.反向无法重算
答案:A。反向梯度需要 S/P,V2 选择重算来避免存储,多花一次前向 FLOPs,省掉 O(N²) 显存访问,总时间反而更快。
Q9. 长上下文训练中 FlashAttention 带来的好处有哪些?
- A.省下 O(N^2) 显存← 正确答案
- B.训练速度提升← 正确答案
- C.降低结果精度损失
- D.减少分布式通信
答案:AB。中间矩阵不落地,显存和 IO 同时减少,训练更快;它不改变数值精度,也不直接减少分布式通信。
Q10. 合并两个局部 softmax 统计量 (m1, l1) 与 (m2, l2) 时,正确做法是?
- A.取最大值,总和用指数校正后累加← 正确答案
- B.取平均值,总和直接相加
- C.取最大值,总和取两数较大
- D.取最小值,总和直接相加
答案:A。新最大值为 m=max(m1,m2),旧总和必须乘 e^(m1-m) 和 e^(m2-m) 缩放到新刻度再相加,才能得到正确的全局 softmax 分母。