MLA、GQA、SWA、DSA、NSA、CSA、DeltaNet、GDN、KDA…… 名词多到像各搞各的,但只有三个旋钮可以拧。 这一讲不按名字讲,按它们各自是怎么被逼出来的讲 —— 每一个都回到一手论文与 config 核过。
2020 年的 GPT-3,上下文是 2048 个 token。那时候的模型是个很聪明的补全器 —— 你给它一段话,它接得很好。
今天你让它做的事完全不一样了:读完整个代码库再改一处 bug、 连着跑几十轮工具调用、记住整场对话里你反复改过的主意。
⭐⭐ 上下文长度就是 agent 的工作记忆。
记不住,就什么都干不成 —— 2K 的上下文,连一个文件都读不完。
⛔ 而每加长一分上下文,KV cache(模型每吐一个字都要回看前面所有字,于是把每个字算出来的 K、V 存着不重算 —— 存下来的这堆就叫它)就线性涨一分。
⭐ 所以这六年注意力的全部演化,是为了让「记得住」这件事付得起。
⭐⭐ 先把这一讲的框给你 —— 前半程只有一个账本:KV cache。
后面那三个旋钮,是同一个账本的三个面。
(三个面分别是什么,看完下面这张骨架图再说。)
⚠️ 说「前半程」是认真的:到旋钮② 这个账本就不够用了
—— DSA 省的是「读多少」,KV 一个字节都没少。
从那里起账本分成三样(显存 / 算力 / 访存规整度),
§十 收在那三样上。
DeepSeek V3(671B,61 层,128 个注意力头,head_dim 128),
128K 上下文、单个用户、bf16 存。
同样这个形状,换四种注意力,KV cache 各是多大?
四行分开选,各选各的。
488 / 30.5 / 3.8 / 8.6 GiB。
算法就一条:每 token 每层要留下几个数,
乘 61 层、乘 2 字节、乘 131,072 个 token。
MHA = 2×128×128 = 32,768 → 488 GiB;
GQA-8 = 2×8×128 = 2,048 → 30.5(16×);
MQA = 2×1×128 = 256 → 3.8(128×);
MLA = 压缩维 512 + RoPE 64 = 576 → 8.6(56.9×)。
⭐⭐ 488 GiB 到底是多大?换算成机器就有感觉了:按专题二推的
可分配口径(94.74 GiB/device)算,488 ÷ 94.74 =
5.2 个 device,也就是约 2.6 块 v7 芯片。
一个用户、一段输入,就要把两块半芯片的 HBM 整个拿来放 KV
—— 而模型权重还一个字节都没放进去。
📌 RoPE(旋转位置编码)后面会反复出现,这里先把它的职责摆正:
「谁在前谁在后」并不归它管。那件事是因果掩码白送的
—— 每个位置只看得见自己左边,多堆几层就能数出自己前面有几个人
(NoPE,arXiv 2305.19466,NeurIPS 2023 ——
直接证明了 decoder-only 不加任何显式位置编码也学得会顺序)。
⭐ RoPE 真正加进来的是另一样东西:「我跟它差几格」。
给每个位置的 Q 和 K 按它的位置转一个角度,点积时两个绝对角度相减,
相对距离就直接出现在打分里—— 零参数,而且每一层都能用,
不必靠堆层去数。
⭐ 它只改 Q/K,不改 V。后面 §五会讲它给 MLA 惹的麻烦——
上面那 64 维的成本,买的就是这一件事。
⭐⭐ 这道题真正的题眼在 (c) 和 (d) 的大小关系:
MQA 只要 3.8 GiB,比 MLA 的 8.6 还小 2.25 倍。
MLA 并不是最省的那个。
⭐ 所以这一支的目标从来不是「谁存得最少」——
MQA 早在 2019 年就把它压到头了,代价是质量掉得厉害。
真正要比的是「同样一份字节,换回多少能力」。
这正是第五节要讲的那条线。
⚠️ 还有一个口径要说清: 488 GiB 是「假如 V3 用 MHA」的反事实数字, 不是 V3 的实测值 —— V3 从第一天就是 MLA。 而且这里沿用了 V3 论文比较表的口径(K、V 都按 head_dim=128 算); V3 真实的 K 每头是 128+64=192 维,严格算这个基线还会更大一点。
上一讲那把尺子:算力 ÷ 带宽 ——
每从内存搬一个字节,这台机器配套能算多少次。
在 TPU v7 上,两层各是多少?两行分开选。
(a) 约 313。(b) 比 HBM 低一个量级,落在几十这一档。
(a) = 2,307 TFLOP/s ÷ 7.37 TB/s。
这个数不是本讲新造的,它就是专题二整整一节在立的那条屋脊线。
⭐ (b) 最容易选反,而选反的人通常是把「快」和「门槛高」搞混了: 片上更快,所以分母变大 —— 同一个分子除以更大的分母,商只会更小。 越靠近计算,这条线越低。 门槛低意味着:同一个算子挪到片上以后,更容易变成算力受限。
⛔ 而 (b) 为什么只给量级、不给数 —— 这才是这道题最想教的一件事:
VMEM 的带宽官方没有公开。
而屋脊点乘以算力就等于带宽 —— 给出一个精确的屋脊点,
等于把那个没公开的数反推出来。所以我们到「几十这一档」为止,不往下猜。
⭐ 这正是专题二第 6 节那四句问法里的一句:
先问这个数的出处和口径,再用它。
而那四句不是拿来审别人材料的 —— 是先拿来审自己的。
2K = GPT-3 的 max_position_embeddings;1M 与 846 倍见本课模型表(128K、BF16、batch 1,两端都取 ≥100B 的模型)
⚠️ 512 倍(2K→1M)与 846 倍(KV 降幅)是两个口径,本图刻意分两行给出 —— 把它们相乘是把两把尺子当成一把
两张趋势图的点全部由 topic03_models.ROWS 现算,没有手写常数;绿线是「跑到当月为止的最好成绩」,不是拟合曲线
右图只收 ≥100B 的模型 —— 跟「846 倍」同一个口径。不过滤的话包络线会被 Mistral 7B 的 512 MiB 拽到底,而那只是因为它本来就小
RWKV-7 的 KV = 0,对数轴上画不出来,没有进右图
⭐ 这一讲的前半程只有一个账本:KV cache。
在张量形状里找 S(KV 长度)—— 全图只有 K 和 V 两处「留得下来」的带它(注意力矩阵那一处也带 S,但它算完就扔,不进 KV cache),
那就是唯一需要跨 token 留下来的东西。
三个旋钮是同一个账本的三个面:① 每份多大 ② 每步读多少 ③ 干脆别让它变长。
看到任何一个新名词,先问它在拧哪一面。
骨架之后,先看一眼全景与全部证据:这些名词是什么时候、按什么顺序冒出来的, 以及今天各家实际上是怎么配的、每一家的 KV cache 到底多大。
📌 这张表的取数规则(⭐ 2026-09-13 补 —— 由表长出来的结论全靠这条规则撑着)
config.json)。max_position_embeddings。⚠️ 它是容量不是能力 —— 声明 10M 不等于 10M 上都好用。⚠️ 凡是上下文 < 128K 的行,那一格的 KV 是反事实值(把它的形状放到 128K 上算)—— 包括「846 倍」的分子 GPT-3,它的实际上下文只有 2K。这个倍数是用来看量级的,不是用来引用的。
⭐ 点表头可以排序—— 时间 / 厂商 / 便宜层占比 / 上下文 / KV 大小,再点一次反向。默认按时间。两种模式下排序都作用在全部 44 行上。
| 时间 | 模型 按厂商 | 一个循环 一格=一层 | 上下文 声明值 | KV cache@128K | 备注 |
|---|---|---|---|---|---|
| 2020-05 | GPT-3 175B 稠密 · 96 层 | MHA —— 每一层都是这个 | 2K | 576 GiB(10 条才画得下) | 基线:KV 按头数线性长,没有任何省法起点。576 GiB 这把尺,后面所有省下来的都拿它比 |
| 2022-04 | PaLM 540B 稠密 · 118 层 | MQA —— 每一层都是这个 | 2K | 15 GiB | 48 头共用 1 组 KV —— 第一次大规模砍 KVMQA —— 第一次把 KV 砍到只剩 1 组 |
| 2023-02 | Llama 1 65B 稠密 · 80 层 | MHA —— 每一层都是这个 | 2K | 320 GiB(6 条才画得下) | 一代还是纯 MHA,下一代才上 GQA |
| 2023-07 | Llama 2 70B 稠密 · 80 层 | GQA —— 每一层都是这个 | 4K | 40 GiB | MQA 砍太狠掉质量,GQA 是折中(arXiv 2305.13245)GQA —— 砍到 1 组太狠,折中版成了此后十年的默认 |
| 2023-09 | Mistral 7B 7B 稠密 · 32 层 | SWA —— 每一层都是这个 | 32K | 512 MiB | ⭐ SWA 进主流的第一枪,窗口 4096SWA —— 「不看全部」这个想法的起点,窗口 4096 |
| 2024-03 | Jamba 52B/12B · 32 层 | MambaMambaMambaMambaMambaMambaMambaGQA | 256K | 2.0 GiB | ⭐⭐ 层间混合的开源起点,比 MiniMax-01 早十个月层间混合的开源起点,7:1 —— 比 MiniMax-01 早十个月 |
| 2024-05 | DeepSeek-V2 236B/21B · 60 层 | MLA —— 每一层都是这个 | 128K | 8.4 GiB | 低秩压缩。KV 降 93.3%=只剩 2.25 组 GQAMLA 首发。换了个思路:不砍头数,改存压缩过的隐向量 |
| 2024-06 | Gemma 2 27B 27B 稠密 · 46 层 | SWAFULL | 8K | 24 GiB | ⭐ 谷歌开始交替:1:1,窗口 4096 |
| 2024-07 | Llama 3.1 405B 稠密 · 126 层 | GQA —— 每一层都是这个 | 128K | 63 GiB(2 条才画得下) | ⛔ 不上花招硬推 128K 的代价:比 Llama 2 还多 |
| 2024-11 | 混元 Hunyuan-Large 389B/52B · 64 层 | GQA —— 每一层都是这个 | 128K | 10 GiB | ⭐ CLA:每 2 层共享一份 KV —— 旋钮①的第三招 |
| 2024-12 | DeepSeek-V3 671B/37B · 61 层 | MLA —— 每一层都是这个 | 160K | 8.6 GiB | ⭐ 专题一的锚点。跟 V2 只差 1 层,KV 几乎相同跟 V2 同机制,但参数大 2.8 倍而 KV 只差 1.7% —— KV 脱钩的证据 |
| 2025-01 | MiniMax-01 456B/45.9B · 80 层 | LTNLTNLTNLTNLTNLTNLTNGQA | 4M | 5.0 GiB | ⭐ 线性首次上旗舰。训练 1M、外推 4M(config 的 10M 是容量)线性第一次上到几百 B 的规模,同样是 7:1 |
| 2025-03 | Gemma 3 27B 27B 稠密 · 62 层 | SWASWASWASWASWAFULL | 128K | 10 GiB | ⭐⭐ 5:1、窗口 1024 —— 小米那个 5:1 不是首创滑窗混合定型:5 层窗口配 1 层全局,窗口反而收到 1024 |
| 2025-03 | RWKV-7 Goose 0.19B–2.9B · 纯 RNN | RWKV —— 每一层都是这个 | 无限(理论) | 0(无 KV) | ⭐ 全表唯一 KV 为零:常数内存、常数单 token 时间另一头的极端:纯 RNN,KV cache literally 是 0 |
| 2025-04 | Llama 4 Scout 109B/17B · 48 层 | SWASWASWAFULL | 10M | 7.1 GiB | 块状局部 8192 + NoPE 全局。⚠️ 声称 10M,又一个报容量的 |
| 2025-04 | Qwen3-235B-A22B 235B/22B · 94 层 | GQA —— 每一层都是这个 | 40K | 24 GiB | ⭐ 千问转线性之前的那一代:纯 GQA-4 |
| 2025-07 | Kimi K2 1T/32B · 61 层 | MLA —— 每一层都是这个 | 128K | 8.6 GiB | ⭐ Kimi 上 KDA 之前:纯 MLA,架构名就是 DeepseekV3 |
| 2025-07 | GLM-4.5 355B/32B · 92 层 | GQA —— 每一层都是这个 | 128K | 46 GiB | ⭐⭐ 智谱上 DSA 之前:46 GiB → GLM-5 的 11,降 4 倍 |
| 2025-08 | gpt-oss-120b 117B/5.1B · 36 层 | SWAFULL | 128K | 4.5 GiB | ⭐ OpenAI 首个开放权重:1:1 交替、窗口 128 + sink |
| 2025-09 | DeepSeek-V3.2-Exp 671B/37B · 61 层 | DSA —— 每一层都是这个 | 160K | 8.6 GiB | ⭐ 稀疏的起点:V3 + Lightning Indexer。⛔ KV 跟 V3 一样DSA —— 层内稀疏起点:不是少几层,是每层只挑一部分 token 看 |
| 2025-09 | Qwen3-Next 80B/3B · 48 层 | GDNGDNGDNgAT | 256K | 3.0 GiB | 36 线性 + 12 全注意力(GQA-2,头维 256)GDN —— Mamba 那一支的直系后代第一次进主流大模型 |
| 2025-10 | Ling-1T(Ling 2.0) 1T/50B · 80 层 | GQA —— 每一层都是这个 | 32K | 40 GiB | ⭐⭐ Ling 2.6 就是从它改造的:40 GiB → 1.4 GiB |
| 2025-10 | MiniMax M2 230B/10B · 62 层 | GQA —— 每一层都是这个 | 192K | 31 GiB | ⛔ 「退回全注意力」≠ 什么都没做:GQA-8 + partial RoPE反例:退回全注意力。证明「全注意力」说的是旋钮③,不是① |
| 2025-10 | Kimi Linear 48B/3B · 27 层 | KDAKDAKDAMLA | 1M | 1008 MiB | 20 KDA + 7 MLA(末层强制 full)。已用 NoPEKDA —— 线性的新一代,配 NoPE 的 MLA |
| 2025-12 | DeepSeek-V3.2 671B/37B · 61 层 | DSA —— 每一层都是这个 | 160K | 8.6 GiB | Exp 转正。index_topk 512 → 2048,KV 与 V3 一样Exp 转正:稀疏从实验走进生产,top-k 512 → 2048 |
| 2025-12 | Mistral Large 3 675B/41B · 61 层 | MLA —— 每一层都是这个 | 288K | 8.6 GiB | ⭐ MLA 超参跟 V3 逐字段一样;参数读自 params.jsonMLA 扩散到了西方:欧洲最大开源旗舰逐字段照抄 V3 的 MLA 超参 |
| 2026-01 | 小米 MiMo-V2-Flash 309B/15B · 48 层 | SWASWASWASWASWAFULL | 256K | 5.0 GiB | 窗口 128。卡上自称 KV 省近 6×,48÷8 正好对上 |
| 2026-02 | GLM-5 744B/40B · 78 层 | DSA —— 每一层都是这个 | 198K | 11 GiB | MLA + DSA。GLM-5.1 同架构,只有后训练不同 |
| 2026-02 | MiniMax M2.5 230B/10B · 62 层 | GQA —— 每一层都是这个 | 192K | 31 GiB | ⚠️ 架构与 M2 逐字段相同,稀疏要等 M3 |
| 2026-03 | Qwen3.5 397B/17B · 60 层 | GDNGDNGDNgAT | 256K | 3.8 GiB | 45 线性 + 15 全(config: full_attention_interval 4)千问把混合注意力从旁支 Qwen3-Next 收进了主线 |
| 2026-04 | 小米 MiMo-V2.5-Pro 1.02T/42B · 70 层 | SWASWASWASWASWASWAFULL | 1M | 6.3 GiB | 60 SWA + 10 全,窗口 128 —— 1M 那档最省的 |
| 2026-04 | DeepSeek-V4-Pro 1.6T/49B · 61 层 | HCAHCACSAHCACSA | 1M | 999 MiB | ⛔ 跟 Flash 不同:前两层是 HCA。1.6T 而 KV 不到 1 GiB |
| 2026-04 | Ling 2.6-1T 1T/63B · 80 层 | LTNLTNLTNLTNLTNLTNLTNMLA | 256K | 1.4 GiB | ⛔ 不是 KDA;思考版 Ring-2.6-1T 架构逐字段相同 |
| 2026-04 | Gemma 4 31B 31B 稠密 · 60 层 | SWASWASWASWASWAFULL | 256K | 11 GiB | ⭐ 全局层 K 维加倍 + K=V 共享,窗口 1024旋钮①又出新招:全局层 K 维加倍再让 K=V 共享一份 |
| 2026-05 | DeepSeek-V4-Flash 284B/13B · 43 层 | SWASWACSAHCACSAHCA | 1M | 697 MiB | ⭐ 2 层 SWA 引导,CSA/HCA 交替。MLA 换成 shared-KV MQA终点。697 MiB,比 GPT-3 小 846 倍,而且 MLA 被整个换掉了 |
| 2026-06 | GLM-5.2 744B/40B · 78 层 | DSA —— 每一层都是这个 | 1M | 11 GiB | ⭐ +IndexShare:四层共用一个 indexer。198K → 1M 靠这步IndexShare —— 稀疏的第二阶段:索引本身变成了新的开销 |
| 2026-06 | MiniMax M3 428B/23B · 60 层 | MSA —— 每一层都是这个 | 1M | 15 GiB | ⭐⭐ GQA-4 + 稀疏。KV 比走 MLA 的 GLM-5.2 还大 |
| 2026-07 | Kimi K3 2.8T/104B · 93 层 | KDAKDAKDAgMLA | 1M | 3.4 GiB | 69 KDA + 24 Gated MLA(末层 92、93 连着两层 full) |
| 2026-07 | 混元 Hy3 295B/21B · 80 层 | GQA —— 每一层都是这个 | 256K | 40 GiB | ⛔ 80 层全 GQA-8 —— 线性一层都没上 |
| 2026-07 | Ling-3.0-flash 124B/5.1B · 42 层 | KDAKDAKDAKDAKDAgMLA | 256K | 1008 MiB | 跟 2.6 换了一支。同代 tiny 用 3:1,它用 5:1;3.0 无 1T |
| 2026-08 | GLM-5.3 744B/40B · 78 层 | DSA —— 每一层都是这个 | 1M | 11 GiB | ⚠️ 跟 5.2 同一个 base,纯后训练,架构没动 |
| 2026-08 | 混元 Hy4-preview 770B/49B · 78 层 | gDSA —— 每一层都是这个 | 1M | 11 GiB | 78 层全稀疏 + IndexCache(每 4 层 1 层算索引)跳过线性那一支,从纯 GQA 直接跳进全层稀疏 |
| 2026-08 | ⭐ GLM-5.3-Flash 320B/18B · 45 层 | KDAKDAKDADSA | 1M | 1.5 GiB | 34 KDA + 11 稀疏 MLA —— GLM 首次线性+稀疏同锅唯一一个把②和③同锅:34 层 KDA + 11 层稀疏 MLA |
| 2026-08 | Qwen3.8-Flash-Next 125B/6B · 48 层 | GDNGDNGDNgAT | 256K | 3.0 GiB | ⭐ Qwen4 架构预览;新东西在 51B 的 n-gram 嵌入表 |
⭐ 这张表的一句话落点:同一个 128K 长度, 从 GPT-3 的 576 GiB 到 DeepSeek-V4-Flash 的 697 MiB,六年 846 倍 —— 而这不是一个旋钮拧出来的,三个旋钮各贡献了一段。
⭐ 这张表一眼能看出八件事 (以下统计恒按全部 44 行算,切到 Highlight 也不变 —— 不然「有几家怎么样」这种话会跟着显示模式变,那就不是结论了)
⭐ 这一整块最该带走的一句:MiniMax 一家、三代模型, 把旋钮③和②各试了一遍,中间还退回过基线(线性 → 退回全注意力 → 稀疏),而且每次转向都公开写了理由 —— 「三个旋钮」这个框架不是我们归纳出来的,是有人真的一个一个试过去了。
先给一个反直觉的事实:你现在用的每一个大模型,在往外吐每一个字的时候,
都退回成了 1990 年那条链的形状。
Transformer 赢在训练能并行。可生成的时候,它一个 token 一个 token 地走,
每走一步都要把全部权重从显存里搬一遍 —— 这跟 RNN 一模一样。
⛔ 它没有治好 RNN 的病,它只是把病从训练挪到了推理;而且挪过去之后更重。
⭐ 这一节跟封面那 512 倍是什么关系?—— 封面问的是「能记多长」, 这一节问的是「它记东西的形状是什么」。 两者是同一件事的两面:正因为那个形状会一路变长,「记得更长」才会贵得离谱。 要涨 512 倍,就得先看清要涨的是什么。
📌 把名字拆开:RNN = Recurrent Neural Network = 循环神经网络。
R 指的是状态回到自己输入端的那条边(recurrent connection)。 词源是拉丁 recurrere = re(回)+ currere(跑)。
⭐ 这个词是从解剖学借的,不是数学造的 —— 解剖学里它一直用来描述环状结构:Cajal 1901 在小脑皮层观察到 "recurrent semicircles",Lorente de Nó 1933 用高尔基染色发现 "recurrent, reciprocal connections"。在那之前神经系统被当成纯前馈的, 1940 年代才有人正经提出脑内存在反馈。 而 §七 末尾 Albert Gu 那句「像一个大脑」,正好把这个词还回去了。
⛔ 中文误译提醒:「递归神经网络」指的是 Recursive Neural Network(沿语法树递归),是另一类模型。 读中文文献时这两个词经常被混用,看到「递归」先确认原文是哪一个。
所以这一节不是背景介绍,是本专题的舞台说明。四个问题,四张图。
序列有先后,所以得有个东西把历史带下去。RNN 的答案是带一个固定大小的状态向量 h
—— 全部设计就这一句。
Elman 1990《Finding Structure in Time》—— context units「copied … on a one-for-one basis, with fixed weight of 1.0」;LSTM: Hochreiter & Schmidhuber 1997;GRU: Cho et al. 2014
⛔ 先别去比总计算量。O(n·d²) 和 O(n²·d) 谁大,取决于 n 和 d 谁大;
Vaswani 原文说的是 n < d 时自注意力更快,而那正是当年的常态。
固定不变的是另一列 —— 串行步数。
| 层的类型 | 每层计算量 | 串行步数 | 两个位置之间的最长路径 |
|---|---|---|---|
| 自注意力 | O(n² · d) | O(1) | O(1) |
| 循环(RNN) | O(n · d²) | O(n) | O(n) |
| 卷积 | O(k · n · d²) | O(1) | O(log_k n) |
这张表是 Transformer 作者自己算的(arXiv 1706.03762 表 1)。 中间那一列就是全部答案。
NVIDIA《Recurrent Layers User's Guide》—— 「a GEMM with one dimension of one」、「can combine these GEMMs over the minibatch size, but not over different sequence steps」;Vaswani et al. 2017 (arXiv 1706.03762) 引言
⛔ 两头堵死:batch 是它唯一的算术强度来源, 而 Vaswani 引言那句原话说的正是另一头 —— 「memory constraints limit batching across examples」:序列一长,显存就不让你把 batch 开大。
📌 两个词先说清,后面一直要用:把整段输入一次算完叫 prefill(预填充);之后一个一个往外吐叫 decode(解码)。 ⭐ 这一讲后面有三处结论在这两个阶段是相反的 —— 看到一个「省了多少」,先问它说的是哪个阶段。
先把那三处列出来,读到时你会认出它们 —— (这一栏是路标,现在不用懂,读到那儿回头看一眼就行)
| 旋钮 | prefill 这边 | decode 那边 | 在哪一节 |
|---|---|---|---|
| ① MLA | ⛔ 压缩不生效:训练与 prefill 的前向要把 KV 解压出来算 | 省得最狠:只读那 576 维 | §五 |
| ② 稀疏 | ⛔ 可能一点不省:要先算出注意力图才知道挑谁 —— NSA 论文 §2 的第一个坑说的就是这个 | 每步只读 k 条 | §6.3b |
| ③ 线性 | 要靠分块才榨得出并行度(块内并行、块间串行) | 就是一条纯递推,每步只碰那块固定大小的板子 | §7.4 |
⛔ 所以「省了 N 倍」这句话,不带阶段就是半句话。 §九那张代价表专门有一列「⭐ 省在哪个阶段」,就是为了逼出这一问。
⛔ ⛔ Ⓒ 那一行的算术强度,必须拆成两半看(接上图)
⭐ 拿 GQA-8 算就是 8 FLOP/byte,对着上一格那条 313 的屋脊线差约 39 倍 —— 而且攒 batch 一点都救不了。这正是后面三个旋钮要动它的原因。
⛔ 口径(这条必须钉死,否则跟上面那条 313 不是一套尺子):读的字节 = K 和 V 各 S·d 个元素 × 2 B = 4·S·d;算的 FLOPs = G 个 query 头 × (QKᵀ + AV) 各 S·d 次乘加 × 2 = 4·G·S·d。两者一除,强度就是 G。⭐ 拿 MHA(G=1)自检:1 FLOP/byte —— 正好对上业界那句「decode 的注意力强度约等于 1」。
Ⓐ 和 Ⓒ 都是「一步一个,每步搬一遍权重」。
唯一的区别是每步还得额外搬什么:
RNN 搬的是一个固定大小的状态;Transformer 搬的是一路线性变长的 KV cache
—— 128K 时它能比权重本身还大(下一节算给你看)。
⭐ 后面三个旋钮拧的全是同一件事:让这一行每步要搬的东西变小。
Bengio, Simard, Frasconi 1994(梯度消失);Hochreiter & Schmidhuber 1997(LSTM);Bahdanau et al. 2014 (arXiv 1409.0473)「a fixed-length vector is a bottleneck」
Vaswani et al. 2017 (arXiv 1706.03762) 表 1;Martin & Cundy 2018 (arXiv 1709.04057):非线性依赖挡住并行,只有线性依赖能用 parallel scan 扫
⚠️ 一个常见的张冠李戴:「固定长度向量是瓶颈」不是 Sutskever 说的。 他那篇只是描述做法(映射到「a vector of a fixed dimensionality」); 「这是个瓶颈」是 Bahdanau 那篇的原话(arXiv 1409.0473: 「we conjecture that the use of a fixed-length vector is a bottleneck」)。 别把后人的批评安到原作者头上。
这一节要留下的只有一句: 解码那一行,从 1990 年到今天,形状没变过。
变的只是每一步额外要搬的那个东西:RNN 搬一个固定大小的状态, Transformer 搬一路变长的 KV cache。 后面三个旋钮拧的全是它 —— 所以这一节不是背景介绍, 是本专题的舞台说明。
RNN 疼在三处:算不快、记不住、装不下。注意力最早只解决了第三条,而且是作为 RNN 的一个附件出现的。于是 2017 年有人问:既然这个附件这么好使,能不能把 RNN 整个扔掉,只留附件?
这一节要回答的就一件事:KV cache 是从哪儿来的 —— §零那张「解码时又变回 RNN」图上 Ⓒ 那一行的病根,在这一节。
① Bahdanau 等 arXiv 1409.0473
② 点积打分出自 Luong 等 arXiv 1508.04025(2015),该文同时给了 dot / general / concat 三种打分
⛔ Vaswani 等 arXiv 1706.03762 §3.2.1 加的是 1/√d_k 那个缩放,不是点积本身 —— 原话是「identical to our algorithm, except for the scaling factor」;③ 同文 §3.2.2(多头补偿分辨率)
⚠️ 画面里的句子、方框数量都是示意,不对应任何一次真实实验
⭐ 读论文的姿势: 看一个机制,先找它在修上一版的哪一个具体毛病。 「有人灵光一闪设计了注意力」什么也教不了你; 「一个固定长度向量装不下长句子,于是让解码器自己去源句里软性地找」 —— 这个你下次能照着用。
Vaswani et al. 2017 (arXiv 1706.03762) 表 1:自注意力 串行步数 O(1)/最长路径 O(1);循环层两项都是 O(n)。因果遮罩见同一篇论文 §3.2.3:把非法连接「setting to −∞」
⛔ 被换掉的不只是那根箭头 —— 状态也没了。
RNN 那个固定大小的 h,被换成了「把所有历史原封不动留着」。
这一句后面每一节都要用到。
📌 先认一个后面要用两次的单位:困惑度(perplexity)。 它衡量「模型对下一个词有多没把握」—— 越低越好。 粗略地讲,困惑度 30 就是「大约在 30 个词之间犹豫」。
⚠️ 它只在同一个基准、同一份数据上可比 —— 所以后面 §五那张表的 30 上下,和 §六那张表的 5 上下, 不能互相比,只能各自看组内差多少。
「query / key / value」与「输出是 value 的加权和」是 Vaswani 2017 论文 §3.2 的原话,不是本课编的比喻;图中那组权重(5/30/8/45/7/5 %)是示意值,不是实测
⚠️ 本图只画注意力 —— 一层 Transformer 里还有 FFN、残差、归一化,它们不在本专题这条轴上(本专题的账本只有 KV cache)
⚠️ 「query / key / value」不是我们编的比喻,是论文自己的措辞:
「mapping a query and a set of key-value pairs to an output … the output is a
weighted sum of the values」。
那个 √d_k 的推导链在图里的蓝框(论文脚注 4,两行)。
说白了:它就是一个标准差。
除掉它只做一件事 —— 把打分的方差拉回 1。
三条性质由 softmax 的定义直接给出;柱子的数由本脚本当场算并断言
「索引器用 ReLU,for throughput consideration」出自 DeepSeek-V3.2-Exp 技术报告 §2.1
⚠️ 图书馆卡片是类比,不是论文原话 —— 论文只给了 query / key / value 三个名字
⭐ 这条判据的通用形式: 看到一个设计的副作用,先回头看它的优点是靠哪条性质换来的 —— 十有八九是同一条。 softmax 那条「一行加起来等于 1」,在这里让加权平均的尺度稳定(优点), 到 §六 就是 attention sink 的成因(副作用)。 而 softmax-off-by-one 这个补丁,本质就是把这一条放松掉。
Vaswani et al. 2017 论文 §3.2.2:「jointly attend to information from different representation subspaces」、「With a single attention head, averaging inhibits this」;h = 8,d_k = d_v = d_model/h = 64
Shazeer 2019 (arXiv 1911.02150)《Fast Transformer Decoding: One Write-Head is All You Need》摘要 —— 本课模型表第二行 PaLM 用的就是它
📌 先回答一个很自然的问题:存不下,为什么不能每步重算?
因为重算的代价不是「再算一遍」,是每一步都把整段历史重算一遍。
生成第 n 个 token 时,如果不存 K/V,就得拿前面 n−1 个 token
重新过一遍投影 —— 而下一步又要重来。
整段生成的总开销从正比于 N² 变成正比于 N³。
⭐ 所以这不是「省一点」的优化,是能不能用的分界。 KV cache 是拿显存换时间,而这一讲全部的账, 都是在算这笔交换到底有多贵。
上一段说多头买到的是分辨率:一个头只管 128 维。 那问题就很自然了 —— 128 个数,怎么可能记得住一整篇文章里上万个词的区别?
⭐ 图里所有数字都是本课当场算的(numpy;随机单位向量,d=128;夹角量的是最挤的那一对),脚本里带断言 —— 不是引来的
⚠️ 但想法不是本课的:「差不多正交能装指数多个」是 Johnson–Lindenstrauss 引理的推论;「叠加(superposition)」这套解释出自 Anthropic Elhage 等《Toy Models of Superposition》(arXiv 2209.10652)
⚠️ 串扰那格的 logit = √d·cos 是推导来的不是测来的:q、k 各分量 O(1) 时内积 ≈ d·cos,注意力再除以 √d,剩下 √d·cos。真实模型里 q、k 的模长会被训练调整,所以这一格读趋势,别读绝对值
★ 答案分两层,而第二层才是有用的那层
第一层是硬上限:要求「谁跟谁都不沾边」(两两垂直), 128 维里最多只能立 128 根 杆子 —— 第 129 根一定能被前面那些拼出来。 如果模型真按这个标准办事,一个头一辈子只认得 128 个概念。
第二层是模型实际在做的:把标准放宽成「差不多不沾边」。 图②是当场算的 —— 128 维里随便丢 一万根,最挤的那一对仍然差着 61° 才会重合。根数涨了 78 倍,最挤的那一对才挤了 9°。
⭐ 所以容量不是 128,是几万。这笔便宜不是白占的 —— 「差不多」的意思就是「有点像」,而有点像会漏票。
漏票有多少?图③把它算成了两个数,这两个数放在一起才是重点:
0.0117%。
完全可以忽略。⚠️ 这就是「记混」,它不是模型偷懒,是 softmax 的直接后果
生活里的同一件事:一个人在台下小声嘀咕,你听不见; 一万个人同时小声嘀咕,台上的人就喊不过了。
⛔ 而长上下文是两头夹击:嘀咕的人变多(图③那根轴), 而且最吵的那一个还离得更近了(图②那根轴,夹角在变小)。 两根轴同时往坏的方向走 —— 这就是为什么上下文一长,模型就开始「串台」。
📌 这一小节后面要用三次,先在这里打个招呼
① 为什么每个头是 128 维,不是 8 维 —— 容量不是线性的。 8 维里丢 1000 根,最挤的一对只剩 12°,那两根基本就是同一根。
② 为什么 MLA 敢压到 512 维(§五)—— 因为它要的从来不是「完全正交」。 「赌的那段」赌的就是这件事:压完之后大家还够不像。
③ 为什么稀疏注意力不只是省钱(§六)—— 如果记混是干扰项太多造成的, 那少看几个反而可能更准。降噪是它的正面效果,不是省钱的副作用。
前面讲的是想法。标上形状之后,这笔账可以用眼睛读 —— 在形状里找那个会越变越长的维度就行。
S(KV 长度):只有两处「留得下来」的带它,K 和 V 的输出。那就是唯一需要跨 token 留下来的东西 —— KV cache。三个旋钮各是一种跟它较劲的方式。图式借自 How to Scale Your Model,本图为重画。★ 先只看一件事:图上什么东西需要留到下一个 token
在形状里找 S(KV 长度)—— 只有两处「留得下来」的带它:
K 的输出 BSKH 和 V 的输出 BSKH。
S 是唯一会随对话越变越长的那一维。
⚠️ 图上带 S 的形状不止两处(K/V 的进出、两个 matmul 的操作数都带)——
「带 S」和「要跨 token 留下来」是两件事:
中间那些带 S 的算完就扔(FlashAttention 连物化都不物化)。
→ 唯一要跨 token 留下来的,是 K 和 V 的输出。
⭐⭐ 给它起个名字吧 —— 这一讲的主角就是这个 S。
后面每一节,你其实只要盯着它一件事 —— 这一招, 是让 S 前面的系数变小(旋钮①)、让每步读到的 S 变少(旋钮②), 还是干脆让 S 从形状里消失(旋钮③)?
⭐ 这三问就是全课的骨架。 名词有几十个,但它们全都只在回答这三问中的一个。
⭐⭐ 整个专题三,就是在跟这一份 KV cache 较劲:
① 让每一份更小(改产生 K/V 的那两条支路)·
② KV 照存但每步只读一部分(改 mask 那一格)·
③ 换成一个固定大小的状态(换一套数学,S 直接消失)。
取序列 128K、BF16、batch 1,把带 S 的那两处换成实际占多少:
| 算到哪一步 | 多大 | 说明 |
|---|---|---|
| 每层 K + V | 8 GiB | 2 × S × K头 × H × 2 B(MHA,128 头) |
| × 61 层 | 488 GiB | 一个用户、一段输入 |
| 对照:整个模型的权重 | 625 GiB | 671B,原生 FP8,1 B/参数 |
| ⭐ 于是 | 78% | 单用户就占 78%;两个并发,KV 就超过权重本身 |
⚠️ 这一栏在训练里是空的。 K/V 是算完就扔的激活,不跨 step 留 —— 全讲所有倍数都只对推理成立。
⭐⭐ 权重是所有人共享的一份,KV cache 是每人一份 —— 所以它决定的不是「装不装得下」,是能同时服务多少人。
这一节要留下的只有一句: KV cache 就是在这里出生的。
把循环换成一张 n×n 的表,换来了训练能并行; 代价是解码时每一步都要把之前所有 token 的 K 和 V 重新读一遍 —— 而这份东西会随对话一直长下去。
⭐ 下一节把这笔账算成具体的字节数,再看它为什么必须省。
Transformer 是 2017 年的东西。KV cache 会涨这件事, 2019 年 MQA 那篇论文的摘要里就写着。 那为什么全行业真正动手改注意力,是 2024 年以后?
装置偷自知乎 姜富春《deepseek 技术解读(1)-彻底理解 MLA》(zhuanlan.zhihu.com/p/16730036197)—— 他用 Qwen-72B 做的这个对照,本图换成本讲一直在用的 V3 口径重算
⚠️ 数全部来自本讲前面已核过的三个:权重 625 GiB(671B 原生 FP8)·MLA 的 KV 8.58 GiB(61 层 · 128K · bf16 · 一个用户)·反事实 MHA 488 GiB(同口径)。488 ÷ 8.58 = 56.9,正好对上 MLA 那个 4.571 × 12.44 —— 两条路算出同一个数,互为交叉验证
📌 「要几张卡」按 TPU v7 每 device 94.74 GiB 算(这个数本课的 AOT 工具链里核过:编译器自己报的 95.38G − 94.74G = 656.93M,只有按 1024 才成立)。⚠️ 换别的硬件只是这两根柱子的刻度变,结论不变;而且这笔账只算了装得下装不下,没算带宽
★ 因为变的不是技术,是工作负载
同一个模型(V3),权重那一段一个字节没动,都是 625 GiB。 只把两个东西拧了一下 —— 上下文从 4K 到 128K,同时服务的人从 1 个到 64 个:
KV cache 从 0.27 GiB 涨到 549 GiB,
占总量从 0.04% 变成 46.8%。主角换人了。
⭐⭐ 而这两个数是相乘的,而且乘的是同一项 —— 这就是为什么它不是慢慢变严重,而是突然变成了首要问题。
这两条必须分开讲。它们指向同一批技术,但出发点完全不同 —— 混在一起讲,就成了名词罗列。
📐 488 GiB 那条乘法链(原来画在图上,2026-09-15 折到这里):每 token 每层 2 × 128 × 128 = 32,768 个数 → × 61 层 = 1,998,848 个数 → × 2 字节 = 3.81 MiB/token → × 131,072 token = 488 GiB
四个对照值均按公式当场算出(脚本里带断言);口径沿用 V3 论文比较表(K、V 都按 d_h=128)—— V3 真实的 K 每头是 128+64=192 维,严格算这个基线还会更大
⚠️ 右栏三个观察是现象,不是本课实测;它们各自对应后面的一支方案(稀疏 → §六、分层 → §六、压缩 → §五)
① 出自 Lin & Tegmark《Criticality in Formal Languages and Statistical Physics》(Entropy 2017, arXiv 1606.06737)
⚠️ arXiv v1 的旧题名是《Critical Behavior in Physics and Probabilistic Formal Languages》,两者是同一篇 —— 标了正式出处就用正式题名
原文口径:互信息在任何概率正则文法下指数衰减,而上下文无关文法下可以是幂律
② 出自 L2M(arXiv 2503.04725, ICML 2025):双部互信息幂律 scaling、「状态维度必须至少同阶增长」的定理、以及 Transformer 自动满足的那段分析
⚠️ 「书 / 笔记本」是本课的比喻;③ 为本课推导,非论文结论
⭐ 还要补一句改口: 「远处可以少看」不等于「远处不重要」。 两半之间的互信息是随长度增长的 —— 只是增长得慢。 所以正确的说法是:Transformer 那条线性增长的 KV 是「供给过量」, 而这条幂律是「实际需求」; 前两个旋钮做的事,是在不掉到需求线以下的前提下把过量的常数压小。
⛔ 图上那 488 GiB 是「假如 V3 用 MHA」的反事实值, 不是 V3 的实测 —— V3 从第一天就是 MLA。 这门课自己的规矩:推出来的数必须带推导链和口径,图里两样都写了。
⭐ 还有一句图上画不出来、后面却要反复用: KV cache 不是显存里的一项开销,它直接决定你能同时服务多少人 —— 权重所有人共享一份,KV cache 每人一份。这一条到专题六 会变成 batch size 的硬上限。
📌 这一节会用到的几个硬件词: MXU=矩阵乘单元(TPU v7 上是 256×256)· VPU=向量单元(做加减、指数这类)· SRAM / VMEM=片上暂存,快但极小 · SM=GPU 上的一个计算单元 · warp=GPU 上 32 条线程一组 · gather=按下标从内存里东一个西一个地捞。
⭐ 它跟这一讲要讨论的东西不是一类。
三个旋钮改的是算什么(模型变了,通常要重训);
FlashAttention 一个字都不改数学,它改的是怎么算
—— 跟谁都能叠,而且必须叠。
📌 正因为它跟谁都能叠、叠上去效果都一样,它在这一讲里没有区分度:
它既不区分 MLA 和 GQA,也不区分稀疏和线性。所以本节只留这一句。
⭐ 它做的事一句话:让 softmax 前后那一步不落到显存, 只在片上一块一块地过。这也是为什么本讲从头到尾不讨论「那个中间矩阵有多大」 —— 它根本不占显存。这一讲的账本只有一个:KV cache。
朴素写法是三步:① 算 A = QKᵀ ② 对 A 做 softmax 得 P ③ 算 O = PV。
⚠️ 记号提醒:FlashAttention 原论文这一步写作 S = QKᵀ。
本课改用 A —— 因为这门课的形状记号里 S 已经被
KV 长度占住了(BSKH 里那个 S),而「在形状里找 S」是全讲重复最多的一条指令。
一个符号只能有一个意思。读论文时看到 S = QKᵀ,那是同一个东西。
三步之间,那个 L×L 的 S 每次都要落一趟 HBM: 写 S、读 S、写 P、读 P —— 三个步骤,四趟。
而这个 S 有多大:128K 上下文、单个头就是 131072² × 2 B = 32 GiB,一层 128 个头。
FlashAttention 把三步融成一个 kernel:S 分块在片上算完, softmax 用在线归约边走边更新最大值和求和项,S 整项消失。
⭐ 所以三笔账的形状是所有算子融合共有的: FLOPs 一分不省,省的全在「中间产物不落地」这一行。
FlashAttention 论文那张图,画的是两层循环: HBM 里躺着 Q、K、V、O,SRAM 是旁边一个小方块; 外层循环搬一块进 SRAM,内层循环扫过另一边的所有块。 (原文 Figure 1 的 caption 把颜色也说死了:红箭头是外层、走 K 和 V, 蓝箭头是内层、走 Q。)
⚠️ 但那张图画的是第一版的顺序,而第二版把它换了过来 —— 这个「换」本身,比图更值得讲。
第一版:外层是 K/V,内层是 Q。
for j in K/V 的每一块: # 外层
把 K_j, V_j 搬进 SRAM
for i in Q 的每一块: # 内层
把 Q_i、O_i、m_i、ℓ_i 从 HBM 读进来
算这一格,更新 O_i、m_i、ℓ_i
再把 O_i、m_i、ℓ_i 写回 HBM
⛔ 看内层那三行:O_i 和那两个统计量每一轮外循环都要读进来、写回去一次。 为什么躲不掉?—— 因为下一块 K/V 还会碰到同一个 Q 块, 它的输出没算完,只能先存回去。
⭐ 这句话下面那张图的①画出来了:一趟外层循环碰到的是一列,
而一个输出要等齐的是一行 —— 两者只交于一格,所以这一趟凑不齐任何一个成品。
第二版:把两层对调。
for i in Q 的每一块: # 外层 ← 换成了 Q
把 Q_i 搬进 SRAM,O_i、m_i、ℓ_i 就地清零
for j in K/V 的每一块: # 内层
搬 K_j, V_j 进来,算,就地累加进 O_i
整个内层跑完,才把 O_i 写出去一次
⭐ 两个后果,都很硬:
O_i 和那两个统计量在整个内层循环里一直待在片上,一次都不落 HBM。⭐ 一句话记住它: 外循环放谁,谁的中间状态就不用来回搬。 而 attention 里「需要被累加到最后」的是 O,所以外循环必须放 Q。
⭐ 那到底省了多少?—— 这个问题值得认真数一遍,
因为只盯着 O 看,会得出一个错的量级。
📌 FA1 arXiv 2205.14135 §3.1 Algorithm 1(外层 K/V、内层 Q;O_i、ℓ_i、m_i 的写回在内层循环里,line 12–13);Figure 1 caption 逐字「In the outer loop (red arrows) ... loops through blocks of the K and V matrices」。
📌 FA2 arXiv 2307.08691 §3.1.1 Algorithm 1(外层 Q、内层 K/V,O_i 与 L_i 在内层结束后各写一次);宣布对调那句在论文 §3.2 “Parallelism”,并注明归功于 Phil Tillet 的 Triton 实现。warp 层全部出自 §3.3 与 Figure 3 —— ⚠️ FA1 论文里 “warp” 出现 0 次,那段是 FA2 的回溯。
⛔ 原词是 “split-K”,不是坊间常见的 “sliced-K”(两篇全文零次)。⚠️ 本图不引用任何加速比 —— FA2 论文里那几个百分比分属不同口径(attention 单算子 vs 端到端训练、不同硬件配置),混用会得出错的结论,本讲专门讲过这条纪律。
⚠️ 「O 不落 HBM」是真的,但它不是这次对调的主要收益。
按块级 HBM 读写数一遍(L = 8192、块 128,按 causal):
只看 O 是 4160 : 64,六十五倍;
四个张量一起数是 6368 : 4288,只差 1.49 倍。
差额去哪了 —— K/V 从外圈挪进了内圈,
每个 Q 块都要把它们重新走一遍,省下的又还回去了。
⭐⭐ 所以这次对调换来的不主要是带宽,是并行度:
FA1 只能按 batch × head 铺,FA2 多出「Q 块」一整维。
batch 小、头数少、上下文长的时候,前两维本来就铺不满几百个执行单元 ——
那正是 FA2 最吃香的场合。
⛔ 这个 1.49 是本课自己按两份 Algorithm 1 数出来的,不是论文报的数 —— FA2 全文没有 IO-complexity 定理,整个 IO 分析 defer 给了 FA1。 论文自己给的理由是两条:减少 non-matmul FLOP(§3.1)、 提高 occupancy 且 thread block 之间无需通信(§3.2)。
📌 三处容易记错,一并说清
这件事在块内部还发生了第二次 —— 而且这一次是官方原话 (上面那张图的 ③ 已经把它画出来了):
⚠️ 出处要说对:FA1 论文里「warp」出现 0 次。 下面这两段对 FA1 warp 切分的描述,全部出自 FA2 §3.3 的回溯,不是 FA1 自己写的。
split-K —— 坊间常见的「sliced-K」两篇论文里一次都没出现过)。 ⛔ 于是每个 warp 都得把中间结果写进共享内存、同步、再加起来 —— 这些读写拖慢了前向。QKᵀ,直接乘共享的那片 V 就得到自己那片输出 —— warp 之间完全不需要通信。⭐⭐ 两个尺度,同一条判据: 切「要被累加的那一维」就得合;切「各自独立出结果的那一维」就不用合。 attention 里前者是 K/V(它们在求和号里面),后者是 Q(每行输出各管各的)。
⚠️ 这条判据的用处远不止 attention —— 任何融合 kernel 在分工时,先问一句「我切的这一维在不在求和号里」。
softmax 要减最大值才数值稳定,而最大值要看完整行才知道。 —— 那 FlashAttention 一次只看一块,凭什么算得对?
在线归约的做法出自 Milakov & Gimelshein《Online normalizer calculation for softmax》(arXiv 1805.02867),FlashAttention(arXiv 2205.14135)把它用进了注意力
⭐ 图里五个打分和五个「内容」是本课随手编的示例数,但两条路的计算是脚本当场跑的,并且断言精确相等(差恒为 0)
⚠️ 「内容」这里简化成了一个数;真实的 V 是一个 d_h 维向量,重标定对整个向量同时做 —— 道理一样,画成一个数只是为了能核
★ 办法是:手里只攥三个数,来了更高分就把它们缩一下
每来一块,就更新一次 running max 和 running sum,
并把已经累好的那份加权和按比例重标定一次。
🏠 生活版:改到一半发现有人考得更高, 不用把前面的卷子重翻一遍 —— 把手里已经算出来的总分乘一个系数就行。
⭐ 它不是近似:图③里两条路算出来的差是 0,不是「误差很小」。 在线 softmax 是恒等变换,不掉点。
📌 顺手把「省的是什么」说清楚,这一条最容易说错
⛔ 不是省算力 —— 该做的乘加一次不少,重标定还额外多了一些。
⭐ 省的是片上暂存:传统要同时摆下整行 S 个数,
在线一次只摆一块。于是那张 S×S 的大表根本不用建。
⚠️ 这跟 §五 的账是两回事:那边省的是要 跨 token 留下来的 KV cache,这里省的是算一步时中途摊开的中间结果。 两个都叫「省显存」,省的却不是同一样东西。
「在线 softmax 只要存一个最大值和一个求和项」—— 这句话是对的, 而且它们确实很小:每行各一个标量。一个 bq = 512 的 Q 块, 两个加起来也就 4 KB 量级。
真正占地方的是 S = Q@Kᵀ 那一整块。 它的形状是 [bq, bkv] —— 512 × 512 的 fp32 就是 1 MB, 2048 × 2048 是 16 MB。比那两个 running 值大两三个数量级。
而且它不是存一次就完了:每来一个 KV 块就重新生成一整块。
所以卡住的是 S 的生命周期,不是那两个数。
一个 KV 块内,S 要连着走完四步才能扔: ① MXU 产出 → ② 沿着行求最大值 → ③ 减掉它再取指数 → ④ 喂给第二个矩阵乘。
这四步里它一直是活的。 ②③ 是向量单元的活,而 ① ④ 是矩阵单元的活 —— 矩阵单元想开始下一块,可它的输出还被 ②③ 占着。
⭐ 再加上流水线:要让下一块的矩阵乘和这一块的向量运算重叠, 就得同时留住不止一块 S。 于是「一块」变成「好几块」。
📌 ② 那一步在 TPU 上还额外贵一点:行方向的最大值是一次跨 lane 归约, 而 lane 正是硬件那 128 的方向。归约越慢,S 活得越久。
这条链在 3.6 会变成一个具体的数字。
块大小不是调着玩的旋钮,它由片上暂存的容量直接顶死。
GPU 侧:一个线程块最多拿 227 KiB 共享内存。 装三块 128×128 的 bf16 tile(Q/K/V)就是 3 × 128 × 128 × 2 B = 96 KiB, 再留双缓冲,基本到顶。
而块开不大,代价是 K/V 被重复读 —— 重读次数 ≈ 序列长度 ÷ Q 块大小。 所以那 227 KiB 不只是「装不下」,它通过块大小间接决定了 HBM 流量。
⚠️ 顺带拆掉一个常见误解:标准 FlashAttention 前向不需要跨块归并。 每个执行单元拿走一个 Q 块,自己走完整条 KV 循环,在线 softmax 在块内就闭合了。 需要合的只有两种:KV 也被切开时(长上下文解码那类做法, 要跨切片重新对齐最大值和求和项),以及反向(对 Q 的梯度要跨块累加)。 所以 GPU 的代价在「分」,不在「合」。
TPU 侧对应的是 Splash Attention。它的灶台看着大得多 (Ironwood 每个 TensorCore 64 MB VMEM,一颗 chip 两个核 = 128 MB), 但块反而不能随便开大。
JAX 里 Splash Attention 的默认块是 128 × 128, 而且源码里挂着一句 TODO:「以后按启发式选更好的参数」。
我们自己在 Hunyuan3-295B 上扫过这个参数(seq = 4096,v7 16 芯片 / 20 层 / pdbs 8, 完整数据见 tpu/Hunyuan3-295B-Pretraining/TUNING-v7.md):
| run | 块布局 | KV 方向切几块 | TFLOP/s/device | vs 基线 |
|---|---|---|---|---|
| B1 | 全 2048 | 2 块 | 223.6 | 基线 |
| S2 | 官方非均匀布局(含 sa_block_kv_compute=512) | 8 块 | 221.3 | −1.0% |
| S1 | 全 2048 + use_max_logit_estimate=30 | 2 块 | 228.4 | +2.1% |
再往上(块 4096,KV 方向只剩 1 块)撞 VMEM 墙。 源文件只记了「往上撞 VMEM 墙」这一句,没有留下 OOM 现场,所以这一档这里不给数。
228.4 那一行同时开了 use_max_logit_estimate=30 —— 那是另一个开关(用一个可证明的 logit 上界替掉 online softmax 的 running max)。 纯粹属于「块大小 = 2048」的成绩是 223.6,差的那 2.1% 得记在 max_logit 头上。
⭐ 一份消融表里同时变了两个旋钮时,「最好的那一行」和「你正在讲的那个旋钮」往往不是一回事。 引数之前先看清那一行还开了什么。
block/seq 的比例。第 ③ 格给出为什么必然如此 —— 片上工作集里一个 seq 都没有。📌 实测数据出自 `tpu/Hunyuan3-295B-Pretraining/TUNING-v7.md` 「消融实测」表(16 chip / 20 层 / pdbs 8 / seq 4096)与「四条可复用结论」①。
⛔ 该文件前后自相矛盾:第 634 行的「方法论教训 ①」说看比例,两千行后的「可复用结论 ①」用 seq 16384 的直接实验说与 seq 无关。后者晚一轮、有实验,以它为准。
⚠️ 面板① 的「并行度墙」是从 kernel 结构推的,源文件只实测了容量墙那一侧;1024 那一档没有可追溯的 splash 实测,故画成虚线。
三堵墙,方向各不相同:
S 块是 b×b,块翻倍它翻四倍;块开到 4096 时,光这一块就正好占满 64 MiB VMEM那个 512 不是我们瞎试的,是官方 tpu7x benchmark 里的值 —— 那份配置是给 max_target_length = 131072 调的。照抄它会反向优化,这一点没错; 但原因不是「比例变了」,而是 512 本身就不是这块硬件的甜点。
后来我们在同一个 kernel 上把 seq 从 4096 拉到 16384(长了 4 倍)—— 最优块还是 2048。它在前者身上是 seq/2,在后者身上是 seq/8: 两次的共同点是那个绝对值,不是比例。
⭐ 为什么必然如此,看上面那张图的第 ③ 格:片上工作集是 Q[b×d] + K[b×d] + V[b×d] + S[b×b],这四项里一个 seq 都没有。 seq 只决定你要绕几趟(seq ÷ b)。甜点由 VMEM 顶死,而 VMEM 不知道你的 seq 是多少。
所以:换序列长度不用重扫块大小,换硬件才要。
这一节此前的版本写的是「要看 block/seq 的比例」,并注明「尚未验证」。 方向反了,而且那次验证早就做完了。
出错的机制很具体:源文件在前面有一段「方法论教训」,是从一个数据点(照抄 512,−1.0%)反推出来的; 两千行之后的「可复用结论」里,后一轮用 seq 16384 的直接实验把它推翻了。 我们读到前一半就停了。
⭐ 判据:同一份长文档里,晚出现的结论可能推翻早出现的。 引一句「教训」之前,先搜一遍全文还有没有同主题的第二段 —— 尤其当那句话你打算加粗的时候。
⭐ 所以两边的墙不是「硬件 vs 调参」,是一面 vs 三面: GPU 那边容量一堵墙顶死,方向反倒清楚 —— 能开多大就开多大; TPU 那边最优往往不在最大处,得在三面之间找那个点。
📌 先把「效率」这个词定义一次(⭐ 2026-09-13 补 —— 原先这一段有四个百分比,一个定义都没有,而它结尾正好在教「引用百分比要写清出自哪个工具页」):
下文的效率 = XLA 记的该算子 FLOP ÷(该算子耗时 × per-device BF16 峰值),
峰值取 1153.5 TFLOP/s(v7 每芯片 2307 ÷ 2),数出自 XProf 的 op stats 页。
测量条件:Hunyuan3-295B · seq 4096 · v7 64 芯片 · 训练(含反向)。
⚠️ 它不是 MFU(MFU 的分子是模型级有效 FLOP,分母是整步耗时); 也不是 MXU 利用率(那是硬件计数器读出来的)。这三个数在同一段里都出现过,别混。
这是这一节最该带走的一段:FlashAttention 不是终点。 我们在 v7 上量到 splash attention 占 23% 的时间、效率只有 35.5%,全场最低。
① 记账口径:报出来的那个百分比,分子是虚高的。 XLA 给 splash 记的 FLOP 是不折 causal 的全量 4·b·s²·h·d。 所以 32.8–39.0% 这个区间是拿虚高的分子算出来的,真实执行效率比它更低。
⚠️ causal 跳过上三角是节省,不是又一道要乘上去的折扣 —— 它只造成记账错位。这两件事最容易混。
② 形状锁死 50%:MXU 是 256×256,而 head_dim = 128 只吃得下一半。
| matmul | 形状 | 浪费在哪 |
|---|---|---|
QKᵀ | [q_len, 128] @ [128, kv_len] | 收缩维只有 128 → 废一半 |
PV | [q_len, kv_len] @ [kv_len, 128] | 输出维只有 128 → 废一半 |
两个矩阵乘各撞一次,所以整个算子的 MXU 利用率封顶 50%。
(⭐ 为什么「输出维只有 128」也会浪费一半,
以及为什么这种浪费能救而收缩维那种救不了 ——
机制在 专题二 3.4。) Google 侧的结论是明确的:head_dim = 128 时 MXU 利用率无法超过 50%, 没有办法绕过。
③ 为什么连 50% 都到不了 —— 回到 3.3 那条链。 持有 Q@K 输出(也就是整块 S,不是那两个 running 值)的寄存器, 在最大值和减法完成前不能释放,于是不断堆积 → 寄存器压力 → spill 到 VMEM → MXU 停在等数据载回。 一句话:VPU 跟不上 MXU。
⭐ 这也解释了 3.5 那三堵墙里最反直觉的一堵为什么存在: 块开大 → S 那一块跟着变大 → 生命周期更长、更容易 spill。 「装得下」和「跑得快」在这里是两回事。
⭐ 三层叠起来的结论很硬: 形状锁死一半、寄存器压到 35%、记账口径还让它看着比实际好看。 这三层没有一层是配置能救的 —— 要么改 head_dim,要么改 kernel 的数据流。 (我们试过的那条出路是把矩阵乘全转置,见 TUNING-v7 的附录。)
「HBM 受限占多少」—— 一个工具页说 35.6%,另一个说 19.5%。 破案的钥匙是两者的 self-time 合计正好差 2.00 倍, 而 v7 恰好是 2 device/chip。分母不是同一个东西,分子上的百分比自然对不上。 ⇒ 判瓶颈用 roofline 那一页,归因到算子用 op stats 那一页; 引用任何百分比都要写清出自哪个工具页。
⊘ 不在旋钮上:它删掉的不是计算,是搬运
朴素写法会把中间那一步写进 HBM 再读回来:写 S → 读 S → 写 P → 读 P,四趟。 ⭐ 而这一步本来就不该落地 —— 融合之后它只在片上过。
⭐⭐ FlashAttention 把这四趟全删了:分块算, 中间结果只在片上暂存里走一遭,从不落 HBM。
📌 所以它跟三个旋钮不是一类东西,可以同时用。 旋钮改的是「算什么」,它改的是「算出来的东西放哪」—— 今天所有方案的实现里都有它,它是地板不是选项。
⚠️ 它也不是免费的:在线 softmax 要多做一遍重标定, 融合之后实测也只跑到约 35%,三层原因就在上面 3.5/3.6 两小节。
「怎么算」这条路,到这里基本走到头了。
FlashAttention 这一套已经是标配,而且已经调到头了 —— 剩下的空间不在「怎么算」里,只能去改「算什么」。 (⭐ 「已经调到头」的三条证据 —— 中间产物不落地、块大小贴着三堵墙、 算子仍只跑到三成多 —— 都在上面那个折叠里。 不展开也不影响这句结论:它要的只是「这条路是熟路,不是新路」。)
⭐ 下一节的三个旋钮,就是「改算什么」的全部可能位置。
这是这个专题的骨架。把所有名词收进一张表:
三步的拆法与三个旋钮的对应关系是本课的骨架,不是某一篇论文的分类;每个旋钮下面的代表方法见 §五 / §六 / §七 各自的出处
⚠️ 「控制面板 / 旋钮」是本课的比喻 —— 它承担的是「封闭性」这个论证,不只是一个好记的名字
各家的出处见 §五 / §六 对应小节;GQA 的 mean pooling + α=5% 续训出自 arXiv 2305.13245 §2.2
⚠️ 「老房改造 / 图纸阶段」是本课的比喻;⚠️ 「四次」只数到旋钮①②,旋钮③ 那一格本讲没有核过数,故留空
⚠️ 这里一定会被问:「那 KV 量化算不算第四个旋钮?」 —— 不算 —— 它跟这三步正交。 三个旋钮管的是存几个数、读几个数;量化管的是每个数用几个 bit。 两者可以任意组合(DeepSeek-V4 就是稀疏 + KV 混合精度一起上)。 精度那一维整个归专题八。
📌 所以「没有第四个位置」这句话的准确版本是: 在「要不要留、留多少、读多少」这件事上没有第四个位置; 「每个数多大」是另一根轴。
| 旋钮 | 在改什么 | 代表 |
|---|---|---|
| ① 每个 token 存多少 | 减少 KV 的份数或维度 | MQA → GQA → MLA → Gated MLA |
| ② 每个 query 看多少 | 限制范围或动态挑选 | SWA · NSA · DSA · CSA / HCA(⚠️ CSA 同时也在拧 ①,见 §6.4c) |
| ③ 换一套数学 | 用固定大小的状态代替不断变长的 KV | 线性注意力:DeltaNet → GDN → KDA |
| ①+②+③ 混着来 | 不同层用不同方案 | Hybrid:V4 的 CSA+HCA、K3 的 KDA+Gated MLA |
⛔⛔ 账本在这里交棒。
开场那句「只有一个账本:KV cache」到这里为止还成立, 但拧下去就不成立了:
⭐ 所以「一个账本」是一个极好的开场钩子,但它不是全讲那句话。 它的职责是 §零–§五 的记账装置:让你在最初那段有个具体的东西可以盯。 从这一节起,账分成三样 —— 显存、算力、访存规整度, §十 收在那三样上,那才是全讲唯一的落点。
⚠️ 这一句不能跳过:带着「省 KV」这一个念头读完 §六 和 §七, 到 §十 会发现对不上 —— 而那时候很容易以为是自己没看懂。
⭐ 这张地图的用法:拿到一个新名字,先把它放进某一格。 放得进去的,它的优点和代价你已经知道了,不用细看; 放不进去的,才值得你花时间 —— 那才是真正的新东西。
⚠️ 这一条曾经被本讲写成「第二个轴」,现在降级了 —— 因为它撑不起「轴」这个词。 上面那张图四行全落在旋钮①②上;旋钮③ 一行都没有。
📌 旋钮③ 那一格为什么空着,值得如实说:本讲那张 44 行表里, 所有线性/混合模型都是 native 的(从第一天就按这个结构训)。 「把一个训好的 Transformer 事后线性化」是有人在做的一支, 但本讲没有核过它的数,所以这一格留空 —— ⭐ 留空比硬凑一行好:空格是可以被后来的人填上的,凑出来的行只会被当成事实背下去。
⭐ 所以它的准确身份是:一条在旋钮①②上反复出现四次的判据, 而不是跟三个旋钮正交的第二根轴。作为判据它很好用,作为轴是虚的。
这张表可以边读边填 —— 每读完一个支线回来补一格:
| 名词 | 出处 | 旋钮 | 一句话 |
|---|---|---|---|
| MQA | Shazeer, arXiv 1911.02150 | ① | 所有头共用一份 K/V |
| GQA | Ainslie 等, arXiv 2305.13245 | ① | 分组共用,MQA 与 MHA 之间的连续旋钮 |
| MLA | DeepSeek-V2 / V3, arXiv 2412.19437 | ① | KV 压成 512 维隐向量,用时再升回 128 头 |
| Gated MLA | Kimi K3, arXiv 2607.24653 | ① | MLA 输出端加一个全秩门控 |
| SWA | Mistral 7B, arXiv 2310.06825 | ② | 只看前面固定窗口(Mistral 是 4096) |
| Attention sink | StreamingLLM, arXiv 2309.17453 | —(现象) | 开头几个 token 被当作"停车位",扔了就崩 |
| NSA | arXiv 2502.11089 | ② | 压缩 / 选择 / 滑窗三条支路,门控融合,训练时就用 |
| DSA | DeepSeek-V3.2, arXiv 2512.02556 | ② | Lightning Indexer 给每个 query 挑 top-k |
| CSA / HCA | DeepSeek-V4, arXiv 2606.19348 | ①(token 维)+② | 先把 KV 按块压缩(=沿 token 维压,见 §6.4c),再稀疏挑选;两档压缩率混排 |
| DeltaNet | 起源 Schlag 等 arXiv 2102.11174;可并行化 arXiv 2406.06484 | ③ | 状态更新用 delta rule:擦掉旧的再写新的 |
| GDN(Gated DeltaNet) | arXiv 2412.06464 | ③ | 在 delta rule 上加遗忘门 |
| KDA | Kimi Linear, arXiv 2510.26692 | ③ | 遗忘门从标量升级成 per-channel 向量 |
| FlashAttention | arXiv 2205.14135 | 不是旋钮 | 数学一个字不改,只改访存顺序 |
| RoPE | RoFormer, arXiv 2104.09864 | 不是旋钮 | 把位置变成转角,让打分直接带上「差几格」。它管距离,不管谁在前;给 MLA 惹出 decoupled 那一路(§5.3) |
| NoPE | arXiv 2305.19466(NeurIPS 2023) | 不是旋钮 | decoder-only 靠因果掩码就能学会顺序 —— 显式位置编码不是必需品(§8.3 混合架构因此能整层不放 RoPE) |
① 三种改法,都只改这两格
| 改法 | 动了什么 |
|---|---|
| MQA | K 从多个头砍到 1 个(K=1) |
| GQA | 砍到几个,多个 query 头共用一个 KV 头(G=N∕K) |
| MLA | 不砍头,改成先压到一个低秩的 c,用时再上投影 |
⭐ 看形状就知道省在哪:KV cache 存的是 BSKH,
里面那个 K 变小,缓存就等比变小 —— 就这么直接。
⚠️ 它不省核心那两个 matmul 的 FLOPs。
Q·Kᵀ 出来的还是 BTSKG,该算的乘加一次不少 ——
GQA 靠的是把 KV 头广播开再算。
⭐ 但训练侧它省:W_K / W_V 跟着缩 —— 参数、优化器状态、投影的 FLOPs 三样都省。
📌 MLA 那条另有一处麻烦:RoPE 必须单独走一路, 因为上投影吸收不了带位置旋转的那几维(见本节前面那段)。
①「顺序归因果掩码,不归位置编码」:NoPE,arXiv 2305.19466(NeurIPS 2023)—— 证明 decoder-only 不加任何显式位置编码也能学会顺序。⛔ 常见讲法拿「你打我/我打你」论证「所以必须有位置编码」,是把前提(没有掩码的双向模型)漏掉了。
②「RoPE 不是靠越远越衰减起作用」+「快盘做位置、慢盘携带语义」:Barbero et al.,arXiv 2410.06205 —— 对训练好的 Gemma 7B 做的内部分析;⚠️ 其中「携带语义」原文是 we suspect,这里照样只当推测。
「RoPE = 位置的 β 进制写法」以及外推/内插/NTK 的统一解释,出自苏剑林 kexue.fm/archives/9675(⚠️ 原文是推导,没有图)
「二进制计数器 → 正弦」的动画装置出自 Fleetwood huggingface.co/blog/designing-positional-encoding;「点积只吃夹角和长度」出自 EleutherAI 的 RoPE 博客
📐 θ_m = 10000^(−2(m−1)/d) = 1/β^(m−1) 这条等式由本脚本当场验证并断言,不是转述;RoPE 原始出处 RoFormer arXiv 2104.09864
⭐ 这张图不含任何数,所以也没有需要核的口径 —— 它画的是四种做法的接线关系,四家各自的出处(MQA arXiv 1911.02150、GQA arXiv 2305.13245、MLA DeepSeek-V2 arXiv 2405.04434)记在后面那几张有数的图上
⚠️ 头的个数画成 4 只是为了一眼能数完,跟任何真实模型的头数都无关;GQA 画成两组同理 —— 真实分组数看讲四种存法的那张散点图
⛔ 图里不区分 K 和 V 各自的份数(它们在这四家里都是同进同出),一个盒子代表「一个头位置上的 K 和 V 合起来的那一份」
📐 横轴那几个数:61 层 · 128K · bf16 · batch 1,一个用户一份 —— MHA 488 GiB / GQA-8 30.50 / MLA 8.58 / MQA 3.81
四个数由公式当场算出并断言(脚本内);MLA 超参出自 V3 论文 §4.2:n_h=128, d_h=128, d_c=512, d_h^R=64, 61 层
MQA:Shazeer arXiv 1911.02150 GQA:Ainslie 等 arXiv 2305.13245 MLA:DeepSeek-V2/V3 arXiv 2412.19437
⭐ 「MLA 那 576 里为什么有个 64」在 §5.4 的 fig3-two-lanes 里 —— 本图只到「四种存法各占多少地方」为止
维度出自 DeepSeek-V2 论文 §2.1(arXiv 2405.04434)与 V3 config:d=7168, n_h=128, d_h=128, d_c=512, d_h^R=64;三个倍数由脚本当场算并断言
事后低秩:Eigen Attention(EMNLP Findings 2024)、Palu、LoRC 「MLA 优于 MHA」的口径见 DeepSeek-V2 仓库 issue #26
⚠️ 「复印 / 缩印」是本课的比喻 —— 论文那一侧的说法是「低秩联合压缩」
①② 出自 MQA 原论文 Shazeer arXiv 1911.02150(§2.4 与表 3 —— ⚠️ 这篇一共只有 3 张表,Billion-Word LM 基准的 dev 困惑度);「正交」是原文用词
③ 出自 GQA 原论文 Ainslie 等 arXiv 2305.13245 §2.1–2.2:mean pooling、α=5% 续训、GQA-1=MQA / GQA-H=MHA
⚠️ 「通讯录 / 查的人」是本课的比喻
⭐ 图里那三行困惑度值得单独记一下:MQA 和「真的只剩一个头」缓存的 K/V 一样多, 差别只在 query 侧还留不留 8 个不同的问法 —— 就这一点差了整整 1.0。 所以后面看任何一个「压缩」方案,都要分清它压掉的是体积还是自由度: 压体积通常还好,压自由度很贵。
「拆开再复原、数字一模一样」这个装置偷自 planetbanatt.net/articles/mla.html 的 Manim 动画;⭐ 但他只画了左栏,右栏是本课补的
两栏的矩阵都由脚本当场构造并断言:左栏真秩 2、右栏真秩 3;低秩逼近用 numpy SVD(Eckart–Young 最优解)
⚠️ 4×4 是示意尺寸;MLA 真实是 32,768 → 576
⭐ 这条推理链本身是可迁移的。 看任何一个压缩方案 —— KV、权重、激活、梯度 —— 都先把它拆成两段问: 哪一段是「表示冗余」白送的,哪一段是「赌它低秩/稀疏/可近似」赌来的? 白送的那段不用做实验,赌的那段必须看掉点。
⭐ 这里真正值得单独说一句的,是「GQA 是一个连续旋钮」这件事本身 —— 它不是一个新机制,是把 MHA 和 MQA 之间的空白填上, 让你可以按需要选一个点。 这门课后面会反复见到这个套路:把一个二选一变成一个可调的连续量。
⭐⭐ 而这个旋钮之所以是连续的,理由比「取个中间值」深一层 —— MHA、MQA、GQA 之间差的,只是同一个位置上同一块矩阵里写了什么。 把这块矩阵一格一格画出来,MLA 也会自己落到同一条轴上。
📌 苏剑林《缓存与效果的极限拉扯:从MHA、MQA、GQA到MLA》kexue.fm/archives/10091 —— 「低秩投影这个角度并不贴近本质」「MLA的本质改进不是低秩投影,而是低秩投影之后的工作」「我们知道分割、复制都是简单的线性变换」三句均为原文逐字。
📌 图上那几个矩阵是本课按 h=4 / d_k=2 自己构造的,脚本用 numpy 断言过「c 乘这个矩阵」与「把 c 按组复制」逐元素相等 —— 这不是类比,是恒等。
📌 MLA 那一格刻意不填数字:本课没有 V3 的真权重,编几个小数放上去会被当成真的。
⭐ 这张图要破的是一个很顺口、但区分不了事情的说法: 「MLA 就是给 KV 做低秩分解」。
低秩这个描述没错,但它区分不了 GQA 和 MLA —— 把 GQA 所有的 K、V 叠在一起,GQA 本身就是一次低秩投影。 苏剑林的原话是:「笔者认为低秩投影这个角度并不贴近本质…… MLA的本质改进不是低秩投影,而是低秩投影之后的工作。」
⭐⭐ 低秩之后那一步,才是分界线。 GQA 用「分割 + 复制」把 c 凑成各头要的 K、V; 而分割和复制本身就是线性变换 —— 它们对应的就是图里那块只有 8 个 1、其余全是 0、而且不可训练的矩阵。 MLA 做的事,就是把这块写死的矩阵松开,让它学。
⚠️ 引这一段时,两处归属要说准。
① 苏剑林写的是「MLA 被视为 GQA 的一般化」, 不是「MHA / MQA / GQA 都是 MLA 的特例」 —— 后一句是网上转述时放大出来的,别挂在他名下。
② 他也没有否认 MLA 是低秩分解(他在另一篇里还写过 「从 MHA 的角度看,MLA 是给 K、V 加了 rank=512 的 LoRA」)。 准确的说法是上面那句:低秩这个描述没错,只是它区分不了这两个。
③ 他给的条件 d_c = g(d_k+d_v) < d 在 MHA 那一端会失效 ——
g 取到 h 时 d_c 就等于 K、V 本身的总宽,根本没压,
图里那块矩阵也退化成单位阵。所以「同一条轴」说的是那块矩阵的位置与形状,
不是说四个成员互为特例。
MLA 的超参出自 V3 论文 §4.2:
n_h=128, d_h=128, d_c=512, d_h^R=64, 61 层
—— 图上那 576 就是 512 + 64。
⛔⛔ MLA 最大的推理部署坑:它在张量并行下会退化(⭐ 2026-09-13 补)
⚠️ 先把作用域说死:下面讲的只是推理。训练侧没有这个问题 —— 训练没有 cache 要复制,WUK/WUV 都是按头切的,TP 照切;被复制的只有那个 576 维的隐向量激活,跟 7168 维的 hidden 比可以忽略。
GQA / MHA 的 KV 是按头切的,TP=8 就每张卡各存八分之一,天然可分。 而 MLA 的 KV 是一份 576 维的隐向量,根本不按头分 —— 于是只剩三条路:
⭐ 这条值得单独记,因为它是本讲主线的又一个例子: 一个在单卡上很漂亮的数学结构(把 K、V 合进一个共享隐向量), 到了多卡上恰恰因为「不按头分」而失去了最自然的切法。 ⛔ 所以引用「MLA 省 57 倍」的时候,要带一句「在什么并行配置下」。
📌 口径:三条做法是公开实现里能看到的;各自的具体开销本课没有实测。⚠️ 再强调一次:这三条全是推理侧的。
前面一直在说 MLA 把 K、V 压成一个 576 维的隐向量存起来。 那生成下一个词的时候,不是还得把它们拆回来才能比对吗?
「吸收」与「RoPE 挡住它」两处原文均出自 DeepSeek-V2,arXiv 2405.04434(§2.1.2、§2.1.3);超参 d_c=512 / d_h=128 / n_h=128 是 DeepSeek-V3 的口径
⭐ ③ 那笔账是本课自己算的,脚本里带断言:天真 = S×(d_h·d_c + d_h),吸收 = d_h·d_c + S·d_c;上限 (d_h·d_c+d_h)/d_c ≈ d_h。⚠️ 只数乘加,没算访存 —— 真机上访存往往才是瓶颈,所以这是个下界不是实测
⚠️ 吸收只在 decode 用得上:prefill 时一批里有很多个 q,「只变换一次」这个便宜就没了(这也是 §五 表里「压缩不生效」那一行的意思)
★ 不用拆。把括号挪一下就行
qᵀ (WUK c) = (WUKᵀ q)ᵀ c
—— 同一个乘法,只是括号换了个位置。
左边括号在 c 那侧:缓存里有几个 c 就得算几次。
右边括号在 q 那侧:一步只有一个 q,所以只算一次,
而缓存里的压缩包一个都不用拆。
🏠 生活版就一句话:与其把一万本外文书全翻译过来, 不如把你的搜索词翻译过去。
⭐ V 那一侧同理 —— WUV 可以吸进输出投影
WO。所以 K 和 V 两边都不用拆。
⚠️ 省多少?算出来的答案跟直觉不一样
直觉会说「解压从 S 次变成 1 次,所以省 S 倍」。不对 —— 吸收之后每个 token 的点积从 128 维变成了 512 维, 这一头贵了 4 倍,在把省下的吃回去。
两笔加起来算(图③),省的倍数有个上限,而这个上限正好是每头维度
d_h = 128:解压一个 token 要 d_h×d_c 次乘加,
而点积只要 d_c 次,两者的比就是 d_h。
S 再长也过不去这个数。
📌 口径:这笔账是本课自己算的,只数乘加,没算访存 —— 真机上访存往往才是瓶颈,所以它是个下界不是实测。
⛔ 两条定律,一条允许、一条禁止 —— MLA 最难的两件事都在这儿
结合律允许你挪括号。上面那一步靠的就是它。
论文原话:due to the associative law of matrix multiplication, we
can absorb WUK into WUQ, and WUV into
WO。
但交换律不成立。RoPE 会往 q 和 WUK
中间塞进一个跟位置有关的旋转矩阵,而夹在中间的东西挪不出去。
原话:a RoPE matrix … will lie between WQ and
WUK and matrix multiplication does not obey a commutative
law。
⭐⭐ 再往下追一层:卡住的不是「中间有东西」,是那东西带下标。
假如塞在中间的是一块固定的矩阵 M,那
WUQᵀ M WUK 照样能预乘成一个矩阵,
吸收完全成立 —— 这一步只用到结合律,压根没要交换律。
可 RoPE 是相对的:RtᵀRj =
Rj−t —— 每一对 (query, key) 对应一个不同的矩阵。
要预乘就得预乘出一整套,有多少种相对距离就有多少个。
这才是真做不到的那一步。
⭐ 而 5.3 那张图画的就是解法:把带位置的那一小块单独拎出来走 64 维一路 —— 宽的那条轨上没有闸,紫块照旧搬得走。
📌 同一个把戏,本讲这是第三次出场
§七 线性注意力:把括号从 (QKᵀ)V 挪成
Q(KᵀV) —— 那个句长×句长的大方块就不用建了。
§五 MLA 吸收(这一小节):把括号从 qᵀ(WUKc)
挪成 (WUKᵀq)ᵀc —— 压缩包就不用拆了。
⭐ 两次是同一个数学恒等式,而且都被同一类东西挡过: §七被因果 mask 挡住,这里被 RoPE 挡住。 挡住结合律的,永远是「中间被塞了个东西」。
⚠️ 作用域:吸收只在 decode 用得上
—— prefill 时一批里有很多个 q,「只变换一次」这个便宜就没了
(这正是本节开头那张表里「压缩不生效」那一行的意思)。
⭐ 于是同一个数学式子有了两种算法实现,选哪种取决于是 prefill 还是 decode —— 怎么在一个引擎里同时装下两套,留到专题六。
§五 到这儿把「存多少」算干净了。但有个问题一直悬着 —— 压这么狠,模型会不会变笨? 前面每一笔账数的都是字节,质量那一维一次实测都没出现过。
📌 全部数字一手核自 苏剑林《Transformer升级之路:20、MLA好在哪里?(上)》kexue.fm/archives/10907 —— Part I / II / VI 三张表。
📌 公共设置:类 LLAMA3 Dense,hidden 2048 / 12 层 / 16 头,优化器 Muon,训练长度 4096,总 16B tokens / 16k 步;除面板③ 外参数量不严格对齐(原文说明)。
📌 「两级台阶 0.039 vs MLA 0.029」这个减法是本课做的,不是原文结论;原文结论为「增大 head_dims 收益最大,Partial RoPE 也有一定帮助」。
⛔ 这组数反转了本讲自己刚说过的一句话
§5.3 讲到 RoPE 那条 64 维窄轨时,本课用的词是「被赶到一条窄轨上」 —— 把它当成被代数逼出来的妥协。 可这组消融指的是反方向:同样 512 的 KV Cache,光把 256 拆成 192+64、 只给那 64 维加 RoPE,loss 就从 2.720 掉到 2.711。
⭐ 那可能不是妥协的代价,而是这个设计顺手做对的一件事。 原作者的说法是「看似无奈的设计,极有可能是它效果优异的关键原因」。
📌 三条猜测,图里只画了两条
原文一共提了三个可能的功臣:head_dims、Partial RoPE、 KV-Shared(K 和 V 共享大部分维度)。前两条图里都有受控对照, 第三条本课只提,不画、不给数 —— 它要跟 RoPE 兼容得额外引入 一套新的位置编码,实验设计绕得多,而原文自己对它的措辞也最保守: 「应该也有一定作用」。
❓ 顺手想一步:如果主因真是 head_dims, 那想让普通 GQA 追平 MLA,该从哪个数字改起、改到多少?
原文给的答复很具体:「head_dims 应该要 192 起步了,并辅以 Partial RoPE」 —— 注意这是一条能直接写进配置文件的结论, 而它是从上面那两级台阶读出来的,不是从哪篇论文的摘要抄的。
② 稀疏,本质上就是换一张 mask
标准因果注意力的 mask 是一个下三角 —— 看全部历史。 ⭐ 所谓稀疏,就是把这张 mask 换成别的形状:
| 方案 | 换成什么形状 |
|---|---|
| SWA | 只留主对角线附近一条带 —— 只看最近 W 个 |
| NSA | 三条路并存:压缩看全局 + top-k 挑重点 + 滑窗看近处 |
| DSA | 拿一个轻量索引器先打分,只留 top-k 那几块 |
| CSA | 先把每 4 个 token 压成 1 个 entry,在压缩后的格上挑 |
⭐ 它跟旋钮① 是正交的:一个改 KV 存多少,一个改这张 mask —— 所以两个可以同时上(GLM-5 就是)。
⚠️ 纸面省下的 FLOPs,要 kernel 跟上了才算数。 不规则的 mask 对硬件不友好 —— 这是这一支真正的门槛, §六 那几家推理期选择器栽的就是这一跤。
mask 图案为示意,用来表达各方案的读取形状,不是实测注意力分布;GQA-8 在 1M 下的 244 GiB 由公式当场算出(脚本带断言)
SWA:Mistral 7B arXiv 2310.06825 sink:StreamingLLM arXiv 2309.17453 NSA:arXiv 2502.11089 DSA:DeepSeek-V3.2 arXiv 2512.02556 CSA/HCA:DeepSeek-V4 arXiv 2606.19348
① 有效射程那一格出自 guangxuanx.com/blog/stacking-swa.html(作者是 StreamingLLM 一作,⚠️ 个人博客非同行评议):纯 SWA ≈ 0.58·W·√L;有残差时跟层数无关。⚠️ 其中 α≈0.95 是作者断言不是实测,所以本图只说「一到两个窗口宽」不写死倍数
gpt-oss-20b 的 1:1 交替与 sliding_window=128 是本课直接读 huggingface.co/openai/gpt-oss-20b 的 config.json 得到的
Mistral 7B arXiv 2310.06825 §2(k×W 射程、W=4096 / 32 层、rolling buffer cache);131,072 由脚本当场乘出来并断言
② 出自 StreamingLLM(Xiao 等 arXiv 2309.17453, ICLR 2024)论文表 1 / 表 2 与 §3.1 / §3.3:5158.07 → 5.40、换行符 5.60、留 1/2/4/8 个的对照
⚠️ 表 1(PG19 第一本书,65K)与表 2(拼接后 400K)不是同一个评测集;⚠️ 「传话」是本课的比喻
⭐ 「为什么会有这么个废票桶」在下一张 fig3-sink 里 —— 本图只到「它崩了」为止
⭐ 一条课程内的接线: 图上那句「量化里那批 outlier 跟这是同一件事」不是顺口一提 —— 那批 outlier 在 专题八 有专门一节。 到那儿你会再遇到同一个形状:一个「毫无道理却极其稳定」的现象, 背后是一条你没注意到的守恒约束。同一个形状分在两讲里各讲一遍, 比在一讲里说两遍有用。
⭐ 图上五张 mask 从左到右,就是这一支的演进: 砍成一条带(SWA)→ 补回停车位(sink)→ 学着挑(NSA / DSA)→ 先压再挑(CSA/HCA)。
上面说了 sink 是怎么来的。但还有半句没说: 它有什么用?—— 如果它纯粹是个副作用, StreamingLLM 特意把它留着就只是在打补丁。而事实不是这样。
装置与两句原话出自 Barbero 等《Why do LLMs attend to the first token?》(arXiv 2504.02732)Figure 1 caption:「The presence of attention sinks slows down the mixing of information between tokens and hence makes Transformers more robust to perturbations of prompts」·「The presence of a sink draws attention away from the rest of the tokens, limiting the spread of perturbed information」
⚠️ ①② 那两张网格是本课的线性简化模拟(均匀注意力 + 残差流 h ← h + Attn(h),sink 吸 80% 且 value 记 0),脚本当场跑并带断言 —— 不是实测。Barbero 的 Figure 1 本身也是示意图,他们的实测在 Figure 2
⛔ 三条别引错:「pump the brakes」不是 Barbero 的原话(全文零命中);「泄压阀」是 MIT HAN Lab 博客的转述不是论文的词;「一滴墨水」这个画面是本课原创,论文里没有任何生活比喻
★ 机制是两半,第二半最常被漏掉
① 吸得多 —— 它把绝大部分注意力吸到自己身上
(Llama 405B 里将近 80% 的注意力落在第一个 token 上),别人分到的就少了。
② 吐得少 —— ⭐ 而它的 value 几乎是零。
所以吸走的那一大块不带任何内容回来。
⛔ 只讲第一半是讲不通的:如果它吸走的注意力照样带内容回来,
那信息一样会混,只是换了条路。论文管这个叫 approximate no-op
—— 一个「几乎什么都不做」的去处。
📌 于是前面那句口径要补一半
「softmax 不许弃权,模型就自己造了一个弃权用的候选人」—— 那只说到成因。 补上的这一半是:那张弃权票不是浪费掉的,它是刹车片。
⭐ 所以 StreamingLLM 为什么砍掉开头几个 token 模型就崩 —— 不是丢了信息,是刹车没了。 (那几个 token 本来就没什么内容,这正是它们能当 sink 的原因。)
⛔ 两个 80%,长得一样,意思完全不同
「Llama 405B 里将近 80% 的注意力落在第一个 token 上」—— 这是权重占比。
「LLaMa 3.1 405B 里有 80% 的注意力头形成了强 sink」—— 这是头的比例
(判据是阈值 ε=0.8)。
⭐ 两句都出自同一篇论文,数字一样、含义毫不相干。 引的时候说串了,懂行的人一听就知道。
① 出自 H2O:Zhang 等 arXiv 2306.14048(「over 95% sparse」与累计注意力分数的幂律分布,均为原文表述)
②③ 出自 DeepSeek-V3.2-Exp 技术报告 §1–§2.1:KL 对齐、冻结主模型热身、跨头求和后 L1 归一、梯度断开、ReLU「for throughput consideration」、index_topk=2048、索引器 64 头且跑在 FP8 上
⚠️ 「书墙 / 师徒」是本课的比喻;⛔ k 的消融论文没有给 —— 2048 这个数只能说它占多少、以及短序列下等于全选
① DSA 的师徒(两阶段训练、冻主模型 + KL warmup)出自 DeepSeek-V3.2-Exp 技术报告 sec. 2;② NSA 的三支路出自 arXiv 2502.11089 sec. 3
③ CSA 每 4 个 token 压成 1 个 entry、在压缩后的格上挑,出自 DeepSeek-V4 相关公开材料(见 CSA 那张图的出处);IndexPool「把 4 个 indexer key 向量加权池化成 1 个」出自智谱 GLM-5.3-Flash 官方博客(z.ai/blog/glm-5.3-flash,2026-08,模型 MIT 许可开源)
⚠️「相似度算出来再稀疏就没好处 → 只能按块打分」这条链子的表述出自 zhouyifan.net 的 Log-linear Sparse Attention 一文;「师徒 / 一份算两用 / 降维打击」是本课的命名
⭐ 这条套路值得起个名字:要省掉一个贵的东西,先让它自己说出答案, 再训一个便宜的去复制那个答案。 DSA 的索引器就是这么来的 —— 它不是一个猜谁重要的启发式, 它是主注意力分布的一个廉价复制品(跨头求和、L1 归一、KL 对齐)。 这个套路在推理优化里到处都是,值得单独记住。
四个坑与三条分支出自 NSA(Yuan 等 arXiv 2502.11089)§2 与 §3;27B backbone / 260B token 亦出自该文
⚠️ 「读厚书 / 翻目录 / 整章拿」是本课的比喻;论文那侧的说法是 compression / selection / sliding window 三分支加一个 learned gate
⭐ 这条判据的用法: 看任何一篇讲稀疏的文章,先问「它省的是 FLOPs 还是字节」。 省 FLOPs 谁都会 —— 在纸上少算 90% 的格子而已; 但只要那些格子散落在显存各处、或者同组的头各挑各的,要搬的字节一点没少。 NSA 的两个看起来很朴素的决定(按块选、组内共享),都是为访存,不是为精度。
NSA 的「native」指的就是训练时就这么做,不是训练完再加的推理优化。 这个区别值得单独说三十秒 —— 它解释了后面一个反复出现的现象。
| 推理期稀疏 | 训练期稀疏(native) | |
|---|---|---|
| 模型知不知道自己会被稀疏 | 不知道 | 知道,权重是在稀疏条件下学出来的 |
| 掉点 | 有,且难预测 | 小得多,甚至能反超 |
| 能不能省训练成本 | 不能 | 能(NSA 报的反向 6.0×) |
| 代价 | 无,随时可开关 | 要重训,没法给已有模型打补丁 |
⭐ 这张表解释了为什么这些新注意力方案总是跟新模型一起发布, 而不是作为一个推理框架的开关。
CSA / HCA 的机制出自 DeepSeek-V4 技术报告 arXiv 2606.19348 论文 §2.3–2.3.1(每 m 个压一条 → DSA top-k → 并上滑窗;HCA 压 m′≫m 但保持密集,m=4 / m′=128)
27% FLOPs / 10% KV cache 出自同一篇摘要与 §2.3.4(1M 上下文、对比 V3.2)
⚠️ 「会议便签 / 两个版本」是本课的比喻;⚠️ 「两种漏法互补所以交错」是从定义推出的解释,论文未给这个理由,也未在此给出层间配比
⭐ 那这三个轴跟三个旋钮怎么对上?—— 而这一处全讲原先有三种说法,必须在这儿定死一个:
⭐⭐ 「横着压」不是第四个旋钮,它是旋钮① 的第二个方向。 旋钮① 问的是「每个 token 留多少字节」,而这件事有两个压法: 沿特征维压(MQA/GQA/MLA —— §五讲的全是这个方向)和 沿 token 维压(CSA:几个 token 合成一条,于是平均每 token 也变小了)。 两个方向都在同一个旋钮上,因为它们改的是同一件事:那份要留下来的有多大。
⛔ 所以三处口径统一成这一条:§四 的名词收纳表写「旋钮①(token 维)+ 旋钮②」, 编年史那条泳道同理。⚠️ 而「一个 query 只做三步」这个封闭性论证不受影响 —— 横着压改的仍然是第一步(要留什么),只是换了个维度下刀。
三个轴互不相干,所以可以同时拧 —— DeepSeek-V4 就是三个一起拧:MLA + CSA/HCA + DSA。
前面四个方案(NSA / DSA / CSA+HCA)都有一个共同的零件:一个决定"该看哪几块"的索引器。 §6.2b 讲 DSA 的时候它叫 Lightning Indexer。到这一步为止,所有心思都花在让每个 query 少看几块上。
但索引器自己也要算。 它要为每一层、每一个 token,跟全部历史块打一次分。稀疏注意力把主体那部分省下来之后, 这笔原本不起眼的账就浮上来了 —— 尤其在 1M 上下文下,历史块本身就有几万个。
2026 年年中,两家公司几乎同时给出了同一个答案:别每层都重新算一遍"该看谁"。
| 模型 | 叫法 | 做法 | 官方给的收益 |
|---|---|---|---|
| GLM-5.2(智谱,2026-06-16,744B) | IndexShare | 每四个稀疏注意力层共用同一个索引器 | 1M 上下文下每 token FLOPs 降 2.9× |
| 混元 Hy4-preview(腾讯,2026-08-28,770B/49B) | IndexCache | 同上:跨层复用稀疏索引 | 未单独给数 |
⭐ 这两家撞了同一个想法,而且证据不用查博客 —— 打开两份 config 就看得见。 它们的 indexer_types 字段都是同一个循环:
GLM-5.2 : full, full, full, shared, shared, shared, full, shared, shared, shared, ...
混元 Hy4 : full, full, shared, shared, shared, full, shared, shared, shared, ...
└──────── 每 4 层里,只有 1 层自己算索引 ────────┘
GLM-5.2 的 config 里还有一个 index_topk_freq: 4 直接把这个 4 写了出来。
⭐ 为什么这一小节值得单独留一块地方:它是一个优化制造出新的被优化对象的干净例子。 稀疏注意力是为了省 attention 的账而来的;省成了,于是索引 —— 它原本只是这个方案的附属零件 —— 变成了新的大头,再被优化一轮。这一节讲的三个旋钮都会经历这一步,不只是稀疏这一支。
⚠️ 这不是白拿的,代价要说出来。 官方博客只报了省下来的 FLOPs。 但共享索引器意味着这四层被迫看同一批块 —— 它们不能各自挑各自的。 这是表达力上的一次让步:原本每一层可以按自己那一层的语义去决定关注哪里,现在四层绑在一起。
📌 口径,分清能推到哪一步:「四层被迫看同一批块」是能推的 —— 索引器共享,选出的 top-k 集合当然就一样,这一步不需要实验。 但「这会不会掉点、掉多少」推不出来,而且两家都没公开。 ⚠️ 所以这里说的是「这是一次表达力上的让步」,不是「它让效果变差了」 —— 两句话差得很远,而且它给出 2.9× 的同时并没有报质量回退, 也可能这一让步在实测上几乎无损。
⭐ 可带走的那一句只到这里为止:看到「降 2.9× FLOPs」这种数字, 先去找它在结构上放弃了什么自由度 —— 找到了不等于代价大,但没找到就说明你还没看懂它省在哪。
📌 DSA 的打分式子值得念一遍:
I(t,s) = Σ_j w_j · ReLU(q_j · k_s) ——
⭐ 用 ReLU 不用 softmax,纯粹是为了吞吐,论文自己这么说的。
索引器 64 头(主注意力 128 头),而且跑在 FP8 上;k = 2048。
⭐ 段尾:两个旋钮拧完了,回头看一眼它们的共同点 —— 旋钮①② 都还在跟那张表打交道:一个让每格更小,一个让读的格子更少, 但那张会一路变长的表,始终都在。
⭐⭐ 下一节是唯一一个不跟它讨价还价的 —— 它直接把表拿走。 而拿走之后换来的,不是一个更快的注意力,是另一个模型。
BKHH,S 不见了。状态大小只跟头维有关,跟序列多长无关。③ 不用听解释 —— 读输出形状就够了
softmax 的分母要对所有位置求和,所以它锁死了乘法顺序: 必须先 Q·Kᵀ(于是必须造出那个平方大的矩阵),再乘 V。 ⭐ 把 softmax 拿掉,乘法就可以重新结合:先 KᵀV,再乘 Q。
⭐⭐ 看点亮那格的输出形状:BKHH —— S 不见了。
状态大小只跟头维 H 有关,跟序列多长无关。
这就是 O(N²) → O(N) 的全部内容,写在形状里,不用相信谁。
⛔ 但代价也写在同一格里。 因果版不能真的这么一乘 —— 状态要按 t 一步步累加,于是串行回来了; 分块并行(chunkwise)就是为了把并行度再找回来(见 §7.4)。
⚠️ 它不是「更快的 attention」,是另一个模型。 固定大小的状态 → 信息必然有损,长程精确检索会力不从心。
装置偷自 Google Research《Rethinking Attention with Performers》(2020-10):括号画成彩色虚线框、矩阵按真实比例画
「mask 才是挡住结合律的那个东西」出自 Hailey Schoelkopf 《Linear Attention Fundamentals》;「结合律是张量收缩顺序的特例」出自 Mamba-2 (SSD) 博客 Part II
⚠️ 图里 8×3 的格数是示意;真实量级是句长 128K、头维 128
递推式、key collision(L > d)、delta rule = Widrow-Hoff、以及「等价于对 ½‖Sk−v‖² 做一步 SGD」,均出自 DeltaNet 论文 Yang 等 arXiv 2406.06484 §2.1–2.2
⚠️ 该文 §6 那句「表达力与并行度之间存在根本权衡」说的是 Recurrent DeltaNet / mesa-layer 那一批比 delta 更强的模型,不是 delta 对纯加法,而且原文是带引用的 suggests
⚠️ 「记事板 / 擦」是现场给的比喻,不是论文措辞
⭐ 它换的东西比前两个旋钮都大: 旋钮①②改的是那张 n×n 的表怎么存、怎么读,表本身一直都在; 旋钮③ 是第一次把那张表整个拿掉,换成一块固定大小的板子。 —— 所以它是唯一一个让 S 从张量形状里消失的旋钮, 也是唯一一个换了数学的。
⭐⭐ §1.4 认的那个主角,在这一节退场了。 前两个旋钮一直在跟 S 讨价还价 —— 少存一点、少读一点; 这一个直接把它请出了张量形状。代价你马上会看到: 它换来的是另一个模型,不是一个更快的注意力。
上面那块记事板有三种写法,区别都在擦不擦、怎么擦。 这一小节就把那个动作拆成三步来看 —— 苏剑林给了它最好的中文名字:除旧迎新。
delta rule 的 (I − βkkᵀ) 与「β∈(0,1] 是部分擦除、不是完整反射」出自 DeltaNet(arXiv 2406.06484);逐通道遗忘门出自 Kimi Linear 的 KDA(arXiv 2510.26692)
⚠️ 图里的比喻都不是本课原创,逐条记明:「除旧迎新」—— 苏剑林 kexue.fm/archives/11033;「手术刀橡皮 + 高压水枪」—— Towards AI《Gated DeltaNet: The Surgical Eraser》;「整屋一个调光开关 vs 每个灯泡一个调光器」—— Amit Kapoor《Inside Kimi K3》;「记忆的敌人不是时间,是别的记忆」—— Eagleman《Livewired》,经 Songlin Yang 的 DeltaNet 博客引用
⭐ 抽屉里的颜色深浅是示意,不对应任何模型的实测门控值
★ 一句话收住这一支的全部改进史
decay 会忘但不会改,delta rule 会改但不会忘。 —— 所以自然的下一步就是两个拼起来,那就是 Gated DeltaNet; 而 KDA 只在它之上改了一处:调光器从一个总开关变成每格一个。
🏠 为什么要分开调?「你现在在写哪门编程语言」这条该留很久; 「刚离开的那个函数里的变量名」可以马上忘掉。 一个总开关做不到这件事。
⚠️ 抽屉那个画面有一处不诚实,这里说破
真实的「地址」不是一格一格的抽屉,是连续的方向;擦也是按比例擦
(β 决定擦多干净),而且会顺带擦到相近的地址。
⭐⭐ 这正好回指 §1.3b:那里算过,128 维里塞一万个方向, 最挤的一对还差 60 度 —— 「差不多不像」的代价,在这里就变成「擦串了」。 所以「定点擦」是个近似,不是真的只动一格。板子越满,擦得越串。
📌 为什么固定大小的板子一定会坏 —— 一软一硬两句
硬的那句(可以验算):d 维空间里最多只能有 d
个互相正交的方向。板子一满,新记录就只能挤在别人旁边。
软的那句(会被记住):「记忆的敌人不是时间,是别的记忆。」
—— 你忘掉一个电话号码,不是因为时间久,是因为你又记了新的。
⭐ 一软一硬配在一起,比任何一句单独说都有用: 诗给画面,数给它一个可以验算的身体。 (后一句出自 Eagleman《Livewired》,经 Songlin Yang 的 DeltaNet 博客引用。)
📌 这一节的记号约定(⭐ 2026-09-13 补 ——
原先三处朝向不一致,数学背景的读者第一眼就卡住):
状态 S ∈ ℝ^(d_v × d_k);转移矩阵 A_t 一律右乘
(S_t = S_{t-1} · A_t + v_t k_tᵀ);读出写作 S_t · q_t。
A_t 是 d_k×d_k —— 只有右乘,维度才对得上。
把 softmax 去掉(换成某个可分解的核函数),求和就可以重排:
softmax 版: out_t = Σ_{s≤t} softmax(q_t·k_s) v_s ← 必须留下所有 (k_s, v_s)
线性版: S_t = S_{t-1} + v_t k_tᵀ ← 一个固定大小的状态
out_t = S_t · q_t
于是:
d_k × d_v 的状态矩阵代价说死:状态大小固定 → 信息必然有损。 序列越长,往同一个矩阵里塞的东西越多,长程精确检索("第 30 万字提到的那个电话号码") 会力不从心。这不是实现问题,是这个换法的性质。
递推式与各家 A_t 的结构见:线性注意力 arXiv 2006.16236、DeltaNet arXiv 2102.11174(2021)与并行化 arXiv 2406.06484、Mamba-2 / SSD arXiv 2405.21060、GDN arXiv 2412.06464、KDA(Kimi Linear)arXiv 2510.26692
⚠️ 矩阵缩略图是示意:只画结构 —— 对角竖条的高度代表衰减强度、斜纹代表「一整块都被动了」(那个 −βkkᵀ),不代表真实数值
⭐ 一条读法: 看到一个新的线性注意力,先把它的 A 写出来,再问「这个形状还能不能分块并行」。 两个问题的答案一配对,你就知道它会不会活下来 —— 表达力和可算性是一起设计的,不是先设计再优化。
⚠️ 两处容易记错的出处,讲的时候要说对:
⭐ 图上最后那两格为什么值得单独说一句: KDA 把逐通道的门做成了一个特殊的 DPLR(对角 + 低秩)形式 —— 正是因为这个特殊形式,才配得出一个比通用 DPLR 便宜得多的分块并行算法。 它不是「先设计一个强的,再去优化」,是一边看着能不能算得动,一边设计。
上面那张图的落点里藏了一个没解释过的词: 「正因为退了,才证得出跟线性注意力的对偶」。 对偶是什么?这一格补上 —— 它不是个术语,是个读论文的技巧。
📌 三句逐字引文均核自 arXiv 2405.21060 的 e-print 源码:半可分矩阵的定义(「every submatrix contained in the lower triangular portion ... has rank at most N」)、Figure 2 caption 里的「equal to the SSM's state dimension」、以及对偶那一句「is exactly the same as quadratic masked kernel attention」。
📌 SMA 骨架 M = QK^T ∘ L 与 “a different contraction ordering” 出自同文 Figure 3 caption。⚠️ 前三张 L 的对应关系照它画;第四张(逐通道门 → GLA / GDN / KDA)是本课的推广,原图没有。
⛔ 图里那个「=」不是修辞:本脚本用 numpy 把递推读法和矩阵读法各算一遍并断言逐元素相等,还额外验了两个退化情形(a ≡ 1 → 全 1 下三角;a ≡ γ → γ^(t−s))。
⭐ 这一格真正有用的地方,是让你会读论文了: 一篇自称 SSM 的和一篇自称 linear attention 的, 很可能在讲同一件事 —— 只是一个从递推那头写,一个从矩阵那头写。 拿到新论文先问一句:它的那张 L 长什么样? 这个问题能穿过命名,直接问到结构。
⚠️ 别把这条对偶讲成「所以它们都一样」。 框架相同不代表模型相同 —— L 换一张,表达力和可算性都跟着变, 这恰恰是 7.2 那条演化线在折腾的全部内容。 对偶说的是「能不能换个顺序算」,不是「算出来的东西一样」。
三个旋钮里,只有旋钮 ③ 改变了模型能表达什么:
先补上前面欠下的那一步。我们说过换括号能把平方变成线性, 也说过因果 mask 会把这个换括号挡住 —— 那为什么切成块之后又能换回来?
⭐ 答案不在「块小所以算得动」,而在一件把矩阵画出来就看得见的事上。
📌 「结合律被挡住/但张量缩并仍可吸收 mask」出自 Mamba-2 作者博客 goombalab.github.io/blog/2024/mamba2-part2-theory,逐字核对。
📌 「块间不需要 mask,因为 chunk r 的每个 token 都能读前面 chunk 的每个 token」出自 snowchord.com/blog/linear-attention-visualized,逐字核对。
📌 「块内用左乘法、块间用右乘法」出自 Lightning Attention-2(arXiv 2401.04658)原文 intra-blocks employing the left product and inter-blocks utilizing the right product。
📌 画法上借了四处:块色=主角张量色、mask 用纹理不用颜色(snowchord);块间一根竖条表示低秩(rudrite research);右侧状态串珠(Songlin Yang 讲座 slides);两级分块与 tensor core 口径(GLA, arXiv 2312.06635 Figure 3)。⛔ 图里的格子数全部是本脚本当场数的,不是引来的。
⭐⭐ 这张图真正的落点,是一句反直觉的话: 被 mask 挡住的,从来只有对角块。
块间那些块里根本没有 mask —— 第 r 块里的每个 token,都能读前面每一块里的每个 token,没有谁被挡。 既然那里从来没有逐元素乘挡路,结合律在那儿也就从来没被挡住过, 自然可以把整块压成一个与句长无关的状态。
⭐ 这也解释了图上那笔账:句长翻倍,整张表的有效格子涨 3.85 倍, 而真正要一格一格算的只涨 2 倍 —— 正好线性。 对角块那一部分不会随句长消失,它只是从平方变成了线性。
⚠️ 一处别讲过头。Mamba-2 的作者在同一段里紧接着写了: 被挡住的是「结合律」这个特例,不是「重排」本身 —— 结合律只是张量缩并顺序的一个特例,换成更一般的缩并顺序, mask 是能被吸收进去的。
所以准确的说法是:分块是通常实现走的那条路,不是数学上的唯一解。 —— ⭐ 这跟本讲反复出现的那条纪律是同一件事: 「做不到」和「这条路上做不到」是两句话。
知道了凭什么能分,剩下的就是硬件上怎么排:
chunkwise 的形式与并行度 / 串行步数的改变出自 DeltaNet 并行化那篇 Yang 等 arXiv 2406.06484(WY 表示 + 分块)
⚠️ 图里的 24 个 token / 每块 6 个是示意,真实实现的块长在几十到几百之间,随硬件变
⚠️ 「排队办事 / 交接单」是本课的比喻
⭐ 这一条可以迁移出去: 块大小被片上内存顶死,这跟本讲 §3.5 里 splash attention 的块大小是同一类问题 —— 而那一节已经证过一次:最优块是个绝对值,不随序列长度缩放 (seq 从 4096 拉到 16384,最优块都是 2048)。换 seq 不用重扫,换硬件才要。
配比与型号见本课那两张配比表(每一行都标了出处,多数可在公开 config 里核);系统性消融的建议区间出自 arXiv 2507.06457
「0:1 反而表现不好」出自 Kimi Linear arXiv 2510.26692 —— ⚠️ 原文只有一句定性描述,没有公开数值
NoPE 见 Kimi Linear 同文(对所有全注意力层用 NoPE)—— ⚠️ 是它先做的,K3 是沿用
⚠️ 「普通员工 / 资深」是本课的比喻
单用任何一个旋钮都有一个致命短板:
| 单用 | 短板 |
|---|---|
| SWA | 跨不了长距离 |
| 纯线性 | 精确检索塌 |
| 纯全注意力 | KV 和 FLOPs 都爆 |
混合的逻辑很朴素:全局层负责精确长程检索,线性/窗口层负责局部与效率,各司其职。 关键在于全局层不需要很多 —— 只要有几层能做无损检索, 信息就能沿着残差流传给其余层用。
📌 「残差流」是什么:主线图上每一层都有两处「+ 残差」—— 每一层不是把上一层的结果换掉,是在它上面「加一笔」。 于是从第一层到最后一层,有一条一路贯通、只被不断加料的通道, 这条通道就叫残差流。
⭐ 这正是「资深不用配很多」成立的原因: 某一层全注意力查到的东西,被加进残差流之后, 后面每一层都读得到 —— 不需要每层都自己再查一遍。
| 模型 | 配比 | 出处 |
|---|---|---|
| Kimi Linear | 27 层 = 每 4 层一个全注意力 + 末层再补一个,实际层数 KDA : MLA = 20 : 7。⚠️ 循环配比是 3:1,但一除是 2.857 —— 跟下面 K3 是同一回事,别写成 3:1 | arXiv 2510.26692 + HF configfull_attn_layers |
| Kimi K3 | 93 层 = 23 × (3 KDA + 1 Gated MLA) + 1 MLA —— 循环配比 3 : 1,实际层数 69 : 24。⚠️ 别写成「69 : 24 = 3 : 1」,一除就是 2.875 —— 末层补一个不是 K3 的花样,上面 Kimi Linear 也是这么排的 | arXiv 2607.24653 表 1 + sec. 2.1 |
| Ling-3.0-tiny | KDA : MLA = 3 : 1 | 模型卡 |
| Ling-3.0-flash | KDA : MLA = 5 : 1 | 模型卡 |
| 一篇系统性消融 | 建议区间 3:1 ~ 6:1 | arXiv 2507.06457 |
⭐ 三件事要讲清楚:
📌 Kimi Linear,arXiv 2510.26692v2 §5.2 Table 1 与同段正文(「16 heads, 16 layers」「same FLOPs budget」)—— 五个配比的训练 / 验证 PPL 均逐字抄自该表。
📌 系统性消融,arXiv 2507.06457 §4.2 正文与 Figure 3 描述 —— 语言建模「大体持平、各架构都在 0.55-0.57」、召回「从 0.1-0.35 涨向全注意力基线约 0.42」两句均为原文转述。⛔ 该文摘要里的「召回近乎翻倍」本图不引:只核到正文这段描述,没有逐格核过它的 Table 6 / 7。
📌 Kimi K3 的 93 层 / 24 层全注意力见本课那张配比表(arXiv 2607.24653 表 1 + §2.1)。「(r+1) 必须整除 16」是本课对该表做的算术观察,不是论文的说法。
⭐⭐ 顺着这张图,把上面第 1 条再往前推一格。 那五个配比看起来像是精心挑的,其实是被 16 除出来的 —— 消融模型只有 16 层,配比 r:1 要摆得匀,每组 (r+1) 层就必须整除 16; 16 的约数只有 1、2、4、8、16,于是 r 只能取 0、1、3、7、15, 正好就是表里那五行。
⛔ 所以 4:1 从来没被试过(它要 5 层一组,16 层摆到第 15 层多出一层), 2:1 也没有。「为什么是 3 不是 4」这个问题,那张表问不出来。
⚠️ 这是我们对那张表做的算术观察,不是论文给的理由 —— 原文没有解释为什么选这五个配比。
⚠️ 还有一个口径问题,跟 §9.1 那条判据是同一件事。 这张消融表量的是 PPL,而全场五个配比的验证 PPL 只从 5.65 到 5.82, 一共差 3%。
另一篇 340M / 1.3B 的系统性消融(arXiv 2507.06457)把两个口径分开画, 结论是一条平的、一条涨的:语言建模分各架构都挤在 0.55~0.57、几乎不受配比影响; 而召回(RULER)从纯线性的 0.1~0.35 一路涨到全注意力基线约 0.42, 多数架构在 3:1 追平或超过。它给这个结论起的标题是 「决定配比的是召回,不是困惑度」。
⭐ 换句话说:一个「最优配比」也得说清它是按哪个指标最优的 —— 跟 §9.1 说「一个倍数得说清它是哪一样的倍数」是同一条纪律。
上面那张消融表只在一个深度上扫配比,所以它答不了一个更基本的问题: 配比 3:1 是超参,还是「几层全注意力」才是超参?
⛔ 这个问题有实际后果。§8.1 写过「全局层不需要很多 —— 只要有几层能做无损检索,信息就能沿残差流传给其余层用」。 这句话按字面读,预测的是绝对条数与深度无关:模型越深,比值就该越大。 它是可以被证伪的 —— 去数配置文件就行。
📌 全部数据来自各模型 HuggingFace 仓库的 config.json 逐个字段:Kimi 两款读 linear_attn_config.full_attn_layers(27 层 7 个 / 93 层 24 个);Qwen3.5 七档读 layer_types 数组;MiniMax-Text-01 读 attn_type_list 求和;Granite / Bamba 读 layer_types 与 attn_layer_indices。
📌 Qwen3-Next(interval=4)、Jamba v0.1(period=8, offset=4)、Ring 两款(layer_group_size)是按字段推算的,推导链已写在图注与脚本头里。
⛔ 两个相关系数是本脚本当场算的;「⌊每 4 层一个⌋ + 末层补一个」这条排法由 assert 对着两份 config 的实际数组核过。⚠️ 只有两个点,把它当「观察到的排法」,不要当 Kimi 公布的规则。
⛔ 数完之后:那个字面读法是错的。
Qwen3.5 七个尺寸(深度 24 → 64)的 layer_types 逐层写死,
恰好 3:1 一次不差;Kimi 自己更是最干净的反证 ——
Kimi Linear 27 层 / 7 条,K3 93 层 / 24 条,
绝对数涨了 3.4 倍,比值纹丝不动。
所以「不需要很多」的准确意思是占比低(各家落在 1/4 到 1/10 之间), 不是绝对条数少。反过来说:模型越深,你要付的全注意力层就越多 —— 混合省下的是一个固定比例,不是「越深越划算」。
⭐ 但真实规则是两条叠加,不是单一的「按比例」:
① 主体按固定比例铺(3:1、7:1、每 10 层一个)—— 这部分随深度线性涨; ② 外加几个按「位置」钉死的全局层 —— 这部分是常数。
Kimi 两个模型都是「每 4 层一个 + 末层必为全局」。 正是末层那一个额外的,把实际比值从 3.0 压下来一点点: 27 层时是 2.857,93 层时被摊薄到 2.875 —— 表里那两个别扭的小数,是这么来的。
⚠️ 假说 B 也不是全无依据,但依据不在「数量」上。 Hymba 的全局层只有首 / 中 / 末三层,理论上多深都是 3 —— 但它是按位置定的,不是按数量定的,这是两回事。
⛔ 还有一个反面参照:MiniMax-M2 干脆退回了全注意力 (62 层全是 full attention)。混合不是一条只进不退的路。
Kimi Linear 就已经这么做了,K3 只是照搬:全注意力(Gated MLA)层完全不加位置编码(NoPE)—— 没有 RoPE,没有 YaRN,什么都没有。
四句原话均出自 Kimi Linear(arXiv 2510.26692):「we apply NoPE to all full attention (MLA) layers」·「delegates the entire responsibility for encoding positional information and recency bias … to the KDA layers」·「KDA is thus established as the primary position-aware operator」·「NoPE enables their conversion to the highly-efficient pure Multi-Query Attention (MQA) during inference」
⚠️ 图里 576 = 512 + 64 用的是 DeepSeek-V3 的形状(当尺子用,机制一样);Kimi Linear 自己那套 MLA 超参本课没有核过,别把这三个数安到它头上
⭐ K3 是照搬 Kimi Linear 这套做法,不是它先做的 —— K3 自己写的是「follows the hybrid design of Kimi Linear」
为什么敢这么做? 因为它们中间夹着的 KDA 层, 本身就是靠递归的衰减和门控在编码顺序 —— 一个天然带时序的算子。 位置信息由线性层提供,全注意力层只管检索。
两个后果,一个比一个实在(⚠️ 原先这里写「三个」—— 第三条已经在下面那个框里撤回了,数字忘了跟着改):
⛔ 这里原先还列了第三条「KV cache 最多降 75%」—— 那一条不是 NoPE 的功劳。 75% 来自 3:1 的配比(四层里只有一层是全注意力),跟加不加位置编码无关。 ⭐ 这个错误值得留在页面上:本节那张配比图的 ⚠️ 注早就写对了, 而正文没跟着改 —— 图改对了不等于文改对了,同一个事实有两个落点就会有两个版本。 1M 下 TPOT 从 11.48 ms 降到 1.84 ms(6.3×)这个数仍然成立,它记在配比头上。
⭐ 这才是"混合"真正的意思:不是"两个方案各跑一半凑合用", 而是让每一层只做自己擅长的事,然后把别人不用做的事一并省掉。 一个架构选择(混合)解开了另一个看起来完全无关的约束(位置编码)。 这门课想教的就是这种"看见约束之间的连接"的能力。
三个旋钮到这里就拆完了。这一小节反过来 —— 按公司排一遍,看每一家实际拧的是哪个旋钮。 都是能在公开 config 或官方博客里查到的,信息截至 2026-09-07。
| 家 | 代表型号 | 拧的是哪个旋钮 | 配比 / 形态 |
|---|---|---|---|
| 阿里 千问 | Qwen3-Next(80B/3B) | ③ 线性(Gated DeltaNet) | 3 : 1 |
| Qwen3.5(0.8B–397B) | ③ 线性 | 3 : 1,全家族统一 | |
| 月之暗面 Kimi | Kimi Linear(48B/3B) | ③ 线性(KDA) | 3 : 1 |
| Kimi K3(2.8T) | ③ 线性 + NoPE(沿用 Kimi Linear) | 93 层 = 23 × (3 KDA + 1 MLA) + 1 MLA | |
| 蚂蚁 百灵 Ling | Ling 2.6 | ③ 线性(Lightning) | 7 : 1 |
| Ling-3.0-flash(124B/5.1B) | ③ 线性(KDA) | 5 : 1 = 35 KDA + 7 MLA | |
| 小米 MiMo | MiMo-V2-Flash | ② 稀疏(SWA,窗口 128) | 5 : 1 |
| MiMo-V2.5-Pro | ② 稀疏(SWA,窗口 128) | 6 : 1 | |
| DeepSeek | V3.2 | ② 稀疏(DSA) | 层内稀疏 |
| V4 | ② 稀疏(CSA + HCA) | 层内稀疏,按距离分层压缩 | |
| MiniMax | 01 → M2 → M3 | ③ → 退回基线 → ② | 7 : 1 → 纯全 → 层内稀疏 |
⭐ 这张速查表里有两个型号值得单独点名,各代表一种「不走大路」的走法:
tpu/Hunyuan3-295B-Pretraining/)—— 全课唯一的一手反例。
然后 Hy4 一步跨到全层稀疏,整个跳过了线性这一支。① 腾讯混元 —— 跳过线性那一支,直接进稀疏
| 看哪一项 | Hy3(295B/21B) | Hy4-preview(770B/49B) |
|---|---|---|
| 发布 | preview 2026-04-23,正式版 2026-07-06 | 2026-08-28 |
| 层数 | 80 | 78 |
| 注意力 | 纯 GQA-8(64 头 / 8 KV 头,head dim 128) | 全部 78 层都是 Gated DSA(layer_types 全为 deepseek_sparse_attention) |
| 混合 | 没有 —— 不掺线性,不掺稀疏 | 不是层间混合,是层内稀疏;索引器 32 头 × 128 维,top-k 2048 |
| 上下文 | 256K | 1M |
| 另外 | 192 专家 + 1 共享,top-8 | 256 + 1 共享 top-8;iHC(4 条残差流);gated_mla;IndexCache |
② 智谱 GLM —— 半年之内走完三步,而且步步可查
| 版本 | 时间 | 注意力 | 这一步新增了什么 |
|---|---|---|---|
| GLM-5(355B–744B) | 2026-02-12 | MLA + DSA | 智谱第一次上稀疏 |
| GLM-5.2(744B) | 2026-06-16 | MLA + DSA + IndexShare | 每四个稀疏层共用一个索引器(见 §6.5b),1M 下省 2.9× FLOPs |
| GLM-5.3-Flash(320B/18B) | 2026-08-26 | KDA 线性 + NoPE 稀疏 MLA | ⭐ GLM 家族第一次把线性和稀疏放进同一个模型;原生多模态 |
GLM-5.3-Flash 的 layer_types 是一个干净的四层循环:
linear, linear, linear, deepseek_sparse_attention, ← 重复 11 次
linear ← 第 45 层多出来的一层
45 层 = 34 层 KDA + 11 层稀疏 MLA 循环配比 3 : 1
下面三个机制,本讲都提到过但没有明确归位。 每一个只回答三问,各一个词:
| 题 | 机制 | 本讲在哪儿提过 |
|---|---|---|
| A | CLA —— 每 2 层共享同一份 KV | 44 行表里有一行 |
| B | KV 量化 —— 每个数从 16 bit 降到 8 bit | §四 那个 ⚠️ 框 |
| C | IndexShare —— 每四个稀疏层共用一个索引器 | §6.5b |
| 哪个旋钮 | 事后 / native | 省字节还是 FLOPs | ⭐ 这题在考什么 | |
|---|---|---|---|---|
| A · CLA | ①(少存几份) | native | 字节 | 旋钮① 的第三招:MQA 砍头数、MLA 压维度、CLA 减层数 —— 三招都在回答「那份要留下来的有多大」 |
| B · KV 量化 | 都不是 | 两种都有 | 字节 | ⛔ 这是个陷阱题,而且是§四主动说掉的那个: 三个旋钮管「存几个数、读几个数」,量化管「每个数几个 bit」—— 它正交 |
| C · IndexShare | 都不是 | native | FLOPs | ⭐ 最难的一道:它不改「读哪些」,它省的是旋钮② 自己的开销 (索引器)—— 所以它是旋钮上的优化,不是旋钮。 ⚠️ KV 一个字节都没少 |
⭐⭐ 三道里有两道答案是「都不是」—— 这是故意的。 一张分类表真正的用处不是「什么都装得下」,是让装不进去的东西显形。 §4.2b 那句话反过来说一遍就是: 放不进去的,才值得你花时间。
一张表把所有方案摆在一起:
| 方案 | KV 显存 | 计算量 | ⭐ 省在哪个阶段 | 长程质量 | kernel 复杂度 | 能否给已有模型打补丁 |
|---|---|---|---|---|---|---|
| MHA | 基准 | 基准 | — | 基准 | 简单 | — |
| GQA | ↓↓ | — | decode(省带宽) | ↓ | 简单 | 需微调 |
| MLA | ↓↓↓ | ↑(训练时) | decode(训练前向反而更贵) | ≈ | 中 | 不能 |
| SWA | ↓↓↓ | ↓↓ | 两边都省 | ↓↓↓ | 简单 | 勉强(要留 sink) |
| DSA | —(KV 全存,只是不读) | ↓↓↓ | prefill 为主(decode 省的是读) | ≈ | 高 | 需专门训练阶段 |
| CSA/HCA | ↓↓↓ | ↓↓↓ | 两边都省 | ≈ | 很高 | 不能 |
| 线性(KDA 等) | 无 KV,但有固定状态 | ↓↓↓ | decode(prefill 要 chunk 化才不亏) | ↓↓ | 很高 | 不能,必须从头训 |
上面这张表每一格都是箭头。箭头能排序,但它回答不了「省的是哪一样」 —— 而那正是本节唯一想说死的那条。所以把同一批方案,换成一个能拿计算器核的问题: 吐一个字,要从 HBM 上搬多少字节?
装置偷自 Epoch AI 那条「字节 → 毫秒 → 钱」的换算链 —— 抽象的「省了多少」一路换算到有直觉的量,每一步都能自己验算。本图走到毫秒为止(再往下换成钱要报价,那个本课核不了)
⚠️ 数全部来自本讲前面已核过的:KV 488 / 30.50 / 3.81 / 8.58 GiB(61 层 · 128K · bf16 · 一个用户)· 权重 625 GiB · 每 token 激活 37B(671B/37B,官方模型卡,见开篇那张年表)· v7 每 chip 7.37 TB/s、每 device 94.74 GiB
📌 这张图不引入任何新数,全部是前面核过的数做除法 —— 所以读者可以拿计算器逐格核,这也是它敢把那张「每一格都是箭头」的代价表替换掉的底气
三行收口,都能自己验算:
⚠️ 这笔账只成立在 batch = 1 上。 权重那一段是所有人分摊的,KV 那一段不摊 —— 人一多,前者被摊薄、后者成倍长,画面会翻回 KV 主导。
⭐ 这正好是 §二那张图的另一面:那里是把人加上去, 让 KV 变成主角;这里是只留一个人,让权重变成主角。 同一个模型,问法不同,答案就不同 —— 这本身就是本节的主题。
⭐ 这张表要配着 §十那张落点图 的第二格看 —— 那里把四个最容易被低估的取舍画在了一起: 省显存 ≠ 省计算 · 训练时省 ≠ 推理时省 · 不规则访存的代价常被低估 · 收益有天花板。
⭐ 这一节唯一要在这儿说死的是提问顺序: 问「省了多少」之前,先问「省的是哪一样」。 这张表的每一列,就是一样不同的资源。
回到全课那条主线:每一个变体都是被硬件逼出来的,也都对硬件提出了新要求。
四个取舍与硬件假设表见 §九 / §十 正文(每条都可追到前面对应小节)
③ 的三根条由本脚本当场算并断言,公式与常数同专题一那条曲线(V3:61 层 / 128 头 / MoE top-8+1 共享,因果掩码按半算)
⚠️ 它是 FLOPs 口径,不是时间 —— 同样 seq=4096,本讲实测(Hunyuan3-295B · v7 64 芯片 · XProf op stats 页)splash attention 占 23% 的时间、效率只有 35.5%;效率最低的算子,时间占比一定高于算力占比⚠️ 这是量级示意,具体占比随模型结构、批大小、序列长度变
⭐ §9.1 刚给这条判据补上了另一半: 一个倍数除了要带「在多长的上下文下」,还得说清它是哪一样的倍数。
同一个 MLA:显存 56.9×,单用户 decode 的时间只有 7.08×。 两个数都对,而它们差了整整八倍 —— 报哪一个,取决于你想让听的人以为你省了多少。
| 变体 | 它假设了什么硬件条件 | 条件不成立会怎样 |
|---|---|---|
| MLA | 算力相对充裕、显存相对紧张 | 算力紧张的机器上,用计算换显存这笔交易不划算 |
| 稀疏(DSA/NSA/CSA) | gather 不太贵 | 对规整访存友好的加速器反而吃亏 —— 纸面 64 倍拿不到 |
| 线性(KDA) | 片上内存够放下 chunk 的中间量 | chunk 被迫调小 → 并行度掉 → 优势被吃掉 |
| 长上下文 + MoE 同时上 | HBM 带宽够两边分 | all-to-all 与 KV cache 抢同一份带宽 |
那张表里「稀疏(DSA/NSA/CSA)假设 gather 不太贵」这一行, 在 TPU 上就是一整个工程战场。而这恰好是这门课唯一有资格讲、别人讲不了的部分 —— 所以它值得单独占两张图。
三处疼与那句摘要出自 Ragged Paged Attention(Jiang 等,arXiv 2604.15464,2026-04)§1 与 §2.4;TPU 的三条约束亦见该文 §1
⚠️ 「中央厨房 / 点单现做 / 仓库取货」是本课的比喻 —— 论文那侧的说法是 static-first 编译、tiled 粗粒度布局、以及「从动态算出来的不连续地址 gather」
⚠️ 先把「谁是给谁设计的」说清楚,免得听成 TPU 的黑历史。 这一讲从 §四 到 §八 讲的每一个机制, 它们的第一版 kernel 全部是在 GPU 上写出来的 —— FlashAttention、PagedAttention、NSA 的三支路、DSA 的 indexer,无一例外。 所以「搬到 TPU 上有难度」不是 TPU 的缺陷,是这批机制自带的一条硬件假设: 随手 gather 不太贵。 ⭐ 而这条假设,正是 TPU 为了换取规整访存下的高效率而主动放弃的。
三招、「SREG 在计算密集阶段欠用」、以及 MBU 86% / MFU 73%,均出自 Ragged Paged Attention(Jiang 等 arXiv 2604.15464)§3–§5
SparseCore 的定位与「必须声明静态上界、超了就 mini-batch 或丢 ID」出自 openxla.org 的 SparseCore 公开文档
⚠️ 「中央厨房 / 前台后厨 / 拣货小队」是本课的比喻;⚠️ 跨层共享在 TPU 上更值钱那一条是本课的推导,无公开对照实测
⭐⭐ 「能不能在卡里边完全算完、要不要发回 CPU」这一问,答案是分两层的 —— 别答成一个字。
| 哪一层的决定 | 谁来算 | 频率 | 为什么放在这一层 |
|---|---|---|---|
| 批次级:这一步有哪些请求、各自多长、页表长什么样 | host CPU(服务框架) | 每步一次 | 它本来就是调度器的产物,而且一步只算一次,摊到几千个 token 上可以忽略 |
| token 级:这个 query 要读哪 2048 条、对应哪些 HBM 地址 | 卡上的标量单元 | 每 token 每层 | 发回 host 是不可能的 —— 一次 PCIe 往返以微秒计,而这一步的预算是几十微秒 |
⭐ 所以准确的说法是:top-k 那个「决定」不出卡;出卡的只有本来就在 host 上的批次级元信息。 ⛔ 不要说成「全在卡上算」—— 页表是 host 给的;也不要说成「要发回 CPU」—— 逐 token 的地址计算发回去一次就废了。
⭐⭐ 跨层共享那一支(§6.5b 的 IndexShare / IndexCache), 在 TPU 上比在 GPU 上更值钱 —— 这是一条本课的推导,写清楚它多省的是什么:
⭐ 最后那条是这一节真正想留下的判据: 在一台 static-first 的机器上,动态性的次数本身就是成本 —— 不只是每次动态有多贵。 ⚠️ 「TPU 上额外更值钱」是本课从 RPA 那篇描述的机制推出来的, 没有公开的对照实测;GLM-5.2 的 2.9× 是 FLOPs 口径、且不是在 TPU 上测的。
⭐ 段尾:这一节把全部机制放回了它们出生的那台机器上 —— 你会发现同一个聪明办法,换台机器就要重新算一遍值不值。
⭐⭐ 下一节只剩最后一件事:回到开场那句话, 把我们许下的那笔账算完。
前面那些节讲的是谱系(可迁移的判断框架),这一节是时间线(记忆的挂钩)。 顺序不能反 —— 先给框架,时间线才有意义;先给时间线,框架就变成了流水账。
🖥 回到开篇那张编年史图(三条泳道那张)—— 这一节不需要新画面,需要的是一个新问法。
⭐ 试着自己先从那条线上读出三件事,再往下看:
开场我们说:2020 年的 GPT-3 记 2048 个 token,今天的模型记 100 万 —— 这一讲讲的就是这 512 倍 是怎么换来的。 现在把这笔账算完。
488 GiB / 8.58 GiB(61 层 · 128K · bf16 · 一个用户)· 3:1 配比 · k=2048 这四个数,本讲前面都已逐一核过并标了口径,这一张只做乘除
⭐ 两个 512 为什么会相等,说破了很简单:GPT-3 的上下文长度(2048)和 DSA 的 k(2048)恰好是同一个数 —— 两条完全不相干的设定撞成了一个数,所以「今天 ÷ 当年」和「今天 ÷ k」才算出同样的 512。⚠️ 这是巧合,不是什么规律,但它让这笔账好记得多。脚本里两个等式都断言了
★ 那 512 倍,是这么换来的
不是内存变大了。显存这一笔,是把每个 token 的开销压了 227.5 倍 (旋钮① 的 56.9× × 混合 3:1 的 4×)—— 要还 512 倍,还上了 227.5 倍,剩下那 2.25 倍,才是真正多买的硬件。
也不是带宽变快了。带宽这一笔,是干脆不读了 —— 稀疏注意力每步固定只看 2048 个, 上下文涨了 512 倍,它一个都没多读。
⭐⭐ 一句话收:这六年真正变的不是机器, 是「一个 token 到底该花多少钱」这件事被重新定价了。
★ 还有一条线,到这儿也该合上了
§1.4 我们认了一个主角 —— 那个 S,
张量形状里唯一会越变越长的一维。
之后每一节做的都是同一件事:旋钮① 让 S 前面的系数变小, 旋钮② 让每步读到的 S 变少,旋钮③ 干脆让 S 从形状里消失。 混合是把三个答案摆在不同的层上。
⭐⭐ 所以下次再看到一个没听过的注意力名字,你只需要问它一句: 你是在跟这个 S 讨价还价,还是打算把它请出去? —— 答得上来,它就已经在你这张地图上了。
📌 顺手看一眼这笔账是怎么算出来的 —— 它就是这门课的方法
它不是一笔账,是两笔:显存账问「一个人要占多少」,带宽账问「每走一步要读多少」。 而三个旋钮各还各的那一笔 —— 旋钮① 只还显存,旋钮② 只还带宽(它一个字节都不省显存), 旋钮③ 两笔一起还。
⭐ 所以 §九 那句「问『省了多少』之前,先问『省的是哪一样』」 不是一句方法论口号 —— 不分开问,这两笔账根本对不上。
⭐ 整页所有 arXiv 编号都是可点的 —— 正文里的、表里的、连图上那些小字出处,点一下直接开论文。 (图里的链接带下划线 —— SVG 的链接不像网页那样自动变蓝, 所以特意标出来。)
⚠️ 几个不是 arXiv 的,在表里单独挂了链接: DeepSeek-V3.2-Exp 技术报告(GitHub)、NVIDIA 的 RNN 性能指南、 张量形状记号沿用的 How to Scale Your Model。 各家模型的 config 没挂链接 —— 它们在各自的 Hugging Face 仓库里, 版本会动,写死一个链接迟早指到改过的那一版。
| 要什么 | 在哪 |
|---|---|
| MQA / GQA | arXiv 1911.02150 / 2305.13245 |
| MLA | DeepSeek-V3, arXiv 2412.19437 sec. 2.1 + 4.2(超参那段给了 n_h/d_h/d_c/d_h^R 的准确值) |
| Gated MLA / K3 全貌 | Kimi K3, arXiv 2607.24653 sec. 2.1.2、表 1(93 层 / 69 KDA + 24 MLA / 2.78T-104.2B) |
| SWA | Mistral 7B, arXiv 2310.06825(窗口 4096) |
| Attention sink | StreamingLLM, arXiv 2309.17453(4 个 token / 400 万 / 22.2×) |
| NSA | arXiv 2502.11089(三支路 + 门控;64k 下 11.6× / 9.0× / 6.0×) |
| DSA + Lightning Indexer | DeepSeek-V3.2-Exp 技术报告 sec. 1–2.1(ReLU 打分 / FP8 / k=2048 / 稠密预热阶段 / KL 对齐)—— ⚠️ 不是后来那篇 arXiv 2512.02556(《DeepSeek-V3.2》),两者节号对不上;本讲图里核的数全部来自 Exp 那份。我们有一手实测 |
| CSA / HCA | DeepSeek-V4, arXiv 2606.19348 sec. 2.3 + 2.3.4(m=4 / m′=128 / top-k / 27%·10% / 2%) |
| 线性注意力谱系 | 2006.16236(线性)→ 2102.11174(delta rule, 2021)→ 2406.06484(可并行化)→ 2412.06464(GDN)→ 2510.26692(KDA) |
| 混合配比 | 2510.26692(3:1 + 消融)、Ling-3.0 模型卡(3:1 / 5:1)、2507.06457(建议 3:1~6:1) |
| FlashAttention | arXiv 2205.14135 + TPU 侧 Splash Attention 实测(tpu/ 下多处) |
| MHA 本体(§一) | Vaswani et al. 2017, arXiv 1706.03762 —— sec. 3.2 / 3.2.1 / 3.2.2 / 3.2.3 + 表 1;四条原话见下方折叠 |
| KV cache 被点名成瓶颈 | Shazeer 2019, arXiv 1911.02150(MQA 那篇)—— 「memory-bandwidth cost of repeatedly loading the large keys and values tensors」 |
| RNN 一支(§零) | Elman 1990《Finding Structure in Time》;Bengio, Simard, Frasconi 1994;Hochreiter & Schmidhuber 1997;Cho et al. 2014;Bahdanau et al. 2014, arXiv 1409.0473 |
| 只有线性依赖才扫得动 | Martin & Cundy 2018, arXiv 1709.04057(ICLR'18)—— 实测最高 9× 加速 |
| RNN 在硬件上为什么慢 | NVIDIA《Recurrent Layers User's Guide》—— 「a GEMM with one dimension of one」、「can combine these GEMMs over the minibatch size, but not over different sequence steps」 |
| 我们自己的 kernel 实战 | Tokamax KDA kernel、tpu/ 下 DSA 相关 |
| §2.1 那张 KV cache 对照表 | 自己按公式推的:2·n_h·d_h·L 与 (d_c+d_h^R)·L,输入全部来自 V3 论文 sec. 4.2。口径(K/V 都按 d_h=128)要在讲的时候声明 |
| 这一讲怎么讲的 | 论文原话 |
|---|---|
| 「query 问、key 挂牌、value 是货」不是我们编的比喻 | 「mapping a query and a set of key-value pairs to an output … the output is computed as a weighted sum of the values, where the weight assigned to each value is computed by a compatibility function of the query with the corresponding key.」(sec. 3.2) |
| √d_k 别只说「防止 softmax 饱和」,那是结论不是理由 | 「for large values of d_k, the dot products grow large in magnitude, pushing
the softmax function into regions where it has extremely small gradients.」
(sec. 3.2.1) ⭐ 理由在脚注 4:q、k 各维独立、均值 0、方差 1 时, q·k 均值 0、方差 d_k —— 标准差就是 √d_k。 |
| 多头不是不够用,是会把该分开的关注平均掉 | 「jointly attend to information from different representation subspaces at different positions. With a single attention head, averaging inhibits this.」 (sec. 3.2.2;h = 8,d_k = d_v = d_model/h = 64) |
| §零那张「解码时又变回 RNN」图 Ⓒ 那一行不是我们的推论,是原文 | 「training these layers is generally fast and simple, due to parallelizability
across the length of the sequence, incremental inference (where such parallelization
is impossible) is often slow, due to the memory-bandwidth cost of repeatedly loading
the large "keys" and "values" tensors.」(Shazeer 2019 摘要) ⭐ 2017 年造出这个形状,2019 年就有人把它命名成问题了;那篇给的解法 MQA 正是本讲模型表的第二行。 |
📌 记号约定:§X.Y 是本课的小节号,
sec. X.Y 是被引论文自己的小节号。
⛔ 两者曾经用同一个记号,结果整体重编号时把论文的节号也改了 ——
而且改完恰好落在真实存在的本课小节上,体检全绿。
← 回 课程总纲 · 硬件背景在 专题二 · TPU 与 GPU · 量化那一支在 专题八 · 精度与量化 · 📝 讲义
本页由 Courses/tools/topic03-build-L300.py 生成 ——
正文就写在那个脚本的 BODY 里(md 只是大纲)。本目录采用 CC BY-NC-SA 4.0。