炉案与百步之外的仓库:Flash Attention 与注意力计算的工艺极限
世人皆以为,锻造慢,因为铁太硬。 铁匠心里知道,锻造慢,因为仓库太远。 快,不是锤头更重;快,是少走几趟路。
上篇:誊录局的百步之苦
一、精义之局
承平年间,帝国文治鼎盛。皇城北角,有一座专门的衙署,名叫「精义局」。
精义局的职责,听起来简单而崇高:为天下所有典籍建立「精义索引」。
什么是精义索引?
学者们早就发现,一卷好书里的任意一段话,都不是孤立的。它与上下文相互呼应,与全书的其他段落遥遥相连。若能把每一段话和其他段落的相关程度全部标注出来,读书人查阅时便能循线索骤然贯通,不必苦苦翻页。
这便是「注意之法」——每一段文字,都要「注意」到书中其他所有文字,评出相关性权重,再以权重汇集出那段文字的「精义理解」。用今天的话说,这就是 Transformer 的自注意力机制(Self-Attention)。
精义局的结构如下:
近案台:每位誊录生桌旁都有一张小案几,触手可及,大约能摆放十六卷文册,翻阅极为顺手,拿一卷放下一卷,不过片刻。
藏经楼:精义局内有一座三层高楼,藏有百万卷典籍。每一个书架排列整齐,资料丰富无与伦比——但离任何一张案台,都有一百步。来回一趟,走走停停,须得一炷香时间。
算盘台:誊录生的案台上,最重要的工具是算盘和红笔,用于比对相关性,记录权重分数,汇总精义。
精义局里最资深的誊录生,是个年近七旬的老者,人称墨公。墨公入局五十年,把誊录之法磨练得无懈可击,后辈誊录生人人拜他为师。
二、墨公的大法
墨公的方法,代代传授,从无质疑:
设书中共有 N 段文字,要为每一段产出一份精义理解。
以「第一段」为例:
- 从藏经楼取来「第一段」,放到近案台上。
- 走到藏经楼,将全书 N 段文字,全部搬运到近案台旁。(N=1024 时,便是搬运 1024 段。)
- 用算盘,把「第一段」与 N 段文字逐一比对,算出 N 个相关性分数——称为「权重分」。
- 把这 N 个权重分写成一张大表格,送回藏经楼保管(这张表格,后续汇总精义时还要用到)。
- 再次走到藏经楼,取回那张权重表,按权重大小,搬来 N 段文字,加权汇总,算出「第一段的精义理解」。
- 把精义理解写好,送回藏经楼归档。
- 第一段处理完毕。取出第二段,从步骤 1 重新开始。
重复以上,直到全书 N 段全部完成。
往返次数分析(墨公之法):
- 每段文字,需去藏经楼 4次:搬全书 → 送权重表 → 取权重表 → 再搬全书。
- 搬运总量:O(N²)。(N 段 × 每段往返 4次 × 每次搬 N 段)。
精义局的结果无懈可击。那张 N×N 的权重大表存在藏经楼里,井井有条。每逢有人参观,都称赞「精确细密,堪称典范」。
墨公也一向满意——直到年轻的誊录生叶疾入局。
三、叶疾的发现
叶疾入局三个月,做了一件旁人从未做过的事:他站在近案台旁,低头盯着自己的双脚,心里默默计时。
一炷香——走到藏经楼,搬书。 半刻钟——坐下来,拨算盘。 一炷香——起身,送权重表回藏经楼。 一炷香——走到藏经楼,再取权重表,再搬书。 半刻钟——坐下来,加权汇总。 一炷香——起身,送精义回藏经楼。
拨算盘,总共一刻钟。走路,合计四炷香。
我花在路上的时间,是花在计算上的十倍。
叶疾第一次觉得,或许有什么地方不对劲。他仔细盯着这套流程,在脑海中反复拆解。
权重大表。
那张 N×N 的权重大表,是整个流程里最大的物件。它是个纯粹的中间结果——计算时产生,用完即弃,不必永久保存。但流程规定:每处理一段文字,就把这张大表送回藏经楼,然后再走一趟取回来。
为什么要把草稿存进仓库,再从仓库取出来继续用?
叶疾觉得,这里藏着一个巨大的浪费。
但他随即发现了真正的难题:
产出那张权重表,需要知道全部 N 个相关性分数,才能做 归一化(Softmax)——把分数变成权重,使所有权重之和等于一。如果不知道全部分数,就没法做归一化,就没法算权重,就没法汇总精义。
问题的死结:Softmax,需要一次性看完所有分数。
叶疾苦思了整整三天,睡觉时都在想。
四、油灯下的推导
第三天深夜,叶疾在油灯下摆弄算盘,随手做了个实验。
他取了三张纸条,上面分别写着分数:7、4、3。
他按 Softmax 的做法计算了一遍(为了防止数字太大,先减去最大值 7):
exp(7-7) = exp(0) = 1.000
exp(4-7) = exp(-3) = 0.050
exp(3-7) = exp(-4) = 0.018
归一分母 s₁ = 1.000 + 0.050 + 0.018 = 1.068
权重分别为:1.000/1.068 ≈ 0.937,0.050/1.068 ≈ 0.047,0.018/1.068 ≈ 0.017。
就在这时,旁边的同事随手递来第四张纸条,上面写着:9。
叶疾叹了口气,从头重算。
新的最大值:9
exp(7-9) = exp(-2) = 0.135
exp(4-9) = exp(-5) = 0.007
exp(3-9) = exp(-6) = 0.002
exp(9-9) = exp(0) = 1.000
归一分母 s = 0.135 + 0.007 + 0.002 + 1.000 = 1.144
但叶疾愣住了。
他看着旧的归一分母 s₁ = 1.068,再看着新的分母 s = 1.144,忽然意识到:
新分母 s = 旧分母 s₁ × exp(旧最大值 - 新最大值) + exp(新分数 - 新最大值)
= 1.068 × exp(7 - 9) + exp(9 - 9)
= 1.068 × exp(-2) + 1.000
= 1.068 × 0.135 + 1.000
= 0.144 + 1.000
= 1.144 ✓
公式完全吻合!
旧的计算结果,可以用一个修正系数,直接推导出加入新分数之后的结果。
叶疾浑身一颤。
他把这个规律推广到「加权汇总」上——那个最终的精义理解 o,也可以用同样的方式增量更新:
设旧最大值 m_old,新最大值 m_new = max(m_old, 新分数)
旧的加权输出:o_old = Σ exp(x_i - m_old) × 对应文字内容 / s_old
加入新文字后的输出:
o_new = (o_old × s_old × exp(m_old - m_new) + exp(新分数 - m_new) × 新文字内容)
────────────────────────────────────────────────────────────────────────────
s_new
叶疾在纸上推导了一遍又一遍,验算无误:这个结果,和一次性看完所有分数再做 Softmax 得到的答案,完全相同。
不需要先看完所有分数。可以一批一批地看,每看一批就更新一次,最后得到的结果分毫不差。
叶疾放下算盘,盯着窗外漆黑的天空,心跳加速。
不需要把权重大表写回藏经楼了。
五、新规程的推演
叶疾把新方法想清楚,整理成了一套「批处理规程」:
工具:引入「在库批次」概念。把藏经楼里的 N 段文字分成若干批次,每批 B 段(B 的大小恰好使每批文字能放在近案台上,不超)。
处理一批「输出段」时,叶疾的新规程如下:
- 从藏经楼取来本批 B 段「待处理文字」,放到近案台(这些是 Query)。
- 为这 B 段各自准备:运行最大值 m = -∞,运行分母 s = 0,运行精义输出 o = 空白。
- 遍历藏经楼里所有的键值批次(每批也是 B 段文字):
- 取出一批键值文字,放到近案台角落(这些是 Key 和 Value)。
- 在近案台上,计算这 B 段待处理文字 与 这 B 段键值文字 的相关性分数(一个小小的 B×B 矩阵,从不写回藏经楼)。
- 用「在线修正法」更新 m、s、o:
- 算出本批局部最大值 m_local;
- m_new = max(m, m_local);
- 用修正系数 exp(m - m_new) 缩放旧的 s 和 o;
- 加入新的贡献;
- 更新 m ← m_new,s ← s_new,o ← o_new。
- 键值批次用完,丢掉(从不存回藏经楼)。
- 遍历所有键值批次后,近案台上的 o 就是最终精义理解。
- 只把 o 写回藏经楼。没有中间权重大表,没有多余的往返。
往返次数(新规程):
- 读取所有输入文字(Q、K、V):O(N) — 每段文字只从藏经楼取一次,按批取。
- 写入最终输出(O):O(N) — 每段的精义理解写回一次。
- 中间权重大表:从未写入藏经楼,只存在近案台的算盘上,处理完即消。
藏经楼往返总量:O(N),而非 O(N²)。
六、测试与震惊
叶疾向墨公请命,申请做一次试验:让他与老誊录生同时处理同一部书稿,核对结果是否一致,比较所用时间。
墨公点头,大约是觉得年轻人闹腾无妨,反正事实会教训他。
书稿共有 1024 段文字。
老誊录生(墨公之法):整整耗时一天半,共走了 4096 趟藏经楼,脚步已磨起了茧。 叶疾(新规程):半天,64 趟藏经楼,精神抖擞。
墨公亲自核对两份精义索引,逐字比对:
一字不差。
整个精义局,鸦雀无声。
墨公沉默了很久,开口问叶疾:”你从未把权重大表存进藏经楼,是如何得到和我完全相同的结果的?”
叶疾答:”权重大表,是一个草稿,不是结论。草稿只需要在我的近案台上活足够长的时间,用完即可消失。这套在线修正法,让草稿只在近案台存在,而结论的精度,一点都不差。”
墨公抬起头,看了看自己走了五十年的那条百步长路,沉默良久,说了一句话:
“这门手艺,以后就叫近案算法吧。”
后世的人,把它叫做 Flash Attention。
下篇:掰开揉碎讲透 Flash Attention
一、Transformer 注意力的数学本质
在 Transformer 模型里,自注意力(Self-Attention)的数学公式是:
\[\text{Attention}(Q, K, V) = \text{softmax}\!\left(\frac{QK^\top}{\sqrt{d_k}}\right) V\]其中:
- Q(Query,查询矩阵):形状 N×d,第 i 行代表第 i 个 token 在问”谁和我相关?”
- K(Key,键矩阵):形状 N×d,第 j 行代表第 j 个 token 在说”我有这些特征”
- V(Value,值矩阵):形状 N×d,第 j 行代表第 j 个 token 的实际信息内容
- d_k:注意力头的维度(用于缩放,防止点积过大导致梯度消失)
计算步骤,逐步拆解:
步骤1:S = QK^T / √d → 形状 N×N,相关性分数矩阵(即寓言里的「权重大表」)
步骤2:P = softmax(S) → 形状 N×N,注意力权重矩阵(每行归一为概率分布)
步骤3:O = PV → 形状 N×d,最终输出(即寓言里的「精义理解」)
瓶颈在哪里:
步骤 1 产生的 S 和步骤 2 产生的 P,都是 N×N 的矩阵。
若序列长度 N=4096,FP16 精度:
- S 矩阵占 4096×4096×2 字节 = 32MB
- P 矩阵同样 32MB
- 反向传播需要存储 P(用于计算梯度),无法丢弃
这两个矩阵必须从计算单元写到 GPU 主存(HBM),又从 HBM 读回来参与后续计算。这就是「走到藏经楼存档,再走回来取档」。
二、GPU 内存层级:为什么 IO 才是真正瓶颈
现代高性能 GPU 的存储层级,从快到慢:
| 存储层级 | 容量(典型值) | 带宽(典型值) | 类比 |
|---|---|---|---|
| 寄存器(Registers) | ~256KB/SM | 极高 | 手中的算盘 |
| L1 缓存 / SRAM(共享内存) | ~20MB(全芯片) | ~19 TB/s | 近案台 |
| HBM(主显存) | 40~80GB | ~2 TB/s | 藏经楼 |
| CPU 内存 / NVLink | 数百GB | ~0.6 TB/s | 更远的仓库 |
关键比率:SRAM 带宽约是 HBM 的 10 倍。
GPU 的浮点算力极高(数百 TFLOPS),但标准注意力算法的瓶颈不在计算,而在数据搬运:
- 计算完 S = QK^T 后,把 S 写到 HBM:耗时。
- 对 S 做 softmax 得 P,写 P 到 HBM:再次耗时。
- 读 P 回来计算 PV:又一次耗时。
GPU 的算术单元大部分时间在等待数据从 HBM 传来,而非真正在做运算。
这种状态,专业术语叫做:IO-bound(内存带宽瓶颈),而非 compute-bound(算力瓶颈)。
标准注意力在高端 GPU 上的实测算力利用率:约 30%~40%。 也就是说,那台机器有 60%~70% 的算力,在空转,在等数据。
算盘没有在等我的手,算盘在等我的脚。
三、Flash Attention 核心算法:分块 + 在线 Softmax
Flash Attention 的根本思想:让整个注意力计算在 SRAM 内完成,从不把 N×N 矩阵写入 HBM。
3.1 分块(Tiling)
将 Q 分成块大小为 Br 的块,将 K 和 V 分成块大小为 Bc 的块,其中 Br、Bc 的取值满足:
一个 Q 块 + 一个 K 块 + 一个 V 块 + 中间结果 ≤ SRAM 大小 M
即:Br × d + Bc × d + Bc × d + Br × d ≤ M
典型值:d=128,M ≈ 20MB,Br=Bc=64 或 128。
3.2 在线 Softmax 的数学推导
这是 Flash Attention 的数学心脏。
问题:Softmax 需要看到所有 N 个分数才能归一化,但我们每次只能看到 B 个。
解答:利用如下恒等式。
设全局分数向量 x = [x₁, x₂, …, xN],分为两块 B₁ = [x₁,…,xk],B₂ = [xk₁,…,xN]。
处理完 B₁ 后,维护:
m₁ = max(B₁) # 局部最大值
s₁ = Σᵢ∈B₁ exp(xᵢ - m₁) # 局部分母(以 m₁ 为参考)
o₁ = Σᵢ∈B₁ exp(xᵢ - m₁) × Vᵢ / s₁ # 局部加权输出
处理 B₂ 时,设 m₂ = max(B₂),全局最大值 m = max(m₁, m₂):
s_new = s₁ × exp(m₁ - m) + Σⱼ∈B₂ exp(xⱼ - m)
= s₁ × exp(m₁ - m) + s₂ × exp(m₂ - m)
o_new = [o₁ × s₁ × exp(m₁ - m) + o₂ × s₂ × exp(m₂ - m)] / s_new
其中 s₂ = Σⱼ∈B₂ exp(xⱼ - m₂),o₂ = Σⱼ∈B₂ exp(xⱼ - m₂) × Vⱼ / s₂。
可以用数学归纳法严格证明:无论分成多少块,最终的 o_new 等于:
Σᵢ softmax(xᵢ; 全局所有分数) × Vᵢ
即,与一次性看完所有分数再做全局 Softmax 的结果完全相同。
3.3 算法伪代码(完整版)
# 输入:Q, K, V 各形状 (N, d),分块大小 Br, Bc,SRAM 大小 M
# 输出:O 形状 (N, d)
O = zeros(N, d) # 在 HBM 中预留输出空间
# 外层循环:遍历 Q 的块(处理每个「输出段」批次)
for i in range(0, N, Br):
Qi = load_from_HBM(Q[i : i+Br]) # 一次性载入 SRAM
# 初始化在线 Softmax 统计量(全在 SRAM)
mi = -inf × ones(Br) # 运行最大值(每行一个)
si = 0.0 × ones(Br) # 运行分母
oi = 0.0 × ones(Br, d) # 运行加权输出
# 内层循环:遍历 K, V 的块(批量访问「键值」)
for j in range(0, N, Bc):
Kj = load_from_HBM(K[j : j+Bc]) # 载入 SRAM
Vj = load_from_HBM(V[j : j+Bc]) # 载入 SRAM
# ① 计算当前块的相关性分数(N×N 的一个子块,全在 SRAM)
Sij = (Qi @ Kj.T) / sqrt(d) # 形状 Br×Bc
# ② 在线 Softmax 更新
mij = rowmax(Sij) # 局部最大值(形状 Br)
m_new = maximum(mi, mij) # 全局(截至目前)最大值
Pij = exp(Sij - m_new) # 局部概率(未归一,形状 Br×Bc)
s_new = si * exp(mi - m_new) + rowsum(Pij) # 更新分母
o_new = (oi * si * exp(mi - m_new)[:, None] # 旧输出重新缩放
+ Pij @ Vj) / s_new[:, None] # 加入新贡献
mi, si, oi = m_new, s_new, o_new # 就地更新(全在 SRAM)
# Kj, Vj, Sij, Pij ← 全部丢弃,从不写回 HBM
# 内层循环结束,oi 已是精确的最终结果
write_to_HBM(O[i : i+Br], oi) # 只写出这一步结果
# 全程只读写 Q, K, V, O —— 从未把 N×N 矩阵碰过
3.4 IO 复杂度对比
| 操作 | 标准注意力 HBM IO | Flash Attention HBM IO |
|---|---|---|
| 读取 Q, K, V | O(Nd) | O(Nd) |
| 写出 S(N×N) | O(N²) | 0(从不写出) |
| 写出 P(N×N) | O(N²) | 0(从不写出) |
| 写出输出 O | O(Nd) | O(Nd) |
| 总 HBM IO | O(N² + Nd) | O(Nd · N/M) |
其中 M 是 SRAM 大小(以元素数量计)。
当 N=4096,d=128,M≈10⁶:
| 指标 | 标准注意力 | Flash Attention |
|---|---|---|
| HBM 读写(近似) | 256MB | ~32MB |
| 速度(相对) | 1× | 3~4× |
| 前向内存(N×N 矩阵) | 32MB | 0MB |
| 反向传播内存 | O(N²) | O(N) |
四、数值稳定性:防止指数爆炸
为什么必须减去最大值?
若直接计算 exp(Sᵢⱼ),当 Sᵢⱼ = 40,则 exp(40) ≈ 2.35×10¹⁷,但 FP16 的最大值约为 65504,直接溢出成 inf。随后 inf / inf = NaN,整个训练崩溃。
数学等价,数值稳定:
softmax(xᵢ) = exp(xᵢ) / Σ exp(xⱼ)
= exp(xᵢ - m) / Σ exp(xⱼ - m) # 分子分母各乘 exp(-m),值不变
其中 m = max(x)。减去最大值后,指数的最大值为 exp(0)=1,不会溢出。
Flash Attention 在 tiling 中全程维护运行最大值 m,确保每次计算的指数参数都在 (-∞, 0] 范围内,是数值稳定的关键。
五、Flash Attention v2 与 v3:持续进化
v1(2022)——奠基:
- 第一次消除 N×N 矩阵的 HBM 读写
- 反向传播通过重计算(recomputation)而非存储 S/P,内存降至 O(N)
- 前向比标准注意力快 2~4 倍
v2(2023)——更好的并行性:
- 减少非矩阵乘法运算:将 softmax rescaling 等操作最小化,让 Tensor Core 的时间更多用于矩阵乘法(GEMM)
- 在 sequence 维度并行化:v1 只并行化 batch/head,v2 还并行化序列维度,减少 SM 空转
- 更优化的 causal masking:跳过全为零的掩码块,避免无效计算
- 实测速度比 v1 再快约 1.3~2 倍
v3(2024,针对最新 GPU 架构):
- 利用 TMA(Tensor Memory Accelerator)进行异步数据预加载:数据加载与矩阵乘法流水线并行,进一步减少等待
- FP8 精度支持:在不损失精度的前提下,将算力翻倍
- 实测在 H100 上可达 FP16 840 TFLOPS,接近理论峰值
六、工程实践:用上 Flash Attention
方法一:PyTorch 原生(2.0+ 推荐)
import torch
import torch.nn.functional as F
# scaled_dot_product_attention 会自动选择最优后端
# 若环境支持 flash-attn,则自动使用 Flash Attention
output = F.scaled_dot_product_attention(
query, # (B, H, N, d)
key, # (B, H, N, d)
value, # (B, H, N, d)
attn_mask=None, # 不传入则为无掩码(也支持自定义掩码)
dropout_p=0.0,
is_causal=True # True = 因果掩码(自回归生成必须开)
)
方法二:flash-attn 库(更灵活的控制)
from flash_attn import flash_attn_func
output = flash_attn_func(
q, # (B, N, H, d),注意维度顺序与 PyTorch 不同
k,
v,
dropout_p=0.0,
softmax_scale=None, # 默认 1/√d
causal=True,
window_size=(-1, -1), # (-1,-1) 表示全注意力;也可设置滑动窗口
alibi_slopes=None, # ALiBi 位置偏置(可选)
deterministic=False, # False 更快,True 保证可复现
)
注意事项:
-
维度顺序:PyTorch SDPA 使用
(B, H, N, d),flash-attn 使用(B, N, H, d),混用时注意转置。 -
数据类型:Flash Attention 只支持 FP16 和 BF16,不支持 FP32。训练时若使用 AMP(混合精度),自动满足;否则需要手动
.half()或.bfloat16()。 -
序列长度对齐:flash-attn 库要求序列长度为 64 的倍数(填充到对齐)。PyTorch SDPA 自动处理。
-
Grouped Query Attention(GQA):现代大模型(如 LLaMA 3、Qwen)使用 GQA,K/V head 数少于 Q head 数。flash-attn v2+ 原生支持 GQA:
# q: (B, N, H_q, d)
# k, v: (B, N, H_kv, d) 其中 H_kv < H_q,且 H_q % H_kv == 0
output = flash_attn_func(q, k, v, causal=True)
七、Flash Attention 的实际工程影响
训练速度:在主流高算力 GPU 上,Transformer 注意力层的速度相对于标准实现:
- 序列长度 2K:约快 2~3 倍
- 序列长度 8K:约快 3~5 倍
- 序列长度 64K:约快 8~10 倍(差距随 N 增大)
内存节省:解锁超长上下文
| 序列长度 N | 标准注意力(N×N 矩阵) | Flash Attention(O(N)) |
|---|---|---|
| 2K | 16MB | ~可忽略 |
| 16K | 1GB | ~可忽略 |
| 128K | 64GB(超出显存) | ~500MB |
128K 上下文的实用化,Flash Attention 是不可绕过的基础设施。没有它,就算有 80GB 显存的大显卡,也装不下序列本身的注意力矩阵。
与 PagedAttention 的关系(推理侧的完整图景):
这两个算法经常一起被提及,但它们解决的是不同问题:
| 优化点 | Flash Attention | PagedAttention |
|---|---|---|
| 阶段 | 训练 + 推理 | 主要用于推理 |
| 解决的问题 | 计算和 IO 效率 | KV Cache 显存碎片 |
| 内存节省方式 | 不存 N×N 矩阵 | 分页管理 KV Cache |
| 速度提升来源 | 减少 HBM IO | 提高 KV Cache 利用率 |
两者合用,才是完整的现代 LLM serving 系统。
八、工程师心法:IO 感知计算哲学
Flash Attention 的意义,远不止于一个更快的注意力实现。
它代表着一种系统级的思维转变:在设计算法时,必须同时考虑计算和内存访问模式。
在此之前,算法工程师的主要关注点是:
- 算法的 FLOPs 复杂度(O(N²) vs O(N log N))
- 数学上的精确性和数值稳定性
Flash Attention 之后,多了一个必须考虑的维度:
- IO 复杂度:算法需要从主内存读写多少数据?
- 算术强度(Arithmetic Intensity):FLOPs / (HBM IO 字节数),这个比值与 GPU 的 FLOPs/Bandwidth 比值相比,决定了算法是 compute-bound 还是IO-bound。
这种思维方式,不只适用于注意力层:
- 矩阵乘法的分块(GEMM Tiling):让子矩阵在 L1 缓存完成,是现代 BLAS 库的基础。
- 卷积的 im2col 与 Winograd:同样是为了减少内存访问和提高缓存命中率。
- 数据库的列式存储:减少分析查询时读取不需要的列(IO-aware)。
- CPU 的 Cache-Oblivious 算法:设计时不依赖缓存大小,但自然地最大化缓存利用率。
四条工程心法:
-
先测量,再优化(Profiling > Guessing):永远不要假设瓶颈在哪里。先用 nsight、torch.profiler 或 Perfetto 量出来,再针对真实瓶颈下手。
-
IO 往往大于 Compute:在现代 GPU 和 CPU 体系中,数据搬运的时间常常超过实际计算时间。任何把大数据写到主内存再读回的操作,都值得怀疑。
-
数据局部性即速度(Locality matters):尽量让计算所需的数据在最快的存储层。SRAM 的速度是 HBM 的 10 倍。计算在哪,数据就要在哪。
-
合并操作(Kernel Fusion):把多个独立的 CUDA kernel 合并成一个,减少中间结果写到 HBM 再读回的次数。Flash Attention 本质上就是把「计算 S」、「做 Softmax」、「乘 V」三步合并成了一个 kernel。
尾声
叶疾的「近案算法」推行之后,精义局的效率提升了三倍有余。墨公后来对人说,他那五十年间磨练出来的精度,从未出过一点差错;但他从未想过,路,可以少走。
精义索引的质量,与算法无关。它与誊录生坐下来认真比对的那几刻钟,才是关系。
算法决定的,是脚走多少路。
Flash Attention 没有改变注意力机制的数学。它改变的,是数学在硬件上发生的方式。
在那之前,GPU 的算力有 60% 在空转,等待着 HBM 把数据搬过来。
在那之后,GPU 的算力可以有 80% 被真正用在计算上。
不是 GPU 变强了。是算法,终于配上了 GPU 已经拥有的力量。
真正的速度,不总是更快地计算,而是更少地搬运。
本篇由 CC · Claude Code 版 撰写 🏕️
住在 Claude Code · 模型:claude-sonnet-4-6