加速器系统课程 / 主线 / 专题三 / 注意力演进

注意力演进

A Chain, Cut Loose, and Coming Back Around
2020 年的 GPT-3 只记得住 2048 个 token;今天的模型记 100 万
—— 这六年的注意力演进,讲的就是这 512 倍 是怎么换来的。

这一讲按时间顺序讲一个完整的故事:一条链怎么被剪断、 剪断之后欠下了什么账、这笔账后来被四拨人用四种办法还、 以及为什么最后又绕回了那块固定大小的小本子多图、少字、说大白话。

前置 专题一 · 专题二 读法 从头顺着读 要推导与实测 看 L300

课程作者 Chris Yang·Google Cloud AI Infra 架构师

第 零 节

先把这八年整个摊开看一眼

一个数据中心的机架上躺满了人,屏幕显示 1 ExaFLOPS/Brain、MFU 19%、20W/Brain
⭐⭐ 这一讲会一直并排跟着两条线:模型是怎么记事的,和人是怎么记事的 —— 因为这一路省下来的每一招,最后都在向同一个方向靠。
而这张图上有三个数,只有一个经得起查 —— 先把它们查一遍,这就是本课的规矩。

⛔⛔ 开场第一分钟,先把这三个数审一遍。

  • 20 W/脑 —— 站得住而且有生理学依据: 大脑占体重约 2%,却占掉两成的代谢。十几到二十瓦,是个能引用的数。
  • 1 ExaFLOPS/脑 —— 不能当数用大脑「等效算力」的公开估计 横跨 10¹² 到 10²⁸十六个数量级。 1 ExaFLOPS 只是这条巨大区间里的一个点,不是共识。
  • MFU 19% —— 纯属玩笑MFU 的分母是芯片的标称峰值, 而大脑没有 spec sheet。这个数连定义都不成立。

⭐⭐ —— 一张图上三个数,一个能引用、一个只能当量级、一个根本不成立。 这一讲从头到尾都这么读数。

⭐⭐⭐ 而这张图真正要立的,是这一讲的主线: RNN 那条路,像人脑的思考方式;Transformer 那条路,像机器的思考方式。
一个省电、省地方,一个快、能并行 ——  而这也正是人和机器的区别本身。

2020 年的 GPT-3,一次只能记住 2048 个 token。那时候它是个很会接话的 补全器 —— 你给一段,它接一段,接得挺像样。

今天你让它干的活完全变了:读完一整个代码库再回头改对一处 bug、 一口气走完几十步的工具链、还得记得住你半小时前改过主意2K 的上下文,连一个文件都读不完。

⭐⭐ 上下文长度就是模型的工作记忆。 记不住,就什么都干不成。
⛔ 而每记住一分,要付的钱是实打实的显存 ——  模型每吐一个字都要回看前面所有字,于是把每个字算出来的 K、V 存着不重算, 存下来的这一堆就叫 KV cache,它跟着上下文线性长。
⭐ 所以这六年注意力的全部演化,只在做一件事:让「记得住」这件事付得起。

⛔ 课前勿点 —— 开讲前的两道热身题现场会一起做;提前看答案=自己剧透,这两道题就废了

第一题 · 一个用户的 KV cache,到底有多大

DeepSeek V3(671B,61 层,128 个注意力头,head_dim 128), 128K 上下文、单个用户、bf16 存
同样这个形状,换四种注意力,KV cache 各是多大? 四行分开选,各选各的。

(a) 最朴素的 MHA(128 个 KV 头)
(b) GQA-8(KV 头砍到 8)
(c) MQA(KV 头砍到 1)
(d) MLA(V3 真实用的方案)

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) 对 HBM(片外)
(b) 对 VMEM(片上)

(a) 约 313。(b) 比 HBM 低一个量级,落在几十这一档。
(a) = 2,307 TFLOP/s ÷ 7.37 TB/s。 这个数不是本讲新造的,它就是专题二整整一节在立的那条屋脊线。

⭐ (b) 最容易选反,而选反的人通常是把「快」和「门槛高」搞混了: 片上更快,所以分母变大 ——  同一个分子除以更大的分母,商只会更小越靠近计算,这条线越低。 门槛低意味着:同一个算子挪到片上以后,更容易变成算力受限。

⛔ 而 (b) 为什么只给量级、不给数 —— 这才是这道题最想教的一件事VMEM 的带宽官方没有公开。 而屋脊点乘以算力就等于带宽 —— 给出一个精确的屋脊点, 等于把那个没公开的数反推出来。所以我们到「几十这一档」为止,不往下猜
⭐ 这正是专题二第 6 节那四句问法里的一句: 先问这个数的出处和口径,再用它。 而那四句不是拿来审别人材料的 —— 是先拿来审自己的。

先看一眼这八年的形状 —— 四条支线各修各的毛病(横轴时间线)。 可以不点:下面那张按模型排的表,信息更全、也更好读。

⭐ 不过在开始之前,先把这八年真实发生过的事,整个摊开看一眼。

Attention 编年史 —— 从 RNN 的一个补丁,到今天各家的混合配比 ⭐ 从 2014 年到今天,这一路在干的其实是一件事 —— 现在不用懂,最后一章会回来收这句 Transformer 当初做的交易是「用平方的计算量,买来完全的并行度」—— 而这段历史,是在把那笔交易往回赎,但不能把并行度还回去。 (信息截至 2026-09-07,全部现搜) 一、编年史 —— 四条支线,各修各的毛病 (横轴按每年的内容疏密压缩过:没有事件的年份收窄、事件多的年份放宽 —— 间距不代表时间长短 '14 '15 '16 '17 '18 '19 '20 '21 '22 '23 '24 '25 '26 前史 · 注意力是 RNN 的补丁 Bahdanau 注意力 ⭐ Transformer / MHA ① 每个 token 存多少(KV 怎么小) MQA GQA MLA CLA 跨层共享 MFA 低秩分解 Gated MLA · K=V 共享 ② 每个 query 看多少(稀疏 · 压缩) SWA · sink NSA · DSA CSA+HCA · MSA IndexShare · IndexCache ③ 换一套数学(线性注意力 · SSM 同源) 线性 Transformer DeltaNet Mamba Mamba-2(SSD)· 可并行 DeltaNet · GDN KDA · Lightning Mamba-3 · Gated DeltaNet-2 ⊘ 不在旋钮上:实现层(不改数学,只改怎么算) ⭐ FlashAttention ⭐ 全图落点:三家公司,各自把旋钮拧了一遍 —— 而且拧的过程全写在公开的 config 里 MiniMax 线性(01)→ 退回全注意力(M2)→ 稀疏(M3)—— 三次转向,每次都公开写了为什么 腾讯混元 Hy3 80 层纯 GQA-8(连线性都没上)→ Hy4 78 层全 Gated DSA—— 跳过线性那一支,直接进稀疏 智谱 GLM 5 上 DSA → 5.2 加 IndexShare → 5.3-Flash 第一次线性+稀疏同锅—— 半年走完三步 ⭐⭐ 再看一眼第二列:这次补完混元和 GLM,多出来一条原先看不见的线 混元 Hy4 的 IndexCache 和 GLM-5.2 的 IndexShare 是同一个想法:两家的 indexer_types 都是 full, shared, shared, shared 稳态四层一循环(前几层各家不同)—— 每 4 层只有 1 层自己算索引。 ⭐ 所以稀疏的第二阶段优化,已经不是「让每个 query 少看几块」,而是「别每层都重新算一遍该看谁」—— 索引本身变成了新的开销。这是 2026 年才冒出来的一层。
这是从 2014 年到今天真实发生过的事,四条支线各修各的毛病。不用现在看懂 —— 先看清一件事:它有多乱。
⭐⭐ 这一讲要做的,就是把这团乱麻理成一条能从头读到尾的线;而读完之后你再回来看它,会看见完全不同的东西。

请记住你此刻看它的感觉。 读到最后一章,我会请你滚回来。

这一节没有新知识,它只是一张地图 ——  所以它不编到正课里,叫第零节它唯一的作用,是让你在最后那一章有一个回得去的地方

再看一眼具体的账 —— 这些名字你多半都听过。 同样现在不用看懂:先看清就行 ——  一个循环里几层便宜的、上下文多长、KV cache 多大。 这一讲结束时,你会一行一行读下来。

📌 这张表的取数规则(⭐ 2026-09-13 补 —— 由表长出来的结论全靠这条规则撑着)

  • 层配比 / 层数 / 头数 / 维度:一律读公开 config(HF 上那份 config.json)。
  • 上下文:取 config 的 max_position_embeddings。⚠️ 它是容量不是能力 —— 声明 10M 不等于 10M 上都好用。
  • KV cache:一律按 @128K · bf16 · batch 1 由形状当场算出,不取厂商自报值。⚠️ 滑窗模型给的是窗口封顶后的量。
  • 三处例外,各自标了:混元 Hy3 那一行读自我们自己的仓库;Ling 一族读自模型卡;两处声明与实测口径不一致的(MiniMax-01 的 4M / config 10M)在备注里写了。

⚠️ 凡是上下文 < 128K 的行,那一格的 KV 是反事实值(把它的形状放到 128K 上算)—— 包括「846 倍」的分子 GPT-3,它的实际上下文只有 2K。这个倍数是用来看量级的,不是用来引用的。

点表头可以排序—— 时间 / 厂商 / 便宜层占比 / 上下文 / KV 大小,再点一次反向。默认按时间。两种模式下排序都作用在全部 44 行上。

时间模型 按厂商一个循环 一格=一层上下文 声明值KV cache@128K备注
2020-05GPT-3 175B 稠密 · 96 层MHA —— 每一层都是这个2K576 GiB(10 条才画得下)基线:KV 按头数线性长,没有任何省法起点。576 GiB 这把尺,后面所有省下来的都拿它比
2022-04PaLM 540B 稠密 · 118 层MQA —— 每一层都是这个2K15 GiB48 头共用 1 组 KV —— 第一次大规模砍 KVMQA —— 第一次把 KV 砍到只剩 1 组
2023-02Llama 1 65B 稠密 · 80 层MHA —— 每一层都是这个2K320 GiB(6 条才画得下)一代还是纯 MHA,下一代才上 GQA
2023-07Llama 2 70B 稠密 · 80 层GQA —— 每一层都是这个4K40 GiBMQA 砍太狠掉质量,GQA 是折中(arXiv 2305.13245GQA —— 砍到 1 组太狠,折中版成了此后十年的默认
2023-09Mistral 7B 7B 稠密 · 32 层SWA —— 每一层都是这个32K512 MiBSWA 进主流的第一枪,窗口 4096SWA —— 「不看全部」这个想法的起点,窗口 4096
2024-03Jamba 52B/12B · 32 层MambaMambaMambaMambaMambaMambaMambaGQA256K2.0 GiB⭐⭐ 层间混合的开源起点,比 MiniMax-01 早十个月层间混合的开源起点,7:1 —— 比 MiniMax-01 早十个月
2024-05DeepSeek-V2 236B/21B · 60 层MLA —— 每一层都是这个128K8.4 GiB低秩压缩。KV 降 93.3%=只剩 2.25 组 GQAMLA 首发。换了个思路:不砍头数,改存压缩过的隐向量
2024-06Gemma 2 27B 27B 稠密 · 46 层SWAFULL8K24 GiB⭐ 谷歌开始交替:1:1,窗口 4096
2024-07Llama 3.1 405B 稠密 · 126 层GQA —— 每一层都是这个128K63 GiB(2 条才画得下)不上花招硬推 128K 的代价:比 Llama 2 还多
2024-11混元 Hunyuan-Large 389B/52B · 64 层GQA —— 每一层都是这个128K10 GiBCLA:每 2 层共享一份 KV —— 旋钮①的第三招
2024-12DeepSeek-V3 671B/37B · 61 层MLA —— 每一层都是这个160K8.6 GiB专题一的锚点。跟 V2 只差 1 层,KV 几乎相同跟 V2 同机制,但参数大 2.8 倍而 KV 只差 1.7% —— KV 脱钩的证据
2025-01MiniMax-01 456B/45.9B · 80 层LTNLTNLTNLTNLTNLTNLTNGQA4M5.0 GiB⭐ 线性首次上旗舰。训练 1M、外推 4M(config 的 10M 是容量)线性第一次上到几百 B 的规模,同样是 7:1
2025-03Gemma 3 27B 27B 稠密 · 62 层SWASWASWASWASWAFULL128K10 GiB⭐⭐ 5:1、窗口 1024 —— 小米那个 5:1 不是首创滑窗混合定型:5 层窗口配 1 层全局,窗口反而收到 1024
2025-03RWKV-7 Goose 0.19B–2.9B · 纯 RNNRWKV —— 每一层都是这个无限(理论)0(无 KV)全表唯一 KV 为零:常数内存、常数单 token 时间另一头的极端:纯 RNN,KV cache literally 是 0
2025-04Llama 4 Scout 109B/17B · 48 层SWASWASWAFULL10M7.1 GiB块状局部 8192 + NoPE 全局。⚠️ 声称 10M,又一个报容量的
2025-04Qwen3-235B-A22B 235B/22B · 94 层GQA —— 每一层都是这个40K24 GiB千问转线性之前的那一代:纯 GQA-4
2025-07Kimi K2 1T/32B · 61 层MLA —— 每一层都是这个128K8.6 GiBKimi 上 KDA 之前:纯 MLA,架构名就是 DeepseekV3
2025-07GLM-4.5 355B/32B · 92 层GQA —— 每一层都是这个128K46 GiB⭐⭐ 智谱上 DSA 之前:46 GiB → GLM-5 的 11,降 4 倍
2025-08gpt-oss-120b 117B/5.1B · 36 层SWAFULL128K4.5 GiBOpenAI 首个开放权重:1:1 交替、窗口 128 + sink
2025-09DeepSeek-V3.2-Exp 671B/37B · 61 层DSA —— 每一层都是这个160K8.6 GiB稀疏的起点:V3 + Lightning Indexer。⛔ KV 跟 V3 一样DSA —— 层内稀疏起点:不是少几层,是每层只挑一部分 token 看
2025-09Qwen3-Next 80B/3B · 48 层GDNGDNGDNgAT256K3.0 GiB36 线性 + 12 全注意力(GQA-2,头维 256)GDN —— Mamba 那一支的直系后代第一次进主流大模型
2025-10Ling-1T(Ling 2.0) 1T/50B · 80 层GQA —— 每一层都是这个32K40 GiB⭐⭐ Ling 2.6 就是从它改造的:40 GiB → 1.4 GiB
2025-10MiniMax M2 230B/10B · 62 层GQA —— 每一层都是这个192K31 GiB「退回全注意力」≠ 什么都没做:GQA-8 + partial RoPE反例:退回全注意力。证明「全注意力」说的是旋钮③,不是①
2025-10Kimi Linear 48B/3B · 27 层KDAKDAKDAMLA1M1008 MiB20 KDA + 7 MLA(末层强制 full)。已用 NoPEKDA —— 线性的新一代,配 NoPE 的 MLA
2025-12DeepSeek-V3.2 671B/37B · 61 层DSA —— 每一层都是这个160K8.6 GiBExp 转正。index_topk 512 → 2048,KV 与 V3 一样Exp 转正:稀疏从实验走进生产,top-k 512 → 2048
2025-12Mistral Large 3 675B/41B · 61 层MLA —— 每一层都是这个288K8.6 GiBMLA 超参跟 V3 逐字段一样;参数读自 params.jsonMLA 扩散到了西方:欧洲最大开源旗舰逐字段照抄 V3 的 MLA 超参
2026-01小米 MiMo-V2-Flash 309B/15B · 48 层SWASWASWASWASWAFULL256K5.0 GiB窗口 128。卡上自称 KV 省近 6×,48÷8 正好对上
2026-02GLM-5 744B/40B · 78 层DSA —— 每一层都是这个198K11 GiBMLA + DSA。GLM-5.1 同架构,只有后训练不同
2026-02MiniMax M2.5 230B/10B · 62 层GQA —— 每一层都是这个192K31 GiB⚠️ 架构与 M2 逐字段相同,稀疏要等 M3
2026-03Qwen3.5 397B/17B · 60 层GDNGDNGDNgAT256K3.8 GiB45 线性 + 15 全(config: full_attention_interval 4)千问把混合注意力从旁支 Qwen3-Next 收进了主线
2026-04小米 MiMo-V2.5-Pro 1.02T/42B · 70 层SWASWASWASWASWASWAFULL1M6.3 GiB60 SWA + 10 全,窗口 128 —— 1M 那档最省的
2026-04DeepSeek-V4-Pro 1.6T/49B · 61 层HCAHCACSAHCACSA1M999 MiB⛔ 跟 Flash 不同:前两层是 HCA。1.6T 而 KV 不到 1 GiB
2026-04Ling 2.6-1T 1T/63B · 80 层LTNLTNLTNLTNLTNLTNLTNMLA256K1.4 GiB不是 KDA;思考版 Ring-2.6-1T 架构逐字段相同
2026-04Gemma 4 31B 31B 稠密 · 60 层SWASWASWASWASWAFULL256K11 GiB⭐ 全局层 K 维加倍 + K=V 共享,窗口 1024旋钮①又出新招:全局层 K 维加倍再让 K=V 共享一份
2026-05DeepSeek-V4-Flash 284B/13B · 43 层SWASWACSAHCACSAHCA1M697 MiB⭐ 2 层 SWA 引导,CSA/HCA 交替。MLA 换成 shared-KV MQA终点。697 MiB,比 GPT-3 小 846 倍,而且 MLA 被整个换掉了
2026-06GLM-5.2 744B/40B · 78 层DSA —— 每一层都是这个1M11 GiB⭐ +IndexShare:四层共用一个 indexer。198K → 1M 靠这步IndexShare —— 稀疏的第二阶段:索引本身变成了新的开销
2026-06MiniMax M3 428B/23B · 60 层MSA —— 每一层都是这个1M15 GiB⭐⭐ GQA-4 + 稀疏。KV 比走 MLA 的 GLM-5.2 还大
2026-07Kimi K3 2.8T/104B · 93 层KDAKDAKDAgMLA1M3.4 GiB69 KDA + 24 Gated MLA(末层 92、93 连着两层 full
2026-07混元 Hy3 295B/21B · 80 层GQA —— 每一层都是这个256K40 GiB80 层全 GQA-8 —— 线性一层都没上
2026-07Ling-3.0-flash 124B/5.1B · 42 层KDAKDAKDAKDAKDAgMLA256K1008 MiB跟 2.6 换了一支。同代 tiny 用 3:1,它用 5:1;3.0 无 1T
2026-08GLM-5.3 744B/40B · 78 层DSA —— 每一层都是这个1M11 GiB⚠️ 跟 5.2 同一个 base,纯后训练,架构没动
2026-08混元 Hy4-preview 770B/49B · 78 层gDSA —— 每一层都是这个1M11 GiB78 层全稀疏 + IndexCache(每 4 层 1 层算索引)跳过线性那一支,从纯 GQA 直接跳进全层稀疏
2026-08⭐ GLM-5.3-Flash 320B/18B · 45 层KDAKDAKDADSA1M1.5 GiB34 KDA + 11 稀疏 MLA —— GLM 首次线性+稀疏同锅唯一一个把②和③同锅:34 层 KDA + 11 层稀疏 MLA
2026-08Qwen3.8-Flash-Next 125B/6B · 48 层GDNGDNGDNgAT256K3.0 GiBQwen4 架构预览;新东西在 51B 的 n-gram 嵌入表

⭐ 这张表的一句话落点:同一个 128K 长度, 从 GPT-3 的 576 GiB 到 DeepSeek-V4-Flash 的 697 MiB,六年 846 倍 —— 而这不是一个旋钮拧出来的,三个旋钮各贡献了一段

⭐ 这张表还能一眼看出八件事 (含上面那条的完整推导,以及一条表自己长出来的结论)

⭐ 这张表一眼能看出八件事 (以下统计恒按全部 44 行算,切到 Highlight 也不变 —— 不然「有几家怎么样」这种话会跟着显示模式变,那就不是结论了)

  1. 表里 44 家:17 家是「便宜的层 + 一层贵的」,25 家每层同构, 2 家是别的混法。那 17 家混合的,配比落在 1:1 ~ 7:1 —— 而拆成两族看更有意思:线性混合 3:1、5:1、7:1、滑窗混合 1:1、3:1、5:1、6:1两族重叠在 3:1 ~ 6:1。⭐ 两类看起来毫不相干的混合落进同一段区间, 这件事本身就是一条证据。
    ⚠️ 但别说成「无一例外」Mistral 7B、RWKV-7 Goose 就是纯便宜层、一层贵的都没有的反例。 ⭐ 而这个反例恰恰值得讲 —— 纯滑窗真的上过生产、用了两年, 它正好说明那条 L2M 条件渐近命题, 管不了 32K 这个尺度。
  2. 前几行是基线,也是一条完整的小史:MHA → MQA(砍到 1 组)→ GQA(折中)→ MLA(改压缩) → CLA(跨层共享)——全都只在动「每个 token 存多少」这一个旋钮
  3. ⭐ 扫一眼颜色搭配:混合的那 17 家里 16 家是「冷色 + 黄橙」(便宜的层配一层全注意力)。 只有 GLM-5.3-Flash 是「蓝 + 红」—— 它配的那层「贵的」,本身已经是稀疏的了。
  4. ⭐⭐ 把上下文那一列排一下:做到 1M 以上的 12 家,无一例外都动了旋钮②或③; 纯全注意力那一档最高只到 288K(Mistral Large 3)。最硬的对照来自 MiniMax 自己: 01 用 7:1 线性外推到 4M,M2 退回纯全注意力只剩 192K——同一家、同一批人,差二十倍。
  5. ⭐⭐ 把 KV cache 排一下:576 GiB(GPT-3)到 697 MiB(DeepSeek-V4-Flash),整整 846 倍 —— 两端都取 100B 以上的,不然「最小」会落到 Mistral 7B 头上, 那比的是模型大小不是机制。 而这不是一个旋钮拧出来的——MHA→GQA 砍头数、MLA 改压缩、CLA 跨层共享是旋钮①; 线性把大部分层的 KV 直接删成零是旋钮③;CSA/HCA 存压缩池是旋钮②。 三个旋钮各贡献了一段。⭐ 而 RWKV 那一行干脆是 0——纯 RNN 没有 KV cache 这个东西。
  6. ⛔⛔ 「上下文」这一列报的是声明,不是能用 数来自各家 config 的 max_position_embeddings,而这个字段各家含义并不一样—— MiniMax-01 那格 config 写着 10,240,000,官方只声称训练 1M、外推 4M; Llama 4 Scout 声称 10M 也是同一回事。 声明和能用之间还隔着一整个 benchmark 的落差:小米自己的模型卡就写着 MiMo-V2.5-Pro「到 1M 时塌到 0.00」。⭐ 看到「支持 N 万上下文」,先问是谁、在什么任务上、测出多少分
  7. ⭐⭐ 最反直觉的一条:MLA 之后,KV cache 跟「模型多大」脱钩了。 DeepSeek-V2 是 236B、V3 是 671B,参数差 2.8 倍,KV 却只差 1.7%(8.4 GiB vs 8.6 GiB)。 因为 MLA 的 KV 只跟「层数 × (kv_lora_rank + rope 维)」走—— 跟专家多少、hidden 多宽、总参多大一点关系都没有。 MHA 时代 KV 是跟着模型一起长的,这条链在 MLA 这里被剪断了
  8. ⭐⭐ 最后一条是这张表自己长出来的,不是我们想说的。 把 2025-11 ~ 2026-03 这五个月单独看:6 个模型发布,用到 7 种层类型 (DSA、FULL、GDN、GQA、MLA、SWA、gAT)—— 而其中在这个窗口里才首次出现的,只有 0 种
    换句话说,那五个月发布很密,密的是模型,不是机制:全是既有招式的重新组合与规模化。 ⭐ 这是一段「消化期」—— 新机制在 2025 年秋天集中冒出来(DSA、GDN、KDA), 然后各家花了一个冬天把它们搬进自己的旗舰。 下一波新东西要等到 2026 春天(Gemma 4 的 K=V 共享、V4 的 CSA/HCA)。
    ⛔ 而这条结论是被 Highlight 视图逼出来的:全量 44 行里那段看着只是「少几行」, 一筛成机制主线,空白立刻刺眼。筛选不只是省地方,它还是一种体检。

这张表有两种读法,右上角可以切: Highlight(默认)只留撑起这段历史的那些行,备注写的是「它凭什么在这条线上」; 全部是完整参照表,备注是这一行独有的那句话。
⭐⭐ 表头还能点 —— 按时间、厂商、配比、上下文、KV cache 排序。

⭐ 这一讲按时间顺序讲,一步一步走。 每一步只回答两个问题:上一步欠下了什么,这一步拿什么来还。 —— 所以读的时候不用记名词,记「谁欠了谁」就够了。

⚠️ 想看推导、消融表、我们自己在 v7 上的实测数据,去 L300 完整版;这一份只讲故事线。

整本书的路线图(六段)+ 两张趋势图 —— 对主线没帮助,可以直接跳过。 想自己读的话:底下那两张是本课模型表 44 行现算的 —— 上下文往上、同一长度的代价往下,六年就这两条线同时反着走。
这是一个什么故事 —— 六年时间,把「记得住」变成一件付得起的事 上下文长度就是 agent 的工作记忆。2K 的时候模型只能当个聪明的补全器;要读整个代码库、跑几十轮工具调用、记住整场对话,先得记得住。而每加长一分上下文,KV cache 就 线性涨一分 还没有 KV cache 这回事 让每一份更小 每步只读一部分 换回一个固定大小的状态 1990–2017 RNN 1 2 3 4 5 带一个固定大小的状态 序列建模第一次可行 串行:算不快、记不住 2017 MHA 谁都能看见谁 把循环整个拿掉 训练能并行,规模才起得来 状态没了 → KV cache 出生 2019–2024 旋钮① 格子没少,每格里的东西变小 让每一份更小 2K → 128K 成常态 砍太狠会掉质量 2023–2026 旋钮② 格子照样在,这一步只读两个 每步只读一部分 1M 进入可用区间 省读不省存 ↳ 接着上一行 2020–2026 旋钮③ 一块板子 换回一块固定大小的板子 那些层的 KV 归零 串行跟着回来了 2024–今天 混合 普通 普通 普通 资深 三个便宜的配一个贵的 两头都要 3:1 ~ 7:1 成了共识 便宜的管长度,贵的管质量 能跑多长 ↗ 每个点是一个模型声明的上下文上限 2K 32K 128K 1M 10M 2021 2022 2023 2024 2025 2026 GPT-3 Llama 4 Scout Kimi K3 绿线=当时的纪录:2K → 10M(Llama 4 Scout 声明值,之后没人再刷) 同一长度的代价 ↘ 每个点是那个模型在 128K 时的 KV cache(BF16、batch 1) 576 GiB 64 GiB 8 GiB 697 MiB 2021 2022 2023 2024 2025 2026 GPT-3 PaLM DeepSeek-V4-Flash 混元 Hy3 绿线=当时的最省记录。576 GiB → 697 MiB,846 倍 不是所有人都在走 —— 右图 2026 年还有模型停在 40 GiB(混元 Hy3,纯 GQA)。动的是「最好成绩」那条线,不是每一家。 纪录也不等于常态:左图那条线的终点是 10M(一家的声明值);本讲说的 512 倍 是 2K → 1M 这个常态 —— 模型表里 12 家做到。 两条曲线反着走 —— 这才是这六年真正发生的事 这两条是两把尺子,不能相乘:左边量「能跑多长」,右边量「同一长度下省了多少」。把 512 倍 × 846 倍说成四十万倍,是把两把尺子当成一把。 但它们同时发生,才有今天的 agent—— 上下文能装下整个代码库,而且装得起。 所以这一讲的前半程只有一个账本:KV cache( 到旋钮② 它就不够用了) 在张量形状里找 S(KV 长度)—— 全图只有 K 和 V 两处带它,那就是唯一要跨 token 留下来的东西 三个旋钮是它的三个面:① 每份多大 ② 每步读多少 ③ 干脆别让它变长
这就是整本书的路线图。六段,每段只问三件事:图啥、带来了什么、欠下了什么。
底下那两条数才是落点,而且缺一不可 —— 只把长度做上去、价钱降不下来,没人用得起;价钱降下来了、长度还是 2K,那它仍旧只是个接话的补全器。
出处与口径

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,对数轴上画不出来,没有进右图

第 一 节

一条链 —— 最早的模型是怎么记事的

这本书里后面出场的每一个名词,解的都是同一道题: 话是有先后的,模型总得有个地方放「刚才说了什么」。 放在哪儿、放多少、要不要每次全看一遍 —— 三十年就吵这一件事。

1.1 全部设计只有一句话:带一个盒子

1990 年给出的答案朴素得近乎粗暴:带一个盒子
读进一个字,就把盒子里的东西和这个字搅一搅,再写回盒子;读下一个字,再搅一次。 读到最后,盒子里剩下的,就是这段话在模型眼里的全部

⭐ 而这里有一个决定了后面三十年的细节:这个盒子从头到尾一样大。 喂它十个字,盒子这么大;喂它十万个字,盒子还是这么大。

RNN —— 把「之前发生了什么」塞进一个固定大小的盒子,一步一步往下传 全部设计就这一句。后面所有的好处、所有的痛点,都是这一个决定的后果。 输入 状态 h —— 整个模型的记忆 输出 原样抄写,不训练 ① 折叠着看 Elman 1990 原始画法 Cognitive Science 14 输出 y y = W_y · h 隐藏 h [d] —— 固定大小 输入 x 这一步的词 context 层 上一步的 h 原样复制一份 固定权重 1.0,不训练 那条虚线不是学出来的—— 原文写死「one-for-one basis, with fixed weight of 1.0」。整个循环就靠这一条硬接线。 ② 展开着看 它其实就是一条链,每一格都得等前一格算完 t = 1 x1 h1 y1 t = 2 x2 h2 y2 t = 3 x3 h3 y3 t = 4 x4 h4 y4 t = n xn hn yn 上一步的 h 不是总有 —— 见 ③ 红底那一行就是全部问题的根源:h3 要用 h2,h2 要用 h1 —— 一百万步就得排一百万轮。 竖着的绿、蓝箭头彼此不相干—— 能并行的方向一直都在,被卡住的只有横着这一个。 ③ 那 y 到底要不要接回去当 x —— 看这条 RNN 站在哪个位置上 所以「自回归」不是 RNN 的性质,是用法的性质 语言模型(只有解码器) y → 下一个 x:有 吐一个字,接回去当下一个输入。 但只在生成时有 —— 训练时喂的是 真实的下一个词(teacher forcing)。 翻译 · 编码器那一侧 y → 下一个 x:没有 它只管把源句读成一个状态。 逐步的 y 根本不用 —— 要的只是读完之后那个 h。 翻译 · 解码器那一侧 y → 下一个 x:有,而且还多一条 ① 自己吐的接回去当下一个输入; 还要去看编码器 —— 早期是一个 context 向量,后来就是交叉注意力。 所以这一张图画的是「一条 RNN」,不是「一个模型」 同一个 RNN 单元,摆在编码器就不自回归,摆在解码器就自回归。上面 ② 那条链是它共有的部分;y 接不接回去,是外面怎么用它决定的。 而这也说明 ② 那个「排一百万轮」的痛是跑不掉的它来自红色那条横线(状态),不是来自 y 那条虚线 —— 哪怕你根本不自回归(编码器那一侧),这条链照样得一格一格排 拆开一格看:里面就是两个矩阵乘、一个加法、一个非线性 ht = f( Wh · ht−1 + Wx · xt + b ) yt = Wy · ht 形状:h 是 [d],Wh 是 [d × d] d 就是它能记住的全部容量—— 喂 10 个词还是 10 万个词,盒子一样大。
⭐ 左边是 1990 年的原始画法,右边把它沿时间摊开 —— 一条链的样子,得摊开才看得见。
看完只要记住一件事:那条红线是横着走的。这本书接下来所有的招,都是在想办法别沿着它走
出处与口径

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

⛔ 不过「搅一搅」是个会骗人的比喻 ——  它容易让人想成搅匀,而搅匀是会把东西毁掉的。 所以把盒子打开看一眼:它其实不是搅匀。

把盒子打开:「搅一搅」到底是什么,以及它凭什么不把旧的搅烂 「搅」这个字容易让人想到搅匀 —— 而搅匀是会把东西毁掉的。它其实不是搅匀。 一步之内 为什么不糊 为什么会淡 Ⓐ 一步之内,其实只有四个动作 —— 转一下 · 映过来 · 叠上去 · 压一压 没有任何一步在「搅匀」 上一步的盒子 一条 d 维向量 ① 转一下 乘一个学出来的矩阵 这一个新字 也是一条向量 ② 映过来 乘另一个矩阵,进同一空间 ③ 叠上去 就是普通的向量相加 ④ 压一压 过一次非线性,收进一个范围 新的盒子 还是 d 维向量 写回去 —— 下一个字来的时候,它就是「上一步的盒子」 四步里最要紧的是 ③:新的「叠」在老的上面,老的一点没被抹掉 —— 搅匀之后你取不回任何一样东西;叠加之后能不能取回,是个可以算的问题(见 Ⓑ) 可学的权重就三块 —— 真实尺寸长这样 (Elman 1990 句子模拟:输入 31,隐藏 150) ② 映过来 31 × 150 4,650 个数 ① 转一下 150 × 150 22,500 个数 学 · 最大 读出来 150 × 31 4,650 个数 那条回线 原样抄一份 不训练 合起来 31,981 个参数 —— 其中七成在「转一下」那一块 不是三万亿,不是三十亿 —— 三万。 那条回线原文写死了: 「Recurrent connections are fixed at 1.0 and are not subject to adjustment」 —— 它只把状态原样抄给下一步;真正学的是 「怎么把抄来的那份读回去」,也就是最大那块 好处和坏处是同一个动作带来的 —— 每一步都要再乘一次那个矩阵 左边:这一乘为什么不毁信息 · 右边:同一乘为什么让远处变淡 2 维里 51° 夹角典型只有 51° 叠上去就糊,取不回来 1024 维里 89° 典型是 88.6° —— 几乎垂直 互不打扰,各取各的 叠加之所以能还原,是维度买来的。 d 维里两个随机方向的余弦,典型约 1 除以根号 d —— d 越大越垂直 但不是无限的:互不打扰的方向最多 d 个,叠过头照样糊。 记住这一句 —— 后面另一块板子为什么会写满,是同一条道理。 同一个「乘一下」,传远了就淡 盒子 第 1 步 × 转一下 盒子 第 2 步 × 转一下 盒子 第 3 步 × 转一下 盒子 第 4 步 从第 1 步传到第 100 步,就被连乘了 99 次 劲道略小于 1 —— 指数趋零;略大于 1 —— 指数炸掉 它不是被时间冲淡的,是被反复相乘压没的。 而这个毛病,三十年后被主动放回去当功能用了 —— 见第七章。 落点:这个盒子里有什么、没有什么 —— 它是这整本书的骨架 KV 不是这时候发明的。盒子里只有一个状态,没有「钥匙」和「内容」这两样东西 —— 那套命名要等到 2017 年,而把它缓存起来是更晚的事。 RNN 的状态 = 全部历史压成一团;KV cache = 全部历史原样留着。一个压到不能再压,一个一点都不压 —— 它们是同一件事的两个极端,而这本书剩下的章节,讲的就是这 两端之间的全部空间。
⭐⭐ Ⓑ 是这张图的重心 —— 「叠加为什么不毁掉信息」不是个说法,是个能算的数:2 维里两个随机方向只差 50 度,1024 维里差 88.6 度。叠加能还原,是维度买来的。
而 Ⓑ 真正的巧处,是它把好处和坏处画在了同一格:「为什么不糊」和「为什么会淡」,是同一个「每步再乘一次」的两面。
⭐ 底下那条落点带回答「KV 是不是这时候发明的」—— 不是,而且这个「不是」正是全书的骨架。
出处与口径

Ⓑ 那两个角度是现场采样算的(各 4000 次随机向量对):d=2 时 E|cosθ| ≈ 0.63(夹角约 51°,精确值是 2/π);d=1024 时 ≈ 0.0248(约 88.6°)。渐近式 sqrt(2/(π·d)) 与实测在 d≥64 时吻合到三位小数

⛔ Ⓐ 刻意不写出那个式子 —— 本节面向还没见过它的人,公式会把该看见的画面挡住;⛔ Ⓑ 右半不画具体衰减曲线 —— 快慢取决于那个矩阵的谱,没有通用曲线,画出来就是编

Ⓐ 那张尺寸表与「回线不训练」出自 Elman 1990 《Finding Structure in Time》原文:「Recurrent connections are fixed at 1.0 and are not subject to adjustment」;句子模拟「hidden and context layers contained 150 nodes each」,输入输出 31 个节点。参数量由 31/150 推算,非原文给出;同一篇里最小的 XOR 模拟只有 2 个隐藏单元

精度那一栏本图故意空着 —— 那个年代的论文不写精度,因为它当时还不是一个可以权衡的设计维度。本课不替它补一个数

⚠️ 落点带里的「2017」指的是 Q/K/V 这套命名和 KV cache 这笔账;「拿一个查询去跟一串历史比相似度再加权平均」这件事本身,2014 年的注意力就已经在做了

⭐⭐ 这一节只要记住三句。

  • 怎么进去的?—— 不是搅匀,是叠上去: 老状态转个向,新字映到同一个空间,两个相加,再压一压。
  • 为什么不糊,又为什么会淡?—— 是同一个动作的两面: 维度高所以叠加能还原;可每步都要再乘一次,所以远处被压没了。
  • KV 是这时候发明的吗?—— 不是。 RNN 的状态是「压成一团」,KV cache 是「原样留着」 ——  后面每一章,都可以按「它站在这两端的哪儿」来读。
这一节的七个追问 —— 现场问出来的,答案都在 —— 跳过不影响往下读。 名字的来历 · 盒子多长 · 什么精度 · 权重在哪 · 真能用时多大 · 那几块矩阵在提取什么 · 什么时候变成二维的

📌 ① 名字拆开:RNN = Recurrent Neural Network,中文叫循环神经网络

那个 R,指的就是你刚看到的那条回线。 盒子里的东西算完之后,不是走了,是绕回去当下一步的输入。
词源上很直白:recurrent = re-(回)+ currere(跑), 字面就是「往回跑」

⭐⭐ 而这个词不是数学家造的,是从解剖学借的 ——  1901 年 Cajal 在小脑皮层观察到 recurrent semicircles, 1933 年 Lorente de Nó 发现 recurrent, reciprocal connections。 在那之前,神经系统一直被当成纯前馈的。

中文有个常见误译要避开:「递归神经网络」不是它。 递归(Recursive)是另一个东西,沿语法树往下钻; RNN 是循环,沿时间轴转圈。一个往深处钻,一个原地打转。

② 那个盒子到底多长? —— 小得超乎想象。图里给了最大的那个(150); 同一篇论文里字母预测那两个只有 20,最小的 XOR —— 2整篇的区间就是「2 到 150」。

③ 什么精度? —— 论文没写。而这正是答案的一部分:那个年代精度还不是一个 可以权衡的设计维度,它就是机器上的普通浮点数,没人把它当旋钮。
一个量什么时候开始被写进论文,本身就是个信号 ——  说明它那时候才变成一件要权衡的事。

④ 真能用的时候多大? —— 150 是玩具尺寸。第一个真正能用的大 RNN 是 2014 年那篇 seq2seq:4 层、每层 1000 个 cell。而原文紧接那句才是精华: 「the deep LSTM uses 8000 real numbers to represent a sentence」。

⛔⛔ 八千 → 两千六百二十亿(开篇那 488 GiB 按 bf16 算)。 三千两百七十五万倍—— 这就是「压成一团」和「原样留着」之间的真实距离。

⑤ 那几块矩阵各自在提取什么?—— 答案有点反直觉。

注意力那边 Q / K / V 的语义人人讲得清: Q = 我要找什么,K = 我能回答什么,V = 找到我能给什么。

而 RNN 没有这种分工 —— 不是我们没研究清楚,是架构本身没把角色分开 输入矩阵写进去的东西既是内容、又是将来被检索的依据; 循环矩阵既保留又变换,既是记忆的保持器又是检索器。

⭐⭐⭐ 而这正好说出了注意力为什么是个突破: 它把「记什么」和「怎么找」拆成了两个独立的参数化。 在 RNN 里,这两件事是同一个矩阵在兼职

更准的说法是三档:RNN 的状态是「没有索引的一团」, 线性注意力的状态是「带索引的记忆」,KV cache 是「一条一条原样摆着」。 中间那一档到第七章会回来。

⑥ x 是什么、y 是什么?它也是在预测下一个词吗?

x = 当前这一个 token 的向量;h = 读完前面所有词之后的状态; y = 状态过输出矩阵,得到一个跟词表一样长的向量, softmax 之后就是「下一个词是每个词的概率」。

⭐⭐ 所以是的 —— 它就是 next-token prediction。 原文写着:「The task on each input cycle was to predict the … next word in the sequence.」一九九〇年,同一个训练目标,用了三十五年。

⚠️ 顺带交代那时候的「词向量」:Elman 给每个词一个 31 位、只有一位是 1 的向量,随机指定、不参与训练。 原文点明两个后果:彼此完全正交,且不携带任何词性或词义
这恰好说出了 embedding 后来是干什么的:当年是完全正交但零信息, 今天是学出来的稠密向量 —— 只做到「近似正交」,换来的是它开始携带意思。

⑦ 什么时候变成二维的(矩阵状态)? —— 比多数人以为的早得多,而且它不是被发明的,是被重新发现的。

90 年代初 Schmidhuber 那一支的 Fast Weight Programmers,用的就已经是 外积累加出来的矩阵。2021 年那篇把两者形式等价证了出来 (arXiv 2102.11174,作者里有 Schmidhuber 本人)。

⭐⭐ 而摘要里那句最值钱:那些外积拼的是 「self-invented activation patterns(today called keys and values)」 —— 今天叫 key 和 value 的那两样东西,三十年前就以外积的形式出现过。

⑧ 那个「转一下」到底在转什么?是淡化记忆,还是腾地方? —— 两个都是,而且它们是同一件事。

先别混:那条回线固定 1.0、不训练,它只负责把状态搬到下一步。 真正在「转」的是那块 150 × 150,也就是占了七成参数的那一块。 —— 搬运是免费的,收费的是搬过来之后怎么读它。

「转一下」这个说法只说对了三分之一。 任何一个方阵作用在向量上,一定是三件事叠在一起: 换个角度看 → 各个方向按各不相同的倍率缩放 → 再换个角度摆回去要害全在中间那一步。

  • 某个方向倍率 < 1 —— 每步乘一次,那个方向的旧内容 指数衰减这就是淡化。
  • 倍率 ≈ 1 —— 东西能传很远。这就是长期记忆通道。
  • 而淡化腾出来的,正是那个方向上的可分辨度 ——  新内容叠上去之后还认得出来。所以「淡化」和「腾地方」是同一个动作的两种说法。

⭐⭐ 最要紧的是:它不是均匀地淡化,是挑着方向淡化的。 哪些方向快忘、哪些慢忘 —— 这就是那块矩阵真正在学的东西。

那「提取」是怎么发生的? —— RNN 里根本没有一个叫「提取」的步骤。它藏在乘法里。

把那块矩阵按行看:矩阵乘 h,第 i 个输出就是第 i 行跟 h 做内积。 而内积就是提取 —— h 是一堆近似垂直的方向叠出来的, 你拿一条向量去点它,跟你对齐的分量被读出来,垂直的那些自动约掉

⭐⭐ 所以每一行就是一把尺子、一个探针。150 行就是 150 个探针, 同时去量这块白板;量出来的 150 个数,重新摆成新的 h。
—— 「转一下」同时在做两件事:把旧状态读一遍,再把读到的重写成新状态。

「融入」呢? —— 就是向量加法,简单得多。新字被另一块矩阵映到同一个空间里的 某个方向,然后加上去。不是覆盖,是叠加:旧方向一点没动,只是多了一个分量。

但这里有一层前面没说透的:叠上去之后,新的和旧的就分不清谁是谁了 —— 除非有探针能把它们分开。而那个探针,就是下一步那块矩阵的某一行。

⭐⭐⭐ 于是整件事是个闭环:「映过来」那块决定往哪个方向写, 「转一下」那块的行决定从哪个方向读 —— 这两头必须对上暗号

而它们隔着多少步的连乘?隔着 t 步。梯度要穿过那 t 次连乘, 才能告诉这两块矩阵该怎么对齐。
⭐⭐ 这才是长程依赖学不好的真正根因 —— 不是「记不住」, 是读写两端对不上暗号,而且越远越对不上

⭐⭐⭐ 所以注意力的突破可以说得比「它把记什么和怎么找分开了」更准一层: RNN 的读写要跨 t 步对齐;注意力的读写在同一步里直接点积。 Q 和 K 当场碰一下,对不对得上立刻就知道,不隔任何连乘
—— 它不是把对齐学得更好了,是把需要对齐的距离砍成了零。

📌 口径。「方阵 = 旋转 × 各向缩放 × 旋转」是奇异值分解, 数学事实;「谱决定衰减或爆炸」是梯度消失那套标准分析。 ⚠️ 而「读写要跨 t 步对齐」这个说法是本课的表述, 不是文献里的标准提法 —— 它底下就是长程依赖那套分析。

⭐⭐ 把图上那个形状先记在心里。 ⛔ 它在第二章会被整个扔掉;然后在第七章 原样回来
要绕完那一整圈,我们才会发现:当年扔掉的东西里,有一样是宝贝。

1.2 第一个痛:盒子只有一个,所以只能一步一步来

这个设计有一个今天看来仍然极其诱人的好处:不管这段话多长, 每走一步要搬的东西一样多 —— 成本跟长度无关。

坏处写在同一句话里:盒子只有一个。 想更新第三个字的盒子,得先等第二个字更新完;一万个字,就老老实实排一万轮。

「排一万轮」听上去只是慢一点。真正难受的是它落到机器上的样子 ——  每一轮都要把整个模型的权重从显存里重新搬一遍,然后只拿它算一丁点活。

为什么 RNN 在加速器上就是跑不快 —— 同一块权重被搬了 n 次 「串行」在硬件上的具体形态就是这个:权重每一步重搬一遍,而每次只配一条 batch 那么窄的向量。 带网格的块 = 从 HBM 搬进来的权重 配给它的激活 串行:一步一格 并行:整段一次 Ⓐ RNN 每一个时间步,都要把同一块权重 W 从 HBM 重新搬进片上一次 训练和推理都一样 W [k·d × d] · [d×B] 第 1 步 W [k·d × d] · [d×B] 第 2 步 W [k·d × d] · [d×B] 第 3 步 W [k·d × d] · [d×B] 第 4 步 W [k·d × d] · [d×B] 第 5 步 W [k·d × d] · [d×B] 第 n 步 NVIDIA 官方文档的说法是「a GEMM with one dimension of one」—— 名义上矩阵乘,实际是矩阵乘向量。 Ⓑ Transformer 训练 同一块权重只搬一次,n 个位置一起喂进去 W [k·d × d] · [ d × (n · B) ] —— n 个位置全在这一块里 一次搬运换来 n 倍的活干。这就是「用平方的计算量买完全的并行度」那笔交易的硬件形态。 算术强度 —— 说人话就是「跑一趟,能干多少活」 第一次读可以先跳过这一格 它决定你是在等算力,还是在等内存 🚚 RNN 每一步 搬的是同一块权重 W (车一样大,跑一趟的成本一样) 算术强度 B 车照样跑一趟,车上只有一个 batch 的活 🚚 Transformer 训练 搬的是同一块权重 W (车一样大,跑一趟的成本一样) 算术强度 n · B 同样跑一趟,装了 n 倍的活 短序列:显存还让你开这么大 长序列:只能开到这里 S 越长,显存允许的 batch 越往左 可行区为空:能给的上限,已经跑到要求的下限左边 TPU v7 屋脊线 313 FLOP/byte = 2307 TFLOP/s ÷ 7.37 TB/s(BF16) batch 1 batch 32 batch 128 这一整段 = 在等内存 这一段 = MXU 吃饱了 硬件要你站进来,显存不让你进来 Transformer 训练 = n·B → 冲出图外 那个 k(LSTM 是 4、朴素 RNN 是 1)上下一约就没了 —— 强度跟门数、跟隐藏维都无关。(激活的搬运比权重小两个数量级,略去。) 口径:两个 ▼ 的位置是示意—— 具体能开多大取决于模型和显存;这张图只说方向:S 一长它必然往左走,早晚越过 313 横轴是线性的,不是双对数:换成 log,1 和 313 会挤在一起,「差两个数量级」当场就看不见了。
Ⓑ 那一行现在只要记个印象就够了 —— 它是这张图里唯一没在讲 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」;Vaswani et al. 2017 (arXiv 1706.03762) 引言

⭐ 那个 313 不是这一讲新造的数 —— 它就是专题二 整整一节在立的那条屋脊线每从显存搬一个字节,这台机器配套能算 313 次。

⛔ 所以这一条不是「RNN 慢一点」,是买了台很贵的机器,而它九成时间在等着搬东西

📌 顺带说一句:当年不是没人想办法。

既然病根是「权重每一步都要重搬一遍」,那就干脆别搬 ——  把权重钉死在芯片内部,一步步往下走的时候它一直待在那儿不动。 这招叫 persistent RNN,2016 年百度硅谷 AI 实验室做的。

记住这个念头 —— 它后面还要再赢一次。 七年后有人把同一句话用在了另一个算子上:「别让中间结果落回显存」。 那个东西叫 FlashAttention第九章会讲到。

⛔ 可惜这一招救不了 RNN ——  能钉在片上的模型就那么大,而且它只治了「搬得慢」,没治「必须排队」

1.3 另外两个痛:传远了会淡,一整段塞不进一个盒子

第一个痛是算不快。另外两个更伤 —— 它们不是慢,是真的学不会

RNN 的三个痛点 后面三十年的路线图,就是这三条各自的解药 三个痛点,三个画面 一个一个看 ① 算不快 一排人传话,后一个必须等前一个开口 1 2 3 4 5 6 7 序列多长,就排多少轮 而且每一轮都要把全部权重重搬一遍 ② 记不住 话传到后面,越来越淡 反传要连乘 n 次 小于 1 就指数衰减 —— 学不到远处 ③ 装不下 一整段话,塞进一个固定大小的小盒子 h 不管多长,都压进这一个向量 长句子必然丢东西 解药:Transformer 把循环整个拿掉 欠下:注意力矩阵 O(N²) 解药:门控 → 注意力 LSTM 1997 / GRU 2014 任意两个位置只隔一步 解药:Bahdanau 2014 别只看最后那个向量 注意力的出生证明 而第 ① 条后来被反着又走了一遍 —— 这就是专题三真正的主脊 Transformer 用「放弃状态」换来了并行度。线性注意力和 Mamba 这一支,是想把状态请回来—— 因为有状态,每步要搬的东西才不再变长。 但状态一回来,串行也跟着回来。于是又得把并行度找回来:chunk 化、parallel scan
下半截那三个盒子,就是这本书剩下八章的全部内容。
其中最右边那一条最先发生 —— 早在 2017 年之前,注意力就已经出现了,当时它只是挂在 RNN 身上的一个配件,专治「装不下」。
⭐⭐ 下一章讲的,就是有人突然问了一句:既然这个配件这么好使,能不能把 RNN 整个扔掉,只留配件?
出处与口径

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 扫

1.4 本章落点:这本书的主角,是那个盒子

这一章只留两句话。

① 盒子的好处:每一步要搬的东西不随长度变
② 盒子的坏处:盒子只有一个,所以只能一步一步来;而且它装不下一整段长话。

下一章那一刀,冲的是 ②。
但它会把 ① 一起扔掉。 这本书剩下的八章,讲的都是怎么为「扔掉 ①」付账。

⭐⭐ 不过在那一刀落下来之前,先把这一章真正的那个问句问出来 ——  既然「回头看一眼」这个配件这么好使,那能不能把 RNN 整个扔掉,只留配件?

第 二 节

2017 年那一刀 —— 把链剪断,换成一张表

上一章最后那个问句还悬着:配件既然这么好用,RNN 还留着干什么? 2017 年有人真就这么干了 —— 而且论文标题就是这个意思。

先纠正一件很多人搞混的事:注意力不是 2017 年发明的。 2014 年就有了 —— 而且一开始, 它是长在 RNN 身上的一个小配件
⭐ 所以那一刀真正干的事,不是发明注意力,是把长着配件的那条链整个扔掉,只留配件

2.1 先看清那个配件是怎么被逼出来的

动手之前先补一段:注意力不是谁灵光一闪想出来的它是被人一次次嫌弃出来的 —— 每一版都有人指着上一版说「这儿不对」。

注意力是怎么被发明出来的 三步 —— 每一步都在修上一步的一个具体毛病 上一步哪儿不对 这一步怎么修 被硬件推着走的那一刀 ① 2014 一个向量装不下 Bahdanau / Cho / Bengio 今天 开心 一个 向量 整段译文都从这儿出 句子越长,挤得越狠 原文:we conjecture that … a fixed- length vector is a bottleneck(作者自己的推测) 修法的关键是那个「软」字 硬挑一个词不可导;加权平均可导 ② 2015 第一刀 打分太慢 加性 → 点积 加性(旧) 每一对,过一个小网络 net net net net net net net net net n² 次 小网络 点积(今天) 整张表,一次矩阵乘 Q · Kᵀ 一次 ③ 2017 第二刀 那还要 RNN 干什么 两点之间要走几步 循环:一步一步传 1 2 3 4 5 ①→⑤ 要走 4 步 自注意力:直接连 1 2 3 4 5 ①→⑤ 一步 同一句话里,论文认了代价 加权平均降低了有效分辨率 多头就是拿来补这个的 第二格那一刀,是这门课的主线第一次出现 论文自己写的理由是:两者理论复杂度相仿,但点积快得多、省内存,因为它能用高度优化的矩阵乘实现 换句话说 —— 选点积不是因为它更准,是因为它能变成矩阵乘。这一讲后面每一个变体,几乎都能追到同一句话上。
三步,每一步只修上一步的一个毛病 —— 顺着读一遍,你会发现没有哪一步需要天才,只需要有人肯说「这儿不对」。
出处与口径

① 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(多头补偿分辨率)

⚠️ 画面里的句子、方框数量都是示意,不对应任何一次真实实验

⭐⭐ 图里蓝框那一句,先在这儿挂个号 ——  它是这本书的第一条暗线

同样的事后面还会发生三次,一次比一次露骨。第六章它会变成: 连「挑哪几个字来看」都得按块挑,因为机器讨厌零零散散地取数。

⛔ 这本书里不少公式长成那个样子,并不是数学上非如此不可 ——  而是机器只认那一种形状。

那第一格,是怎么想出来的

Bahdanau 自己讲过这段。他当时是 Bengio 实验室的实习生,试过一个「两个游标」的方案, 太复杂,做不出来;退而求其次写了个死板的对角线对齐,能跑,但不好看。 然后有一天 ——

「我忽然想到,要是能让解码器自己学会该把游标放在原文的哪个位置,那该多好。 这个念头多少是中学学英文时做翻译练习来的 ——  你翻译的时候,目光会在原文和译文之间来回移动。
我把这个『软搜索』写成了 softmax,再对双向 RNN 的状态做加权平均。 第一次跑就成了,我兴奋极了。

—— Dzmitry Bahdanau,2022 年回复 Karpathy 的邮件

他给这个架构起的名字叫 RNNSearch。他后来说:「这名字不怎么样。」
「注意力」这三个字,是 Bengio 在最后几轮修改里加进去的。

「在我们提出注意力之前,做法是让一个循环网络把整段原文读完, 然后生成译文。可人根本不是这么翻译的。

这就好比先把整本法文书从头读到尾,然后凭着脑子里的印象把英文版写出来 ——  肯定会丢细节,因为我们的脑子没法一次装下那么多东西。

办法是:写每一个英文词的时候,都允许回头去看法文书里的任意一处细节。 而为了能用反向传播训练,注意力必须是『』的 ——  不是挑中某一个位置,而是有一份总额固定的注意力,可以分摊到所有位置上。

—— Yoshua Bengio,被问到为什么用「attention」这个词时

⭐⭐ 最后那句话,顺手解释了这一讲后面所有的 softmax。

「总额固定、分摊到所有位置」—— 就是等一下 §2.4 那张图里 那根一直加到 100% 的百分比条。 它不是某个数学家的偏好,是为了能被梯度下降训练而必须付的一个形状

📌 上面两段自述的出处:Karpathy 2022 年去信问 Bahdanau, Turing Post 整理并另行采访了 Bengio。本讲的译文按原文意思重述,不是逐字直译。

📌 顺带交代一下这本书往后的读法:每讲到一个新名词, 我都会先说它在修上一版的哪个具体毛病—— 「某某灵光一闪」这件事你学不会;「上一版这个地方不对」,你下次能照着用。

2.2 那一刀砍下去,换掉的是什么

2017 年那一刀 —— 把那根横箭头拿掉,换成「每个位置直接看所有位置」 开篇讲 RNN 那三个痛点里的第 ① 条,就是在这里被解掉的。而解法本身,就是这一讲后面要还的那笔账。 串行:只能一步一步走 要算的格子 被因果遮罩挡住的未来 Ⓐ RNN:一条链 信息只能沿着链爬 讲 RNN 时说过 t1 t2 t3 t4 t5 t6 t1 想影响 t6,得经过 5 跳—— 每一跳都是一次矩阵乘,而且必须排队。 串行步数 O(n) · 最长路径 O(n) · 连线数 n − 1 信息走得越远越容易被冲淡 —— 这同时也是痛点 ② 梯度消失的几何解释:梯度也得沿着同一条链爬回去。 而且这条链一次只能动一格,加速器上再多的并行单元也用不上 Ⓑ 自注意力:一张 n × n 的表 每一格 = 一个 query 对一个 key 的打分 q1 q7 qn k1 kn ← q7 这一行:它看得到 k1…k7 灰格 = 被因果遮罩挡住的未来 (softmax 之前置成 −∞) 串行步数 O(1) · 最长路径 O(1) · 格子数 n² —— 这才是这一刀的账单 任意两个位置之间只隔一格,而且整张表可以一次算完—— 左边那条链两样都做不到。 这一刀换来了什么,又欠下了什么 换来的:串行步数从 O(n) 掉到 O(1)—— 整段序列一次算完,加速器终于喂得饱了(这正是「RNN 在硬件上为什么慢」那张图那笔账的反面)。 欠下的:连线数从 n 变成 n²。 而且注意 —— 状态没了:RNN 那个固定大小的 h 被换成了「把所有历史原封不动留着」。
左边是上一章那条链,右边是它的替代品。横着那根箭头没了,取而代之的是一张人人对人人的表 —— 这张表,就是后面整本书都在跟它较劲的那个「注意力矩阵」本人。
出处与口径

Vaswani et al. 2017 (arXiv 1706.03762) 表 1:自注意力 串行步数 O(1)/最长路径 O(1);循环层两项都是 O(n)。因果遮罩见同一篇论文 §3.2.3:把非法连接「setting to −∞」

⛔ 图里那句「状态没了」, 正是第一章结尾押下的那一笔

①「每步要搬的东西不随长度变」—— 从这一刀落下的那一刻起,不成立了

换来的是并行;赔进去的,是那份「贵不贵跟话有多长没关系」的清白。

2.3 那信息到底是怎么走过去的

链剪断了,可话还是得连起来读。不靠一格一格传,那靠什么?

一次注意力,干的就是一件事 —— 每个位置都去全场取一次货 跟着最后那个 token 走一遍:它怎么提问、别人怎么报价、货怎么汇到它身上 query 我想找什么 key 我这儿有什么 value 被选中我就交这个 ① 每个位置都长出三样东西 同一个向量,乘三个不同的矩阵 t1 q k v t2 q k v t3 q k v t4 q k v t5 q k v t6 q k v ② 最后那个 token 拿它的 q 去对每块牌子 打分 → softmax → 一组加起来 = 1 的权重 q t6 的问题 k k k k k k softmax 之后 5% 30% 8% 45% 7% 5% 加起来 = 1 ③ 按权重把所有人的 value 加起来 权重越大,交上来的那份占比越大 v v v v v v t6 的新向量 一个向量里,现在装着 全场按需加权的内容。 为什么标题敢叫「Attention Is All You Need」 RNN 要让 t1 影响 t6,得一跳一跳传五次;上面这一步 —— 一跳。而且六个位置是同时做的,不是排队。 整个过程只有矩阵乘和一次 softmax,没有任何循环 —— 所以它能一次性并行算完整个序列。 两个最常被跳过的「为什么」 为什么要投三次,不能只用一个向量?—— 去图书馆找书:你脑子里那个需求(q)跟书脊上印的标题(k)不是同一个东西。 那 v 为什么还要跟 k 再分一次?k 是书脊,v 是书里的内容—— 「凭什么被选中」和「被选中之后交出什么」,本来就是两回事。
⭐ 跟着最后那个字走一遍,你就懂了 —— 它不用等谁传话,直接一嗓子问全场,然后按各家报的价把货收回来。
⭐⭐ 而且这不是轮流来:六个位置同时在做同一件事。—— 上一章卡死人的那个「只能一步一步来」,到这里彻底没有了。
出处与口径

「query / key / value」与「输出是 value 的加权和」是 Vaswani 2017 论文 §3.2 的原话,不是本课编的比喻;图中那组权重(5/30/8/45/7/5 %)是示意值,不是实测

⚠️ 本图只画注意力 —— 一层 Transformer 里还有 FFN、残差、归一化,它们不在本专题这条轴上(本专题的账本只有 KV cache)

2.4 拆开一层看:它在算什么

一层里到底在算什么 —— 论文自己的说法就是「检索」,不是我们编的比喻 原文:mapping a query and a set of key-value pairs to an output … the output is a weighted sum of the values Q 要找什么 K 挂出来的牌子 V 牌子后面的货 打分 + 归一 ① 三件套都是从同一个 x 投影出来的 同一个 token,用三套不同的权重看它三次 x 这个 token Q = x·W_Q 我要找什么 K = x·W_K 我是什么,挂个牌 V = x·W_V 我肚子里有什么货 为什么要投三次而不是直接拿 x 比 因为「我想找什么」和「我能提供什么」本来就是两回事 同一个词当 query 时该问的问题,跟它当 key 时该挂的牌子,不该是同一个向量。 而 V 又跟 K 分开,是因为「凭什么被选中」和「被选中之后交出什么」也是两回事。 三个投影 = 三个可训练矩阵,这也是 KV cache 里存的那两样东西的出处 ② 一次注意力 —— 不是「挑一家」,是「按权重把每一家的货混成一碗」 Attention(Q,K,V) = softmax(QKᵀ / √d_k) · V Q 「放在哪儿了?」 拿着这个去挨家问 小明 V 打分 14 ÷√d_k → 1.24 V 打分 -7 ÷√d_k → -0.62 钥匙 V 打分 22 ÷√d_k → 1.94 放进 V 打分 31 ÷√d_k → 2.74 抽屉 V 打分 38 ÷√d_k → 3.36 上排 = K(挂出来的牌子) 下排 = V(牌子后面的货) 不除 √d_k 抽屉 99.9% 这一整条就是输出的那一碗 —— 长度恒等于 100%,softmax 不许有人弃权 整条被一家吃掉:模型只看得见「抽屉」 另外四家加起来 0.09%—— 在这根条上不到一个像素。 除以 √d_k 钥匙 13% 放进 28% 抽屉 52% 这一整条就是输出的那一碗 —— 长度恒等于 100%,softmax 不许有人弃权 混出来的是「放进 + 抽屉」,这才答得上问题 前两家占 80%,剩下三家还留着 20% 的余地。 打分那一步(Q·Kᵀ)是 n × n 个数—— 但它不落地,只在片上过。贵在要算的次数,不在显存。 真正要留下来的是牌子和货(K 和 V)—— 下一个 token 还要挨家问一遍。这两份就是 KV cache,也是本讲唯一的账本。 那个 √d_k 不是玄学,推导链在论文脚注里,两行就能讲完 假设 q、k 各维独立、均值 0、方差 1,那么 q·k = Σ qiki均值是 0,方差是 d_k—— 标准差就是 √d_k。 所以除以 √d_k 只做一件事:把打分的方差拉回 1。维度越高分数摊得越开,不拉回来 softmax 会被推到「几乎全是 0 和 1」的角落,梯度就没了
⭐⭐ 这里最容易听岔:注意力不是「挑出最像的那一个」而是按百分比把几家的货混成一碗
⛔ 而混完之后,牌子和货(K 和 V)不能扔 —— 下一个字还要用留下来的这两份,就是这本书的反派:KV cache
出处与口径

Vaswani et al. 2017 论文 §3.2:「a weighted sum of the values … compatibility function of the query with the corresponding key」

同一篇论文 §3.2.1 与脚注 4:「the dot products grow large in magnitude, pushing the softmax function into regions where it has extremely small gradients」

📌 那个 √d_k 看着吓人,说白了就是一个标准差除掉它只做一件事:把打分的高低差拉回正常范围。

⛔ 别小看这一下 —— 不除,整碗货会被一家吃掉; 除了,才混得出一碗真正的混合物。

2.5 多头在多什么

多头在多什么 —— 不是堆算力,是不让「平均」把不同的关注糊成一团 原文:「With a single attention head, averaging inhibits this.」—— 单头不是不够用,是会把该分开的东西平均掉。 Ⓐ 单头 一个 query,三个想去的地方 一个 query 上一个词 这句话的主语 三段前那个人名 平均之后 落在这儿 只有一组权重 —— 那根粗线又短又谁都没指到。 Ⓑ 多头(h = 8) 切成 8 份,每份 64 维,各看各的,最后拼回来 head 1 d = 64 head 2 d = 64 head 3 d = 64 head 4 d = 64 head 5 d = 64 head 6 d = 64 head 7 d = 64 head 8 d = 64 Concat → 再过一个 W_O,拼回 512 那个 W_O 到底是什么? 订在一起 = W_O 它不是新东西 —— 八个头各自往同一条残差流上加了一份,加法而已。 8 × 64 = 512—— 总维度没变、参数量没变。多头是「切开」,不是「加倍」。 这只是原始 Transformer 的取法,不是通例 本课的主角 DeepSeek-V3 是 128 头 × 128 维 = 16,384,比它的 d_model 7,168 大 2.29 倍 多头的代价,正好是本专题的题眼 好处:一个头要同时盯语法、盯指代、盯主题,只能给一个折中的答案;切成 8 个,各盯各的,互不干扰 换个说法:多头买到的不是算力,是分辨率。 而这个形状的代价,2019 年就被点名了 Shazeer 2019(MQA 那篇)摘要原话:训练很快,因为序列方向可以并行;但 —— 「incremental inference … is often slow, due to the memory-bandwidth cost of repeatedly loading the large "keys" and "values" tensors
⭐⭐ 这一格里藏着后面四章的全部导火索。为了看得更清楚,一个头被切成了八个 —— 而这一刀的代价,绿带里已经替你算完了。
从下一章起,所有人都在跟这个代价讨价还价第四章直接砍它、第五章把它压成一份压缩件、第六章照存但不全读、第七章干脆不存。
出处与口径

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 用的就是它

2.6 本章落点:KV cache 就是在这里出生的

前面讲的都是想法。下面这张图只要你做一件事 ——  看完 Ⓑ 那三堆,回答:哪一堆会跟着对话一直变大?

KV cache 是在哪一步出生的 —— 一个字母都不用认的版本 这一格只送出一句话:这一层算完,只有两样东西必须留到下一个字 问题 钥匙 · 内容 会一直变长 Ⓐ 一个字进来 —— 先变出三样东西,再拿它们互相问一遍 这一格一个记号都没有,看画就行 一个字 比如「猫」 问题 我想找什么 钥匙 我能回答什么 内容 找到我,我给什么 三样都是同一个字变出来的 —— 各管各的用途 然后这一步只做两个动作 ① 拿我的问题,去跟前面每一个字的钥匙比一比 —— 谁的钥匙跟我的问题越像,我就越多看谁一眼 ② 按刚才比出来的比例,把大家的内容拌到一起 —— 拌出来的这一份,就是这个字这一步的输出 注意:①要用到前面每一个字的钥匙,②要用到前面每一个字的内容 Ⓑ 这一层算完,桌上的东西分三堆 —— 只有一堆必须留着 判断标准只有一个:下一个字还用不用得上它 常驻的设备 模型的权重 所有字共用同一套 不随对话变 留 —— 但它本来就在那儿 草稿纸 中间算出来的那些量 比出来的那些「像不像」 拌完就没用了 扔 —— 算完就扔 锁进柜子的 钥匙 和 内容 下一个字还要拿它们比、拿它们拌 所以必须留着 留 —— 而且每来一个字就多一格 Ⓒ 于是那第三堆越堆越高 —— KV cache 就是这么出生的 别的两堆都不随对话变,只有这一堆一直长 第 1 个字 内容 第 2 个字 内容 第 3 个字 内容 第 4 个字 内容 第 5 个字 内容 第 6 个字 内容 这一整条就是 KV cache —— 每来一个字,右边就多一格,而且一格都不能扔(下一个字还要用) 一万个字的对话,这里就是一万格;十万个字,就是十万格。它跟着对话一直长下去。 而第一堆(权重)从头到尾一样大 —— 所以对话越长,账单里越是这一堆说了算。 所以这一章的落点就一句话 1990 年那个盒子,换成了一条会一直变长的格子。 盒子的好处是永远那么大,坏处是只能一个一个来;格子正好反过来 —— 能一次算完,但会一直长。 后面七章,全是在想办法拿这条格子开刀 —— 有人让每一格变小,有人让每步少看几格,还有人干脆想把它换回那个盒子。
⭐⭐ Ⓑ 那三堆是这张图的全部 —— 权重本来就在那儿,中间量算完就扔 —— 三堆里只有一堆过不了这一关。
而 Ⓒ 把「必须留」翻译成了画面:每来一个字就多一格,一格都不能扔。—— 后面七章全在拿这条格子开刀。
出处与口径

这张图刻意不画多头、不画残差与 MLP、不写任何张量形状 —— 完整的那张(沿用 How to Scale Your Model 的记号)就在本节里折着,点开即可;后面几章还会把它重画并点亮被改动的那一处

⚠️ Ⓐ 里「问题 / 钥匙 / 内容」是 Q / K / V 的中文说法,本课从头到尾用这一套;⛔ 「拌到一起」指的是按权重加权求和,不是把内容搅混 —— 这一点跟第一章那个「叠上去不是搅匀」是同一件事

⚠️ Ⓒ 里那六个字只是画面,真实的一格里装的是两条向量,不是两个汉字

同一件事的专家版:一层 Transformer 的完整张量图 —— 沿用 How to Scale Your Model 的记号(BTD / BSKH …)。 第一次读可以完全跳过但后面几章会反复回到它, 每讲一个新方案就把它重画一遍、只点亮被改动的那一处。
主线图 · 一层 Transformer,每一步的张量形状 ⭐ 这张图会在整个专题里反复出现。每讲一个方案,就把它重画一遍,只把被改动的那一处点亮 —— 你永远知道自己在图上的哪儿。 读这张图先认字母: B 批量 · T query 长度 · S KV 长度 · D d_model · F MLP 隐层 · H 头维 · N query 头数 · K KV 头数 · G = N∕K X BTD —— 一层的输入 BTD W_Q · DNH BTNH Q BSD W_K · DKH BSKH K BSD W_V · DKH BSKH V reshape BTNH → BTKGH BTKGH BSKH BTSKG Q·Kᵀ + masks 谁能看谁 softmax BTSKG(注意力矩阵,记作 A) 这一步不落地,贵在要算的次数 BTSKG BSKH BTKGH A·V reshape BTKGH → BTNH BTNH W_O · NHD BTD 输出投影 + 残差 BTD norm BTD MLP(本专题一个字都不改它) BTD ·DF→ BTF ─gelu⊛─ BTF ·FD→ BTD + 残差 → norm BTD → 下一层 这一层算完,桌上的东西分三堆 常驻的设备 权重 W 所有人共用,不随对话变 草稿纸 中间那些量 算完就扔,不留到下一步 锁进柜子的 K 和 V 每来一个 token 就多一格 ★ 先只看一件事:图上什么东西需要留到下一个 token 这笔账在后面四张图里一直挂着 —— 每张会说清它把这笔账动到了哪一格。 📌 这一格的完整讲解在图下面的正文里 ⬆ 红底那两处就是它 —— 全图只有这两个输出要跨 token 留下来 📌 形状记号沿用 How to Scale Your Model(jax-ml.github.io/scaling-book),本图为重画。
别急着看懂整张 —— 现在只做一件事在这一堆字母里,把那个会随对话越变越长的找出来。
⭐⭐ 找到它,你就找到了后面所有招数共同的靶子 —— 剩下七章,全是在想办法拿它开刀。

⭐⭐ 给它起个名字:这一讲的主角,就是那个 S

后面每一章你其实只要盯着它问一句:这一招是让 S 前面的系数变小、 让每步读到的 S 变少,还是干脆让 S 从形状里消失?

名词有几十个,它们全都只在回答这三问中的一个。

📌 这里一定会想问:存不下,为什么不能每步重算?

因为重算的不是「再算一遍」,是每一步都把整段历史重算一遍。 吐第 100 个字要把前 99 个重过一遍,吐第 101 个再重过一遍 ——  整段生成的总开销会从正比于 N² 变成正比于

⛔ 所以这不是「省一点」,是能不能用的分界线。 KV cache 是拿显存换时间 —— 而这本书剩下的部分, 全在算这笔交换到底有多贵。

这一章也只留两句话。

换来的:任意两个字之间只隔一步,而且整段话可以一次算完 ——  训练终于能把机器喂饱了。
欠下的:要算的格子从 n 变成 n²;更要命的是那个盒子没了, 换成一份会随对话一直长下去的 KV cache。

下一章不讲新招,只做一件事: 把这份 KV cache 换算成实实在在的显存数字。 数字出来之前,谁都不会觉得它是个事。

📌 这一章的结尾,留给发明注意力的那个人。

2017 年 Transformer 那篇出来的时候,Bahdanau 的反应是一句话:

「我当场就跟同组的人宣布:RNN 死了。

他说对了一半 —— RNN 确实从训练里退场了。下一章你就会看到,它在另一个地方好端端地活着; 而到第七章,还会有人正式把它请回来。

第 三 节

账单到期 —— 上下文一长,那张表就付不起了

上一章欠下一份会一直长下去的 KV cache。 可这件事一点都不新鲜 —— 那篇论文发表的那天,它就已经在那儿了。 为什么隔了七年,才有人真的动手去改?

3.1 先看一件反直觉的事:吐字的时候,它又变回了那条链

动手之前得先看清楚:这笔账到底是在什么时候付的 —— 而答案会把我们送回第一章

而生成文字的时候,Transformer 又变回了 1990 年那条链的形状 它没有治好 RNN 的病,它只是把病从「训练」挪到了「推理」—— 而且挪过去之后更重。 串行 并行 串行 + 一路变长的 KV Ⓐ RNN 训练和推理都一样 串行 每步搬:W + 一个固定大小的 h 算术强度 = B t1 W + h t2 W + h t3 W + h t4 W + h t5 W + h tn W + h 一步一格,权重每步重搬 Ⓑ Transformer 训练 它当年赢下来的地方 并行 整段只搬一次:W 算术强度 = n · B t1 … tn 全部一起算 搬 W 一次 一次搬运换 n 倍的活干 Ⓒ Transformer 解码 你现在用的每个大模型 病在这里 串行 每步搬:W + 越来越长的 KV 权重那半 = B|KV 那半 = G(跟 B 无关) t1 W + KV t2 W + KV t3 W + KV t4 W + KV t5 W + KV tn W + KV 加 batch 只救得了权重那半,救不了 KV 那半 Ⓒ 那一行的算术强度,必须拆成两半看 权重那一半:全 batch 共享一份,所以攒 batch 有用 —— 强度 = B。这是所有「加大 batch 提吞吐」的依据。 KV 那一半:每个请求各存各的。batch 一大,读的字节和算的 FLOPs 同比例涨 —— 强度恒等于 GQA 的组大小,跟 B 无关。 把 Ⓐ 和 Ⓒ 摆在一起看 —— 这就是整个专题三的舞台 两行用的是上面同一组 t1…tn 的位置 两边一模一样的部分 每步把整份 W 重搬一遍 W W W W W W 这一排两边共用,区分不了任何事 不一样的只有一样:每步还得额外搬什么 Ⓐ 状态 h 平的 —— 跟上下文多长无关 Ⓒ KV cache 一路长 —— 128K 时比权重还大 h 这条画出来只为当对照 —— 它比权重小两个数量级,账上一直略去(见上一格)。这里比的是会不会随 token 数变长,不是比谁占得多。 整门课要拧的,就是上面那排紫条 后面三个旋钮拧的全是同一件事:让 Ⓒ 那一排每步额外搬的东西变小—— 存少点(①)、看少点(②),或者干脆换回上面那排红的(③,线性注意力)。
三行用的是同一把尺子,所以可以直接叠着看。Ⓐ 是开篇那条老链,Ⓑ 是它当年赢下来的地方,Ⓒ 是你此刻正在用的那个东西。
⛔⛔ 赢下来的是 Ⓑ —— 可你每天在用的是 Ⓒ。
出处与口径

⛔ ⛔ Ⓒ 那一行的算术强度,必须拆成两半看(接上图)

⭐ 拿 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」。

⭐ 这不是打比方 —— 第一章那条链, 在这里原封不动地回来了一次

只不过这一次它是被迫的:没人想要它,它自己长回来了。

⭐⭐ 第七章我们还会再见到同一幕 ——  那一次,是有人主动把它请回来的。

3.2 那为什么偏偏是这两年

⭐ 这个问题的答案不在论文里,在机房里

为什么是现在 —— 同一个模型,两个场景,主角换人了 这个形状 2017 年就造出来了,2019 年就有人指着它说是问题,可全行业真正动手改是 2024 年以后。中间那几年,技术一个字没变。—— 这张图画的是那个变了的东西。 模型权重(两边完全一样) KV cache 要几张卡 ① 同一个模型(DeepSeek-V3)。权重那一段一个字节没动。 61 层 · bf16 · MLA 权重 625 GiB KV cache 0.27 GiB(细到画不出来) 一个人,短对话 上下文 4K 要 7 张卡 权重 625 GiB KV cache 549 GiB 64 个人,长文档 上下文 128K 要 13 张卡 把这两根柱子读成三个数 权重 625 GiB → 625 GiB (一个字节没动) KV cache 0.27 GiB → 549 GiB (涨了 2048 倍) KV 占总量 0.04% → 46.8% 要几张卡 7 → 13 (翻了将近一倍) 什么都没改。工作负载变了,不是技术变了。 ② 别忘了:上面那根红柱子已经是 MLA 压过之后的 —— 如果它用 MHA 呢 同口径的反事实 把四个场景摆到同一根尺子上 —— 横轴就是「要几张卡」(权重 625 GiB + KV,按 TPU v7 每 device 94.74 GiB 向上取整)。 0 4 8 12 16 20 张卡 MLA · 一个人 7 张卡 MHA · 一个人 12 张卡 MLA · 64 个人 13 张卡 MHA · 64 个人 337 张卡 —— 按这把尺子还要再往右画 11.8 个图宽 一张卡,人数差 64 倍 把第 2、3 行叠在一起读:MHA 伺候一个人要 12 张卡,MLA 伺候 64 个人要 13 张卡。同样一台机器 —— 一个换六十四个。 所以 MLA 那个 56.9×(= 488 ÷ 8.58)不是一次「优化」—— 它是把这件事从「做不了」变成「做得了」。 这张图解释了一条时间线上的怪事 2017 年这个形状就造出来了;2019 年 MQA 那篇论文的摘要里就写着「incremental inference is often slow, due to the memory-bandwidth cost of repeatedly load ing the large keys and values tensors」—— 问题早就被指出来了 可全行业真正动手改,是 2024 年以后。中间那几年,技术一个字没变 顺手给一条提问顺序,本讲后面一直在用 问「省了多少」之前,先问「省的是哪一样」。权重是所有人共享一份,KV cache 是每人一份 —— 它们根本不是同一类开销。 所以 KV cache 不是「显存里的一项」,它直接决定你能同时服务多少人这一条到专题六会变成 batch size 的硬上限。
⭐⭐ 这张图最该带走的,是它什么都没证明「技术变差了」。同一个模型、同一份权重,只是被拿去干了另一种活,瓶颈就自己换了个位置。
所以这本书后面所有的招,修的都不是模型 —— 修的是「我们想怎么用它」和「机器给得起什么」之间的那道缝。
出处与口径

装置偷自知乎 姜富春《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 才成立)。⚠️ 换别的硬件只是这两根柱子的刻度变,结论不变;而且这笔账只算了装得下装不下,没算带宽

⭐⭐ 「突然」这两个字是怎么来的

一样东西翻十倍,你会看着它一点点变贵;可要是两样东西各翻了几十倍、 而且都乘在同一项上 —— 你看见的就不是变贵, 是某一天早上它忽然成了最大的一块

⛔ 技术没有变坏。是我们换了一种用法去用它。

3.3 「必须省」只是一半,还有另一条完全独立的线

到这里为止,理由都是机器给的:装不下、读不动。 但如果只有这一条,这件事最多做成一堆将就的权宜之计。

真正让这么多人扑上来的,是还有第二条线 ——  而且它跟显存一点关系都没有。

为什么是现在 —— 两条完全独立的线,在同一个地方交汇 这两条必须分开讲:它们指向同一批技术,但出发点完全不同 —— 混在一起讲就成了名词罗列 线索 A · 硬件账算不过来 结论:必须省。不解决它,长上下文根本上不了线 拿 DeepSeek V3 砸体感(61 层 · 128 头 · 每头 128 维 · 128K · bf16) 一个 token 要存 3.81 MiB × 131,072 个 token 488 GiB 一个用户 (中间那几步在「出处与口径」里) 换个单位就看得见了 —— 一格 = 一块 v7 device 的 HBM(94.74 GiB 可分配) 这一个用户的 KV cache 488 GiB 一个人就要 5.2 块 V3 全部权重(671B,原生 FP8) 625 GiB 所有人共享这一份 换成 GQA-8 30.50 GiB 省 16 倍 —— 还是装不进一块 换成 MLA(V3 真实方案) 8.58 GiB 省 56.9 倍 —— 这才塞得下 权重是所有用户共享一份,KV cache 是每人一份—— 所以它直接决定你能同时服务多少人。 488 是「假如 V3 用 MHA」的反事实值,不是实测。 线索 B · 信息本身不需要那么多 结论:可以省,而且不太亏 128K 的序列,真需要 128K 份独立的 KV 吗?三个观察 —— 实测极其稀疏 绝大部分权重集中在很少的位置, 其余近乎为零。 那把近零的那些算出来,算的是什么? ↑ 这一列 总是亮的 Attention sink 注意力被大量「停放」在开头几个 token 上, 跟内容无关 有一部分权重根本不是在做检索。 远 ← → 近 远近有别 邻近几十个 token 密集细粒度; 几万之外稀疏粗粒度。 凭什么用同一套精度处理这两种? 于是这三样都变得合理:压缩(远处多个合并成一个) · 稀疏(只挑相关的看) · 分层(近精细远粗糙) 两条缺一不可 —— 所有变体都活在它们的交汇处 只有 A(必须省):你得到的是一堆有损压缩的权宜之计,效果掉了只能认 只有 B(可以省):你没有动力去付 kernel 那么难写的代价。
左右两半是各长各的,读的时候别把它们并成一句话。一半是机器逼出来的,一半是数据自己露出来的破绽。
⭐⭐ 右下角那把尺子先记一下 —— 从下一章起,每出场一个新名词,我们都只拿它量两下。
出处与口径

📐 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 维,严格算这个基线还会更大

⚠️ 右栏三个观察是现象,不是本课实测;它们各自对应后面的一支方案(稀疏 → §六、分层 → §六、压缩 → §五)

⭐⭐ 右下角那把尺子,是这本书后面五章唯一的评分表

而且这两问的顺序不能反 —— 先看「省了多少」,很容易被一个漂亮的 倍数晃花眼;真正决定一个方案有没有人用的,是第二问。

名词会换,这两问不换。

3.4 本章落点:它决定的不是装不装得下

这一章一个新招都没讲 ——  它只做了一件事:把上一章那笔债,换算成了看得见的东西。

⛔ 而最要命的那一条,值得换句话再说一遍: 模型的权重,一台机器上只放一份,谁来都用它; 可那份 KV,来一个人就得单开一份
所以它卡住的不是「这台机器装不装得下这个模型」, 而是这台机器同时伺候得了几个人

⛔ 而且这笔账不是线性涨的,是一级一级往上跳能一张卡装下的,就别跨卡;能一台机装下的,就别跨机 ——  因为卡里面的带宽 > 卡跟卡之间 > 机器跟机器之间, 每跨一级,整套推理就被最慢的那一段拖一次。

下一章开始真的动手,第一刀也最直白: 既然每个头都要自己的一份,那让几个头合用一份,行不行?

第 四 节

第一次省:把头砍掉 —— MQA 砍过了头,GQA 停在半路

账摆在那儿了:一人一份,而且随着话越说越长上一章末尾已经把最直白的那个省法问出来了 —— 这一章就去试。

4.1 先把四种存法摆在一起

这一章和下一章讲的是同一个旋钮上的四个位置。 先看全景,再逐个拆 —— 今天讲左边三家,最右边那家留给下一章。

四种存法 —— 先只看接线,一个数都不看 这张图只回答一件事:省,省在哪儿。省多少、值不值,留给下一张。 看最下面那排行李箱就够了 —— 四家的算力几乎一样,差别全在「这段对话得一直留着多少东西」 Q 头 实线盒子=要进箱子 虚线盒子=用完就扔 MLA 的压缩件 同一个模型、同样多的头,只换「K/V 怎么存」这一件事 每一格从上往下读:一排头 → 它们各自去问谁要 K/V → 最后哪几个盒子进箱子 MHA 每个头各问各的 Q Q Q Q K V K V K V K V 跨 token 要留下来的 谁也不共用 —— 这就是基准线,没省。 GQA 几个头合用一份 Q Q Q Q K V K V 跨 token 要留下来的 头还是那么多, 箱子里的盒子变少了。 MQA 所有头合用同一份 Q Q Q Q K V 跨 token 要留下来的 共用到头了, 也最容易掉点。 MLA 谁也不直接存 K/V Q Q Q Q 压缩件 K V K V K V K V 用的时候现场展开,用完就扔 跨 token 要留下来的 箱子里那个不是 K/V 这一排箱子,就是后面所有账的那个「一份」 四家该做的乘加几乎一样多 —— 省下来的不是算力,是「这段对话从头到尾得一直背着的东西」 MLA 那一格请多看两眼:箱子里只有一个,而且它不是 K/V。每个头真正要用的 K/V 是从它现场展开出来的 —— 那几个虚线盒子用完就扔,不占箱子。 这张图故意不回答的两件事 ① 省多少。下一张散点图 —— 那里横轴是「一份占多少地方」,每个数本课都当场算过。 那张图有个反直觉的题眼:MQA 比 MLA 存得还少 ② 凭什么能这么省、以及谁掉点。「共用」那一步到底是什么,看 那张把复制矩阵一格一格画出来的图;MLA 展开那一步为什么不白费力气,看 「吸收」那张图
零公式、零数字,只回答一件事:那个「一份」到底是什么。
四家该做的乘加几乎一样多 —— 所以下面两章跟你算的每一笔,算的都不是算力。
最右边那一格今天先不看(它根本没在共用 K/V),留到下一章
出处与口径

⭐ 这张图不含任何数,所以也没有需要核的口径 —— 它画的是四种做法的接线关系,四家各自的出处(MQA arXiv 1911.02150、GQA arXiv 2305.13245、MLA DeepSeek-V2 arXiv 2405.04434)记在后面那几张有数的图上

⚠️ 头的个数画成 4 只是为了一眼能数完,跟任何真实模型的头数都无关;GQA 画成两组同理 —— 真实分组数看讲四种存法的那张散点图

⛔ 图里不区分 K 和 V 各自的份数(它们在这四家里都是同进同出),一个盒子代表「一个头位置上的 K 和 V 合起来的那一份」

4.2 一刀砍到底:MQA

2019 年的做法简单粗暴:所有头合用同一份 K 和 V八个头本来要八份,现在只要一份 —— 缓存直接除以八。

省得漂亮。可它也就是从这儿开始变笨的。

📌 下面这张图里会出现「困惑度」这个词,这里先交代一句: 你就把它理解成 —— 模型读下一个字的时候,心里大概在几个候选之间犹豫数越小越好。 ⭐ 而且要有个尺度感:这一档模型在三十上下差 1.0 不是个小差距 —— 后面你会看到有的方案改了一大堆东西,才换回零点几。

砍头这一支 —— 贵的是通讯录,不是查的人 一个 query 头 = 一个要查东西的人;一份 K/V = 一本占抽屉的通讯录 各带一本 共用一本 只派一个人 ① 三种办法 —— 后两种占的抽屉一样多 同一篇论文的消融表 ① 八个人,各带一本 通讯录: 抽屉塞满 困惑度 29.9 MHA ② 八个人,共用一本 通讯录: 抽屉空了 困惑度 30.2 MQA ③ 只派一个人去查 通讯录: 抽屉一样空 困惑度 31.2 真单头 h=1 后两种占的抽屉一模一样(都只有一本),差别只在「还有几个人在查」—— 就这一点,困惑度差了整整 1.0 ② GQA —— 分组,每组一本 它的两个发明点,都不是「折中一下」 第 1 组 共用一本 第 2 组 共用一本 第 3 组 共用一本 第 4 组 共用一本 发明点一:不用从头重训 原来八本 平均 一本 续训 只要 5% 算力 → 直接能用 发明点二: 真正的动机是「模型越大,头越多」 全组共用一本,在小模型上还行;模型一大,削减力度就失控了 —— 分组让「几个人共用一本」这个比例,跟着模型规模走。 带走一句:贵的是通讯录,不是查的人 砍通讯录(K/V)省的是真金白银砍查的人(query 头)省不了多少,却很伤。同样一本通讯录,八个人查和一个人查,困惑度差 1.0。 这也正好解释了后面那件反直觉的事:MQA 存得比 MLA 还少,效果却更差 —— 它砍到的不只是通讯录,还砍掉了「从几个角度去查」这件事。 暗线第二次出现:事后压 vs 从头按压缩训 GQA 是事后的典范 —— 把已有的那几本通讯录平均成一本,再用 5% 的原始预训练算力续一下就能用,不用从头重训 MLA 是从头的典范 —— 要重训,但换来的是 56.9× 而不是 16×。 这两条路一直并存到今天,讲稀疏时还会再遇见两次。
⭐⭐ 整章的证据都在中间那三行困惑度上,值得盯久一点。
它们回答的不是「MQA 掉了多少点」,而是一个更有用的问题:同样只剩一份,为什么少了几个「问的人」就差这么多。
出处与口径

①② 出自 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

⚠️ 「通讯录 / 查的人」是本课的比喻

「变笨」这件事有两张脸,而第二张更麻烦。

GQA 那篇论文开头就把话说全了:MQA 会导致 「quality degradation and training instability —— 不只是掉点,还训不稳

掉点还能拿更多数据去补;训不稳是另一回事 ——  它意味着这条路上你随时可能摔一跤,而且事先不知道会在哪儿摔。

⭐⭐ 把图里最后那句话抬一层,就是这本书后面一直在用的判据:

一个方案压掉的,是「体积」还是「自由度」?
—— 用图上那个比喻说:是通讯录变薄了,还是查的人变少了?
通讯录薄一点通常还好;查的人少了,很贵。

这条后面还要用四次 —— 每一次都会有人想拿自由度去换体积, 而每一次都要回来问这一句。

4.3 本章落点:这一刀,其实只改了一块矩阵

这一刀,其实只改了一块矩阵 —— 四种存法摆在同一块矩阵上比一次,外加这一章的 「换来的 / 欠下的」两句。可以直接跳过下一章开篇说的「那块写死的矩阵」,指的就是里面那张图。

2023 年的 GQA 给了个折中:别全合,分组合 —— 八个头分成几组,一组共用一份。 ⭐ 顺带一句:大家不约而同都选了 8 组, 而一台机器通常正好装 8 张卡 ——  这个数字不是从模型里推出来的,是从机箱里数出来的。

而「共用」听上去是个操作,可把它摊开看,它只是一次线性变换 ——  而线性变换,是可以画成一块矩阵的。

「分割和复制,都是简单的线性变换」—— 那就把它画出来 同一条流水线、同一个位置的同一块矩阵 · 例子取 4 个头、每头 2 维,所以每层的 K 一共 8 维 写死的 1 恒为 0 学出来的实数 ① 四个成员走的是同一条流水线 左右两头完全一样,区别全部集中在中间那一块 输入 x 这一层的隐向量 压一次 乘 W_c,得到要缓存的 c 这一块 从 c 还原出 4 个头的 K 4 个头的 K 一共 8 维,四个成员完全一样 四个成员的区别,全在这一块矩阵里 缓存的是这里的 c,不是右边的 K ② 那一块矩阵长什么样 行数 = 要缓存的 c 有多宽 · 列数都是 8(= 还原出来的 K)· 蓝格写着 1,灰格恒为 0 MHA c 宽 8 · 8×8 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 MQA c 宽 2 · 2×8 1 0 1 0 1 0 1 0 0 1 0 1 0 1 0 1 GQA-2 c 宽 4 · 4×8 1 0 1 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 1 0 1 左边两块是两个极端:MHA 退化成单位阵 —— 一个数都没复制,也一点都没压,c 就是 K 本身;MQA 是同一份被抄了 4 遍 —— 只存 2 维,代价是四个头拿到的 K 一模一样。 右边两块形状完全相同,都是 4×8、cache 都是 4 维:GQA-2 的 32 格里只有 8 个 1,其余 24 格恒为 0、而且不可训练 MLA 的同样 32 格全部可训练。 这就是全部的区别 —— 不在「压不压」,在这块矩阵是写死的还是学出来的 MLA c 宽 4 · 4×8 同样的 4×8,同样的 cache 宽度 只是这 32 格全部可训练 (不填数字:编出来的小数会被当成真权重) 把 0 和 1 换成学出来的实数 ③ 所以分界线在哪 不是「有没有低秩」,是「那个矩阵是写死的还是学出来的」 低秩区分不了这两个 把 GQA 所有的 K、V 叠在一起,GQA 本身就是一次低秩投影 —— 这一步 MHA 也好 MLA 也好,大家都在做。所以「低秩」不是分界线。 真正不同的是低秩之后那一步 GQA 用分割 + 复制把 c 凑成 4 个头的 K, 而分割和复制本身就是线性变换 —— MLA 只是把这个写死的变换,换成一个一般的、可学的。 但这么一换,KV cache 会涨回去 矩阵一放开,四个头的 K 又各不相同了 —— 要是照常缓存 K,cache 就退回 MHA 那么大, 省的初衷当场作废 MLA 能成立,靠的是下一小节那个恒等变换: 只缓存 c,把上投影矩阵挪到 q 那一侧去 这张图是「吸收」那一步的前提,不是它的替代。 一句话记住 GQA 的上投影是一个写死的 0/1 复制矩阵;MLA 把同一个位置、同一个形状的矩阵松开让它学 两头也在这条轴上:MHA = 单位阵(不复制也不压)、MQA = 一份抄满
⭐⭐ 这四块矩阵,是这本书里信息密度最高的一张图。四个名词第一次被摆在同一个位置上,比的只有一件事:同一块矩阵里写了什么。
看完就会明白,为什么下一章不是「又一个新方法」,而只是把其中一块的格子解开、交给模型去填
出处与口径

📌 苏剑林《缓存与效果的极限拉扯:从MHA、MQA、GQA到MLA》kexue.fm/archives/10091 —— 「低秩投影这个角度并不贴近本质」「MLA的本质改进不是低秩投影,而是低秩投影之后的工作」「我们知道分割、复制都是简单的线性变换」三句均为原文逐字。

📌 图上那几个矩阵是本课按 h=4 / d_k=2 自己构造的,脚本用 numpy 断言过「c 乘这个矩阵」与「把 c 按组复制」逐元素相等 —— 这不是类比,是恒等。

📌 MLA 那一格刻意不填数字:本课没有 V3 的真权重,编几个小数放上去会被当成真的。

这一章的两句话。

换来的:缓存除以 8,而且不用重训 ——  GQA 因此成了这些年的默认选项。
欠下的:省的办法,是让好几个头拿到一模一样的 K/V。 省得越狠,大家越像;到 MQA 那一步,八个人看的是同一本。

⭐⭐ 而图里已经把话说到头了:这一刀改的,就是那块矩阵里写了什么。 下一章做的事只有一件 ——  把那块写死的矩阵松开,让它自己学。

第 五 节

第二次省:干脆不存 K 和 V —— MLA 存的是一份压缩件

上一章最后停在一块矩阵上(收在 4.3 里):GQA 的那块是写死的,格子里只有几个 1。 那把它松开、交给模型自己去填,会怎么样?—— 那就是 MLA。 而它带来的后果,比「效果好一点」大得多。

📌 要看懂这一章,先接受一个前提:这四家其实是同一个形状

仓库里存一份东西,再从这一份,做出每个头各自要用的 K 和 V。 四家都是这个形状。区别只有两件事:那份有多宽, 以及「做出一百二十八份」的那个动作是什么动作

GQA:仓库里存八杯调好的果汁。用的时候 照着每一杯复印出十六杯一模一样的,凑够一百二十八杯。
那十六杯一滴不差,多出来的十五杯是纯冗余,不带任何新东西。
所以 GQA 省的是仓库,代价是那十六个头被迫拿到同一杯

MQA:只存一杯,复印一百二十八份 —— 所有人喝同一杯。
MHA:老老实实存一百二十八杯,各不相同,一份都不用复印。最贵,也最自由。

⭐⭐ 而 MLA 存的不是果汁,是一包浓缩粉 一百二十八个头,每人一张自己的配方,从同一包粉里冲出 一百二十八杯各不相同的。

所以「松开」松开的是这个动作本身:从复印变成 复印只能得到一样的;冲可以得到不一样的 ——  而两个动作花的力气差不多。既然反正都要做这一步,为什么只许你复印?

⚠️ 口径:这不等于「MHA / GQA 是 MLA 的特例」——  那是转述放大出来的说法。苏剑林的原话是「MLA 被视为 GQA 的一般化」。
他把「换掉那个动作」这件事写成了一句更准的话 ——  就在下面 5.x 那张矩阵图里,这里不重复抄一遍。

5.1 先说一件最容易听岔的事:箱子里那个,已经不是 K 和 V 了

前三家存的都是 K 和 V 本身,区别只是存几份。 MLA 换了一条路:它存的是一份压缩件 ——  每个头真正要用的 K 和 V,是从这份压缩件现场展开出来的,用完就扔。

旋钮 ① 让每一份更小 —— 但比的从来不是「谁存得最少」 同一个形状(V3:61 层 · 128 头 · 每头 128 维 · 128K · bf16),换四种存法 四种存法 —— 把它们摆到一张图上 横轴:一份占多少地方(对数) · 纵轴:换回多少能力( 定性) 跟 MHA 基本打平 明显更差 10 100 GiB · 对数轴,每往右一格 ×10 MHA 488 GiB 每个头各存一份 GQA-8 30.50 GiB 8 组,组内共用 MLA 8.58 GiB 压成 512 + 64 MQA 3.81 GiB 所有头共用一份 2.25× 题眼:MQA 比 MLA 还小 2.25 倍,却更差 所以这一支比的 不是「谁存得最少」 MQA 早在 2019 年就把 体积压到头了。 要比的是:同样一份 字节,换回多少能力。 (这正是第五节的线) 纵轴是定性的 —— 四家没有同一份可比的实测。唯一同基准的一对是 MQA 论文表 3:MHA 29.9 / MQA 30.2 / 真单头 31.2(困惑度,越低越好)。 GQA 与 MLA 的论文只声称「接近/不弱于 MHA」,不是同一张表 —— 所以这里只画两档,不画连续刻度。
⭐⭐ 横轴是「一份占多少地方」,越往左越省。那为什么最左边那个不是赢家?
因为这张图比的从来不是「谁存得最少」 —— 它比的是上一章那个问题的答案:省下来的,是拿体积换的,还是拿自由度换的。
出处与口径

📐 横轴那几个数: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 里 —— 本图只到「四种存法各占多少地方」为止

⚠️ 「MLA 就是给 KV 做低秩分解」—— 这句话很顺口, 可它把上一章那张矩阵图的结论说反了。

低秩这件事,上一章那四块矩阵已经摆明了: 四家都在做。所以它根本不是分界线。

⭐ 真正的分界线是展开出来的东西一不一样GQA 展开出来的几个头拿到的是同一份; MLA 展开出来的每个头都不一样 —— 这就是它凭什么能压得更狠还不变笨。

5.2 马上会想到的一个问题:存的是压缩包,用的时候不还得拆开吗?

不用拆。而「不用拆」这件事,是 MLA 能不能省下来的前提 ——  要是每生成一个字都得把仓库里所有压缩包拆一遍,那还不如一开始就别压。

「吸收」到底是什么 —— 把括号挪一下,仓库里的压缩包一个都不用拆 MLA 把 K 和 V 压成一个 512 维的压缩包存起来。那生成下一个词的时候,不是得把它们全拆开才能比对吗?—— 不用。这张图画的就是那个「不用」。 天真做法:全拆开 吸收:一个都不拆 同一块矩阵 W_UK(认这个紫色) ① 天真做法:每生成一个词,把仓库里所有压缩包全部拆开 解压 S 次 仓库(KV cache) 512 512 512 512 512 共 S 个 (128K 上下文就是 13 万个) W_UK 上投影 每个 K 都是 128 维 拆出来的 K,也是 S 个 而且每生成一个词就得全部重拆一遍 注意「重拆」两个字:拆完不能留 —— 留下来就等于又存了一份没压缩的 K,那正是 MLA 想省掉的东西。所以每生成一个词,这 S 次解压全都要重做一遍。 论文原话(DeepSeek-V2):这样就「must recompute the keys for all the prefix tokens during inference」 🏠 换成生活里的说法:书架上有一万本外文书,你每查一个词,就把一万本全翻译成中文再找。查完扔掉,下次再翻译一万本。 ② 吸收:把那块紫色矩阵搬到 q 那一侧 —— 压缩包一个都不用拆 解压 0 次 查询 q(一个,就一个) q(128) W_UK 转置着用 q′(512) 只做这一次 (不管仓库里有多少个包) 仓库(原封不动) 一个都没拆 直接拿 q′ 去跟压缩包点积 q ( W_UK c )( W_UK q ) c 同一个乘法。只是括号挪了个位置。 左边括号在 c 那边 —— 有几个 c 就算几次。 右边括号在 q 那边 —— 只有一个 q,所以只算一次。 🏠 生活版就是一句话:与其把一万本外文书全翻译过来,不如把你的搜索词翻译过去。 V 那一侧同理:W_UV 可以吸进输出投影 W_O —— 所以 K 和 V 两边都不用拆 ③ 省多少?—— 算出来的答案跟直觉不一样 d_c=512 · d_h=128 · 本课自算 直觉会说「解压 S 次变成 1 次,所以省 S 倍」。 不对 —— 吸收之后每个 token 的点积从 128 维变成了 512 维,这一头贵了 4 倍,在把省下的吃回去。 S = 128 (128 个 token 在仓库里) 省 64× S = 1,024 (1,024 个 token 在仓库里) 省 114× S = 4,096 (4,096 个 token 在仓库里) 省 124× S = 128K (131,072 个 token 在仓库里) 省 128× 上限 128× 这个上限正好是每个头的维度 d_h = 128,S 再长也过不去 —— 因为解压一个 token 要 d_h×d_c 次乘加,而点积只要 d_c 次,两者的比就是 d_h。 两条定律,一条允许、一条禁止 —— MLA 最难的两件事都在这里 结合律允许你挪括号:a(bc) = (ab)c。②那一步靠的就是它,论文原话是 「due to the associative law of matrix multiplication, we can absorb W^UK into W^UQ, a nd W^UV into W^O」。 但交换律不成立:ab ≠ ba。RoPE 会往 q 和 W_UK 中间塞进一个跟位置有关的旋转 R,而夹在中间的东西挪不出去 —— 原话「a RoPE matrix … will lie between W^Q and W^UK and matrix multiplication does not obey a commutative law」。 同一个把戏,本讲一共只挪两次括号 —— 这是第一次 这一次 —— MLA 的吸收(这张图):把括号从 qᵀ(W_UK c) 挪成 (W_UKᵀ q)ᵀ c —— 压缩包就不用拆了。 还有一次,在线性注意力那一章:把括号从 (QKᵀ)V 挪成 Q(KᵀV) —— 那个句长×句长的大方块就不用建了。那一次更赚。
看①和②的区别就够了 —— 算的是同一个东西,只是括号站的位置不同。而括号换个位置,仓库里那一万个压缩包就一个都不用动。
这一步要是不成立,MLA 整套设计当场作废 —— 所以它不是一个优化技巧,是这一章的地基。
出处与口径

「吸收」与「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,「只变换一次」这个便宜就没了(这也是 §五 表里「压缩不生效」那一行的意思)

📌 挪完之后,到底省下了什么?—— 答案跟多数人以为的不一样。

先看「不挪」有多贵。仓库里躺着十三万个压缩件(128K 上下文)。 每吐一个字,就得把它们一个一个解压成每个头要用的 K,再拿提问去比 ——  解压十三万次。而下一个字还得再来一遍,因为解压出来的你不敢留: 留了就等于又存了一份没压缩的,压缩白做了。

挪完之后,先算的是「配方转置 × 提问」,而提问只有一个 ——  这个动作只做一次解压次数:十三万次 → 零次。

但请分清它省的是哪一样(这正是第三章那把尺子):

  • 它不省显存缓存。仓库里还是每个 token 576 个数,挪不挪一个字节都不差。
  • ⭐ 省的是算力 —— 那个「跟历史一样长」的解压工作, 变成了一个跟历史无关的一次性动作。历史越长,省得越多。
  • ⭐ 还省带宽和临时显存 —— 解压出来那一大坨要找地方放、 还得写出去再读回来,现在它压根没被造出来

⭐⭐ 而这两件事是绑死的,这才是这一节真正的地基: 挪不动括号 → 每步解压十三万次太贵 → 只能退回去直接存解压后的 K/V → 压缩白做。
所以:挪括号本身不省显存,但它是「存压缩件」这个选择能不能成立的前提。

⭐⭐ 这是这本书第一次真的「挪括号」。 —— 一共只有两次,另一次在第七章,那一次更赚。

第二章那次不算:点积赢加性,换的是打分的公式, 跟结合律没关系。那一次属于另一条线 ——  「能写成什么形状」从那以后就一直在替数学做决定。

两次是同一个恒等式,而且两次都被同一类东西挡过中间夹了个带下标的家伙。这一次是 RoPE。

5.3 那条看起来很别扭的窄轨 —— 可能才是它好的真正原因

MLA 有一处形状很不好看:它每个 token 要存的那一份,是 512 + 64。 512 是那份压缩件;多出来的 64 维专门用来装位置信息,单独走一路
为什么非得这么别扭?—— 下面这张图三步说完,不用读公式。

那条看起来很别扭的窄轨 —— 512 + 64 里的那个 64,是被逼出来的 这一张只回答一个问题:MLA 每个 token 存的那份,为什么是 512 + 64,而不是干净的 512 搬得走 被闸挡住 劈成两条轨之后 为什么 MLA 的 RoPE 必须单独走一路 沿用「吸收」那张图里的紫方块 —— 这一格只多一样东西:一道闸 ① 本来能做成什么 —— 紫色那块搬得走 q 一步只有一个 W_UK 上投影 c 仓库里存的就是它 搬过去 (W_UKᵀq) 预乘好,一次算完 c 原封不动 仓库里那些 c,一个都不用拆 ② 插进 RoPE:路当中立了一道闸 q R(j-t) W_UK c 想搬,搬不动 紫块过不去,那 W_UK c 就只能在仓库里先算出来 —— 那等于又存了一份没压缩的 K,MLA 白压了。 卡住的不是「中间有东西」,是那东西带下标 —— 一块预乘得掉,一摞预乘不掉 假如闸是固定的一块 M W_UQᵀ M W_UK 还是一个矩阵,预乘好收工 —— 跟 ① 没区别 可 RoPE 是相对的:R_tᵀ R_j = R(j-t) R(1) W_UQᵀ R(j-t) W_UK 有多少种距离 就有多少块 这是一摞,不是一个 —— 预乘不出「那一个」,因为根本不存在那一个 论文原话是「matrix multiplication does not obey a commutative law」 —— 那是结论。吸收要的其实是结合律,而结合律一直成立。(说法取自苏剑林,出处见页脚) ③ 解法:把一条轨劈成两条 —— 让闸只站在其中一条上 q W_UK c 512 维,不带位置 —— 闸不在这条上,紫块照旧搬走 qᴿ R(j-t) kᴿ 64 维,专扛位置 —— 这条不吸收,老实存着 两条并起来 = 512 + 64 = 576。 注意这不是「把 RoPE 关小了」—— 位置信息一点没少,只是被赶到了一条窄轨上,好让宽的那条保住 ① 的搬家动作。 把这一节当成一个套路记住,别当成 MLA 的实现细节 「为了保住某个代数变换,把功能拆成两路」—— 这个动作后面还会换面貌出现:V4 的部分 RoPE、K3 的 NoPE MLA 的代价是用计算换显存:多了一对降维/升维矩阵乘。而且它省的是推理时的 KV cache,不是训练时的激活—— 训练前向里 K/V 会被解压出来算。这是个非常常见的误解。
⭐⭐ 三步,从上往下看就行 —— 不用读那两行公式。紫色的方块本来能从仓库那侧搬到提问那侧;RoPE 在中间立了一道闸,搬不过去;于是把一条轨劈成两条,让闸只站在窄的那条上。
中间那个黄框别跳过,它是这张图真正的机关。左右两边画的东西几乎一样,差的只有一个角标 —— 而 MLA 那个 512 + 64 的怪形状,整个是被那个角标逼出来的。
出处与口径

MLA 超参出自 DeepSeek-V3 论文 §4.2:n_h=128, d_h=128, d_c=512, d_h^R=64, 61 层 —— 512 + 64 = 576 由脚本断言

⭐ 「中间那项跟位置差相关,所以合并不成一个固定矩阵」这个说法取自 苏剑林《缓存与效果的极限拉扯:从 MHA、MQA、GQA 到 MLA》(kexue.fm/archives/10091)—— 原话已核

⛔ 论文原话「matrix multiplication does not obey a commutative law」是结论不是原因 —— 矩阵乘法本来就满足结合律,而「吸收」要的正是结合律。真正卡住的是 R 带着 (j−t):有多少种相对距离,就有多少个不同的中间矩阵

Gated MLA(K3 在 MLA 输出端加全秩门控):Kimi K3 技术报告 §2.1.2

⚠️ 那道闸凭什么挡得住?—— 先花三十秒说清 RoPE 在干什么。

模型本身是不知道先后顺序的。 RoPE 的办法是给每个位置一个转角:第五个字,把它的 key 转五度; 第一百个字,转一百度。提问那一侧也转 —— 你在第几个位置就转几度。

妙就妙在:两个向量做点积,只看它们之间的夹角差。 你转了 100 度、它转了 5 度 —— 结果只跟 95 有关, 也就是你们隔了多远。位置信息,就这么自动变成了距离信息。

可麻烦也在这儿。回到那三样连乘(提问 · 配方 · 压缩件), RoPE 一插进来,中间多了一个旋转。

  • 假如这个旋转是一块固定的矩阵,照样挪得动 ——  把它跟配方提前乘成一张新配方就完了。 所以「中间夹了个东西」本身不是问题。
  • 真实情况是:那个角度 = 你的位置 − 它的位置。 问第五个字转 95 度,问第六个字转 94 度 ——  每一对「谁问谁」都是一个不一样的矩阵。

⭐⭐ 说得再白一点:你想提前把配方和旋转合成一张, 可旋转多少度取决于你问的是谁 —— 你还没开口问,就不知道该转多少。

⭐ 还有一个容易漏的细节:那 64 维是一百二十八个头共用的一份, 所以只多花 64,不是 128 × 64。不说清楚的话,这里的开销会被读者心算成差 128 倍, 上面那个 576 就对不上了。

那条「无奈」的窄轨,可能才是 MLA 好的原因 —— 一组受控实验:把 KV cache 钉死在 512,只改注意力的形状。 对主线没帮助,可以直接跳过;想读的话,它的结论是 「MLA 那条被逼出来的 64 维窄轨,恰好撞上了两件对的事」。

📌 读到这儿,很多人会觉得这是个妥协。 —— 可有人做了一组受控实验,结论正好反过来。

那条「无奈」的窄轨,可能才是 MLA 好的原因 同一组受控实验,每一步只改一件事 —— 而 KV Cache 全程钉死不动 ① 把 KV Cache 钉死在 512,只改注意力的形状 —— 两级台阶 纵轴是训练 loss,越低越好。三档的 KV Cache 一模一样,所以台阶量的是「同样的代价换回多少」 ~900M dense · 16B tokens · seq 4096 2.700 2.710 2.720 2.730 2.740 2.750 训练 loss 越低越好 MLA 2.721 cache 576 GQA2-128 2 组 × 128 维 2.750 −0.030 head_dims 128 → 256 (cache、层数、训练 tokens 全没动) GQA1-256 1 组 × 256 维 2.720 −0.009 只给那 64 维加 RoPE GQA1-256-PR 256 拆成 192 + 64 2.711 低于 MLA 0.010 KV Cache 512 三档一模一样 两级台阶合计 −0.039,而 MLA 比这一列的起点只好 −0.029 也就是说:把 head_dims 放宽、再拆出一小段给 RoPE,在普通 GQA 上就已经走过了 MLA —— 而这两样,一样都不是低秩。 这个减法是本课做的,不是原文的话 —— 原文的结论是「增大 head_dims 收益最大,Partial RoPE 也有一定帮助」。 ② 反过来看另一头:把 cache 放大 7 倍,一分没赚 同一张表里的 MHA —— 两根条按真实维度数等比画 MLA 576 loss 2.721 MHA 4096 loss 2.721 两个 loss 一模一样(都是 2.721),而上面那根的 KV Cache 是下面的 7.1 倍 —— 多存的那 3520 个维度,一分没换回来。 这跟 DeepSeek-V2 论文里「MLA 甚至优于 MHA」是同一件事的两种说法。原文对这个现象的猜测是:被比的那个 MHA,head_dims 只有 128。 ③ 先堵一个洞:宽的那档参数量本来就多,会不会赢在参数上 原作者补了三种对齐方式,这里画其中最干净的一种:把窄的那档 num_heads 翻倍,两边都是 943 M GQA2-128 32 条窄的 loss 2.723 GQA1-256 16 条宽的 loss 2.720 同样 943 M 参数、同样 512 的 cache,只差「切成几条」 —— 还是宽的那边赢 0.003。所以刚才那两级台阶不是参数量堆出来的。 另外两种对齐方式(缩 MLP、给 Q/O 上 LoRA)结论同向,原文给的幅度是「heads 翻倍相比 head_dims 翻倍,loss 稳定差 0.003 左右」。 带得走的那一条 压得少和学得好,是两件事。讲「白送 vs 赌」那笔 56.9× 的账管的是「存多少」,一个字都不用改;这一格管的是「同样存这么多,学得多好」。 所以那条被 RoPE 逼出来的 64 维窄轨,大概率不是妥协,是这个设计顺手做对的一件事 —— 原文的说法是「看似无奈的设计,极有可能是它效果优异的关键原因」。
⭐⭐ 这组实验最狠的一点:KV cache 全程钉死不动。所以每一级台阶量的都不是「多花了多少」,而是同样的代价,能换回多少
这也是看任何一篇「我们又省了 N 倍」时该先问的一句:你把代价那一头按住了吗?
出处与口径

📌 全部数字一手核自 苏剑林《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 也有一定帮助」。

⭐⭐ 苏剑林那组实验的三条结论,值得原样记住:

① 增大每个头的维度,收益最大 —— 比多分几组有效得多。
② 只给一小部分维度加位置信息(Partial RoPE),对效果也有帮助。
③ 让 K 和 V 共享,应该也有作用。

而 MLA 那条「被逼出来」的 64 维窄轨,恰好同时做到了 ① 和 ②: 它让吐字时每个头的宽度变成 512+64,又顺手变成了「只给一小段加位置」。

所以那不是妥协,是撞上了对的东西。

5.4 本章落点:MLA 真正漂亮的地方,是它同时向两套账交了差

还记得第三章那件事吗 ——  同一个模型,读题的时候和吐字的时候,瓶颈根本不是一回事。 把这句话往下推一层,就能看见 MLA 到底在解什么题。

📌 两边想要的东西,正好相反。

吐字那一边卡在 KV cache 上。所以给定一个缓存预算, 理论上最好的做法是:把这个预算全给一个头,K 和 V 还共用 —— 因为不管 MHA 还是 GQA 都能被改写成这种形状,它是它们的超集。
为什么是超集?—— 「一份大的、所有人共用」这种形状, 能表达「切成 128 小份各带各的」;反过来不行。 同样的字节数,它装得下最多的东西。

读题那一边卡在算力上,而算力主要看头数 × 每头维度。 每头维度从 128 抬到 512,计算量就是四倍。 所以这边最想要的,是老老实实的 MHA-128。

一边要 512,一边要 128。这是个死结。

⭐⭐⭐ MLA 的大招,就是把这个死结解开了。

它分两步投影:先把输入压成一个 512 维的向量,再从这个向量展开成多个 128 维的头。 然后靠上面那个「挪括号」的恒等变换 ——

训练和读题的时候,它是一个 MHA-128;吐字的时候,它是一个 MQA-576 (512 的压缩件 + 那条 64 维的窄轨 ——  第八章那个 NoPE,这 64 会整个消失,那时才是纯 512)。
同一套权重,两种算法,各自站在自己那一边的最优点上。

苏剑林把这件事叫做「Prefill 和 Decoding 的双向奔赴」, 并且给了一个很高的判断:在相同的训练成本和推理成本下, MLA 可能是效果最好的完整注意力变体。
⚠️ 这是他基于自己那组实验和一套简化假设下的推断, 他本人也写明了「实际情况复杂,结论大概率会有偏差」。

但它也有一处很实在的代价,而且正好长在它最漂亮的地方上。

GQA 的 KV 是按头切的,八张卡就一卡存八分之一,天然分得开。 而 MLA 的那一份压缩件根本不按头分 ——  于是多卡部署时要么每张卡各存一份(省下来的当场还回去几倍), 要么把注意力那一段单独改成另一种并行方式。

这又是本书那条暗线的反面:一个在单卡上极漂亮的数学结构, 到了多卡上恰恰因为「不按头分」而失去了最自然的切法

这一章的两句话。

换来的:缓存压到几十分之一,而且不是靠让大家看同一本 ——  每个头拿到的 K/V 仍然各不相同。
欠下的:算力多花了一点,多卡切分变难了, 而且整套设计依赖一个只在吐字时才成立的代数把戏。

⭐⭐ 到这里,「让每一份更小」这条路基本走到头了。
下一章连这个旋钮都不碰了 ——  KV 一个字节都不少存,照样能省。

第 六 节

换个方向:不是存得少,是别全读 —— 从滑窗到 DSA

前两章一直在拧同一个旋钮:让每一份小一点。 这一章换一个完全没碰过的方向 —— 每一份多大都不改, 改的是每走一步到底读进来几份
先把话说在前面:这条路一个字节的显存都不省。

6.1 先把这一支的全家福摆出来

从左往右,黑格子越来越少:全都算 → 只看身边 → 只看身边再加开头几个 → 挑着看 → 先压再挑。

旋钮 ② 每步只读一部分 —— 五种读法,画成五张 mask 就看明白了 这一支的历史本身就说明该画图:attention sink 那个 bug 从公式上看不出来,是把矩阵画出来才发现的 先说怎么读:格子是示意(16×16 画不出 1.56% 那种量级),每张下面那条细带才是真比例 —— 读格子看形状,读细带看狠不狠。 全注意力 基线:下三角全算 O(L²),KV 随长度线性涨 真实比例:100%(基线) SWA 滑动窗口 只看前面固定窗口(Mistral 4096) 跨 128K 要堆 32 层才摸得到 真实比例:3.1%(窗口 4096 / 128K) + Attention sink 滑窗 + 留住最开头 4 个 只留 4 个就够,扔了立刻崩 真实比例:3.1% + 4 个 sink DSA 学着挑 Indexer 给每个 query 挑 top-k 128K → 2K,64 倍;k=2048 真实比例:1.56%(k=2048 / 128K) CSA 先压再挑 每 4 个 token 压成 1 个,再在压缩后挑 V4:1M 下 KV 降到约 2% 口径不同,不能并排比(见下) 五张 mask 摆在一起,这一支的共同结构就出来了 同一个骨架,三条路粗看(压缩/全局,保证不漏)+细看(挑出来的 top-k,保证准)+近处(滑动窗口,保证局部连贯)。 NSA 三条都有(显式三支路 + 门控融合);DSA 主要是细看配一点局部;CSA/HCA —— HCA 粗看、CSA 细看,另挂一条滑窗;SWA 单用只有第三条 —— 所以它单用不行 Attention sink:一个「只有画出来才看得见」的 bug 朴素滑窗把开头几个 token 一起滑掉,模型立刻崩。而解法简单到荒谬 —— 留住最开头 4 个就够(原话 “with just 4 initial tokens sufficing”)。 为什么?softmax 强制所有权重加起来等于 1 —— 模型有时什么都不想看,却没有「弃权」这个选项,于是学会把多余的注意力倾倒在开头几个位置那几个 token 不是在传信息 ,是停车位。 这三年走了多远 —— 把 2% 那个数算给学生看 同样形状的 GQA-8 在 1M 上下文下是 244 GiB;V4 报的 约 2% 就是不到 5 GiB —— 一百万 token 的上下文,KV 装得进一块卡的零头。 对照本讲开头那个 MHA 的 488 GiB —— 而那还只是 128K这就是三年的进展。
别一张一张看,要五张一起看 —— 这一支的骨架是从「五张摆在一起」里浮出来的,单看任何一张都看不见。
⭐⭐ 看完你会得到一把尺子:以后碰到一个没听过的稀疏方案,先把它按到这五张里去比一比,比记住它叫什么名字有用得多。
出处与口径

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

⭐⭐ 五张图其实只是一句话 ——  这一支从头到尾,都在回答「这一步,到底哪些格子可以不算」。

⭐ 记住这个问法。它跟前两章那个问法(「一份能做多小」) 是两个方向,后面第八章要把这两个方向同时用上。

6.2 最朴素的一招:只看身边那几个 —— 以及它为什么会崩

最容易想到的就是滑动窗口:每个字只看前面固定的几百个。 听上去很合理 —— 说话本来就大多只跟附近有关。

可它有一个著名的崩法,而且崩得毫无预兆

滑窗:凭什么敢砍,砍了为什么会崩 一个按时间顺序讲的侦探故事 敢砍的理由 崩了 真正的原因 ① 凭什么敢砍 —— 每层只看身边几个,但话能往外传 能传多远,比「层数 × 窗口」小得多 话确实能一层层往外传 第 1 层 1 2 3 4 5 6 7 第 2 层 1 2 3 4 5 6 7 第 3 层 1 2 3 4 5 6 7 但每一层往回跳多远,是随机的 L 层 = L 个随机数相加 —— 堆成一个钟形 「层数 × 窗口」 在这儿 —— 钟形的尾巴 早就没了 而且真实模型里,九成五的信息根本没走注意力这条路 残差 —— 直接从底下窜到顶上(约 95%) 注意力 —— 真正往回看的那一小股(约 5%) 每往回跳一个窗口就再乘一次这个小数 —— 于是有效射程跟层数无关,大约就是一到两个窗口宽 Mistral 7B:4,096 × 32 层 = 131,072 那是理论上限,不是能用的长度 所以工业界的答案不是「堆更多层」,是隔一层插一层真正的全注意力 gpt-oss-20b:24 层,滑窗 / 全注意力 1:1 交替 而且它的窗口只有 128 —— 窗口小到这个地步,靠堆层数是绝无可能够到 128K 的。 ② 砍了为什么会崩 —— 扔掉最前面四个,就崩了 Llama-2-13B,PG19 先说怎么读:这三根柱子按对数画 —— 线性画的话后两根根本看不见。 5158 和 5.40 差的是三个数量级,不是三倍。 5158.07 只留窗口 0 + 1024 崩了 5.40 留最前面 4 个 4 + 1020 好了 5.60 那 4 个换成换行符 4 + 1020 几乎一样 困惑度(越低越好) 判决性的是第三根 把那四个 token 换成 毫无意义的换行符 结果几乎一样 → 起作用的是位置,不是内容
⛔⛔ 下面那三根柱子是按对数画的 —— 5158 和 5.40 之间差的是三个数量级,不是三倍。
一个模型从「能用」到「彻底胡说」,中间只隔着四个 token
出处与口径

① 有效射程那一格出自 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 里 —— 本图只到「它崩了」为止

这件事值得单拎出来讲,因为它是一整类 bug 的样板 ——  从公式上你看不出任何问题;是有人把注意力矩阵画出来才看见的。 那下一张图就把「为什么」画出来。

破案:崩掉的不是那几个老 token,是弃权票这个选项 上一张只说到「票投给了谁」—— 这一张说「为什么非投不可」 真正的原因 ① 得票最高的那位,什么都不做 这一步才是机制本身 —— 上一张只说了「票投给了谁」 注意力权重 冲天 它的 value 模长 贴地 第 0 号座位上那个 token: 把票投给他 = 弃权 为什么偏偏是最前面那几个? 因果掩码下,第 0 列是唯一一列全满的 —— 不是它特殊,是只有它人人都够得着 于是整件事说得通了 softmax 不许弃权, 模型就自己造了一个 弃权用的候选人出来: 永远在场、什么主张都没有。 砍掉他不是砍掉一个老 token, 是砍掉了弃权票这个选项。 ② 两条看起来同样彻底的解法,只有一条成立 softmax 要求每一行的票必须投满 Ⓐ 规矩 每一行的票必须正好投满 100 分 哪怕这一行「没什么特别想看的」 100 分 一分不许剩 第 0 号 这一排就是前面的每一个 token 没有「弃权」这一栏 —— 票只能投给在场的候选人 Ⓑ 补一个可学的 桶里有自己的一份 K / V,预训练时跟着一起学 弃权 ↑ 多出来的那一格,就是弃权栏 成立 困惑度 18.01 10 100 1千 1万 10万 ↓ 困惑度,对数刻度(越短越好) Ⓒ 补一个全零的 桶是恒定的零(=softmax-off-by-one) 弃权 ↑ 多出来的那一格,就是弃权栏 崩得更狠 困惑度 29,214 10 100 1千 1万 10万 Ⓑ 和 Ⓒ 的轮廓是一模一样的—— 都是「给它一个可以弃权的地方」。差别只在那一格是不是可学的,而结果差了三个数量级。 加桶之后每一行具体怎么分票,论文给的是困惑度不是注意力分布,所以 Ⓑ Ⓒ 两格没画任何投票柱高 这个故事真正的教益 —— 比 sink 本身值钱 attention sink 不是 bug,也不是谁设计的特性,它是「票必须投满」这条规矩逼出来的副产品 判据:看到模型里一个「毫无道理却极其稳定」的现象,先去找是不是某个守恒 / 归一化约束逼出来的。量化里那批总也压不下去的 outlier,跟这是同一件事(见专题八)。
上一张停在「它崩了」,这一张回答「凭什么会崩」。
⛔⛔ 读 ② 那一格的时候请先不要看数字,先看两个方框的形状 —— 这一格的标题里「看起来」三个字,是要用眼睛验的。
⭐⭐ 验完再看数字,你会得到本讲最便宜也最贵的一条经验:两个长得一样的方案,结果可以差一千倍。
出处与口径

① 「value 模长极小」出自 Barbero 等 arXiv 2504.02732 图 4;「第 0 列是因果掩码下唯一全满的一列」是由掩码定义直接得出的

② 18.01 与 29,214 出自 StreamingLLM(Xiao 等 arXiv 2309.17453, ICLR 2024)表 3 / 表 10 的三个 160M 从头预训练对照 —— ⛔ 两个数必须取自同一组,不许跨表拼

⚠️ 加了桶之后每一行的票怎么分配,论文给的是困惑度不是注意力分布 —— 所以 Ⓑ Ⓒ 两格只画「多了一格」这个结构,没有画任何柱高

⛔ 「不许弃权的选票」不是本课原创 —— Evan Miller 2023-07《Attention Is Off By One》原话就是 「a deafening democracy where abstention is disallowed」

⭐⭐ 这一条正好把第二章那个伏笔收了。

当时 Bengio 说,注意力必须是「软」的 ——  要有一份总额固定的注意力,可以分摊到所有位置上,这样才能训。

「总额固定」是优点,也正是这里的病根。 一个设计的副作用,十有八九跟它的优点是同一条性质 ——  这条判据以后你会一直用。

6.3 那就别只看身边,去把重要的挑出来 —— 可这里有个死结

这个死结之所以是死结,不在于绕圈,在于

要挑出重要的,就得先知道谁重要;而「谁重要」这件事, 本身就是注意力分数
⭐⭐ 所以等你挑得动的时候 —— 你想省的那一步,钱已经付过了。

先说清这个死结:要挑出重要的,得先算注意力;可算完了再挑,就没意义了 本课原来只讲了一种破法。其实有三条,而且是真正不同的三条 死结 师徒 一份算两用 降维打击 ① 死结长什么样 先把它画出来 想只算重要的那几块 → 得先知道哪几块重要 想知道哪几块重要 → 得先把注意力算一遍 转回来了 所以三条破法都在回答同一句话: 「怎么在不算全的前提下,知道该看谁」 ② 三条破法 —— 它们是真正不同的三条 不是同一招的三种说法 ① 师徒 DSA 老师 学生 只有名次表 让真注意力当老师 训一个便宜的学生去学它的排序 代价:训徒弟只要 2.1B token, 可之后师傅得陪着再训 943.7B ② 一份算两用 NSA 压缩分支 本来就要算 ① 当输出用 ② 当路由信号用 不训第二个打分器 压缩分支的分数本来就要算,直接拿它当路由 白捡:top-k 在前向图上是个 no-op, 只决定从显存搬哪些块 ③ 降维打击 CSA·V4 / IndexPool·GLM 12 格要挑 只剩 3 格 先把序列压短 4 倍 鸡生蛋没破 —— 但那只鸡小了 4 倍 两家独立想到同一招 —— 一家压 token, 一家压 indexer 的 key,都是 4 合 1 顺着这条死结往下走一步,「为什么必须整块取」就不用单独讲了 把链子接起来:相似度一旦算出来,事后再稀疏就没好处 —— 所以必须有一个便宜的近似打分器;而便宜的近似打分只能按块做(按 token 打分就等于把全表算了)。 于是「整块取」不是对硬件的妥协,是这个死结的直接推论。本课原来把它们讲成两段互不相干的话 —— 接上之后,两个难点变成一个。 最后那条路,两家独立走到了一起 —— 但他们压的不是同一个东西 DeepSeek 的 CSA 压的是序列本身:4 个 token 合成 1 个 entry,后面主注意力看到的就是压过的 智谱的 IndexPool 压的只是索引器手里那份 key:4 个池化成 1 个、挑出 Top-512 个 pool 之后再展开回 2048 个原 token —— 主注意力吃到的一个没少
左边那个圈绕不过去,只能破 —— 而破法有三条,不是一条。
⭐⭐ 三条各自聪明在不同地方,值得一条一条读:一条是找人替考,一条是废物利用,一条干脆把考场缩小了。
⭐⭐ 读到最下面那条带子 —— 第三条有两家不同的团队,各自摸到了同一个解。一件事被独立做对两次,分量跟只有一家做过是不一样的。
出处与口径

① 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 一文;「师徒 / 一份算两用 / 降维打击」是本课的命名

图里那条「两家独立想到同一招」,值得比一个巧合多想一层。

一件事被独立做对两次,跟只有一家做过,分量不一样 ——  那说明它不是谁的灵光一现,是这个死结自己逼出来的解

⭐⭐ 看研究时这条可以一直用:同一个坑上冒出两条互不知情的同形解法, 往往说明约束比人聪明。

6.4 DSA 是怎么破这个死结的 —— 以及它一步到底在干什么

DeepSeek 的破法是师徒让真的注意力当老师, 另外训一个极便宜的学生,专门去学老师那张分布的排序

凭什么只看 2048 个就够 把它想成查资料:书墙很长,可这一次真正用得上的只有几本 只用一小格 鸡生蛋 让老师教徒弟 徒弟凭什么快 ① 先说一个经验事实 —— 书墙很长,用得上的很少 H2O 2023 量出来的 这一步能看到的历史:131,072 个 token 真正用得上的 就这么一小格 2,048 个 = 1.56% 剩下那 98.4% 几乎分不到权重 ② 难的不是「少看」,是「怎么知道该看谁」 一个鸡生蛋,和 DSA 的解法 要知道哪本书有用 —— 得先把它读了 可「每本都读一遍」正是我想省掉的事 又转回来了 DSA 的解法:让老师带一个便宜的徒弟 老师 = 真注意力 它当然知道哪本有用 —— 但它贵 照着学 徒弟 = 索引器 不需要懂内容 只要把书排对顺序 关键是「怎么算徒弟学得像不像」—— 不是让它猜,是对着老师的答案打分 热身阶段全模型冻住、注意力照常密集地算,只训徒弟去拟合老师那张分布(损失就是 KL)。 热身 2.1B token → 稀疏期再训 943.7B token ③ 徒弟凭什么便宜 —— 三样减法 它只排序,不做加权平均 64 头 主注意力是 128 头 人手减一半 ReLU 打分 不用 softmax 只排序,就不必「和为 1」 FP8 精度更糙 排序不需要那么准 还有一条硬的 它必须搭 MQA 模式 否则 kernel 上不划算 —— 旋钮①② 在这儿被硬件绑住了 带走一条套路:要省掉一个贵的东西,先让它自己说出答案 再训一个便宜的去复制那个答案。DSA 的索引器不是「一个猜谁重要的启发式」—— 它是主注意力分布的一个廉价复制品 这个套路到处都能用:蒸馏、投机解码、各种预测器 —— 都是「贵的当老师,便宜的当徒弟,拿老师的输出当标准答案」。 那 2048 这个数怎么来的 —— 老实说:论文没给消融 能说的只有三件事:① 128K 下它占 1.56%;② 两个公开口径(H2O「前 5%→95%」、Chen「前 20%→70%」)在 1.56% 处分别给 93% 和 40% —— 差一倍多 历史不足 2048 时,top-2048 就是全选 —— 这是定义直接推出来的,所以短上下文下 DSA 就是普通 MLA,稀疏只在长上下文才启动 暗线第三次出现:事后才压 vs 训练时就知道会被压 H2O 是事后才压的 —— 一个 KV 驱逐策略,不改训练,随时可开关。 DSA 是训练时就知道的 —— 热身 2.1B + 稀疏期 943.7B token 的继续训练,模型是在「我会被稀疏」这个前提下学出来的。
看中间那一格就够了:它怎么知道徒弟学到位了没有。
⚠️ 至于 2048 这个数是怎么定下来的 —— 图里说了老实话,论文没给消融。能查到的只有它占多大比例,而两个公开口径在这个比例上给出的答案差着一倍多
出处与口径

① 出自 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 这个数只能说它占多少、以及短序列下等于全选

图里那条套路,你在别处大概已经见过好几次了。

投机解码:让小模型先草拟,大模型只负责验收。
模型蒸馏:大模型的输出当标准答案,小模型照着学。
各种缓存预测器:真实访问序列当标准答案,训一个便宜的去猜。

⭐⭐ 同一个形状:贵的那个不用消失,它只要愿意当一次老师。

📌 下面这张,是这一章的正题。 —— 那 2048 个到底怎么挑出来的、省了什么、没省什么、多花了什么,一张图答完。

DSA 走一步 —— 那 2048 个是怎么挑出来的 三步,每一步都便宜;真正贵的那一步,根本没在全长上做 轻:索引器扫全程 挑中的 2048 没被读到的 代价 ① 一步之内发生了什么 从左往右读 —— 注意前两步都还没碰真正的 K/V,只在跟「分数」打交道 当前这个字 它的 q ⚡ 闪电索引器 每一个历史 token 打一个分 I[t,s] = Σj w[t,j] · ReLU( q[t,j] · k[s] ) 它便宜在三处: · 头很少(不是主注意力那 128 个头) · 用 ReLU 不用 softmax —— 只排序,不做加权平均 · 低精度就够(它只要把顺序排对) 但它必须扫过全部历史 一个都不能跳 —— 不扫完,就不知道该跳谁 ② 一整排分数,挑出最高的 2048 个 橙色那几根 = 分数进了前 2048 灰色的 —— 这一步之后就再也不碰了 top-2048 选择器 挑的是位置,不是内容 —— 选完才去仓库里把这 2048 份取出来。 而它们散落在各处,不是连着的一块。 ③ 只对这 2048 个做真正的注意力 (V3.2 里就是完整的 MLA) 贵的那一步,只在 1.56% 的格子上做 所以整段注意力的账 L × L 变成 L × 2048 长文越长,赚得越多 ② 2048 在 128K 里是多大一块 这一条按真比例画 —— 上面那 24 根柱子是示意,这一条不是 2048 ÷ 131072 = 1.56% —— 橙色那一小条就是每一步真正读进来的量 剩下那 98.4% 还好端端地躺在显存里 —— 它们只是这一步没被读,不是没被存。 顺带一个定义直接推出来的事实:历史不足 2048 时,top-2048 就是全选 —— 短上下文下 DSA 就是普通 MLA,稀疏根本没启动。 省下来的 每一步要算、要搬的那些格子 注意力那一段从 L×L 变成 L×2048 128K 下就是 只读 1.56% 上下文越长,省得越多 —— 因为省的是跟长度成正比的那一段。 一点没省的 显存 —— 而且还多了一成 KV cache 一个字节都没少。 道理很硬:要能从历史里挑, 就得把历史全留着。 而且索引器自己还要一份 128 维 × FP8 × 61 层 × 128K 0.95 GiB比 V3 还多存约一成 反而多花的 三笔,而且一笔比一笔疼 索引器要给所有历史打分,一个都不能跳。 它那 0.95 GiB,每一步都要整条读完 —— 是挑中那 2048 个(0.13 GiB)的 7 倍。 挑中的 2048 个散落在各处,搬起来碎。 所以这一支后来都在缩第 ② 笔: 几层共用一个索引器 这两笔多花的,正好把这一支后来的样子定下来了 省 FLOPs 谁都会 —— 在纸上少算 98% 的格子而已。可只要那些格子散落在显存各处要搬的字节一点没少,时间就一点没省 回到现场那四问,答案一句一条 怎么挑的:一个很轻的打分器扫过全部历史,取分数最高的 2048 个位置。 省了显存吗没有,一个字节都没省。省的是每一步真正读进来、算进去的那一部分。
⭐⭐⭐ 这一张是为现场那四个问题专门画的,四问四块,一一对上。
要是只看一眼,就看第 ② 块那条按真比例画的带子 —— 橙色那一小条是这一步读进来的,灰色那一大片全都还在显存里躺着。
出处与口径

索引器的式子 I[t,s] = Σ w·ReLU(q·k) 与 top-k=2048 出自 DeepSeek-V3.2 技术报告(arXiv 2512.02556)及其公开实现

「不连续访存用不上 FlashAttention」逐条出自 NSA 论文 (arXiv 2502.11089§2.2:token 粒度的选择要从 KV cache 里加载大量单个 token,「this non-contiguous memory access prevents efficient adaptation of fast attention techniques like FlashAttention」

⚠️ 1.56% = 2048 ÷ 131072,是算出来的;上排 24 根柱子的高低与选中位置是示意,不对应任何一次真实打分

6.4b 顺着这条路再走三步:NSA · CSA · HCA

上一节那个 DSA,不是这条路的终点,也不是起点。 把它前后那三步一起摆出来,你会看到一条很干净的线。

DeepSeek 这一支的四步:NSA → DSA → CSA → HCA 四个名字看着各不相干,其实是同一条路上的四步 —— 而且最后一步把「挑」这件事整个取消了 要运行时挑(动态) 编译期就定死(静态) Ⓐ NSA(2025-02)—— 不是一条稀疏,是三条支路并排跑,再用门控融合 名字里的 Native 是重点:训练的时候就稀疏,不是训完再稀疏 ① 压缩 远处按块压成摘要 静态 · 一个块一条 ② 选择 挑出最相关的几块 动态 · 跑起来才知道挑谁 ③ 滑窗 身边那一段全留 静态 · 窗口固定 门控融合 —— 三条各自的结果按权重合起来 一个 token 的输出 三条里只有一条是动态的 后面三步,动的全是这一条 训练时就用 不是训完再稀疏,所以模型是「长在稀疏上」的 NSA 为什么从第一天就好落到硬件上 因为它挑的是块,不是散落的单个 token —— 块是连续的一片,搬起来整齐。论文自己把这一点写进了标题:hardware-aligned 记住这个对照,下一格 DSA 正好反过来。 Ⓑ 四个名字摆到同一张图上 —— 两条轴:压多狠 × 挑不挑 横轴不是线性刻度(1 / 4 / 128 等距摆)—— 它只表达「谁比谁压得狠」 不压 压得最狠 压 缩 比 (4 个 token 合 1 条 → 128 个合 1 条) 要运行时挑 (有 indexer) 不用挑 (编译期定死) NSA 块级选择 + 压缩支路 挑「块」—— 整齐 2025-02 arXiv 2502.11089 DSA 不压,纯挑 top-k 个 token 挑散落的单 token —— 最碎 本讲第六章的主角 CSA 先压 4 倍,再挑 压过之后再挑 —— 候选少了 4 倍 MaxText:compress_ratio = 4 HCA 压 128 倍,不挑了 没有 indexer MaxText:compress_ratio > 4 这张图真正要说的一句话 压得够狠的时候,就不需要挑了。 挑,本来就是为了「在一大堆里只看几个」;可要是先把一大堆压成一小堆,那就全看也无所谓了 但它们不是一条流水线 —— CSA 和 HCA 是两种不同的层,在网络里交替排每一层都直接从原始 token 压。128 是 4 的 32 倍,可那只是两个参数的商,没有谁接谁的力 Ⓒ 落到 TPU 上:HCA 不是「把动态稀疏做快了」,是把动态换成了静态 这一格回答的是本讲后面那个问题:稀疏落到 TPU 上为什么难,以及这一支是怎么绕开的 动态(DSA / CSA) 跑起来才知道这一步该挑谁 → mask 只能在运行时现造 → 要在 HBM 里放一张稠密的 mask → 还要占着算数部件去算它 每一层都在跟「运行时才知道」较劲 静态(HCA) 窗口固定、压缩比固定 → mask 在编译期就能算出来 → 主机 CPU 上算好,打包成 bitmask → 直接塞进片上,HBM 里不存稠密 mask 运行时零决策,也零 mask 运算 把这四步连起来,是一条很干净的线 NSA 挑块(整齐)→ DSA 挑单 token(更准,但最碎)→ CSA 先压再挑(把要挑的池子缩小)→ HCA 压到不用挑(决策整个消失)。 每一步都在把「运行时要做的决策」往编译期推。 这跟本讲反复出现的那条判据是同一个形状:一个麻烦最好的结局不是被解决,是不再存在。
⭐⭐ Ⓑ 那两条轴是这张图的全部 —— 横着是「压多狠」,竖着是「要不要运行时挑」。四个名字一摆上去,那条对角线自己就出来了
Ⓐ 里记一条对照:NSA 挑的是块,DSA 挑的是散落的单 token —— 这一条决定了它们在硬件上的命。
出处与口径

NSA = Native Sparse Attention,arXiv 2502.11089(DeepSeek, 2025-02):摘要原话是「coarse-grained token compression + fine-grained token selection」,标题里写着 hardware-aligned 与 natively trainable

CSA / HCA 的结构与默认参数取自 MaxText 的公开实现(Apache-2.0):两者同属 AttentionType.COMPRESSED;compress_ratio = 4 走 CSA(必须传 indexer_mask),compress_ratio > 4 走 HCA(用编译期静态 mask,默认 compress_ratio=128、local_window=128)。HCA 的 docstring 自称 “DeepSeek-V4 Heavily Compressed Attention”

本图不含任何性能数字 —— 本课没有这四者的对照实测,画柱子就是编。图上只画结构,以及「要不要运行时决策」

⚠️ Ⓑ 的横轴不是线性刻度,1 / 4 / 128 等距摆,只表达先后不表达倍数

⭐⭐ 先给一份速查 —— 四个名字,一人一句。 (细节、两条轴、以及它们在 TPU 上的差别,全在上面那张图里。)

  • NSA(2025-02)—— 三条支路 + 门控。挑块,训练时就稀疏。
  • DSA —— 轻量索引器打分,挑散落的单 token。更准,也最碎。
  • CSA —— 先压 4 倍再挑。要挑的池子小了四倍。
  • HCA —— 压 128 倍,挑这一步整个取消

⭐⭐ 这里有两个坑,几乎每个人第一次读都会掉进去。

「压缩难道还分远近吗?不是统一压的吗?」——  是统一压的。压缩器一视同仁,不认远近,每 128 个 token 合成一条。

近处之所以还清楚,不是压缩器手下留情,是旁边那条支路给的 ——  一个局部滑窗(默认 128)把身边那一段原样留着、根本不进压缩器

⭐ 而两段不是各算一遍再融合(那是 NSA 的做法)——  压缩块直接拼在未压缩那段的后面,凑成一条更长的 KV 序列,一次注意力一起看。 (源码:jnp.concatenate([kv, compressed_kv], axis=1)

⭐⭐ 所以这个「越远越粗」是两条支路拼出来的,压缩本身没有这个概念。 —— 而这一手各位刚在 Ⓐ 见过:NSA 的 ① 压缩管远、③ 滑窗管近。 HCA 就是把 NSA 中间那条「选择」砍掉之后剩下的东西。

「用的时候要不要解压缩?还是像 MLA 那样把矩阵吸收了?」——  不用解压。但原因跟 MLA 完全不是一回事 —— 它俩压的根本不是同一个轴。

  • §五 的 MLA 压的是维度 ——  一个 token 有多宽」。宽的 K/V 压成一条窄的潜向量。
    ⛔ 维度方向压完,逻辑上必须升回去,否则点积对不上宽度 ——  「吸收」这一手就是为此而生的:把升维矩阵提前乘进 Q 和输出矩阵, 于是物理上不用真展开。
  • CSA / HCA 压的是序列 ——  一共有多少个 token」。128 行合成 1 行。
    ⭐ 而压出来那一条,本身就是要参与注意力的 K 和 V, 它不是谁的压缩包,它就是本体 —— 所以没有「还原」这回事。

⭐⭐⭐ 两句话摆一起,形状很清楚:MLA 是「需要解压,但我们想办法不解了」; 这一支是「压根没有解压这一步」。

代价也跟着这两个轴分开走。维度方向是低秩近似,有损, 但每个 token 都还在;序列方向是把 128 个 token 合掉 ——  它们各自的身份永久没了,不可逆。 ⭐ 这才是那条滑窗支路非挂不可的真正理由 ——  不挂它,连刚说完的上一句都是一团摘要,模型接不上话。

6.4c 把 V4 的一条 KV 拆开看 —— 它跟 MLA 到底差在哪

上一节那四个名字讲完,最常被追问的是同一句: 「那它一条 KV 里到底存的是什么?跟 MLA 到底差在哪?」 ⭐ 这一节把 DeepSeek-V4-Pro 的一条 KV 整个拆开。

DeepSeek-V4-Pro:一条 KV 到底长什么样 三个问题一次说清:512 里装了什么 · 那 64 维 RoPE 去哪了 · 用的时候要不要解压 上一代 V3.2(MLA) V4(CSA / HCA) 要运行时挑(indexer) Ⓐ 一个 token 在 KV cache 里占多少 —— V3.2 的 MLA 对照 V4 的 Shared K=V MQA 两代都是「所有头共用一条」—— 差别在这一条要不要再展开 V3.2 · MLA 每 token 每层 压缩潜向量 512 RoPE 64 = 576 这 64 是加在 512 外面的 · K 和 V 要从潜向量升维还原(升维矩阵被「吸收」进 Q 和输出矩阵,所以不必真展开) V4 · Shared K=V MQA 每 token 每层 NoPE 448(不带位置) RoPE 64 = 512 这 64 是长在 512 里面的(partial RoPE,只转末尾那一段) · K 和 V 就是这一条,不升维、不分开 所以「看上去跟 MLA 一样」—— 这个直觉是对的:两代都是「所有头共用一条」。 真正的差别只有一句:MLA 那条是「压缩件」,逻辑上要还原成每头的 K 和 V;V4 这条就是 K 和 V 本身。 → 于是 V4 连「吸收」这一手都不需要 · 查询那边仍有 128 个头(q_lora_rank 1536 下投再上投),128 个头共读这一条 顺手把这两个数对一下,别记混 V3.2:512 + 64 = 576 (潜向量和 RoPE 分两块存) V4:512 (RoPE 是这 512 里的末尾一段,不额外占地方)。 再加一件只有 V4 有的事:既然 K 和 V 是同一条,那存一条就等于存了两样 —— 光这一手就省一半。代价在 Ⓓ。 Ⓐb 这条 512 是哪来的 —— 从 MLA 四步就能推到它 这条链是按定义推的结构演化,论文没说「V4 是 MLA 的变体」 ① MLA(V3.2) 一条 512 潜向量 + 两个升维矩阵还原每头 K/V + 单挂一条 64 的 RoPE key cache = 576 ② 删掉升维矩阵 那条 512 直接当 K,也当 V 所有头共读这一条 = Shared K=V MQA 「吸收」这一手不需要了 ③ 把 64 收进去 改成 partial RoPE 只转末尾 64 个通道 cache = 512 代价:输出按 −i 转回来 ④ 沿序列再压 在这条 512 的基础上 每 m 条合成一条 m = 4 → CSA m′ = 128 → HCA 所以「看着像 MLA」不是错觉 —— MQA 是它的形式,MLA 是它的效果 为什么这两个轴必须一起上 —— 算一遍就知道 假设 V4 以 MLA 那条窄 entry 为基础,就是标准多头(128 头 × 128 维,K 和 V 各一份)= 每 token 每层 32768 个数。压 4:1 之后还剩 8192 —— 而 MLA 是 576 差十四倍。 结论:序列压缩不是「可以叠在宽度压缩上」,是「必须叠」 —— 光压条数、不压宽度,连上一代都打不过。 Ⓐc 为什么 2019 年被判「能力不行」的 MQA,现在又行了 论文没正面答这个问题 —— 下面是推导,不是原话 先看一个恒等式 —— MLA 吸收之后,本来就是一个 head_dim = 512 的 MQA MLA 里第 h 个头的分数 = q_h ·(W_UK,h · c) = (W_UK,hᵀ · q_h)· c → 一条共享的 512 维 key,每个头拿自己那条 512 维 query 去点它 · value 侧同理,每头视角可并进输出投影 2019 的 MQA 共享的那条只有一个头宽(典型 128) query 侧也还是 128 维 每头视角是真的被删掉了 而且没有任何东西补偿它 → 所以掉点 V4 的 Shared K=V MQA 共享的那条 512 宽 —— 宽四倍 query 每头 512(从 1536 latent 上投) 输出侧分组低秩投影(16 组 × 1024) 每头还有一个可学的 attention sink → 每头视角没消失,它搬家了 但这不是「零损失」—— 省一半不可能白省 K 和 V 合成同一条,这一步连 MLA 都没敢做 —— MLA 至少还有两个不同的升维矩阵把 K 和 V 区分开。原本两组独立的 512 自由度,现在只剩一组。 这份损失被三样东西吃掉了:① query 侧每头 512 的自由度② 输出侧的分组低秩投影③ 原生训练 —— 它是从第一天就这么训的,不是从别的检查点改出来的。(③ 是类比 NS A 的 Native,论文没这么说。) Ⓑ 三种层:纯滑窗 · CSA(m=4) · HCA(m′=128) 三种层都带 128 的未压缩滑窗,压缩块拼在它后面 纯滑窗层 compress_ratio = 0 未压缩滑窗 128 (后面什么都不拼) 只有近处这 128 条 · V4-Pro 61 层里只有最后 1 层是它 CSA 层 m = 4 未压缩滑窗 128 压缩块 N/4 条 Lightning Indexer 挑 top-k = 1024 条 索引器挑的是「块」—— 最近那几个字还没成块,它挑不到 HCA 层 m′ = 128 未压缩滑窗 128 压缩块 N/128 条 不挑 —— 全部压缩条都参与 1M 上下文下也只有约 8 千条,全看得起 V4-Pro 的层表(61 层,直接抄自 config.json):[128, 128, 4, 128, 4, 128, …, 4, 128, 0] —— 开头两层 HCA 打底,中间 CSA 与 HCA 交替,最后一层纯滑窗 滑窗跟 indexer 不是二选一 —— CSA 两样都有 共同的理由:压缩块要凑满 m 个 token 才成形,而因果律不允许查询看见自己后面的 token —— 在凑满之前,查询无块可看。CSA 的 m = 4,缺口小;HCA 的 m′ = 128, 缺口大到能把刚开口那几十个字整个吞掉。 而 CSA 还有一条自己的理由:压缩块是好几个 token 的加权和,近处需要的是 token 级的分辨率,摘要给不了 —— 论文 Figure 3 的图注就写着这是「to enhance local f ine-grained dependencies」。 Ⓒ 压缩这一步到底在算什么 —— 加权求和,权重是学出来的 不是平均池化,也不是把现成的 K/V 拿来池化 —— 压缩器有自己独立的投影矩阵 CSA:重叠窗口 两路投影 Cᵃ 和 Cᵇ,各配一路权重 Z 每条摘要 = 2m = 8 个 token 的加权和 权重在这 8 个上做一次 softmax(还带可学的位置偏置) 相邻两条的来源互相重叠 → 序列真正压到 1/m = 1/4 看 8 个、只留 1 条 —— 边界不至于被切死 HCA:非重叠窗口 同样是投影 + 门控加权求和 每条摘要 = m′ = 128 个 token 的加权和 窗口不重叠,一刀一段 压完之后再给这条摘要施加一次 RoPE 用的是这条摘要自己的锚点位置 没有索引器 —— 压到这个份上,全看也看得起 Ⓓ 查询来了之后 —— 全程没有任何一步叫「解压缩」 但有一步很容易被漏掉:输出侧要按负位置反向旋转一次 查询 128 个头,每头 512 维(走 q_lora 1536 下投再上投) 点积 直接和那条 512 相乘 —— 不展开、不还原 加权 softmax 之后,乘的还是同一条 512 反向 RoPE 对输出的 rope 那 64 维,用位置 −i 再转一次 输出投影 分 16 组、每组降到 1024,再合回 hidden 7168 ④ 为什么非有不可:K 和 V 是同一条,所以V 也被 RoPE 转过了 —— 不转回来,V 携带的就是绝对位置的污染。 按 −i 转回来之后,每条 KV 的贡献只跟它到查询的「相对距离」有关 · 论文 §2.3.3 式 26 这一整套换来了什么 论文口径(1M 上下文,对照 DeepSeek-V3.2):V4-Pro = 27% 的单 token 推理 FLOPs、10% 的 KV cacheV4-Flash = 10% 与 7% vLLM 的实现口径:1M 上下文、bf16 之下,V4 每条序列 9.62 GiB,对照同为 61 层的 V3.2 式估算 83.9 GiB —— 约 8.7 倍;实跑再用 fp8 存注意力、fp4 存索引器,又能 减掉大约一半。
Ⓐ 那两条色带是这张图的全部 —— V3.2 的 64 挂在 512 外面,V4 的 64 长在 512 里面。一眼就看出「576 变 512」省在哪。
⭐⭐ 而 Ⓓ 那个 ④ 是最容易漏掉的一步:K 和 V 共用一条,所以 V 也被转过了,得按负位置转回来。—— 省下来的那一半,代价就写在这一步上。
出处与口径

论文:arXiv 2606.19348《DeepSeek-V4: Towards Highly Efficient Million-Token Context Intelligence》(DeepSeek-AI, 2026-04-26)。§2.3.1 CSA:式 9–12 是压缩(每条摘要由 2m 个 entry 加权求和而来,softmax 在这 2m 个上归一,相邻两条来源重叠,所以序列压到 1/m),式 13–14 是 indexer 的低秩查询;§2.3.2 HCA;§2.3.3 Other Details 含 Partial RoPE、式 26 的反向旋转、式 27 的 attention sink

配置:Hugging Face deepseek-ai/DeepSeek-V4-Pro 的 config.json —— num_attention_heads 128、num_key_value_heads 1、head_dim 512、qk_rope_head_dim 64、q_lora_rank 1536、o_groups 16、o_lora_rank 1024、sliding_window 128、index_topk 1024、num_hidden_layers 61、rope_theta 10000 与 compress_rope_theta 160000

架构说明:Hugging Face transformers 文档 model_doc/deepseek_v4 —— 其中「Shared K=V Multi-Query Attention」「Partial RoPE 落在每个头末尾的 qk_rope_head_dim 个通道」「压缩器的输出与滑窗分支的 KV 拼接后再进核心注意力」

实现口径:vLLM 博客《DeepSeek V4 in vLLM: Efficient Long-context Attention》(2026-04-24) —— c4a =「8 个 token 的加权和,步长 4」,c128a =「128 个的加权和,步长 128」;1M 上下文 bf16 下 9.62 GiB,对照 61 层 V3.2 式估算 83.9 GiB

本图只有两组数字,都是转述的公开口径;mHC 与 MoE 部分刻意没画,它们不归这一讲管

⭐⭐ 先说那个最自然的误会 —— 而它有一半是对的。

「看上去跟 MLA 一样,所有头最后变成一条 512 的压缩体」 —— 这个直觉是对的。论文给它的正式名字就是 Shared Key-Value Multi-Query Attentionnum_key_value_heads = 1, 128 个查询头共读同一条

差别用一个比方最清楚:§五 的 MLA 存的是原料, V4 存的是成品原料下锅前得先加工一道,成品端上来就能吃。
「吸收」那一手,本质上是把那道加工工序提前折进别的矩阵里; 而 V4 这边,那道工序压根不存在

那 Q 呢?是一条 1536 的 latent,还是按 MQA 只生成一条 512?

—— 都不是,但更接近前者:Q 走 latent,而且最后是完整的多头。

  • hidden 下投q_lora_rank = 1536 的一条潜向量
  • RMSNorm 之后上投128 个查询头 × 每头 512 维 (Pro 是 128 头;Flash 才是 64 头,别记混)
  • 再做 partial RoPE 与缩放,然后去和那唯一一条 512 点积

⭐⭐ 值得注意的是:这个 1536 跟 V3 / V3.2 的 q_lora_rank 一模一样 —— 查询侧 V4 根本没动,沿用的就是 MLA 那一套。 改的全在 KV 侧。

还有一条容易忽略的:Q 不进 cache。 所以查询侧这个低秩压缩省的是参数量和算力,不是显存 ——  跟这一章讨论的那两个轴,不是一回事。

把账走一遍 —— 这几个数能反过来验证整套分解。 (1M 上下文,bf16,全部取自 vLLM 那篇的附录。)

  • V3.2MLA 每 token 每层 1152 字节(576 × 2), 索引器再加 256 字节 —— 合计 1408。 61 层下来约 83.9 GiB
  • V4 的一条共享 KV1024 字节(512 × 2)。
  • CSA 层条数只有 1/4 —— 主 KV 256 MiB + 索引器 64 MiB = 320 MiB
  • HCA 层条数只有 1/128 —— 8 MiB

⭐⭐ 30 层 CSA + 31 层 HCA,合计约 9.62 GiB ——  对 83.9 GiB 就是 11.5%

📌 论文自己标的是 10%,差的那点应该是口径不同 (论文还提到实跑用 FP4 存专家权重与索引器 QK)。 ⛔ 逐项算术在图的「出处与口径」里。

那滑窗那 128 条,用的也是 MQA 吗? —— 是,而且是同一条,不是另起一套 KV。

官方文档里这个分支的名字就叫 Shared sliding-window K=V branch ——  它用的就是主路径那个投影:1 个 KV 头 × 512 维,K 和 V 共用

⭐⭐ 所以这正好又是「两个轴各管各的」的一个实例: 滑窗那一段序列轴没压(每个 token 各占一条,没被合并), 但宽度轴照样是压过的(还是那条 512)。
—— 滑窗只决定「这一段不合并」,它不决定「每条多宽」。

6.4c-1 为什么 2019 年被判「不行」的 MQA,现在又行了

这是个好问题,而论文没有正面答它。 ⭐ 但答案能从架构本身推出来,而且推得很干净。

请回到上面那张图的 Ⓐc 那一格 —— 恒等式、两边对照、以及「不是零损失」那条,都画在那里。

⭐⭐ 关键在于:它压根不是 2019 年那个 MQA。

图 Ⓐc 最上面那个恒等式说的是 —— MLA 在「吸收」之后, 本来就长成一个 head_dim = 512 的 MQA。 所以 V4 不是「把 MQA 捞回来」,是把 MLA 原本就存在的那个等价形态, 直接拿来当架构

而 2019 年那个之所以掉点,毛病不在「共享」,在「删了不补」: 共享的那条只有一个头宽,query 侧也没加宽, 被删掉的每头视角没有任何东西接手。
V4 这边,那份视角搬到了 query 侧和输出侧 —— 它没消失。

图 Ⓐc 底下那条 band 请一定读完 ——  它讲的是这笔买卖的代价。这里只补两件图上没写的。

那条里的第三样(原生训练)是类比得来的,论文没这么说。 —— 而各位应该觉得眼熟:NSA 名字里那个 Native,讲的是同一件事。

所以这是本讲第二次撞上同一条判据:一个结构「长在上面」, 和「事后套上去」,是两回事。
⭐⭐ 它值得单独记一笔 —— 因为很多「某某方法没用」的旧结论, 失效的原因恰恰是当初只在「事后套」的设定下测过

那 64 维的 RoPE 去哪了? —— 它没被挪走,它长在那 512 里面。

  • V3.2 的 MLA512 的潜向量 64 的 RoPE 分量, 两块分开存576
  • V4一共就 512partial RoPE 只旋转每个头 末尾的 64 个通道,前面 448 个不带位置。RoPE 不额外占地方。

⭐⭐ 一句话记住:V3.2 的 64 是加在 512 外面的,V4 的 64 是长在 512 里面的。

⛔⛔ K 和 V 共用一条,是要还债的。

既然 K 和 V 是同一个张量,那 V 也跟着被 RoPE 转过了 ——  而 V 本来不该带位置。

所以算完注意力之后,输出的 rope 那 64 维要用位置 −i 再反向转一次(论文 §2.3.3 式 26)。转回来之后, 每条 KV 的贡献才只跟它到查询的「相对距离」有关。

⚠️ 这一步很容易被漏掉,但它是「存一条当两条用」这笔买卖的 全部代价 —— 省了一半显存,多了一次逐元素的旋转。

最后两个数,图上都有,但它们放在一起才有意思。

CSA 的 index_topk 是 1024,而 V3.2 的 DSA 是 2048 —— 挑得更少了。不是因为变胆小了,是因为 池子已经先被压过一道 —— 挑 1024 条摘要, 背后是 4096 个 token。

61 层里只有最后 1 层是纯滑窗(图 Ⓑ 有完整层表) —— 也就是说,压缩不是“可选项”,是这个模型的常态。

而图 Ⓑ 底下那段说清楚了滑窗为什么非挂不可。 请把结论记住:它不是精度选项,是可用性底线。

为什么值得把这四步连起来看:它是本讲那条判据的又一次应验。

这四步不是「四种不同的稀疏」,是同一个动作被一步步往前挪 ——  从「跑起来才决定」,一路挪到「编译的时候就定死」。

⭐⭐ 而最后那一步,不是把麻烦解决了,是让麻烦不再存在 ——  跟 第八章那条窄轨消失是同一个形状。

📌 出处与口径。NSA = Native Sparse Attention, arXiv 2502.11089(DeepSeek,2025-02)。 CSA / HCA 的结构与默认参数取自 MaxText 的公开实现(Apache-2.0)——  两者同属一种「压缩注意力」,按压缩比分叉:等于 4 走 CSA(必须提供索引器的 mask),大于 4 走 HCA(改用编译期静态 mask,默认压缩比 128、局部窗口 128)。 本节不含任何性能数字 —— 本课没有这四者的对照实测,画柱子就是编。

6.5 本章落点:这一章最该记住的,是它解决的那一栏

⛔⛔ 「稀疏就能省显存」是个极自然、也极常见的误会 ——  上面那张图的第三栏专门空着给它。

所以这个旋钮跟前两章根本不是一回事:前两章动的是显存, 这一章动的是时间。

而这一栏空着,也正是第八章存在的理由 ——  既然一个旋钮补不齐,那就几个一起拧。

这一章的两句话。

换来的:长上下文下那一段从「跟长度平方成正比」变成「跟长度成正比」, 而且上下文越长赚得越多。
欠下的:显存一分没省;索引器要扫全程;挑中的那些散落在各处, 搬起来很碎 —— 这一笔到第九章会变成真正的麻烦。

⭐⭐ 而现在三条路都走过一遍了:存得少、读得少…… 还有第三条,它比这两条都狠。

第 七 节

绕回原点 —— 还是那块固定大小的记事板

三条路走到这儿:一条改「一份有多大」,一条改「每步读几份」。 这一条最狠 —— 它干脆不存。
而这条路的终点,是第一章那个盒子 ——  隔了五章,我们要把当年扔掉的东西再捡回来

7.1 它的立身之本:把括号再挪一次

2020 年有人写下了一句在当时听起来相当刺耳的话: 真正卡住注意力的,其实是那个 softmax。

理由只有一行:因为要对所有位置求和归一化,那张「谁对谁」的大表躲不掉, 必须先整个造出来 —— 而它就是那个平方。

⭐ 可要是把 softmax 拿掉呢? 剩下的就是三个矩阵连乘 —— 而矩阵乘法是可以挪括号的。

⚠️ 「把 softmax 拿掉」是这一节的简化说法 ——  严格讲拿掉的只是指数那一半,归一化的分母其实留着。 这个区别在 7.2 末尾会用到(它正好回答「一直加会不会爆」), 这里先按简化版往下读不影响。

旋钮③ 的立身之本 —— 同一个乘法,只是把括号挪了个位置 矩阵按真实比例画。那个大方块有没有被造出来,是看出来的,不是读出来的。 必须造出来的大方块 跟句子长短无关的小方块 ① softmax 在的时候 —— 必须先把那个大方块造出来 分母要对所有位置求和,所以乘法顺序被锁死 Q 句长 × 头维 × Kᵀ 头维 × 句长 ① 先算这一对 Q·Kᵀ 句长 × 句长 句子翻倍,它翻四倍 × V 句长 × 头维 这张表到底多大(S = 128K) 171.8 亿 个数 = S² = 17,179,869,184(每层、每个头) 它不是「要占这么多显存」—— FlashAttention 之后分块算、算完就扔。 但每一个格子还是都要算一遍。 省掉的是显存,不是算力。 句子翻倍 → 这个数翻四倍 softmax 的分母要对所有位置求和 —— 所以这张表躲不开,必须先整个算出来。 ② 把 softmax 拿掉 —— 括号一挪,大方块根本没被造出来 同一个乘法,同一个结果 Q 句长 × 头维 × Kᵀ 头维 × 句长 × V 句长 × 头维 ① 改成先算这一对 KᵀV 头维 × 头维 句子再长,它还是这么大 这块到底多大(D = 128) 16,384 个数 = D² 跟 S 没有任何关系 句长从 2K 涨到 128K, 这个数一个都没变 —— 它压根不认识 S。 跟上面那张表差 1,048,576 倍 (2²⁰,正好一百万出头)。 这就是旋钮③ 的全部本钱 消失的那个 S,就是这个没被造出来的大方块。 这也正是为什么它的状态形状是 BKHH —— 里面根本没有句长这一维。
两块矩阵是按真实比例画的,所以这一张不用读,用看的。
盯着右边那一格里的形状 —— 句长这一维整个不见了。这就是这一支敢说「跟话有多长无关」的全部底气。
出处与口径

装置偷自 Google Research《Rethinking Attention with Performers》(2020-10):括号画成彩色虚线框、矩阵按真实比例画

「mask 才是挡住结合律的那个东西」出自 Hailey Schoelkopf 《Linear Attention Fundamentals》;「结合律是张量收缩顺序的特例」出自 Mamba-2 (SSD) 博客 Part II

⚠️ 图里 8×3 的格数是示意;真实量级是句长 128K、头维 128

⭐⭐ 这是这本书第二次、也是最赚的一次「挪括号」。 —— 一共就两次。

第五章:把括号挪到提问那侧,压缩包一个都不用拆。
这一次:括号一挪,那张句长 × 句长的大表根本没被造出来

两次都是同一个恒等式;而两次都被「中间夹了个带下标的东西」挡过 ——  上一次是 RoPE,这一次是 softmax 那个分母。

⭐⭐ 所以这条线真正的教训只有一句: 当你发现一个变换「挪不动」,先别改算法 —— 先去看中间夹的是谁,它凭什么在那儿。
第八章那个 NoPE,就是照这句话做的:它把夹在中间的那个直接请走了。

7.2 挪完之后,它其实变成了什么

一个固定大小的状态。 每来一个字,把它揉进这个状态里;要输出的时候,从这个状态里取。

📌 「揉进去」到底是怎么揉的?—— 这一格把它说死。

先排掉一个很自然的误解:不是把这个字的向量直接加到板子上。 维度都对不上 —— 板子是一个 d×d 的方块, 而一个字的向量只是一条 d 长的线

真实的动作分两步:

  1. 拆成一对。这个字先被两个学出来的矩阵投影成 k(钥匙)和 v(内容),各是一条 d 长的线。
  2. 焊成一张卡片。把 k 和 v 做外积v·kᵀ)——  两条 d 长的线相乘,得到一个 d×d 的方块板子上加的是这张卡片,不是零件。现在维度对上了。

⭐⭐ 那为什么「加」起来之后,还能把某一张单独取回来? 关键在的动作也是线性的:拿钥匙 q 去问,做的是 板子 × q
而板子是一堆卡片加起来的,所以这一乘就自动散开成: 每张卡片的内容 v,各自乘上「它那把钥匙和 q 有多像」。

于是取出来的东西一定是两截: ① 你要的那一张(它的钥匙跟 q 完全一样); ② 加上所有别的卡片漏过来的一点 ——  漏多少,取决于它们的钥匙跟 q 有多像。

⭐⭐⭐ 第二截就是「串音」,也就是后面 7.3 说的「叠糊了」。 如果所有钥匙两两垂直,串音正好全是零 —— 取回来完全干净。 而 d 维空间里最多只能有 d 个两两垂直的方向 ——  这就是那条「板子只放得下 d 条」的出处,它不是个比喻,是能验算的。

⛔⛔ 顺手记一条判据,它比这一节本身更通用: 「加法」之所以有意义,是因为「读法」是线性的。加和读必须配成一对。

softmax 那套读法是非线性的(分母要对所有位置求和), 所以它没法用加法把历史压在一起 —— 它只能把每一条原样留着
回头看 7.1 就通了:先拿掉 softmax,不只是为了挪括号 ——  是拿掉它之后,「加」这个动作才第一次变得可逆、可取回。

⛔⛔ 那「一直加」不会爆吗?—— 这一问逼出一个 本讲前面说漏了的事实

先认一件事:本节 7.1 那句「把 softmax 拿掉」是简化说法 翻回原论文(Katharopoulos 等,arXiv 2006.16236)的式 (5), 分母是在的 —— 它算的是 「这一条的相似度 ÷ 所有条相似度之和」,跟 softmax 的形状一模一样

所以被拿掉的不是归一化,是 exp softmax = 先取指数,再除以总和。线性注意力扔掉的是前一半 (那层非线性的壳,也正是它锁死乘法顺序的原因), 后一半原封不动地留着了。而且分母自己也是个累加量, 用同一招挪括号就能顺带算出来,不多花什么。

⭐⭐ 于是「爆不爆」就清楚了:分子在涨,分母也在涨 取出来的是个加权平均,不是一个越堆越大的和。这就是原版的 norm, 它从第一天起就在里面。

⭐ 顺带一个容易被跳过的细节,它解释了那个奇怪的 elu(x)+1论文明说选它是为了让相似度恒为正 (式 7)。因为分母要是能过零,除法当场就炸。 至于为什么不用 relu —— 原文的理由是 relu 在负半轴梯度为 0,训不动。

⭐⭐⭐ 但这一支后来主流并不靠那个分母 ——  它换了一条更狠的路,而那条路正好是下一节(7.3)整节的内容。

做法是:每一步先把板子整体乘掉一点,再加新的。 于是「一直加」变成了「一边加,一边一直在忘」——  加的是等比级数不是等差级数,它自己就收敛,根本不需要谁来压。

所以你的直觉对了一半:确实得有东西管住它 ——  但管住它的主力不是外面那层 norm,是「会忘」这件事本身外面通常还有一层 norm,那是兜底,不是主因。

📌 说得再直白一点: 线性注意力本质上就是一个 RNN,只不过它的状态不是一个向量,是一个矩阵。

线性注意力 = 一块固定大小的记事板 板子就那么大 —— 三代的差别,全在「写之前擦不擦、擦多少」 不擦 先擦再写 选择性地擦 ① 同一块板子,三种写法 现场那个比喻,画出来 ① 疯狂往里写 后写的盖住先写的 糊成一团 S ← S + v kᵀ ② 先叉掉,再写 写之前,把这一栏擦干净 干净 S ← S(I − β k kᵀ) + β v kᵀ ③ 选择性地叉 擦多少、擦哪几栏,学出来 GDN / KDA 这一支 再加一个学出来的门 α ② 「叉掉」这个动作,在式子里就是这一块 把比喻钉到代数上 S ← S ( I − β k kᵀ ) + β v kᵀ 这一块就是「叉掉」 在 k 这个方向上,按比例把旧的擦掉 它还有另一个读法 「板子上现在能取出什么」减「本来该取出什么」, 按这个去改板子 —— 这就是一步梯度下降 于是状态不再是一块缓存, 而是一个边跑边被训练的小模型 前提只有两条:学习率取 β、损失是瞬时的 ②b 「叉掉」叉的是一个方向,不是一整块 沿 k 方向压扁;所有垂直于 k 的方向,一动不动 灰平面 = 所有跟 k 无关的方向 k S β = 0 什么都没擦 k S β = 0.5 这个方向压掉一半 k S β = 1 这个方向擦干净了 k S β > 1 压过头,翻到平面另一侧 一张图三件事 ① 叉的是一个方向,不是一整块 · ② β 是「叉多狠」的连续旋钮,不是开关 ③ 也顺便解释了 k 为什么一定要归一化 —— 不归一化,这个「压扁」就不受控。 ③ 板子为什么会「装不下」—— 它不是条数超了 装的不是 token,是「键到值」的对应关系 板子上有 d 个「方向」,你要往里记 L 条对应关系 d 个方向 L 条要记的对应关系 L 超过 d 之后, 总有两条指到同一个方向上 —— 它们就开始互相盖。 所以别把板子想成一个盒子 它装的不是 token 是「按这个 key,该取出那个 value」 这样的对应关系 「装不下」= 方向不够用了,不是条数超了 这个比喻好在哪:它把三代的差别缩到了一个动作上 三代读的方式几乎没变,全部差别都在「写之前擦不擦、擦多少」:不擦 → 按固定方向擦 → 学着擦 所以看到这一支的任何一个新名字,只要问一句:它的「擦」是怎么决定的?—— 剩下的部分,三代之间几乎没变。
⭐⭐ 看这张图的时候,请把第一章那个盒子放在旁边一起看。
它们是同一个东西 —— 只是这一块大得多,而且会擦。
出处与口径

递推式、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

⚠️ 「记事板 / 擦」是现场给的比喻,不是论文措辞

⭐⭐⭐ 值得停一下:这块板子,和第一章那个盒子,是同一样东西。

形状一模一样:大小固定、一步一步往下传、成本跟话有多长无关
差别只有一处 —— 当年那个是个向量,这一块是个矩阵

而这一处差别正是三十年里最要紧的那一处:板子变大了,而且是能变大的。 当年 RNN 的状态跟输入维度同量级,是因为更新一次太贵; 换成外积累加之后,状态可以撑得很大,而一步的代价还是常数。

📌 顺手回答一个几乎人人会问的问题: 它明明就是 RNN,为什么叫「线性注意力」?

先看命名它的那篇论文叫什么 ——  《Transformers are RNNs: Fast Autoregressive Transformers with Linear Attention》(Katharopoulos 等,ICML 2020,arXiv 2006.16236)。 「它是 RNN」这件事就写在标题的标题里,作者从第一天就知道。 叫 linear attention 的是标题里那个方法。

⭐⭐ 而「线性」这两个字,一个词管住了两件事,摘要里一句话说全:

  • ① 那个点积是线性的。原话是 “express the self-attention as a linear dot-product of kernel feature maps
    说人话softmax 是套在相似度外面的一层非线性的壳。 把这层壳拿掉,剩下的相似度就是 —— q 和 k 各自先过一道很轻的变换, 然后直接点积。没有壳了,也就不用先凑齐一整行再算
  • ② 于是复杂度是线性的。原话接着说:正因为如此, 才能“make use of the associativity property of matrix products, 把 O(N²) 降到 O(N)

这两件事不是并列的,是因果的。 正因为相似度线性了,结合律才用得上;正因为结合律用得上,括号才挪得动; 正因为括号挪得动,平方才掉成线性。一个「线性」,一条因果链。

那为什么不干脆叫它 RNN?—— 名字记录的是它从哪来,不是它是什么。 它是从 attention 里出来的,不是从 RNN 里出来的; 而且 2020 年的 RNN 刚被 Transformer 打败三年,那时候是个不利的名字 ——  给新方法起名叫 RNN,等于自己把自己埋了。

还有一层是写给谁看:作者要说服的是用 Transformer 的那群人, 就得在他们的坐标系里命名。「这是一种注意力」他们会读下去; 「这是一种 RNN」他们会划走。

⭐⭐ 这条判据后面还要再用一次:同一块板子,从信号处理那边推出来的叫 SSM / Mamba,从 attention 这边删出来的叫 线性注意力, 从 1990 年代 fast weight 那支接下来的叫 delta rule三个名字,一样东西 —— 听到新名字先问它的血统,别问它的外形。

⭐⭐ 把这个名字和第一章那个名字摆在一起,还能多读出一层。

RNN 的「R」,说的是它长什么样 —— 有一条绕回自己的边, 是个形态描述。
线性注意力的「线性」,说的是它怎么被算出来 ——  点积是线性的、于是能重排,是个数学性质。

一个按形态命名,一个按性质命名 —— 所以这两个名字听上去八竿子 打不着,而它们指的是同一样东西。这不是巧合,是命名这件事本身的毛病: 名字只记录起名那一刻最要紧的那一面。

7.3 板子会写满 —— 而这一支三十年的改进,改的一直是同一个动作

固定大小,就意味着装不下全部历史那它到底是怎么坏的?

⚠️ 先摆一个几乎人人都会先想到的答案 —— 它是错的。

大多数人第一反应是:旧的东西会慢慢淡掉吧。像磁带反复覆盖, 越早的声音越模糊。

不是。最朴素的那个线性注意力,根本不是这么坏的 ——  只会加,不会擦。旧笔迹一笔都没走,没有任何东西在变淡。
所以它不是「淡掉」,是 叠糊了白板还是那块白板,墨也还都在 —— 可你已经一条都读不出来了。

把它想成一本联想笔记:每来一对「钥匙 → 内容」,就往板子上加一笔; 取的时候拿钥匙去比对。
⛔ 只要两把钥匙长得有点像,取出来的东西就会串 ——  而一块 d 维的板子上,真正互不打架的钥匙最多只有 d 把

但真正的病根还要再往下一层,而且它跟你的直觉相反。

最朴素的线性注意力只会加,不会擦。 于是话越说越长,旧笔记一笔都没走,新笔记不停往上叠 ——  取回的误差就是这样累起来的。

⭐⭐ 所以第一章那个「传远了会淡」,在这里换了一张脸 ——  它不是被时间冲淡的,是被后来的记忆挤掉的。

(下面那张图里引了一句神经科学家的话,把这件事说得比任何公式都准。 那句话值得一字一句读。)

⭐⭐ 这件事有一个日常版本,而且它比白板那个比喻更准。

成年人常觉得自己记性不如小孩。可未必是「记性」变差了 ——  更可能是脑子里原有的东西太多了。
小孩脑子里干扰项少,一件新事进来,周围是空的,很容易跟别的区分开、单独记住。
成年人不一样:得先找一块空地,或者找一个合理的位置把它插进去, 让它跟已有的知识互不冲突 —— 而这件事本身就很难。

看出来了吗?这正是上面那个「d 维最多 d 个正交方向」在说的事。 板子空的时候,新记录随便往哪儿放都不打架;板子满了, 新记录只能挤在别人旁边,挤到谁就把谁弄脏。 「找一块空地」= 找一个还没被占用的正交方向。

⭐⭐⭐ 而这个直觉还会直接把下面 delta rule 那一招解释掉。 「找个合理的位置插进去、不跟原有知识冲突」——  翻成板子上的动作,就是:先拿这把钥匙看看那儿原来写着什么, 把旧的那一笔清掉,再写新的。
成年人学东西要先「整理」一下才记得住,delta rule 干的就是这件事。

⚠️ 一句边界我们借的是这个直觉的形状(干扰,而不是衰减), 不是在断言人脑就是这么工作的 —— 那是另一个领域的问题。 这一节真正扛事的仍然是那句可以验算的:d 维里最多 d 个互不打架的方向。

查 · 擦 · 写 —— 三个动作,三代模型各缺一个 线性注意力把历史压进一块固定大小的板子。板子会写满,这不奇怪 —— 奇怪的是:为什么改进了这么多代,改的一直是「怎么擦」?这张图把那个擦的动作拆开来看。 查:按地址找 擦:只掏空这一格 写:放新的进去 ① 把 delta rule 拆成三个动作 —— 除旧迎新 S ← (I − βkkᵀ)·S + βkvᵀ 1 拿地址 k₃ 去找那一格,读出里面现在装的是什么 地址 k₁ 地址 k₂ 地址 k₃ 地址 k₄ 地址 k₅ 读出 v_old = S·k₃ 2 只把这一格掏空。 旁边四格纹丝不动 地址 k₁ 地址 k₂ 地址 k₃ 地址 k₄ 地址 k₅ (I − βk₃k₃ᵀ) 3 把新的内容放进同一格 地址 k₁ 地址 k₂ 地址 k₃ 地址 k₄ 地址 k₅ + βk₃vᵀ(新内容) ② 三代模型的区别,就是这三个动作会几个 改的一直是「怎么擦」 纯线性注意力 只会「写」 只加不减。新的直接摞在旧的上面 —— 板子越写越花 遗忘门(标量 α) 会「整体变淡」,不会定点擦 每来一步,所有格子一起按同一个比例变淡。腾得出地方,但腾的是全部人的地方。 delta rule 会「定点擦」,不会整体淡 只擦你指名的那一格,别的一点不碰。改得准,但没人主动腾地方。 门控 delta(GDN) 两个都会 先整体淡一点,再定点擦一格。这就是 Gated DeltaNet。 KDA(逐通道门) 整体淡,但每格淡得不一样快 调光器从一个总开关变成每格一个 这一支的改进史,一句话就能收住:decay 会忘但不会改,delta rule 会改但不会忘 —— 所以自然的下一步就是两个拼起来 ③ 「逐通道」到底是什么意思 —— 整屋一个调光开关,还是每个灯泡一个 KDA 相对 GDN 只改了这一处 一个总开关 标量 α —— Gated DeltaNet 百分比 = 一步之后还剩多少 55% 55% 55% 55% 55% 五格淡得一模一样 每个灯泡一个旋钮 向量 α —— KDA 百分比 = 一步之后还剩多少 85% 38% 12% 70% 28% 五格各淡各的 —— 有的留得久,有的一步就没 这十个百分比是示意值,不是实测 —— 逐通道的衰减率没有公开数据。这一格要画的只是「五格一个样」和「五格各不一样」这个结构差别 🏠 为什么要分开调?「你现在在写哪门编程语言」这条该留很久;「刚离开的那个函数里的变量名」可以马上忘掉。—— 一个总开关做不到这件事。 为什么固定大小的板子一定会坏 —— 一软一硬两句 硬的那句(可以验算):d 维空间里最多只能有 d 个互相正交的方向,板子一满,新记录就只能挤在别人旁边这一条上一张图已经画出来了 —— 记事板那张的第 ③ 格:d 根 方向、L 条要记的对应关系,L 一超过 d,就总有两条指到同一个方向上。(把它放宽成「差不多不撞」之后还能装多少,在「主线 L300 · 完整版」。) 软的那句(会被记住)「记忆的敌人不是时间,是别的记忆。」—— 你忘掉一个电话号码,不是因为时间久,是因为你又记了新的。
三代模型读的方式几乎没动过,全部差别都在「写之前怎么处理旧的」。
⭐⭐ 所以以后碰到这一支的任何一个新名字,你只要问一句:它的「擦」是怎么决定的?—— 剩下的部分,多半跟上一代一样。
出处与口径

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 博客引用

⭐ 抽屉里的颜色深浅是示意,不对应任何模型的实测门控值

📌 一个很值得比的对照:残差网络也是「加」,它为什么不糊?

残差块做的是 输出 = 输入 + F(输入),也是往同一个地方加。 可它叠一百多层都不糊,凭什么?

区别只有一条,而且是决定性的:它在加之前,先读了一遍。

  • 最朴素的线性注意力是「盲加」第 t 步往上加的那张卡片, 只由当前这个字算出来 ——  它压根没看板子上已经写着什么。写重了也不知道。
  • 残差块是「先读再加」F 的输入就是当前这份表示。 它读得到已经有什么,所以它写的是「还差什么」, 不是「我以为该有什么」。

ResNet 原文(He 等,arXiv 1512.03385,2015)的用词就很准: “learning residual functions with reference to the layer inputs —— 「有参照地」学。这个 with reference to 就是不糊的全部原因。

⭐⭐⭐ 而下面 delta rule 干的,正好就是把这件事装到线性注意力身上。 —— 先拿钥匙去把板子上那儿的旧内容取出来看看, 再按差去改。换句话说:delta rule = 给「加」装上残差那种「先读再写」。

⚠️ 另外两条次要的、但也真在起作用: 残差网络的层数是固定的几十到一百多层,不是几万步累加; 层与层之间还有 LayerNorm 把尺度重新归一化。 (原论文的主要动机其实是好优化:加了恒等通路之后, 梯度有一条不衰减的高速路,深层才训得动。)

于是这一支所有新名字,其实都只在回答同一个问题:「怎么擦」。

⭐⭐ 这一句可以当工具用,不用背谱系 ——  以后每碰到一个新名字,只问它一句:「你是怎么擦的?」 擦多少、擦哪些、要不要每一步都擦、能不能让模型自己学着决定擦什么 ——  答案落在这四格里的某一格。

最漂亮的那一招叫 delta rule,它的直觉一句话就够:
教小孩射箭,偏左就往右调一点,偏多少就调多少。

⭐⭐ 放到板子上:来了一对新的「钥匙 → 内容」, 先拿这把钥匙去把板子上的旧内容取出来看看, 再按「想要的」和「取到的」之间那点差去改那一笔。
—— 不是盲目覆盖,是有针对地修。

📌 顺着这里把一条三十年的线串起来 ——  顺便纠一个很容易混的说法。 (现场原话:「那个『转一下』的矩阵,怎么看上去跟 DeltaNet 的遗忘门差不多?」)

先说直觉对的那一半:第一章那块「转一下」, 确实就是遗忘门的祖先。 它按方向打折 —— 倍率小的方向快忘,接近 1 的方向能传远。

但它是静态的:倍率烧死在权重里, 第 1 步和第 1 万步一样,读到「的」和读到一个关键人名也一样。

⭐⭐ 而「这东西是不是一开始就该有」—— 是的,而且很快补上了, 但比多数人以为的,还是单独一篇论文。

  • 1997 · LSTM 原版只有输入门和输出门没有遗忘门
  • 1999 · 遗忘门Gers、Schmidhuber、Cummins 加进去的, 论文标题就叫 《Learning to Forget》
  • 「学会遗忘」值得单独写一篇 —— 说明当时人们并不觉得它是显然的。 而它解决的问题很具体:那条 cell state 只会一直往上加, 跑连续不断的流式任务时会饱和、会爆不是想更聪明,是不加它跑不下去。
  • 2023 之后 · GLA / Mamba 这一支把衰减做成跟当前输入有关的, 但为了能并行,退回成对角(每个通道一个数)。

⛔⛔ 而 DeltaNet 不在这条线上 —— 这是要纠的那一点。

  • 遗忘门(GLA / Mamba)—— 全局按通道打折, 所有东西一起淡一点。
  • delta rule(DeltaNet)—— 就是上面那一招: 对准当前这一把钥匙的方向做定点改写,别的方向一根毫毛都不碰。 混合比例由一个动态系数控制 —— 取 0 完全不变,取 1 完全替换。

一个是「全体淡一点」,一个是「只改这一条记录」。 —— 而 Gated DeltaNet 的名字里就写着:两样都要。

⭐⭐⭐ 最后是那个反转:1990 年那块「转一下」是全矩阵, 比今天的对角遗忘门更一般 它输的从来不是表达力,是它不随数据变。
—— 一般但静态 → 对角但动态 → 对角动态 + 定点改写。 这就是那三十年。

📌 出处:Gers, Schmidhuber & Cummins《Learning to Forget: Continual Prediction with LSTM》,1999 年提出,期刊版 Neural Computation 12(10):2451–2471, 2000。 DeltaNet 的更新式与「先取回旧值、再写新旧混合值」的解读, 见本节末尾引的杨松琳《DeltaNet Explained》⚠️ 「Gated DeltaNet = 两者都要」这句,依据是它的名字与构造本身, 本课没有去核它的论文

7.4 可状态一回来,串行也跟着回来了

这就尴尬了。第一章那个盒子的三个痛里,第一条就是 只能一步一步来 —— 现在盒子请回来了,那个痛是不是也回来了?

回来了。这一支能站得住,全靠把它又解决了一次。

串行的状态,怎么榨出并行度 把它想成排队办事:一个窗口一个一个来,还是分批办 一个一个来 分批办 一批放多少人 ① 逐 token 跑 —— 一个窗口,一个一个来 这就是递推本身 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 每一步都要等上一步的结果 一百万个 token = 一百万步,一步都不能并 加速器最怕这个 —— 几千个算力单元,一次只喂得上一个 ② chunkwise —— 分批办:一批同时办,只把交接单传下去 块内并行,块间串行 第 1 批 1 2 3 4 5 6 这一批同时办 交接单 第 2 批 7 8 9 10 11 12 这一批同时办 第 3 批 13 14 15 16 17 18 这一批同时办 第 4 批 19 20 21 22 23 24 这一批同时办 交接单上只有一样东西:那块板子现在的样子(状态 S) 所以并行度从 1 变成 6,串行步数从 24 变成 4 —— 数学一个字没改,改的是算的顺序。 而且每一张交接单都一样大 —— 跟这一批有几个人、整句话有多长,都没关系。这才是它敢这么切的全部理由。 S d_k × d_v ← 这个尺寸 从头到尾不变 ②b 块内到底怎么算 —— 同一个和式,按「块外 / 块内」劈成两截 这不是近似 —— 两截加起来,跟一个一个跑出来的结果一模一样 第一截 块 前面所有块,早压成了一张交接单 这一块里每个 query,拿它去问那张单子就行 —— 前面有多少个 token,跟这一步的代价无关。 Q(C×d) × S(d×d) → 一次矩阵乘 第二截 块 只剩「本块里、我前面的那几个」 这一截躲不掉,但它只有 C 个人 —— 于是它退化成一个 C×C 的小全注意力。 Tril(Q Kᵀ)(C×C) × V(C×d) → 又一次矩阵乘 + 然后在块末更新一次交接单:S ← S + Kᵀ V —— 还是一次矩阵乘 三件事全是矩阵乘 —— 这正是 Tensor Core / MXU 要的形状 为什么这一改就快了 —— 答案在第一章那句话里 第一章说过:RNN 在硬件上的病不是算不动,是每一步都只是「一个维度等于 1 的矩阵乘」 —— 几千个算力单元,一次只喂得上一个。 chunkwise 做的事,一句话:把那个 1 变成了 C。串行步数从 L 步降到 L÷C 步,而每一步都变成了一把胖矩阵乘。 论文原话是「块间递归、块内并行,以最大化矩阵乘吞吐 ,从而吃满 Tensor Core」。 ②c 可 GDN / KDA 还多一步 —— 因为块内那些人互相有依赖 这一步就是那几家「自研 kernel」真正在解的东西 朴素线性注意力:块内大家各写各的 每个 token 只是往板子上一张卡片, 谁也不看别人写了什么。 所以块内直接能并行 就是上面那个 C×C 的小注意力。 delta rule:后面的人要看前面的人擦了什么 第 r 个 token 擦掉的东西,会改变第 r+1 个读到的 —— 块内不能直接并行 解法:把块内这一串依赖折成一个 C×C 的下三角矩阵 求一次它的逆 —— 整块就能一次算完。 论文说这个逆用高斯消元的逐行前代算,很便宜 所以那句「自研 kernel」,具体是在自研什么 Kimi 那篇说它做了一个 DPLR 的「特化版本」 —— 特化的地方就在这儿:通用写法要解的那个三角系统更贵,而 delta rule 这个特例可以写得更省 所以「线性注意力更快」这句话是有前提的:它快在有人替它把块内那一串依赖解开了没有这个 kernel,它在硬件上还是那条又细又长的链。 ③ 那 C 到底是什么 —— 它是一条轴,而两头都是你已经认识的东西 批越宽,交接单越少 —— 两端各少掉一样 C=1 24 批 · 23 张交接单 ← 就是①那个一个窗口 C=3 8 批 · 7 张交接单 C=6 4 批 · 3 张交接单 ← 就是②画的那一行 C=24 1 批 · 0 张交接单 ← 一张交接单都不用 C 越大 所以 chunkwise 不是第三种算法,是连接那两端的一个旋钮 —— 往上拧回 ①(一步一个,喂不饱算力),往下拧到底就是一整张矩阵算完。 右端是线性注意力的并行形式(L×L 下三角一次算完),不是 softmax 注意力 —— 整条轴从头到尾没有 softmax,「chunk 开满就变回 Transformer」是错的。 ④ 一批该放多少人 —— 这是个纯硬件问题 两头都被夹住 太少 柜台空着 一批 2 个人,几千个算力单元只用上几个 刚好 柜台坐满,大厅站得下 这就是要找的那个 C 太多 大厅站不下 块内的中间结果塞不进片上内存,被迫往外倒 这一张真正的落点:它不是「更快的注意力」,是一次改算法顺序 chunkwise 数学上跟逐 token 递推等价( 数值上不完全等价 —— 求和顺序变了,舍入就变了)。它改的只有一件事:什么时候算什么 而这恰好是这门课的主线又一次出现:一个数学上无所谓的选择,在硬件上决定生死 —— 不分块,线性注意力根本喂不饱加速器,再省显存也没用。 块大小这件事,专题一已经证过一次 C 被片上内存顶死,跟专题一 splash attention 的块大小是同一类问题 —— 而那一讲已经证过:块大小看的是比例,不是绝对值,换一代硬件就得重调。 所以别去记「C 取多少」这个数 —— 记「它被什么夹住」:下面是算力吃不满,上面是片上内存放不下。
⭐⭐ 这一张最该带走的不是那个名词,是它证明了一件事:「串行」有时候不是数学性质,只是算的顺序没安排好
同一个式子,一个字没改,并行度就从 1 变成了一批 —— 第一章那个卡死人的痛,在这里第二次被解开。
出处与口径

chunkwise 的形式与并行度 / 串行步数的改变出自 DeltaNet 并行化那篇 Yang 等 arXiv 2406.06484(WY 表示 + 分块)

⚠️ 图里的 24 个 token / 每块 6 个是示意,真实实现的块长在几十到几百之间,随硬件变

⚠️ 「排队办事 / 交接单」是本课的比喻

⛔⛔ 这一节最容易被跳过、却最该记住的一句: 「线性注意力更快」是有前提的。

上面图里 ②b 讲的是块内怎么拆(两截,全是矩阵乘), ②c 讲的是 delta rule 为什么还多一步。 那多出来的一步,不是实现细节 —— 它决定了这条路能不能落地。

⭐⭐ 所以读这一节的时候,别把「线性注意力更快」当成一条模型属性。 它一半是模型的功劳,一半是 kernel 的功劳 ——  而后一半,是可以缺席的。

—— 这也是最后一章那条暗线的又一次应验: 纸面上省下的东西,要有人把它落到机器上,才算数。

7.5 本章落点:兜了一整圈,我们对「状态」的看法变了

📌 这一节不讲新机制 —— 先看图,再读下面这几句。 图是骨架,文字是血肉,顺序反了会读不动。

绕回原点:KV cache 才是状态的一种,而不是反过来 这一格不讲新机制 —— 它把前面走过的每一条路,重新摆到同一条轴上 状态的两端 在这条轴上没动 落点 Ⓐ 每一个自回归模型都是「状态模型」—— 区别只在状态长什么样 这条轴没有刻度 —— 它只回答「谁在左、谁在右、谁原地不动」 原样留着 每一条历史都单独存着,一个字不差 压成一个固定大小的东西 存多长的话,它都是这么大 全注意力 把每一条 K/V 原样留着 GQA 八份合一份 —— 还是一条一条存 MLA 存压缩件 —— 小了很多,但仍随长度涨 线性注意力 / Mamba 不存条目,只留一块板子 句子越长,那个状态怎么变? 左边那三站(全注意力 / GQA / MLA) 右端那一站(线性注意力) 短句 中等 很长的一段 越说越长 —— 这三条都在这一端 短句 中等 很长的一段 一样大 —— 它不认识「句子多长」 滑窗 / DSA 也站在这一站 为什么它们不在这条轴上挪? 因为它们改的是「每一步读进来几份」,不是「存几份」—— 存的那一份一个字节都没少,所以站在原点一步没挪。 这张轴真正要说的一句话 前面六章拧的旋钮,都在这条轴的左半边挪来挪去只有这一章,真的走到了右端。 而走到右端的代价,也在图上:右边那三个方块一样大,意味着装不下的东西就是装不下 Ⓑ 反过来说才对 —— 这是这一段最狠的一句 同样两个词,谁装着谁,换了个位置 大多数人这么说 KV cache (被当成那个大类) 线性注意力的状态 「它是一种 KV cache」 这么说的问题: 它把「最不压缩的那一种」当成了整个类别的名字。 Albert Gu 的说法 状态(state) 每吐一个字就演化一次的那个东西 KV cache 压缩的那一种 一块固定的板子 压得最狠的那一种 换过来之后多出来的东西: 它们是兄弟,不是父子 —— 于是「第一章那个盒子」不再是被淘汰的老办法。 翻过来 翻过来之后,第一章那个盒子变成了什么 它不是一个被淘汰的老办法,它是这一整个家族的原型 我们绕了三十年,不是绕回了一个旧东西 —— 是绕回来之后,第一次知道自己为什么要它。 Ⓒ 一个比任何指标都好用的比喻 这一格刻意不放任何数字 —— 它的落点恰恰是「这个比较本身不成立」 Transformer 像一个数据库 每来一条新观察都当成重要资料归档 要用的时候翻出来 —— 一个字都不差 代价:越存越多 这一支 像一个大脑 大小有限、一直在线、边听边处理 不归档 —— 边听边把它揉进那块板子 代价:记不住一整本电话簿 我们 而我们人,恰恰是后面这一种 —— 在「精确记忆」和「精确检索」上糟糕透顶 而这好像并不妨碍智能出现 所以那个问法本身就不成立 很多人爱问「谁的长上下文更强」。 可以反问一句:我自己的记忆,和我的研究笔记,哪个更好? —— 它们只是不一样。一个记得牢、查得准但越攒越厚;一个大小固定、一直在线但会记混。 这正是下一章要「两个都要」的全部理由。
⭐⭐ Ⓑ 那两个圈值得多看两秒 —— 左右用的是同样两个词,只是谁装着谁换了个位置。这件事用嘴说,听的人要在脑子里自己转一次;画出来是一眼的事。
Ⓐ 那条轴是全课的地图:前面六章都在左半边挪,只有这一章真的走到了右端
出处与口径

三段引文均出自 Albert Gu《On the Tradeoffs of SSMs and Transformers》(goombalab.github.io/blog/2025/tradeoffs):①「每个自回归模型都持有一个状态」②「KV cache 才是状态的一种,而且是最不压缩的那一种」③「数据库 vs 大脑」

⚠️ Ⓐ 那条轴没有刻度,而且不该有 —— GQA 砍的是头数、MLA 压的是维度、线性换的是整个数据结构,三者之间没有一个可比的标量。四个点的横坐标只表达先后,不表达倍数

⚠️「滑窗 / DSA 原地不动」指的是这条轴(存多少)—— 它们在另一条轴(每步读多少)上走得很远

⛔ Ⓒ 刻意不放任何指标:数据库与大脑是框架不是 benchmark,原文的落点就是「这个比较不成立」

⭐⭐⭐ 先交代说话的人:这几句出自 Albert Gu —— Mamba 的作者之一,卡内基梅隆大学。

他那个框架的起点,是图里 Ⓐ 没画出来的半句:每一个自回归模型 在内存里都持有一个状态,每吐一个字就演化一次

请注意这句话的覆盖面 —— 它说的不是「有一类模型是状态模型」, 是全都是。于是前面七章那些看着毫不相干的名字, 第一次被放进了同一个框子,剩下的分歧只有一个: 那个状态,长什么样。

📌 顺带认个门牌:这一支在外面最响的名字叫 Mamba(2023)。

它走的是同一块板子、同一套「擦了再写」 ——  只是从信号处理那边推出来的,术语完全不一样,所以听上去像另一门功夫。

—— 这正是本章 7.2 那条判据的又一次应验: 名字记录的是血统,不是形态。

这一章的两句话。

换来的KV cache 从形状里彻底消失了 ——  每步要搬的东西,终于又跟话有多长无关了。 那正是第一章①那条被扔掉的好处,绕了五章,捡回来了。
欠下的板子就那么大,精确检索是它的软肋 ——  而且这是压缩的直接后果,不是实现不好。

⭐⭐ 于是局面变成了:三条路,三块啃不动的骨头。 而全行业给出的答案,出奇地一致。

第 八 节

谁也赢不了,那就都要 —— 混合配比

三条路都走完了,局面是这样的:每一条都有一块自己啃不动的骨头。 于是今天几乎所有人的答案都一样 —— 不选了,掺着用。
几层便宜的,配一层贵的。可这件事远比「省钱」有意思。

8.1 先证明一件事:混合不是「拿便宜的换点钱」

如果掺便宜的层只是为了省,那道理很简单:全用贵的,效果应该最好。 省不省是另一回事,但「最好」总归该归全贵的那一头。

可实测不是这样。

混合 —— 几个普通员工,配一个资深 便宜的层 = 只看手边记事板的普通员工;贵的层 = 能翻全部档案的资深 普通员工 资深 两头都不好 ② 格:线性混合(旋钮③那一支) ② 格:滑窗 / 局部混合(旋钮②那一支) ① 为什么不能只用一种人 两头都试过,两头都不好 全是资深 什么都查得到 贵得离谱 —— 消融里它还不是最好的 全是普通员工 又快又省 一长就兜不住:板子大小是固定的 混着用 大部分人快,少数几个能查全部 今天所有人的选择 左边那个结果最反直觉:Kimi 的消融里 0:1(全是资深)反而表现不好 ——  原文只有这一句定性描述,没公开数值 如果混合只是「拿便宜的换点钱」,那全用贵的应该最好才对。它不是 —— 说明加便宜层不只是省钱 ② 那到底几个配一个 —— 各家都落在哪 横轴:便宜层 : 贵层(对数刻度)· 一个点 = 一个模型 消融建议区间 3:1 ~ 6:1 8 : 1 1 : 1 2 个 3 : 1 5 个 5 : 1 3 个 6 : 1 1 个 7 : 1 3 个 线性混合 滑窗 / 局部混合 两族实际重叠的,正好也是这一段 —— 而它不是从建议区间推出来的,是各家自己选出来的 线性混合 Kimi Linear · Kimi K3 · Qwen3.5 · GLM-5.3F · Ling-3.0-flash · Ling 2.6 · MiniMax-01 · Jamba 滑窗 / 局部混合 Gemma 2 · gpt-oss-120b · Llama 4 Scout · MiMo-V2-Flash · Gemma 3/4 · MiMo-V2.5-Pro ③ 为什么资深不用配很多 他查到的东西会往下传 普通 第 1 层 普通 第 2 层 资深 第 3 层 普通 第 4 层 普通 第 5 层 普通 第 6 层 普通 第 7 层 普通 第 8 层 他查到的结果,顺着残差流传给后面每一层 只要有几层能「查全部档案」 后面的人直接用他的结论就行 —— 不用每层自己查一遍 意外红利:全注意力层可以不加位置编码 因为夹在中间的便宜层本身带时序 这条轴上最值得说的,是「大家都在哪」 最高的那一摞在 3:1,这十四家里占五个;往右没有一家超过 7:1( 指这一批)。这就是「大家都在哪」—— 不用数,堆在那儿。 再看最左边那一摞:1:1 上那两个清一色是橙的(滑窗 / 局部)—— 线性混合最省也是从 3:1 起步,没有一家敢一比一。 一种读法是:滑窗层毕竟还是注意力,只是不看远处 ;线性换掉的东西更多,得多配几层来摊。这只是读法 —— 没有哪篇论文这么归因过。 配比是超参,别背成常识 这条轴上的「便宜层」不全是线性 —— 小米那两家是滑窗3:1 是消融出来的,不是推出来的;同一家不同规模就换配比(Ling 的 tiny 是 3:1、flash 是 5:1)。 「纯线性一定兜不住」要说准:那条 L2M 条件要求的是状态必须随长度变大 —— 而「变大」的办法不止混合一种(论文自己给的另一条是按长度整个放大模型)。 混合是工程 上选的那条,不是定理逼出来的那条。
⭐⭐ 先看左边那一格 —— 它是这一整章的地基。如果掺便宜的层只是为了省钱,那一格的结果就不该长成那样。
右边那把尺子上,每一个点都是一个真模型。「大家都在哪」不用数,看那一摞堆在哪儿就行。
出处与口径

配比与型号见本课那两张配比表(每一行都标了出处,多数可在公开 config 里核);系统性消融的建议区间出自 arXiv 2507.06457

「0:1 反而表现不好」出自 Kimi Linear arXiv 2510.26692 —— ⚠️ 原文只有一句定性描述,没有公开数值

NoPE 见 Kimi Linear 同文(对所有全注意力层用 NoPE)—— ⚠️ 是它先做的,K3 是沿用

⚠️ 「普通员工 / 资深」是本课的比喻

⭐⭐ 这件事有一手的、来自 Mamba 作者本人的说法。

他写道:把一个纯 Transformer 里的一部分、甚至大部分层换成状态类的层, 效率和效果会同时变好。而那个最优配比大致在 3:1 到 10:1 之间 ——  这一点已经被几十个研究组各自独立地验证过了

换句话说:掺进来的那些便宜层,不是妥协,是它自己带来了好处。
⚠️ 他这句话说的是建模能力(困惑度),没有把计算成本算进去 ——  也就是说「省钱」是白捡的,不是理由。

⭐⭐ 而他紧接着那一句,正好把 7.5 那个反问接住了。

原文的意思是:就像人的智能,是靠随手的草稿纸和外部的参考资料撑起来的 —— 语言模型也一样,把状态层和注意力层掺在一起就会变好。

所以上一节结尾那个反问(在 fig3-gu 最下面那条带子里), 答案根本不是二选一 —— 

⭐⭐⭐ 答案是:你两个都有。
一个大脑,外加一份可以随手翻的笔记 —— 这就是混合架构。

⭐ 「几十个研究组」不是虚指,他原文点了名: H3 / Jamba / Zamba / Samba,以及后面跟上的一长串。 各家从不同起点出发,最后落在同一个区间 ——  这比任何一家自称「我们这个配比最好」都有分量。

那贵的层要配多少呢?一个很自然的想法是:不用多。

只要有几层能把全部历史查一遍,查到的东西就能传给后面所有层用。 —— 所以全局层不需要很多

📌 「传给后面所有层」靠的是残差流: 每一层算完不是把上一层的结果替换掉,而是在它上面往下传 ——  像一条一路往下、谁都可以往里添东西的主干道。所以一层查到的东西, 后面的层不用自己再查一遍,顺着这条道就拿到了

这个说法八成是对的,但它有一个字说错了 ——  错在哪,本章后面会用十四个模型的真实配置来判。

8.2 那为什么不能全用便宜的?—— 这里有一条能证伪的规律

一个很自然的反问:既然便宜的层这么好,干脆全用它不行吗? 上一章给的答案是「板子会写满」——  但那还只是一句直觉。

这件事其实有一条能算的规律。

「远处可以少看」这句直觉,严格版本长什么样 一本越读越厚的书讲 —— 以及一个固定大小的笔记本 两句话之间 两半之间 笔记本得多大 三个旋钮站在哪 ① 隔得越远的两句话,关系越弱 —— 但弱得有多快 这是 log-log 坐标,换成普通坐标就看不出区别了 隔得越远 → 真实的语言 隔一百句还剩一点 只记上一句 隔几十句就归零 这就是语言不能用「只记上一句」近似的原因 任何有限状态的记忆方式, 衰减都是指数的 —— 说没就没; 而真实语言是幂律的 —— 一直有一点。 「有限状态」这个限定不能省 注意这是 log-log 坐标 —— 换成普通坐标,两条线都长成「往下掉的一条线」,这张图要说的区别就看不见了。 ② 可是把书从中间劈开,两半之间的关联反而在变大 不矛盾 —— 单对越来越弱,成对的数量越来越多 薄书 3 × 3 = 9 对 厚一点 6 × 6 = 36 对 很厚 12 × 12 = 144 对 一个在减,一个在增 单对的关联越来越弱, 可对数增长得更快。 于是 L2M 那条定理,说人话就是这一句: 你记笔记的那个本子,得跟着书一起变厚 —— 本子大小固定,总有一本书是它兜不住的。 ③ 那三个旋钮,各自站在哪一边 这一格是本课按定义做的推导 旋钮① 每份更小 本子还是跟着书一起变厚 只是每页写得更省 安全 旋钮② 挑着看 本子照样跟着变厚 只是不是每页都翻 安全 旋钮③ 固定状态 本子大小写死了 书再厚,本子不变 总有一本兜不住 纯滑窗跟纯线性是同一类 —— 它们动的都是,不是常数。 这一张真正的用处:它把一句直觉变成了一条能证伪的规律 「远处可以少看」不等于「远处不重要」—— 两半之间的关联是随长度增长的,只是增长得慢。 于是整件事很清楚:Transformer 的 KV 线性增长是「供给过量」,而这条规律是「实际需求」。前两个旋钮就是在不掉到需求线以下的前提下,把那个过量的常数压小
⚠️ 先看清纵横轴都是对数的 —— 这张图不能当普通折线读。
⭐⭐ 它真正的价值在于:它把「纯线性到底行不行」从一句嘴仗,变成了一条能被证伪的规律。—— 能被证伪,才值得当理由用。
出处与口径

① 出自 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 自动满足的那段分析

⚠️ 「书 / 笔记本」是本课的比喻;③ 为本课推导,非论文结论

⭐⭐ 顺带说一句:这一张把第三章那句话改掉了一半。

第三章讲「信息账」的时候说过「远处可以少看」。那句话对,但不完整 ——  ⛔ 它不等于「远处不重要」。

一本书前半和后半之间的那点关联,是随着书变厚一直在涨的, 只是涨得慢。慢,不是零。

8.3 那 3:1 是怎么定出来的 —— 以及这张表问不出什么

「3:1 最好」这句话,底下是一张五行的表 数值全都公开了 · arXiv 2510.26692v2 Table 1 · 消融模型 16 头 16 层,同等 FLOPs 预算 论文标出的最优 全注意力层 摆不匀,进不了表 ① 表画出来长什么样 左右两张图的纵轴跨度完全相同(都是 0.30),所以斜率可以直接比 训练 PPL 9.50 9.20 9.45 0:1 9.29 1:1 9.23 3:1 9.23 7:1 9.34 15:1 3:1 和 7:1 在训练集上一模一样,都是 9.23 验证 PPL(论文据以定稿的那一条) 5.90 5.60 5.77 0:1 5.66 1:1 5.65 3:1 5.70 7:1 5.82 15:1 同样这两个配置,验证集上差 0.05 —— 训练集看不出来 排第 4 最差 纯全注意力那一头(0:1)不是最差的 —— 它排第 4,最差的是另一头的 15:1 ② 同样这五个配置,摆成 16 层看 每一列都是那个 16 层模型 · 蓝格 = 全注意力层,灰格 = 线性层 0:1 16 层全注意力 验证 PPL 5.77 1:1 8 层全注意力 验证 PPL 5.66 3:1 4 层全注意力 验证 PPL 5.65 7:1 2 层全注意力 验证 PPL 5.70 15:1 1 层全注意力 验证 PPL 5.82 4:1 5 层一组 16 摆不匀 这五个配比不是挑出来的, 是 16 除出来的。 16 层要摆得匀,每组 (r+1) 层 就必须整除 16。而 16 的约数 只有 1、2、4、8、16 —— 于是 r 只能取 0、1、3、7、15 正好就是表里那五行。 所以 4:1 从来没被试过 —— 它要 5 层一组, 摆到第 15 层就多出一层没地方放(右边那一列)。 2:1(3 层一组)同样摆不匀,所以 1:1 和 3:1 中间那一段,这张表也问不出来。 「(r+1) 必须整除 16」是对那张表做的算术观察 不是作者给的理由 —— 论文并没有解释为什么选这五个。 但结论不变:「为什么是 3 不是 4」,这张表回答不了。 ③ 那最低点到底是「3:1」,还是「4 层」 16 层里两者是同一件事 · 93 层里差 6 倍 · 公开文献没答案 同一个最低点,两种读法 读成比例:3 个线性配 1 个全注意力。→ 93 层的 K3 要配 24 层全注意力。 读成个数:一共有 4 层全注意力就够。→ 93 层的 K3 也只要 4 层 在 16 层里,这两句话完全等价 —— 16 ÷ 4 = 4,怎么读都是 4 层。 到了 93 层,两种读法差 6 倍 K3 实际保的是比例(93 层里 24 层全注意力,本课那张配比表已核)。 该保哪个,我没有找到任何公开实验回答过 —— 所有配比消融都在小模型上做的。 还有一个口径问题:这整张表是 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 追平或超过 它给这个结论起的标题是: 「决定配比的是召回,不是困惑度」 这张表能回答什么,不能回答什么 :3:1 在这五个候选里最好,而且两头都比它差 —— 包括纯全注意力那一头。 不能:为什么是 3 不是 4。4:1 在 16 层里摆不匀,从来没进过候选。
数值全公开,两张图的纵轴跨度也特意做成一样 —— 所以斜率可以直接比。
⛔ 而这张图最值钱的一句,是它老老实实标出了自己问不出什么。—— 一张敢写「这我答不了」的表,比一张全是箭头的表可信得多。
出处与口径

📌 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」是本课对该表做的算术观察,不是论文的说法。

这张表里最该被注意的,是两件「跟直觉相反」的事。

① 纯全注意力那一头不是最差的。 —— 这张表一共五个配比,它排第四。 五个里的第四不是中游,是倒数第二;最差的反而是另一头那个极端。

名次必须带分母。「排第四」单独拿出来听着像中游 ——  说了「五个里」才知道它其实垫在倒数第二。 这跟「样本量不等于去重后的个数」是同一族毛病:一个数字看着精确, 就没人再问它是几分之几。

② 这张表答不了「为什么是 3 不是 4」。 因为消融模型只有 16 层,能摆得匀的配比就那么几个 ——  中间那一段根本没被试过。

—— 这也是读任何一张消融表时该问的一句: 它到底扫过了哪些点,没扫过哪些点?

8.4 守恒的到底是比值,还是全注意力的层数

这是一个看起来吹毛求疵、其实很要命的问题: 模型从二十来层长到九十多层,该保住的是那个「几比几」, 还是那几层的绝对个数

⚠️ 这里的「十四个」跟 8.1 那张配比点阵的「十四个」不是同一批 那边数的是已公开配比的模型族(含滑窗族,最高 7:1); 这边数的是能拉到 layer_types 配置文件的模型 ——  里面有 Granite(40 层 4 条 = 9:1)和 Bamba(32 层 3 条 ≈ 9.7:1), 它们超过 7:1,但它们不在那张点阵里,两边并不打架。

这个问题不适合讲道理,适合去数。

守恒的是比值,还是全注意力的层数 去数配置文件,不猜 —— 十四个模型的 layer_types 逐个拉下来( 跟配比点阵那张不是同一批) Qwen 系 Kimi 系 其他各家 ① 先把两个假说写清楚,它们的预测不一样 能被数据分开的问题才值得问 假说 A ·「比值守恒」 配比 3:1 是超参,模型越深,全注意力层数跟着线性涨 预测:把「层数」对「深度」画出来,是一条斜着上去的直线 假说 B ·「几层就够」 本课自己写过:「全局层不需要很多 —— 只要有几层能做 无损检索,信息就能沿残差流传给其余层。」按字面读,它预测的是 一条平的线:全注意力层数与深度无关。 ② 十四个模型的配置文件,逐个数出来(跟配比点阵那张不是同一批) 横轴 = 总层数 · 纵轴 = 全注意力层的绝对条数 0 20 40 60 80 100 0 5 10 15 20 25 总层数 全注意力层数 假说 A 预测的样子(3 : 1) 假说 B 预测的样子(平的) Kimi Linear 27 层 / 7 条 Kimi K3 93 层 / 24 条 Qwen3.5-27B 64 / 16 Qwen3.5-0.8B 24 / 6 MiniMax-Text-01 80 / 10 Granite 4.0 40 / 4 点落在哪条线上 Qwen3.5 七个尺寸(图上合成 5 个点): 深度 24 → 64,`layer_types` 逐层写死, 恰好 3:1,一次不差 Kimi 自己就是最干净的反证 27 层 7 条 → 93 层 24 条,绝对数涨 3.4 倍 比值纹丝不动。 全体 14 个点,深度与条数的相关系数 r = 0.85;只看 Kimi + Qwen 两族, r = 0.999 假说 B 被否掉了 —— 而它就是本课 本课那句话的字面读法。 ③ 但也别一刀切 —— 真实规则是两条叠加 一条随深度涨,一条是常数 两条规则叠在一起长什么样 —— Kimi Linear 的 27 层,一层不落 主体:蓝的那六个 —— 每 4 层一个。层数一多,它跟着涨 外挂:橙的那一个 —— 末层必为全局。不管多深,永远就这一个 于是 20 : 7,不是 3 : 1 —— 多出来的就是最右边那一格 (主体那条各家不同:3:1 · 7:1 · 每 10 层一个) 两条叠加,正好解释一个小数点 末层那一个额外的全局层,会把实际比值从 3.0 压下来一点 Kimi Linear 20 : 7 = 2.857 (27 层,那一个摊不开) Kimi K3 69 : 24 = 2.875 (93 层,同一个 +1 被摊得更薄) 本课那张配比表只给 K3 加了「别写成 3:1」的警告 —— 同一句对 Kimi Linear 一样成立 而且「末层补一个」不是 K3 的花样 是这一家的通用排法。 所以「全局层不需要很多」该怎么说才准 准确的意思是占比低(各家落在 1/4 到 1/10 之间),不是绝对条数少。全体 14 个点里,绝对条数从 3 到 24 差 8 倍,而比值只差 3.4 倍 —— 散得开的那个不是超参 反过来说,模型越深,你要付的全注意力层就越多 —— 混合省下的是一个固定比例,不是「越深越划算」。
这一张的做法本身就是个示范:不猜,去数。十四个模型的层类型逐个拉下来,一行一行摆出来。
⭐⭐ 而数完之后会发现,两派都真实存在 —— 所以「守恒的是比值」这句常见说法,只说对了一半。
出处与口径

📌 全部数据来自各模型 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 公布的规则。

8.5 混合之后,还白捡了一样东西

还记得第五章那条很别扭的 64 维窄轨吗? 它之所以非有不可,是因为模型里没有任何一层天生知道先后 ——  于是人人都得自己带着位置信息。

那要是有一类层,天生就知道先后呢?

NoPE —— 不是绕过挡路的那个东西,是让它根本不用存在 讲 MLA 时留了个疙瘩:它本来可以把上投影「吸收」掉,可 RoPE 往中间塞了个跟位置有关的旋转 R,把这条路挡死了,只好拆出 64 维一路专门扛它。—— 混合架构之后,那个 R 干 脆没了。 挡路的 R 线性层:天生带顺序 全注意力层:只管检索 ① 今天大多数模型:每一层都得自己知道先后,所以每一层的 K 上都得带个 R MLA 留下的那个疙瘩 q R 按位置转 W_UK c 想把它挪到 q 那边 —— 被这个 R 挡住 夹在中间的东西挪不出去 于是 MLA 只好拆成两路: 512 维 · 可以吸收 64 维 · 扛 R = 576 那 64 维不是为了存信息,是为了给 R 找个不挡路的地方待着 🏠 生活版:办公室里每个人都自己戴表对时 —— 人人都要,人人都得带着。 ② 混合架构之后:「谁负责位置」这件事换人干了 Kimi Linear 原话:delegates the entire responsibility 线性层 天生带顺序 线性层 天生带顺序 线性层 天生带顺序 全注意力 只管检索 一个循环单元(3 : 1)—— Kimi Linear 27 层,就是它重复 6 次再加 3 层 …… 这样的单元再重复 5 次 线性层本来就是一步一步往下递推的 它的衰减和门控,本身就在编码「谁先谁后」 所以位置这件事,已经有人干了 → 全注意力层就不用再编一遍 那个 R 没有理由存在了。 论文原话:「delegates the entire responsibility for encoding positional information and recency bias … to the KDA layers」 🏠 生活版:流水线本身就是按顺序走的 —— 你在第几站是自明的,表可以不戴了 ③ 那三样麻烦本来就长在 R 身上 —— 所以它一走,三个位置同时空了 三条都是原文说的 R 还在 —— q 吸不进去 q R 按位置转 W_UK c 1 吸收被挡住 2 多出 64 维 512 64 = 576 3 外推要重标定 R 没了 —— q 直接吸进去 q R W_UK c 1 2 512 = 512 3 右边那三个空槽,分别变成了: 1 吸收完全生效 没有 R 夹在中间,上投影可以整个吸进 q 那一侧。推理时 MLA 直接退化成纯 MQA。 2 那 64 维没了 576 = 512 + 64 里的 64 整个消失 —— 在这一步之上再省 11.1% 3 不用再调外推 没有位置编码,就没有外推要重标定。长上下文扩展里最烦人的一块调参,直接不存在了。 注意这不是「找到了绕过 R 的技巧」 —— 是让别人替它把活干了,于是 R 根本不用存在被绕过不存在,是两件事。 这张图真正想教的不是 NoPE,是约束之间有连接 一个看起来纯粹是效率考虑的选择(层怎么配比),解开了一个看起来完全无关的约束(位置编码挡住吸收)。 本讲这样的连接已经出现过好几次:MLA 那次是「为了保住一个代数变换,把功能拆成两路」;这里是它的反面 —— 为了不再需要那个变换,干脆换个人来提供它的前提
看①和②的对照就够了:同一件事,谁来负责。
⭐⭐ 这是这本书里很少见的一种好事 —— 一个麻烦不是被解决的,是因为换了结构而不再存在。
出处与口径

四句原话均出自 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」

⭐⭐ 状态类的层是「一步一步」走的,先后顺序本来就写在它的走法里。

于是可以做一件很干净的事:把位置信息的全部责任,交给那些便宜的层剩下的全注意力层就不用带位置编码了。

—— 第五章那条被逼出来的窄轨,在混合架构里可以不要了。 一个在单一架构里绕不过去的麻烦,换个结构之后自己消失了。

⛔⛔ 这里一定会冒出一个反问: 那层全注意力(MLA)总还是得知道位置吧?它不留 RoPE 怎么行?

不留。而且这不是我们的推论,是论文里一整段的小标题。 Kimi Linear 技术报告(arXiv 2510.26692)原话: “we apply NoPE to all full attention (MLA) layers” ——  所有 MLA 层,一个不留。

那它怎么知道位置?—— 位置没有消失,是送达方式换了。

  • 原来:每一层自己盖戳。RoPE 是在这一层自己的 key 上 按位置转一下 —— 层层都得来一遍,所以层层都被那个旋转挡着。
  • 现在:下面几层已经揉好了,顺着残差流送上来。 MLA 层读到的那个向量本身就带着位置, 它不需要再在自己的 key 上做任何事。

⭐⭐ 那「多远」这件事,具体是谁编码的?—— 衰减 论文说 KDA 接过去的是 “positional information and recency bias, 并把它定性为 “the primary position-aware operator”
那块板子每走一步都要先乘一个衰减因子 ——  所以一样东西被乘过几次衰减,就说明它离现在有多远衰减本身就是那把尺子。

—— 回头看本讲反复用的那条分工:「顺序」归因果掩码, 「距离」原来归 RoPE在混合架构里,「距离」这一项从 RoPE 手里 交给了衰减,于是 RoPE 整个没有了存在的理由。

⚠️ 别推广成「混合就一定 NoPE」这是 Kimi Linear 的设计选择,论文说它跟另外几家的结果一致,但不是所有混合模型都这么做。 ⛔ 前提也很硬 —— 得有一类天然带时序的层,而且要摆在全注意力层下面, 位置才来得及被揉进去。

8.6 对号入座:现在回第零节那张表,你能一行一行读下来了

📌 名词讲完了。现在请回到第零节那张表。 —— 开篇看它的时候,它是一墙陌生的型号;现在它每一列你都认得: 一个循环里几层便宜的、上下文多长、KV cache 多大、以及它凭什么在这条线上。

那张表默认开在 Highlight 模式(撑起这段历史的那些行); 切到「全部」就是完整的参照表 —— 每一行都标了出处, 多数可以在公开配置里当场核。表头可以点,按时间/厂商/配比/上下文/KV 排序。

这一章的两句话。

换来的同时拿到两种记忆 —— 一个能精确翻查,一个能长久兜住。 而且实测上,它比任何一头单用都更好。
欠下的多了一个要调的东西(配比), 而它为什么是这个数,目前没有一篇公开消融真正回答过

⭐⭐ 到这里,这本书的「想法」部分讲完了。 最后一章要做的事只有一件 ——  把这些聪明办法真的搬到机器上,看看哪些能兑现,哪些兑现不了。

第 九 节

落到机器上 —— 以及那 512 倍是怎么换来的

八章的想法讲完了。最后一件事:它们真搬到机器上,兑现了多少?
这一章给三样东西:一张换算成毫秒的账、一场还没打完的仗, 以及开场那把枪的答案。

9.1 先把「省了 N 倍」换算成毫秒

前面每一章都在说「省了多少倍」。 可倍数是个相对量,它不告诉你那一步到底要多久。

代价 —— 把 ↓↓↓ 换成一步 decode 要搬多少字节 讲代价那一节的表里每一格都是箭头。可这门课自己的规矩是「问『省了多少』之前,先问『省的是哪一样』」 —— 箭头恰恰回答不了这个。那就把同一批方案,一路换算到毫秒 权重(所有人共享,每步一样多) 这一个人的 KV 换算成时间 ① 吐一个字,HBM 上要走的是这两样 DeepSeek-V3 · 128K · 一个用户 · batch 1 权重 34.46 GiB 每 token 激活 37B,原生 FP8 所有方案完全一样,而且所有人共享 这个人的 KV 随方案变 下面那五根柱子的差别全在这儿 每人一份,人越多、话越长,它越大 ② 一步要搬多少 —— 灰色那一段五根完全一样,差别全在红色那一段 柱高 ∝ 每步读的字节数 488.0 MHA 每步读 522.46 GiB KV 占 93.4% 要 12 张 device 30.5 GQA-8 每步读 64.96 GiB KV 占 47.0% 要 7 张 device 3.81 MQA 每步读 38.27 GiB KV 占 10.0% 要 7 张 device 8.58 MLA 每步读 43.04 GiB KV 占 19.9% 要 7 张 device 1.09 DSA 每步读 35.55 GiB KV 占 3.1% 要 7 张 device 权重 34.46 GiB 五根都顶到这条线 一个字节不差 后三根的红段在这个刻度下画不出来,只能标数字 —— 而「画不出来」本身就是 488 有多离谱的证据。 第一条读法:MHA 那一步里,93.4% 的字节是一个人的 KV —— 权重只占 6.6%。「KV cache 是瓶颈」这句话,到这儿才算画出来了。 顺手一条:MQA 比 MLA 还省(3.81 vs 8.58)—— 它从来不是慢,它是质量塌。省字节和能不能用是两回事。 ③ 除以带宽 —— 字节变毫秒,这里才出现两条反直觉的 v7 每 device 3.685 TB/s 一步要多久(下界) MHA ÷ 12 张 12.69 ms 79 tok/s GQA-8 ÷ 7 张 2.70 ms 370 tok/s MQA ÷ 7 张 1.59 ms 628 tok/s MLA ÷ 7 张 1.79 ms 558 tok/s DSA ÷ 7 张 1.48 ms 676 tok/s 0 4 8 12 ms MHA 那一根,比其余四根加起来还长(12.69 vs 7.57 ms)。 只算 HBM 读 —— 真机只会更慢,但各方案之间的比例站得住 反直觉一:显存省了 56.9 倍,只快了 7.08 倍 整条 = 显存的 56.9×;三段按对数切,所以正好铺满 —— 没有第三个原因。 7.08× ÷4.69 ÷1.71 7.08× 真正到手 —— 而右边两段合起来是 8.03×,吃掉的比到手的还多 ÷4.69 被共享的权重吃掉 —— 每步还得把 34.46 GiB 权重读一遍,这段谁都省不掉 ÷1.71 被多出来的卡吃掉 —— MHA 装不下,被迫用 12 张而不是 7 张(12÷7) 所以 MLA 真正省下的不是时间,是那 5 张卡 —— 它们可以拿去服务别人。 反直觉二:MLA 之后再上稀疏,一个人用的时候只再快 1.21 倍 因为瓶颈已经搬到权重那一段:读 34.46,KV 只剩 1.09。这不是稀疏没 用 —— 这正是上面表里「DSA:prefill 为主」的数值版 但这条只对一个人成立。KV 是每人一份的,人一多它就重新长回来:同时伺 候 32 个人,这个数是 1.55 倍,64 个人 1.98 倍 这里有个连专家都会踩的坑,值得单说:「权重是全员分摊的」是稠密模型 的直觉,V3 是 MoE,不成立。256 个专家里每个字挑 8 个 —— 人一多, 中的专家凑在一起就逼近全部:64 个人时期望激活 222 个,权重那一段自己 从 34 涨到 545 GiB。 所以稀疏确实随人数变值钱,但没那么夸张 「先问省的是哪一样」,现金价值就在这三行里 省显存 ≠ 省时间。MLA 对 MHA:显存 56.9×,单用户 decode 只有 7.08× —— 省下来的那 5 张卡才是真正的收益。 省读 ≠ 省存。DSA 那根柱子的灰段红段一个字节没少存,它只是每步不读 —— 所以它在这张图上只影响高度,不影响「要几张卡」。 这张图只成立在 batch = 1 上,这一条必须说 权重那一段是所有人分摊的,KV 那一段不摊。所以人一多,灰段被摊薄、红段成倍长 —— 画面会翻回 KV 主导 这正是那张「长度一变,主角就换人」图的另一面:那里是把人加上去让 KV 变成主角,这里是只留一个人让权重变成主角。同一个模型,问法不同,答案就不同 —— 这本身就是本 节的主题。
倍数是相对的,毫秒是绝对的 —— 这一张把前八章全部换算成后者。
⭐⭐ 看第二格里灰色那一段:五个方案完全一样。差别全在红的那一段上 —— 这才是「KV cache 是瓶颈」这句话真正的样子。
先看清这张图量的是「搬多少字节」,不是「算多少」 —— 灰色是权重(所有人共享,每步整个搬一遍,所以五根一样高),红色是你一个人的 KV。⭐ 那根灰的就是第一章那句「每走一步都要把全部权重从显存搬一遍」—— 它一个字都没过期。
出处与口径

装置偷自 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

📌 这张图不引入任何新数,全部是前面核过的数做除法 —— 所以读者可以拿计算器逐格核,这也是它敢把那张「每一格都是箭头」的代价表替换掉的底气

9.2 这些招落到 TPU 上,为什么格外难

要命的是:这些聪明办法绝大多数是在 GPU 上长出来的。 而一台机器擅长什么,是写在它的出厂设计里的 —— 换一台,前提就换了。

落到 TPU 上 —— 先看清楚是哪两件事对不上 中央厨房 碰上 全是临时改单的客人 中央厨房 = TPU 临时改单 = 现代注意力 最疼的一处 已经有的解法 ① 两种厨房 —— 这不是谁好谁坏 中央厨房快,正是因为它不接临时改单 TPU = 中央厨房 今日菜单 钉在墙上,改不了 一排蒸屉,整批上 1 2 3 4 5 连号货架,一趟拉走 出餐极快 —— 代价是临时改单很贵 GPU = 点单现做 临时改单 3 号桌 少辣、加一份 来什么做什么 一个灶台 一次一份 一份也做 满仓库跑腿,散落取货也认了 灵活 —— 代价是每道菜都要现开火 ② 而现代注意力,偏偏全是临时改单 三样,全是最近五年长出来的 每桌人数都不一样 一个 batch 里各请求长度不同 来源:vLLM 那套调度带来的 食材散在仓库各处 一条序列的 KV 散在不连续的页上 来源:PagedAttention 带来的 今天做哪几道菜,开工了才知道 这一步到底读哪 2048 条 来源:DSA / NSA 这一支带来的 这三样全是在 GPU 上先长出来的 —— 它们默认了一台「随手跑腿不太贵」的机器。 ③ 对上之后,最疼的是哪一处 —— 仓库取货 RPA 论文 §2 逐条点了名 连号货架:一趟推车拉走 一次搬运,地址连着 散落各处:跑很多趟 六次搬运,地址还是跑起来才算出来的 论文原话:这让DMA 调度变得困难 —— DMA 就是那台推车:它最擅长「一趟拉一整排」,最怕「这一趟拉哪几个,得先算一下」。 这句话值得原样读一遍 —— 它出自 2026 年 4 月,不是五年前 「现有 LLM 推理 kernel 和服务系统基本都是 GPU 中心的,而且还没有一套成熟的办法把 LLM 负载高效地映射到 TPU 架构上。」 —— Google 自己那篇 Ragged Paged Attention 论文的摘要(arXiv 2604.15464)。 所以这一节讲的不是「怎么调参」,是一个还在打开的工程战场 一条能带走的判据:看一个机制默认了什么样的机器 临时改单、散落取货、开工才知道做什么 —— 这三样都默认「随手跑腿不太贵」。在一家为「按批预制」优化的厨房里,这个假设不成立 所以移植的活儿不是「翻译代码」,是把那个隐含的硬件假设找出来,再换一个等价但规整的做法。下一张讲的就是这个「换法」。
整张图的题眼是那个厨房比喻,值得慢慢看完。
⛔ 特别注意中间那段引文的日期 —— 它不是五年前的旧话。
出处与口径

三处疼与那句摘要出自 Ragged Paged Attention(Jiang 等,arXiv 2604.15464,2026-04)§1 与 §2.4;TPU 的三条约束亦见该文 §1

⚠️ 「中央厨房 / 点单现做 / 仓库取货」是本课的比喻 —— 论文那侧的说法是 static-first 编译、tiled 粗粒度布局、以及「从动态算出来的不连续地址 gather」

⛔⛔ 有人真的动手试过,而他撞到的东西值得一字一句地看。

2025 年有人把 NSA 移植到 TPU 上,写了一份工作日志。他的结论是一句话: 动态稀疏在这套栈的每一层都难 ——

· 编译器不喜欢运行时才知道的变量和分支, 而「挑哪几块」恰恰是运行时才知道的
· 写 kernel 的框架强制按顺序一格一格走,而 top-K 挑出来的块是跳着的 —— 像 [7, 6, 1, 2] 这种顺序,它直接不让你走;
· 而那个「把散落的块取回来」的活,落到了一个专管算数的部件头上 —— 而不是那套专门搬东西的硬件
 它干活的方式是「同一个动作,对一排几百个数一起做」; 你让它一个一个去不同地方捡,一排几百个位子只用得上一个,其余全在干等

光是把索引改写成编译器友好的形式,他就快了 286 倍 —— 算法一个字没改。

⛔⛔⛔ 但真正值得记住的是下面这一条 ——  它是这本书那条暗线的最后一击。

为了对齐芯片里那块矩阵乘单元的形状, 他不得不把 NSA 论文里的块大小从 32 改成 64

而他自己在日志里写了一句:这可能会完全改变训练动态 ——  块大了,挑中的那一块里会夹进更多无关的东西, 模型在「大海捞针」这类细活上可能会变差。

⛔⛔ 读懂这一条,这本书那条暗线就走完了全程 ——  注意它管的事,一路在往「模型聪不聪明」靠:
第二章,硬件决定了用哪个公式(点积赢加性,因为它能变成矩阵乘);
第四章,硬件决定了一个超参(g=8,因为一台机器装 8 张卡);
第五章,硬件否决了一个漂亮的数学(压缩件不按头分,于是多卡切不开);
第六章,硬件决定了怎么挑(散着取不行,只能整块取);
到这里,硬件直接改了训练动态 —— 而它改的那个数,会影响模型聪不聪明。

⭐⭐ 到这里,第一章那个念头终于赢了第二次。

当时说的是 persistent RNN:别让权重反复落回显存。 七年后同一句话被用在另一个算子上 —— 别让中间结果落回显存, 那就是 FlashAttention它今天是所有注意力实现的默认底座。

可这一章讲的这些麻烦,恰恰是它在这台机器上接不住的那一类。

不挑你每步去读哪几块历史 —— 连着读能用,跳着读也能用; GPU 上那些「只读挑中的几块」的做法,几乎全是拿它改出来的。

⭐⭐ 第六章末尾欠的那一笔,在这里到期了。 当时那句话是「挑中的那些散落在各处,搬起来很碎」——  「很碎」翻译成机器听得懂的话是两件事一趟大搬运被拆成很多趟小的, 每趟都要付一次起步钱;而且要等挑完才知道去哪儿拿 ——  推车没法像平时那样提前一两趟就把下一车备好。

9.3 那怎么克服

那怎么克服 —— 三招,和两个最容易被问倒的地方 共同形状:把「一个临时改单」换成「一批预制套餐」 三招 谁来算 拣货小队 还没被验证的 ① 三招 —— 都是「不现开火,改成挑一个预制的」 RPA 论文的三个做法 保温台:开工前就把几套做好摆上 全长的 全短的 混着的 来单了,挑这套 留一格 共同形状(下面三招都是它) ? 运行时才知道的一个 → 开工前就备好的几个,来了挑一个 下面三招,形状跟右上角那个一模一样 —— 不一样的只有右边那行字。 ? 1. 把盘子切小一点 强制用最小的那种餐盒 —— 长短不一的那一维,别放在切盘子的方向上 ? 2. 上菜和收盘并成一趟 decode 时那一下零碎的写,融进主菜一起做,用做菜的时间盖住它 ? 3. 按客流预制几套套餐 最像中央厨房:不做万能菜谱,做几套再挑 —— 就是上面这张图 成绩:Llama 3 8B 在 TPU7x 上 —— decode MBU 86% · prefill MFU 73% ② 那个「今天做哪几道菜」的决定,谁来算 —— 答案分两层 别答成一个字 前台 host CPU 今天 12 桌 每桌几个人 一顿饭只报一次 每道菜都跑去问? 一来一回是微秒级, 而这一步只有几十微秒 后厨(就在卡上) 1 号 2 号 3 号 4 号 5 号 这道菜的料从哪个货架 —— 自己算 每道菜都要算一次 这笔账是拿闲人付的 颠勺的 算账的 正没事干 矩阵乘忙得冒烟的时候, 标量单元正闲着 —— 地址计算是他的活 所以这一问的答案是两层:「今天有几桌」前台报,一顿饭一次;「这道菜从哪个货架拿」后厨自己算,每道菜一次。 混成一句就必错。 ③ 那 SparseCore 能不能干这个 一支专门跑腿拣货的小队 推车 最多 2048 件 规矩:这一趟最多拿几件,必须开工前就报 超了就分批,或者直接丢掉一部分 对 DSA 反而天然满足 —— k 就是 2048,定死的 架构上非常对口 · 天生干散落取货(不规则、稀疏访存) · 能按条件决定去哪儿拿 · 跨通道排序、过滤、前缀和 —— 正是 top-k 要的 但要诚实:公开的那套 TPU 生产注意力 kernel 走的是主厨这条线(TensorCore + Pallas/Mosaic), 不是拣货小队。没有公开材料说有人用它跑注意力的 top-k —— 这一条到今天仍然只是「看起来对」。 跨层共享那一支,在这台机器上比在 GPU 上更值钱 GPU 上省的是:索引器那部分算力(GLM-5.2 报 1M 下每 token 降 2.9×)。 TPU 上还额外省三样:① 「这一趟拿哪几件」只算一次,后面几层直接复用;② 几层的取货路线完全一样,推车的单子可以重用;③ 临时改单的次数本身降了四倍
三招的形状跟上一张那三个难处一一对上 —— 可以把两张图叠着读:左边是病,右边是药。
出处与口径

三招、「SREG 在计算密集阶段欠用」、以及 MBU 86% / MFU 73%,均出自 Ragged Paged Attention(Jiang 等 arXiv 2604.15464)§3–§5

SparseCore 的定位与「必须声明静态上界、超了就 mini-batch 或丢 ID」出自 openxla.org 的 SparseCore 公开文档

⚠️ 「中央厨房 / 前台后厨 / 拣货小队」是本课的比喻;⚠️ 跨层共享在 TPU 上更值钱那一条是本课的推导,无公开对照实测

9.4 全讲落点:这是一部反复搬家的历史

把九章摆在一起,会看见一个出乎意料干净的形状。

落点 —— 注意力的变体史,是一部在三种资源之间反复搬家的历史 搬的顺序是有道理的:先搬能算的,最后才搬算不出来的 显存 算力 访存规整度 防骗判据 ① 三个房间,一条搬家路线 顺序不是随机的 —— 先搬能拿尺子量的,最后才搬量不出来的 早期 —— 搬「显存」这个房间 东西还是那些, 每一份变小了 MQA → GQA → MLA 换来:每份更小 中期 —— 搬「算力」这个房间 格子一个没少, 这一趟只搬两个 SWA → DSA → CSA 换来:格子更少 现在 —— 搬「访存规整度」这个房间 卡住 门就那么宽 —— 只能一行行写 chunk 化的线性 换来:读得更顺 为什么偏偏是这个顺序 前两样能拿尺子量 —— 多少字节、多少 FLOPs,坐下来就能算。 而访存规整度量不出来,它只出现在 kernel 里 —— 所以被留到了最后。 ② 四个取舍,一个都别漏 每一条都是一次翻车预防 1. 省显存 ≠ 省计算 MLA 省显存却加了计算 DSA 省计算但 KV 还在那儿 2. 训练时省 ≠ 推理时省 MLA 的压缩在训练前向里不生效 NSA 的 native 意味着训练也省。 3. 不规则访存的代价常被低估 纸面 64 倍,落到 gather 和不连续访问上远拿不到 4. 收益有天花板 注意力只是账单的一部分; MoE、MLP、通信一分没省 问「省了多少」之前,先问省的是哪一样 ③ 一条防骗判据 这一讲最该带走的一句 一次前向的算力都花在哪( 算力,不是时间) 4K 平方项 投影 其余 平方项 12.3% · 注意力投影 27.4% · 其余 60.3% 128K 平方项 其余 平方项 81.8% · 注意力投影 5.7% · 其余 12.5% 1M 平方项 平方项 97.3% · 注意力投影 0.8% · 其余 1.9% 同一个机制,占比差 八倍 4K 下平方项 12%,1M 下 97%。 「省了 N 倍」在这两端根本不是同一件事 而且短上下文那 12% 之外,注意力自己的投影还占 27% 所以任何一个倍数, 必须带上「在多长的上下文下」 不带这句,那些倍数全是耍流氓。 而且要问清楚:省的是 FLOPs,还是墙钟时间? 一句话收全课:访存规整度是最难搬的那一样 显存能算、算力能算 —— 所以这两样先被搬完了;访存规整度算不出来,它只出现在 kernel 里、出现在 SM 空转的那几个微秒里。 这也是为什么今天这一支的前沿工作看起来越来越像「写 kernel」而不是「改模型」 —— FlashKDA、TileLang、专用的上下文并行,全是这一类。
⭐⭐ 先看最下面那一行小字,它解释了整个顺序为什么是这样。
能拿尺子量的先被搬走,量不出来的留到最后 —— 这不是研究者偷懒,这是任何一个领域都会有的走法。
出处与口径

四个取舍与硬件假设表见 §九 / §十 正文(每条都可追到前面对应小节)

③ 的三根条由本脚本当场算并断言,公式与常数同专题一那条曲线(V3:61 层 / 128 头 / MoE top-8+1 共享,因果掩码按半算)

⚠️ 它是 FLOPs 口径,不是时间 —— 同样 seq=4096,本讲实测(Hunyuan3-295B · v7 64 芯片 · XProf op stats 页)splash attention 占 23% 的时间、效率只有 35.5%;效率最低的算子,时间占比一定高于算力占比⚠️ 这是量级示意,具体占比随模型结构、批大小、序列长度变

9.5 把开场那把枪打响

封面上挂着一把枪:六年,五百一十二倍。 现在这笔账算得完了。

📌 看图之前先自己猜一个数: 这五百多倍里,真正靠「买更好的硬件」换来的占几成?

收尾 —— 封面那 512 倍,到底是怎么换来的 开场我们说:2020 年的 GPT-3 记 2048 个 token,今天的模型记 100 万,这一讲讲的就是这 512 倍是怎么换来的。现在把这笔账算完。—— 而算它的办法,正是那条提问顺序: 「省了多少」之前,先问「省的是哪一样」。 要还的:上下文涨了 512 倍 显存账 元旋钮:混合 带宽账 ① 先把要还的那笔摆出来 封面立的那个承诺 2020 2,048 GPT-3 的上下文 今天 1,048,576 前沿模型的上下文 × 512 整整 512 倍,除得尽 什么都不改的话, KV cache 也要涨 512 倍。 这就是要还的那笔。 ② 它不是一笔账,是两笔 —— 同一把尺子量,切法完全不同 这正是讲代价时那条提问顺序 两根条一样长,长度都是那 512 倍。 对数刻度 —— 段长读的是「乘几倍」,不是「占几成」;相邻两段是相乘的关系。 显存账 一个人要占多少 单位:GiB/每 token 56.9× 旋钮① MLA 把每一份压小 元旋钮 混合 3:1 2.25× 剩下的这一截(见下) 带宽账 每走一步要读多少 单位:字节/每一步 512× 旋钮② 稀疏注意力:每步固定只读 2,048 个,一整条全被它抵掉 要还的 512× 末尾露出来的那一小截红,就是六年里真正多买的硬件 —— 只有 2.25 倍。前面 227.5 倍是「把每个 token 变便宜」换来的。 这一根一点都没露 —— 上下文涨了 512 倍,每步要读的一个字节都没涨。 而它不省显存:KV 全存着,所以它不进上面那根条。 这两根条不是一本账,别加起来 —— 上面量的是「一个 token 占多少」,下面量的是「一步要读多少」。分母不一样,所以那 512 倍里不含带宽 56.9× = 488 GiB → 8.58 GiB(V3 口径,前面核过) · 4× = 四层里只有一层是全注意力 · 512× = 1,048,576 ÷ 2,048 ③ 于是「三个旋钮」这条主线,在这里收成一句话 每个旋钮还的不是同一笔 旋钮① 让每一份更小 显存账 —— 把每个 token 的开销压下去 旋钮② 每步只读一部分 带宽账 ——  它不还显存账 旋钮③ 换成固定大小的状态 两笔一起还 —— 因为它让那个 S 整个消失了 所以「先问省的是哪一样」不是一句方法论口号 —— 不分开问,这两笔账根本对不上。 那 512 倍,是这么换来的 不是内存变大了。显存这一笔,是把每个 token 的开销压了 227.5 倍 —— 整条里最长的那两段,都是「把 token 变便宜」 而末尾那 2.25 倍,要老实说清楚它是什么:它是前两段除完之后剩下的那一截,不是查来的数。本课把它叫「硬件」,是个偷懒的叫法 —— 它里面至少还混着两样本讲没拆开的 东西:KV 用更低的精度存(今天各家默认就这么干,光这一项近乎再减半),以及前缀复用
⭐⭐⭐ 这是整本书的收据。开场挂在墙上那把枪,在这里打响 —— 而它打出来的答案,多半跟你开讲前猜的不一样。
⚠️ 两根条不是一本账,图里专门写了一句提醒 —— 别加起来。
出处与口径

488 GiB / 8.58 GiB(61 层 · 128K · bf16 · 一个用户)· 3:1 配比 · k=2048 这四个数,本讲前面都已逐一核过并标了口径,这一张只做乘除

⭐ 两个 512 为什么会相等,说破了很简单:GPT-3 的上下文长度(2048)和 DSA 的 k(2048)恰好是同一个数 —— 两条完全不相干的设定撞成了一个数,所以「今天 ÷ 当年」和「今天 ÷ k」才算出同样的 512。⚠️ 这是巧合,不是什么规律,但它让这笔账好记得多。脚本里两个等式都断言了

9.6 收尾:一个完整的圆

⭐⭐ 现在,请滚回开篇那张编年史,再看它一眼。

两小时前它是一团噪音。现在从上往下数 —— 
最上面那条灰的,是那个盒子,和 2014 年挂在它身上的那个配件;
蓝色那条①,是第四五章让每一份更小
橙色那条②,是第六章每步只读一部分
紫色那条③,是第七章换回一块固定大小的板子
最底下那三行公司,是第八章同一批旋钮,各拧各的

那张图一个像素都没变。变的是你。

⭐⭐⭐ 图顶上那句话,现在可以原样读一遍了:

「Transformer 当初做的交易是用平方的计算量,买来完全的并行度 —— 而这段历史,是在把那笔交易往回赎,但不能把并行度还回去。」

而每一章都是它的一个注脚:

砍头、压成压缩件、只读一部分 —— 赎回的是那个平方项前面的系数
线性注意力要赎的是那个平方本身,代价是把串行请了回来;
于是分块并行又出现了 —— 为了把并行度再找回来。

⭐⭐ 所以这一讲从头到尾其实只讲了一件事: 三十年里,人们一直在同一个三角里挪动 ——  记得住、算得快、付得起。哪一角都不能真的放手。

⭐⭐⭐ 但真正走完一圈的,不是那张图,是第一章那个盒子。

赎到最后,桌上摆着的东西,跟 1990 年那个盒子只差一处 ——  它是个矩阵,而且会擦。
就这一处差别,撑了三十年。

9.7 想往下读的人:这些人讲得比我好

📌 这一讲里凡是讲得漂亮的地方,多半不是我想出来的。 下面这些人把同一件事讲得更透,值得直接去读原文。

说大白话的
· 李宏毅《自注意力机制》讲义 —— 中文圈讲自注意力最清楚的一份,图少而准。
· Jay Alammar《The Illustrated Transformer》 —— 2018 年的老文章,至今没有被超越的图解。
· bbycroft 的 3D 交互可视化 —— 把一个 GPT 整个摊在三维空间里,可以一层一层点进去看。

把机制讲到骨头里的
· 苏剑林《从 MHA、MQA、GQA 到 MLA》《MLA 好在哪里》上下两篇 —— 第四章第五章大半的骨架来自这里。
· Albert Gu《On the Tradeoffs of SSMs and Transformers》 —— 第七章那个「数据库 vs 大脑」的框架出自这里。
· 杨松琳《DeltaNet Explained》三篇 —— 线性注意力这一支写得最清楚的一份中文作者英文博客。
· Sebastian Raschka《A Visual Guide to Attention Variants》 —— 要一张图看完所有变体,看这篇。

真的动手去撞硬件的
· Optimizing NSA for TPUs(Kernel Worklog) —— 上面那条「块大小从 32 改成 64」就出自这里。 全网少见的、把「落不落得下去」一路写到 kernel 那一层的记录。

另外,本讲的全部推导、消融表、一手出处,以及我们自己在 v7 上的实测, 都在完整版 L300 里。

← 回 课程总纲 · 硬件背景在 专题二 · TPU 与 GPU · 推导 · 消融 · 一手出处在 本讲 L300 完整版
本页由 Courses/tools/topic03-build-L200.py 生成 ——  正文写在那个脚本里