🔍 2026 五大注意力 · 从 MHA 到 MLA 精读版

架构三层拆解 → 总览对比/演进 → 每个机制 3 张图(宏观 → 结构展开 → 推理/数字) · 统一数字基线 L=4096 · H=32 · d=128 · G=4 · d_latent=256 · 省 KV 缓存 / 省算力全部数字化 · 源码逐行标注对应图部件
🧱 大模型架构 · 三层拆解 以 d_model=8192、词表=128k 为例
大语言模型(LLM)的本质是 N 层 Transformer Block 的堆叠。下面 3 张图由粗到细 展示完整流程。

图 B 特别说明“多头注意力” 作为一个整体概念被保留(蓝色框),内部展开 Q/K/V 投影 → 注意力计算 → O 投影FFN (SwiGLU) 展开为 上投影 (up) / 门控投影 (gate) / 下投影 (down) 三个矩阵。
图 A 整体流程:输入 → N 个 Block → 输出
flowchart TD UserInput["用户输入"] --> Tokenizer["Token 化
词表 128k"] Tokenizer --> Embed["Token 嵌入
128k × 8192 = 1.05B"] Embed --> BlockLoop["循环 N 个 Block
每层 ≈ 1.1B"] BlockLoop --> Output["输出层(LM Head)
通常与嵌入共享权重"] Output --> Result["生成下一个 Token"] Note1["每个 Block 内部见 图 B"] -.-> BlockLoop style Note1 fill:#f1f5f9,stroke:#94a3b8
📋 图 A 各部件具体参数
部件参数量占比(70B 计)
Token 嵌入128k × 8192 ≈ 1.05B~1.5%
Block(每层)≈ 1.1B~1.6% / 层
输出层(共享)0(共享嵌入权重)0%
图 B 单个 Block 展开:多头注意力 + FFN (SwiGLU 展开)
flowchart TD Input["输入 (8192维)"] --> LN1["LayerNorm"] LN1 --> MultiHead subgraph MultiHead["多头注意力 (64头, 每头128维) — 合计 268M (0.27B)"] Q["Q 投影
67M"] --> AttnCalc["注意力计算
(Q·K^T/√d)"] K["K 投影
67M"] --> AttnCalc V["V 投影
67M"] --> AttnCalc AttnCalc --> O["O 投影
67M"] end MultiHead --> Add1["残差连接 +"] Add1 --> LN2["LayerNorm"] LN2 --> FFN subgraph FFN["FFN (SwiGLU) — 合计 805M (0.8B)"] Up["上投影 (up)
8192×32768 = 268M"] --> SwiGLU["SwiGLU 激活"] Gate["门控投影 (gate)
8192×32768 = 268M"] --> SwiGLU SwiGLU --> Down["下投影 (down)
32768×8192 = 268M"] end FFN --> Add2["残差连接 +"] Add2 --> Out["输出 (8192维)"] style MultiHead fill:#dbeafe,stroke:#3b82f6,stroke-width:2px style FFN fill:#fef3c7,stroke:#f59e0b,stroke-width:2px
📋 图 B 各部件具体参数(展开)
部件参数量计算式
多头注意力(整体)268M(0.27B)4 × d_model²
├─ Q 投影67Md_model²
├─ K 投影67Md_model²
├─ V 投影67Md_model²
└─ O 投影67Md_model²
FFN (SwiGLU) 整体805M(0.8B)3 × d_model × d_ff
├─ 上投影 (up)268Md_model × d_ff = 8192×32768
├─ 门控投影 (gate)268Md_model × d_ff = 8192×32768
└─ 下投影 (down)268Md_ff × d_model = 32768×8192
单层 Block 总计≈ 1.1B0.27B + 0.8B
💡 SwiGLU 公式:SwiGLU(x) = Swish(gate) ⊙ up(x)
💡 三个矩阵:up 和 gate 都是 d_model × d_ff,down 是 d_ff × d_model,合计 3 × d_model × d_ff
图 B→C 过渡 从密集 FFN 到 MoE —— 为什么需要 MoE?
💡 核心洞察:并非所有 token 都需要所有专家
MoE 的核心思想是“条件计算”——每个 token 只激活少数最相关的专家,大幅减少计算量。

密集 FFN 的瓶颈:每层 0.8B 参数,64 层 = 51B,每个 token 必须完整计算全部参数
MoE 的解法:把 0.8B 的单一路径复制 256 份变成 138B 总参数,但 Router 只选 8 个最相关的专家(3.1%)干活。
flowchart TD subgraph Dense["密集 FFN (图B)"] DenseIn["输入 (8192维)"] --> DenseUp["up 268M"] DenseIn --> DenseGate["gate 268M"] DenseUp --> DenseAct["SwiGLU"] DenseGate --> DenseAct DenseAct --> DenseDown["down 268M"] DenseDown --> DenseOut["输出"] end DenseOut -.-> Problem["❌ 问题:每 token 必走全部 0.8B 参数"] Problem --> Solution["💡 解法:MoE — 用 Router 动态选择"] subgraph MoE["MoE (图C)"] MoEIn["输入 (8192维)"] --> Router["Router
8192×256 = 2.1M"] Router --> TopK["选择 Top-K 专家 (K=8)"] TopK --> E1["专家1
0.8B"] TopK --> E2["专家2
0.8B"] TopK --> EK["专家K
0.8B"] E1 --> Aggregate["加权求和"] E2 --> Aggregate EK --> Aggregate Aggregate --> MoEOut["输出"] end Solution --> MoE style Dense fill:#fef3c7,stroke:#f59e0b,stroke-width:2px style MoE fill:#e0e7ff,stroke:#6366f1,stroke-width:2px style Problem fill:#fee2e2,stroke:#ef4444 style Solution fill:#d1fae5,stroke:#10b981
📊 密集 FFN vs MoE 核心指标对比
指标密集 FFN (图B)MoE (图C, E=256, K=8)说明
每层总参数0.8B138BMoE 总参数大 170 倍
每层激活参数0.8B4.3BMoE 激活参数仅多 5.4 倍
激活/总参比例100%3.1%⭐ 关键:绝大部分参数闲置
每 token 计算量0.8B FLOPs4.3B FLOPsMoE 计算量增加有限
参数量/计算量比1:132:1⭐ MoE 用参数换计算
🎯 一句话总结MoE 不是让模型更小,而是让模型在同等计算量下变得更大更强——用闲置参数换能力,用 Router 保效率。
图 C MoE 专家内部:Router + 专家 FFN(展开)
flowchart TD InputFFN["输入 (8192维)"] --> Router["Router
8192×256 = 2.1M"] Router --> TopK["选择 Top-K (K=8)"] TopK --> Expert1 subgraph Expert1["专家 1 — 独立 FFN (0.8B)"] E1_Up["up 投影
8192×32768 = 268M"] --> E1_Act["SwiGLU"] E1_Gate["gate 投影
8192×32768 = 268M"] --> E1_Act E1_Act --> E1_Down["down 投影
32768×8192 = 268M"] end TopK --> Expert2 subgraph Expert2["专家 2 — 独立 FFN (0.8B)"] E2_Up["up 投影 268M"] --> E2_Act["SwiGLU"] E2_Gate["gate 投影 268M"] --> E2_Act E2_Act --> E2_Down["down 投影 268M"] end TopK --> ExpertK["专家 K (K=8)
每个 0.8B"] Expert1 --> Aggregate["加权求和
(按 Router 分数)"] Expert2 --> Aggregate ExpertK --> Aggregate Aggregate --> OutFFN["输出 (8192维)"] NoteRouter["Router 输出 E 个分数
选择 Top-K 个专家"] -.-> Router NoteExpert["每个专家结构相同
但参数完全独立"] -.-> Expert1 style Router fill:#e0e7ff,stroke:#6366f1,stroke-width:2px style Expert1 fill:#fef3c7,stroke:#f59e0b style Expert2 fill:#fef3c7,stroke:#f59e0b style ExpertK fill:#fef3c7,stroke:#f59e0b style NoteRouter fill:#f1f5f9,stroke:#94a3b8 style NoteExpert fill:#f1f5f9,stroke:#94a3b8 style Aggregate fill:#dbeafe,stroke:#3b82f6
📋 图 C 各部件具体参数
部件参数量计算式
Router8192 × 256 ≈ 2.1Md_model × E
单专家 FFN (SwiGLU)3 × 8192 × 32768 ≈ 0.8B3 × d_model × d_ff
所有专家(E=256)256 × 0.8B ≈ 138BE × 单专家
推理激活(Top-K=8)8 × 0.8B ≈ 4.3B / 层K × 单专家
💡 关键洞察:MoE 用 138B 总参数每 token 仅 4.3B 计算量,参数量/计算量比达到 32:1
📋 各模型 Block 层数与隐藏维度
模型Block 层数 (N)隐藏维度注意力机制FFN / MoE
DeepSeek-V4~64~8192CSA+HCA+SWAMoE (256专家, 激活8)
MiniMax-M3~56~6144MSA(稀疏注意力)MoE (激活23B)
GLM-5.2~60~7168DSA + IndexShareMoE (激活40B)
Qwen3.8645120Gated DeltaNet + Gated AttnMoE (95B激活) / Dense
Kimi K3~96~10240KDA + Gated MLAMoE (896专家, 激活16)
🔍 三层视角总结
图 A(最粗):输入 → Token 化 → 嵌入 → N 个 Block → 输出。
图 B(中等):每个 Block = LayerNorm → 多头注意力(蓝色框,内部含 Q/K/V/O) → 残差 → LayerNorm → FFN(黄色框,内部含 up/gate/down) → 残差。
图 B→C 过渡:密集 FFN 每 token 必走全部 0.8B 参数 → MoE 用 Router 选 Top-K 专家,用 138B 总参数换每 token 仅 4.3B 计算量。
图 C(最细):MoE 专家 = 独立的 SwiGLU FFN(up/gate/down),Router 动态选择 Top-K。
注意力机制变体(MHA/MQA/GQA/MLA/KDA)仅替换图 B 蓝色框内的实现,Block 骨架不变。
🔵 图 B 蓝色框 → 五种注意力机制 Q/K/V 始终在图 B 的蓝色框内部
图 B 的蓝色框(多头注意力)包含了完整的 Q/K/V/O 投影。五种注意力机制的区别在于:

Q/K/V 投影的形状(头数、分组方式)
注意力计算方式(标准 softmax / 线性 / 稀疏)
KV 缓存的存储策略(全量 / 分组 / 压缩 / 状态机)

下面每个机制的源码中,都标注了对应图 B 的哪个部件
📋 五种注意力如何替换图 B 的蓝色框
机制图 B 中替换位置核心差异2026 代表模型
MHA图 B 蓝色框(标准实现)每个 Q 头独立 K/V(H 组),缓存 L·H·d早期模型
MQA图 B 蓝色框(K/V 改为 1 头)所有 Q 共享单 K/V(1 组),缓存 L·1·d已淘汰
GQA图 B 蓝色框(K/V 改为 G 头)分组共享 K/V(G 组),缓存 L·G·dQwen3.8 / GLM-5.2 / MiniMax-M3
KDA图 B 蓝色框(完全替换为线性)无 Q/K/V 投影,循环状态,O(1) 显存Kimi K3
MLA图 B 蓝色框(K/V 前加压缩层)缓存 Latent,解压后计算,L·d_latentDeepSeek-V4
🚀 2026 四大旗舰模型速览 真实架构归属
DeepSeek-V4(1.6T MoE)、MiniMax-M3(428B MoE)、GLM-5.2(744B MoE)、Qwen3.8(2.4T MoE)分别代表了当前大模型的技术前沿。它们所采用的注意力机制各不相同,但都围绕一个核心矛盾:如何在有限显存内支持超长上下文,同时保持推理速度
模型发布时间总参数 / 激活架构注意力机制上下文长度
DeepSeek-V42026.041.6T / 49B (MoE)MoECSA+HCA+SWA1M
MiniMax-M32026.06428B / 23B (MoE)MoE + MSAMSA1M
GLM-5.22026.06744B / 40B (MoE)MoEDSA + IndexShare1M
Qwen3.82026.082.4T / 95B (MoE)MoEGated DeltaNet + Gated Attn1M / 256K
💡 KDA(Kimi Delta Attention)月之暗面(Kimi) 提出,应用于 Kimi K3(2.8T MoE),它属于 线性注意力 范畴,与 GQA/MLA 有着本质区别。
📊 图1 · 总览对比 + 演进路线 五种机制“缓存里存什么” + 为什么省 + 统一数字基线
大模型解码时,每生成 1 个 token 都会发生两件与 L(上下文长度)成正比的事
显存 O(L):KV 缓存要把前面 L 个 token 的 K/V 都存下来;
算力 O(L):新的 Q 要和前面全部 L 个 K 逐个点积(长程扫描)。

下面 图 1-1 用一个公共的「图 B 投影」节点展开五种机制:Q 始终 32 头,变的只是“K/V 怎么生成、怎么存”,并按演进顺序 MHA → MQA → GQA → MLA → KDA(标准基线在前,改进在后)排列;图 1-2 再把压缩线(存得下)与分支线(算得动)串成完整演进路线。
图 1-1 总览对比:Q/K/V 投影后,“K/V 怎么存”(标准 MHA → 逐步改进)
flowchart TD X["输入 x"] --> Bbox["图 B 蓝色框:Q/K/V 投影
(Q 始终 32 头,五种机制都不变)"] Bbox --> mha Bbox --> mqa Bbox --> gqa Bbox --> mla Bbox --> kda subgraph mha["🟦 ① MHA · 标准基线 · 缓存 16KB/token"] mh1["K/V 投影 → 32头 × 2
(一对一,不压缩)"] mh2["缓存 H=32 组 K/V
8192 元素 = 16KB"] mh3["Q 与全部缓存点积 → 输出"] mh1 --> mh2 --> mh3 end subgraph mqa["🟨 ② MQA · 改进1 · 缓存 0.5KB/token"] mq1["K/V 投影 → 各 1 头
(全员共享)"] mq2["缓存 1 组
256 元素 = 0.5KB"] mq3["广播给 32 头 → 输出"] mq1 --> mq2 --> mq3 end subgraph gqa["🟩 ③ GQA · 改进2 · 缓存 2KB/token"] g1["K/V 投影 → 各 G=4 头
(分组共享)"] g2["缓存 G=4 组
1024 元素 = 2KB"] g3["推理 repeat_interleave
对齐 32 头 → 输出"] g1 --> g2 --> g3 end subgraph mla["🩷 ④ MLA · 改进3 · 缓存 1KB/token"] l1["K/V 压成 Latent
256 维(尺寸压缩)"] l2["缓存 latent
512 元素 = 1KB"] l3["潜在空间注意力 → 输出
(解压吸收进 Q/O)"] l1 --> l2 --> l3 end subgraph kda["🟪 ⑤ KDA · 分支路线 · 状态恒定 ~8MB"] k1["K 投影(写地址)·V 投影(写内容)
(不存列表,直接进状态)"] k2["状态 S ← δ·S + v·k^T
固定大小 O(1)"] k3["Q(读指针) 读状态 → 输出"] k1 --> k2 --> k3 end style mha fill:#dbeafe,stroke:#3b82f6 style mqa fill:#fef3c7,stroke:#f59e0b style gqa fill:#d1fae5,stroke:#10b981 style mla fill:#fce7f3,stroke:#ec4899 style kda fill:#e0e7ff,stroke:#6366f1
🔍 这张图怎么读?
顶部公共节点:五种机制的 Q/K/V 全来自图 B 的蓝色框(Q 始终 32 头),变化只发生在 “K/V 怎么生成、怎么存” 这一步。
阅读顺序 = 演进顺序:先看①标准 MHA,再看改进线 ②→③→④(MQA 全共享 → GQA 分组 → MLA 压尺寸),⑤ KDA 是另一条分支(干脆不存列表)。
图里只能一笔带过的新概念,正文详解
  • 分组 K/V(GQA):K/V 只给 G=4 组,每组对应 8 个 Q 头;推理时 repeat_interleave 对齐 → 见 图 GQA-2 / 图 GQA-3
  • Latent 压缩(MLA):先压成 256 维 latent 再缓存,推理时解压吸收进 Q/O、不展开 → 见 图 MLA-2 / 图 MLA-3
  • 循环状态 S(KDA):不存 K/V 列表,用外积 v·k^T 累加进固定状态并遗忘,Q=读指针 → 见 图 KDA-1 / 图 KDA-2
  • 缓存口径:16KB → 0.5KB → 2KB → 1KB(KDA 恒定 ~8MB),数字出处见下方“统一数字基线”。
图 1-2 演进路线:压缩线(存得下)→ 分支线(算得动)
flowchart LR MHA["MHA
每头独立 K/V
16KB/token"] --> MQA["MQA
全共享 1 份
0.5KB/token"] MQA --> GQA["GQA
分组共享 G=4 份
2KB/token"] GQA --> MLA["MLA
latent 压缩
1KB/token"] GQA -. "换条路:连 KV 列表都不想存" .-> KDA["KDA
固定状态机
~8MB 恒定 与 L 无关"] style MHA fill:#dbeafe,stroke:#3b82f6 style MQA fill:#fef3c7,stroke:#f59e0b style GQA fill:#d1fae5,stroke:#10b981 style MLA fill:#fce7f3,stroke:#ec4899 style KDA fill:#e0e7ff,stroke:#6366f1
一句话:MHA → MQA → GQA → MLA 是“把要存的东西越弄越小”(压缩线,解决“显存放不下”);KDA 是“换一种算法,连存都不存”(线性注意力,解决“长程算不完”)。两条路解决的是同一个瓶颈的两个侧面。
📐 统一数字基线(后文所有数字都从这一组参数算出来,方便横向对比)
参数取值含义
L4096上下文长度(一次推理的 token 总数)
H32Q 头数
d128每头维度
d_model4096= H × d,隐藏维度
G4GQA 组数(每组 8 个 Q 头)
d_latent256MLA 潜在维度
精度FP16缓存按 2 字节 / 元素计
💡 记忆口诀:每 token KV 元素数 = MHA 8192(16KB)→ MQA 256(0.5KB)→ GQA 1024(2KB)→ MLA 512(1KB);KDA 为恒定状态。
📋 核心参数对比 · 2026 代表模型
机制KV 缓存大小推理速度模型质量2026 代表模型核心思想
MHAL·H·d🐢 较慢⭐⭐⭐⭐⭐(早期模型)每 Q 独立 K/V
MQAL·1·d🚀 极快⭐⭐⭐(已淘汰)所有 Q 共享单 K/V
GQAL·G·d⚡ 较快⭐⭐⭐⭐Qwen3.8 GLM-5.2 MiniMax-M3分组共享(G组)
KDA固定状态
(非KV缓存)
⚡ O(L)⭐⭐⭐⭐Kimi K3线性注意力+循环状态
MLAL·d_latent
(d_latent << H·d)
💾 省显存⭐⭐⭐⭐DeepSeek-V4低秩压缩 K/V
* L:序列长度, H:头数, d:头维度, G:组数, d_latent:潜在维度
🔍 总览洞察
GQA 已成为 2026 年工业界的“最大公约数”,提供了 接近 MHA 的质量,同时缓存缩减至 1/G
MLA 通过低秩压缩将缓存压缩至 5%~10%,使 1M 上下文成为可能。
KDA 则另辟蹊径,完全抛弃 KV 缓存,用固定大小的状态机,理论上可处理无限长文本。
一句话主线:前四种机制在回答“KV 缓存怎么存更省”(MQA/GQA 省个数、MLA 省尺寸),KDA 在回答“如果不存 KV 会怎样”(换算法)。
🧩 MHA · 多头注意力 Multi-Head Attention —— 基线,先被优化的对象
MHA 是 Transformer 的“原教旨”设计:每个 Q 头都独立配对一个 K 头和一个 V 头(一对一,绝不共享)。它是后面所有优化的基线——先看清它的浪费在哪,才知道后面每一步在省什么。

浪费在哪:每 token 都要把 32 组 K/V 完整存入缓存,缓存大小 = L × 2·H·d,随 L 线性膨胀。
图 MHA-1 宏观 整体流程:投影 → 缓存 → 注意力(一眼看懂 MHA 在干嘛)
flowchart TD X["输入 x
d_model = 4096"] --> Proj["Q/K/V 三个投影
每个都输出 32 个头(4096 维)"] Proj --> Cache["KV 缓存
每 token 追加 2·H·d = 8192 元素 = 16KB"] Proj --> Q["Q(新 token 的 32 个头)"] Cache --> Attn["注意力计算
Q 与缓存里全部 L 个 K 点积 → softmax → ×V"] Q --> Attn Attn --> Out["输出(加权和)"] Note["每个 Q 头都要扫过全部历史
→ 算力 O(L)、缓存 O(L)"] -.-> Attn
图 MHA-2 结构展开 Q/K/V 一一对应,互不共享
flowchart TD X["输入 x"] --> Wq["W_q:4096 → 4096"] X --> Wk["W_k:4096 → 4096"] X --> Wv["W_v:4096 → 4096"] Wq --> Qs["32 个 Q 头
头0..头31"] Wk --> Ks["32 个 K 头
头0..头31"] Wv --> Vs["32 个 V 头
头0..头31"] Qs --> P["逐头点积:头i 只与 头i 的 K 算"] Ks --> P P --> A["softmax → 加权求和:头i × 头i 的 V"] Vs --> A A --> O["拼接 32 头结果
→ W_o 投影 → 输出"] Note["一一对应:
Q0↔K0/V0、Q1↔K1/V1 ...
没有任何一个头被共享"] -.-> P
图 MHA-3 推理 / 数字 KV 缓存随 L 线性膨胀,长上下文直接爆炸
flowchart TD T1["第 1 个 token
存 16KB"] --> T2["第 2 个 token
追加 16KB → 共 32KB"] T2 --> T3["第 3 个 token
追加 → 共 48KB"] T3 --> TL["第 4096 个 token
共 4096 × 16KB = 64MB / 层"] TL --> B1["L = 100k 时
单层就 1.6GB"] B1 --> B2["64 层合计 ≈ 100GB
显存放不下 ❌"] style B2 fill:#fee2e2,stroke:#ef4444
每 token KV 存储
16 KB
2·H·d = 2×32×128 = 8192 元素
L=4096 总缓存
64 MB (基线)
8192 元素 × 4096 × 2B
每步解码算力
≈ 33.6M MACs O(L)
2·H·L·d = 2×32×4096×128
K/V 投影算力/步
33.6M MACs
2 × d_model × (H·d) = 2×4096×4096
MHA 的病根:缓存和扫描都随 L 增长,100k 上下文就 100GB。后面四种机制都是从这里下手。
class MHA(nn.Module):   # 对应 图MHA-1 的蓝色区域
    def __init__(self, d_model=4096, n_heads=32):
        self.n_heads = n_heads
        self.head_dim = d_model // n_heads          # 128
        self.W_q = nn.Linear(d_model, d_model)      # 图MHA-2 的 Q 投影
        self.W_k = nn.Linear(d_model, d_model)      # 图MHA-2 的 K 投影(同样 32 头,浪费点 1)
        self.W_v = nn.Linear(d_model, d_model)      # 图MHA-2 的 V 投影(同样 32 头,浪费点 2)
        self.W_o = nn.Linear(d_model, d_model)      # 图MHA-1 的 O 投影

    def forward(self, x, past_kv=None):
        B, T, _ = x.shape
        # 投影:每个 token 生成自己的一套 H=32 组 Q/K/V
        Q = self.W_q(x).view(B,T,self.n_heads,self.head_dim).transpose(1,2)  # (B,32,T,128)
        K = self.W_k(x).view(B,T,self.n_heads,self.head_dim).transpose(1,2)  # (B,32,T,128)
        V = self.W_v(x).view(B,T,self.n_heads,self.head_dim).transpose(1,2)  # (B,32,T,128)
        # 图MHA-3 推理:把新 token 的 K/V 追加进缓存(缓存长度 = L,随 L 线性增长)
        if past_kv:
            K = torch.cat([past_kv[0], K], dim=2)   # (B,32,L,128)
            V = torch.cat([past_kv[1], V], dim=2)
        # 图MHA-2 注意力:Q·K^T/√d → softmax → ·V(Q 扫过全部 L 个历史位置)
        scores = Q @ K.transpose(-2,-1) / math.sqrt(self.head_dim)  # (B,32,T,L)
        attn = F.softmax(scores, dim=-1)
        out = attn @ V                                            # (B,32,T,128)
        return self.W_o(out.transpose(1,2).reshape(B,T,-1)), (K, V)  # O 投影 + 回传缓存
⭐ MHA 总结:表达力最强(每头一个视角),但 缓存 = L×8192 元素、每步算力 O(L)两头都随 L 涨,2026 年已不是主流——它是被优化的基线。
🧩 MQA · 多查询注意力 Multi-Query Attention —— 从“每头一份”到“全员共享一份”
🔁 从 MHA 到这里,做了什么变形?
MHA 的问题是 K/V 存了 32 份。MQA 的回答很激进:既然 K/V 是“历史信息”,为什么不让所有 Q 头共用同一份? 于是 K/V 投影从输出 32 个头改为只输出 1 个头。缓存每 token 从 8192 元素骤降到 256 元素。

代价:所有头只能看同一个 K/V 视角,表达力下降(质量损失)——这就是它 2026 年被淘汰的原因,但它把“省个数”的极限演示出来了。
图 MQA-1 宏观 整体流程:1 份 K/V 广播给 32 个 Q 头
flowchart TD X["输入 x"] --> Proj["Q 投影 → 32 个头
K/V 投影 → 各只 1 个头"] Proj --> Cache["KV 缓存
每 token 存 2×128 = 256 元素 = 0.5KB
是 MHA 的 1/32"] Proj --> Q["Q(32 个头)"] Cache --> Br["广播:1 份 K/V 同时喂给 32 个 Q 头"] Q --> Br Br --> Attn["注意力计算"] Attn --> Out["输出"]
图 MQA-2 结构展开 32 个 Q 头 → 同一份 K/V(广播)
flowchart TD X["输入 x"] --> Wq["W_q:4096 → 4096"] X --> Wk["W_k:4096 → 128(只 1 个头)"] X --> Wv["W_v:4096 → 128(只 1 个头)"] Wq --> Qs["32 个 Q 头"] Wk --> K1["K:1 个头"] Wv --> V1["V:1 个头"] Qs --> Br["广播:同一份 K/V 分给 32 个头"] K1 --> Br V1 --> Br Br --> A["所有 Q 头共用同一份 K/V 算注意力"] A --> O["输出"] Note["代价:所有头只能看同一个视角
→ 表达能力下降、质量损失"] -.-> Br
图 MQA-3 推理 / 数字 缓存缩小 32 倍
flowchart TD T1["每 token 只存 1 组 K/V
0.5KB"] --> TL["L=4096 总缓存
4096 × 0.5KB = 2MB"] TL --> SAVE["相比 MHA 的 64MB
节省 97%"] SAVE --> Q1["但 K/V 投影算力也 ÷32
K/V 投影从 33.6M → 1.05M MACs"] style SAVE fill:#d1fae5,stroke:#10b981
每 token KV 存储
0.5 KB
2×1×128 = 256 元素,是 MHA 的 1/32
L=4096 总缓存
2 MB (省 97%)
256 × 4096 × 2B,比 MHA 省 62MB
每步解码算力
≈ 33.6M O(L) 不变
Q 仍是 32 头扫全 L,注意力 FLOPs 没省
K/V 投影算力/步
1.05M (省 32×)
2 × 4096 × 128
注意:MQA 省的是 缓存内存 + K/V 投影算力;注意力点积本身仍是 O(L)(因为 Q 还是 32 个头扫全量历史)。代价是质量损失。
class MQA(nn.Module):   # 对应 图MQA-1:K/V 改为 1 个头
    def __init__(self, d_model=4096, n_heads=32):
        self.n_heads = n_heads
        self.head_dim = d_model // n_heads          # 128
        self.W_q = nn.Linear(d_model, d_model)      # 图MQA-2 的 Q 投影(仍 32 头)
        self.W_k = nn.Linear(d_model, self.head_dim) # ⚡ 图MQA-2 的 K 投影改为只 1 个头
        self.W_v = nn.Linear(d_model, self.head_dim) # ⚡ 图MQA-2 的 V 投影改为只 1 个头
        self.W_o = nn.Linear(d_model, d_model)      # O 投影

    def forward(self, x, past_kv=None):
        B, T, _ = x.shape
        Q = self.W_q(x).view(B,T,self.n_heads,self.head_dim).transpose(1,2)   # (B,32,T,128)
        K = self.W_k(x).unsqueeze(1)                                          # (B,1,T,128) 只 1 个头
        V = self.W_v(x).unsqueeze(1)
        if past_kv:
            K = torch.cat([past_kv[0], K], dim=2)   # (B,1,L,128) 缓存极瘦
            V = torch.cat([past_kv[1], V], dim=2)
        # 图MQA-2 广播:把 1 个头复制成 32 个头(等价 repeat_interleave)
        K = K.repeat(1, self.n_heads, 1, 1)         # (B,32,L,128)
        V = V.repeat(1, self.n_heads, 1, 1)
        scores = Q @ K.transpose(-2,-1) / math.sqrt(self.head_dim)
        attn = F.softmax(scores, dim=-1)
        out = attn @ V
        return self.W_o(out.transpose(1,2).reshape(B,T,-1)), (K[:, :1], V[:, :1])  # 缓存只存 1 份
⭐ MQA 总结:缓存 ÷32、投影 ÷32,但 质量损失明显,2026 年已被 GQA 全面取代——它告诉我们“省个数”不能省过头。
🧩 GQA · 分组查询注意力 Grouped-Query Attention —— 省个数的“黄金折中”
🔁 从 MQA 到这里,做了什么变形?
MQA 把 32 个头全部并成 1 份,太激进、质量掉太多。GQA 的答案是“分组”:把 32 个 Q 头分成 G=4 组,每组 8 个头共享一份 K/V。这样:

• K/V 从 32 份减到 G=4 份(缓存 ÷8,接近 MQA 的收益)
• 但每组仍有独立的 K/V 视角(质量损失远小于 MQA)
结论:GQA = 用 G 调档的“个数压缩器”,是 2026 年工业界的最大公约数。
图 GQA-1 宏观 整体流程:投影只算 G 份 → 缓存只存 G 份 → 推理时对齐 H 份
flowchart TD X["输入 x (d_model=4096)"] --> Proj["Q 投影 → 32 个头
K/V 投影 → 只算 G=4 个头"] Proj --> Cache["KV 缓存
每 token 存 2·G·d = 1024 元素 = 2KB
是 MHA 的 1/8"] Cache --> Align["推理:把 G=4 组复制成 32 组
与 32 个 Q 头对齐(repeat_interleave)
→ 见 图GQA-3"] Align --> Attn["注意力计算"] Attn --> Out["输出"] Note["关键:存储永远只保留 G 组
复制只发生在计算瞬间(视图操作)"] -.-> Cache
图 GQA-2 结构展开 分组共享:H=32 个 Q 头分成 G=4 组,每组共享一份 K/V
flowchart TD X["输入 x"] --> Wq["W_q → 32 个头"] X --> Wk["W_k → G=4 组 K/V"] X --> Wv["W_v → G=4 组 K/V"] Wk --> G0["组0:头0-7
共享 K0/V0"] Wk --> G1["组1:头8-15
共享 K1/V1"] Wk --> G2["组2:头16-23
共享 K2/V2"] Wk --> G3["组3:头24-31
共享 K3/V3"] Wq --> G0 Wq --> G1 Wq --> G2 Wq --> G3 G0 --> A["组内每个 Q 头
都用自己组的 K/V 算注意力"] G1 --> A G2 --> A G3 --> A A --> O["输出"] Note["这一层只回答“投影/存储阶段”:
K/V 每 token 只算 4 份、只存 4 份"] -.-> Wk
🤔 为什么图 GQA-2 明明只有 4 组 K/V,图 GQA-3 突然要 repeat_interleave 成 32 份?
因为这是两个阶段的问题:
图 GQA-2(投影/存储阶段):为了省内存,K/V 只算 4 份、只存 4 份。
图 GQA-3(推理/计算阶段):注意力公式 scores = Q·K^T 要求 Q 的头数 = K 的头数才能逐头点积——Q 有 32 个头,所以需要 32 份 K。于是把 4 份 K/V 各复制 H/G = 32/4 = 8 份,凑成 32 份,编号恰好和 32 个 Q 头一一对应(头 0-7 ↔ 组0,头 8-15 ↔ 组1,…)。
repeat_interleave 就是“沿头维复制”这个操作;它是张量视图/广播不会真的把缓存复制一份占额外显存——缓存里始终只有 G=4 组。
图 GQA-3 推理展开 G 组 K/V → repeat_interleave 复制成 H 份 → 与 Q 对齐计算
flowchart TD Cache["KV 缓存
只存 G=4 组
2KB/token"] --> RI["repeat_interleave
沿头维复制
每组复制 H/G = 8 份"] RI --> F0["组0 复制 8 份 → 头0-7"] RI --> F1["组1 复制 8 份 → 头8-15"] RI --> F2["组2 复制 8 份 → 头16-23"] RI --> F3["组3 复制 8 份 → 头24-31"] F0 --> S["32 个 Q 头 ↔ 32 份 K/V
逐头点积 → softmax → ×V"] F1 --> S F2 --> S F3 --> S S --> O["输出"] Note["为什么必须复制:
点积要求 Q 与 K 头数一致(32 vs 32)
复制是视图操作,缓存仍是 G 组"] -.-> RI
图 GQA-4 数字 省在哪、不省在哪,一目了然
每 token KV 存储
2 KB
2·G·d = 2×4×128 = 1024 元素
L=4096 总缓存
8 MB (省 87.5%)
1024 × 4096 × 2B,比 MHA 省 56MB
K/V 投影算力/步
4.2M (省 8×)
2 × 4096 × 512 = 2×4096×(G·d)
注意力点积算力
≈ 33.6M O(L) 不变
2·H·L·d —— Q 仍是 32 头,没省
诚实的结论:GQA 的注意力 FLOPs 和 MHA 一样(Q 还是 32 头扫全 L)。它真正省的是三样:
缓存内存 ÷8(8MB vs 64MB)——缓存更容易整个装进 GPU 高速缓存,实测推理速度大幅提升;
K/V 投影算力 ÷8(4.2M vs 33.6M);
缓存读写带宽 ÷8(每步只需搬 4 份 K/V 而不是 32 份)。
class GQA(nn.Module):   # 对应 图GQA-1:K/V 改为 G 个头
    def __init__(self, d_model=4096, n_heads=32, n_groups=4):
        self.n_heads, self.n_groups = n_heads, n_groups
        self.head_dim = d_model // n_heads               # 128
        self.W_q = nn.Linear(d_model, d_model)           # 图GQA-2:Q 仍出 32 个头
        self.W_k = nn.Linear(d_model, n_groups * self.head_dim)  # ⚡ 图GQA-2:K 只出 G=4 个头
        self.W_v = nn.Linear(d_model, n_groups * self.head_dim)  # ⚡ 图GQA-2:V 只出 G=4 个头
        self.W_o = nn.Linear(d_model, d_model)           # O 投影

    def forward(self, x, past_kv=None):
        B, T, _ = x.shape
        Q = self.W_q(x).view(B,T,self.n_heads,self.head_dim).transpose(1,2)    # (B,32,T,128)
        K = self.W_k(x).view(B,T,self.n_groups,self.head_dim).transpose(1,2)  # (B,4,T,128)
        V = self.W_v(x).view(B,T,self.n_groups,self.head_dim).transpose(1,2)  # (B,4,T,128)
        # 图GQA-1 缓存:只追加 G=4 组,永远比 MHA 小 8 倍
        if past_kv:
            K = torch.cat([past_kv[0], K], dim=2)        # (B,4,L,128)
            V = torch.cat([past_kv[1], V], dim=2)
        # 图GQA-3 repeat_interleave:沿头维把 4 组复制成 32 组,与 Q 头对齐
        K = K.repeat_interleave(self.n_heads // self.n_groups, dim=1)  # (B,32,L,128)
        V = V.repeat_interleave(self.n_heads // self.n_groups, dim=1)  # 视图操作,缓存仍是 4 组
        # 之后的注意力与 MHA 完全一致
        scores = Q @ K.transpose(-2,-1) / math.sqrt(self.head_dim)
        attn = F.softmax(scores, dim=-1)
        out = attn @ V
        # 回传缓存时只保留 G 组(复制前的原始 4 组)
        return self.W_o(out.transpose(1,2).reshape(B,T,-1)), (K[:, :self.n_groups], V[:, :self.n_groups])
⭐ GQA 总结2026 年最主流(Qwen3.8 / GLM-5.2 / MiniMax-M3 均采用)。缓存 ÷G、投影 ÷G,注意力 FLOPs 不变但带宽与缓存命中大幅受益;组数 G 是核心调档旋钮
🧩 MLA · 多头潜在注意力 Multi-head Latent Attention —— 不减个数,压尺寸
🔁 从 GQA 到这里,做了什么变形?(衔接点)
GQA 优化的维度是“K/V 的个数”(32 份 → 4 份)。MLA 换了一个完全正交的维度:“K/V 的尺寸”

GQA 里每一份 K/V 都是完整的 128 维;MLA 认为这些 128 维向量冗余太多,于是先用一个压缩矩阵 W_kv 把它们压成低维 latent(潜在向量),缓存只存 latent,要用时再解压回来。因为 d_latent=256 << H·d=4096,缓存进一步缩小。

关键区别:MLA 不减少头数(每头仍有自己的表达),而是让所有头的 K/V 共享同一个低秩 latent 表示——既能像 GQA 一样省缓存,又能保住接近 MHA 的质量。
图 MLA-1 宏观 整体流程:压缩 → 缓存 latent → 注意力(解压被吸收)
flowchart TD X["输入 x (4096)"] --> C["压缩 W_kv
4096 → 512
(K、V 各 256 维 latent)"] C --> Cache["缓存 latent
每 token 存 2×256 = 512 元素 = 1KB
是 MHA 的 1/16"] Cache --> A["注意力在 latent 空间完成
解压矩阵被吸收进 Q/O
→ 见 图MLA-3"] A --> Out["输出"] Note["MLA 不减 K/V 的'个数'
而是减小每个 K/V 的'尺寸'"] -.-> Cache
图 MLA-2 压缩与缓存 W_kv 压缩 → 存 latent → 解压成 K/V
flowchart TD X["输入 x"] --> Wkv["W_kv 压缩层
4096 → 512
一次投影同时压 K、V"] Wkv --> CK["c_K:256 维
(K 的 latent)"] Wkv --> CV["c_V:256 维
(V 的 latent)"] CK --> Ca["缓存 c_K
L × 256"] CV --> Cb["缓存 c_V
L × 256"] Ca --> UpK["解压 W_k_up
256 → 32×128 展开成 K"] Cb --> UpV["解压 W_v_up
256 → 32×128 展开成 V"] UpK --> A["与 Q 计算注意力"] UpV --> A Note["对比 MHA:
MHA 缓存存 32×128 完整 K/V
MLA 只存压缩后的 256 维 latent"] -.-> Ca
每 token KV 存储
1 KB
2·d_latent = 2×256 = 512 元素
L=4096 总缓存
4 MB (省 93.75%)
512 × 4096 × 2B,比 MHA 省 60MB
K/V 投影算力/步
2.1M (省 16×)
d_model × 2·d_latent = 4096×512(一次压缩)
质量
≈ MHA (几乎无损)
低秩压缩保留了关键信息
图 MLA-3 关键变形 解压矩阵被吸收进 Q/O —— 这才是 MLA 省算力的精髓
flowchart TD subgraph 朴素["朴素理解(训练时):先解压再算"] CK1["缓存 c_K (256)"] --> EX["W_k_up 展开成 K
256 → 4096"] EX --> S1["score = q^T · K
(q 每头 128 维)"] end subgraph 吸收["等价变形(推理时):不展开,直接在 latent 上算"] Q1["q(每头 128 维)"] --> ABS["q' = W_k_up^T · q
128 → 256 维"] CK2["缓存 c_K (256)"] --> S2["score = q'^T · c_K
直接作用在 latent 上"] ABS --> S2 end Note["数学依据(矩阵结合律):
q^T · (W_k_up · c) = (W_k_up^T · q)^T · c
→ 省掉'把 L 个 latent 展开成 K/V'的
矩阵乘法和带宽"] -.-> 吸收
同理,V 的解压被吸收进 O 投影out = W_o · (Σ a_t · W_v_up · c_V_t) = (W_o · W_v_up) · Σ a_t·c_V_t。于是推理时完全不需要展开 K/V,全程只在 256 维 latent 上做注意力——省掉了每步「展开 32×128 的 K/V」的计算与显存搬运。
class MLA(nn.Module):   # 对应 图MLA-1:K/V 改为 latent 压缩
    def __init__(self, d_model=4096, n_heads=32, head_dim=128, d_latent=256):
        self.n_heads, self.head_dim, self.d_latent = n_heads, head_dim, d_latent
        self.W_q   = nn.Linear(d_model, n_heads * head_dim)       # 图MLA:Q 投影(同 MHA)
        self.W_kv  = nn.Linear(d_model, 2 * d_latent)             # ⚡ 图MLA-2 压缩层:x → (c_K, c_V)
        # 解压权重(训练用;推理被吸收进 Q/O,见 forward_fused)
        self.W_k_up = nn.Linear(d_latent, n_heads * head_dim)     # 图MLA-2:c_K → 32×128 的 K
        self.W_v_up = nn.Linear(d_latent, n_heads * head_dim)     # 图MLA-2:c_V → 32×128 的 V
        self.W_o   = nn.Linear(n_heads * head_dim, d_model)       # O 投影

    def forward_naive(self, x, past_latent=None):   # 训练/理解版:解压再算
        B, T, _ = x.shape
        Q = self.W_q(x).view(B,T,self.n_heads,self.head_dim).transpose(1,2)  # (B,32,T,128)
        c = self.W_kv(x)                                               # (B,T,512)
        c_K, c_V = c[..., :self.d_latent], c[..., self.d_latent:]      # 各 (B,T,256)
        if past_latent:                                        # 图MLA-1 缓存:只存 latent
            c_K = torch.cat([past_latent[0], c_K], dim=1)              # (B,L,256)
            c_V = torch.cat([past_latent[1], c_V], dim=1)
        K = self.W_k_up(c_K).view(B,-1,self.n_heads,self.head_dim).transpose(1,2)  # (B,32,L,128)
        V = self.W_v_up(c_V).view(B,-1,self.n_heads,self.head_dim).transpose(1,2)
        scores = Q @ K.transpose(-2,-1) / math.sqrt(self.head_dim)     # 与 MHA 相同
        attn = F.softmax(scores, dim=-1)
        out = (attn @ V).transpose(1,2).reshape(B,T,-1)
        return self.W_o(out), (c_K, c_V)

    def forward_fused(self, x, past_latent=None):    # 推理版:解压被吸收(图MLA-3)
        B, T, _ = x.shape
        Q = self.W_q(x).view(B,T,self.n_heads,self.head_dim)           # (B,T,32,128)
        c = self.W_kv(x)
        c_K, c_V = c[..., :self.d_latent], c[..., self.d_latent:]
        if past_latent:
            c_K = torch.cat([past_latent[0], c_K], dim=1)              # (B,L,256)
            c_V = torch.cat([past_latent[1], c_V], dim=1)
        # 吸收:q' = W_k_up^T · q(按头分块,W_k_up 每头占 (128,256))
        Wk_up_T = self.W_k_up.weight.view(self.n_heads, self.head_dim, self.d_latent)
        q_abs = torch.einsum('bthd,hdL->bthL', Q, Wk_up_T)             # (B,T,32,256)
        scores = torch.einsum('bthL,blL->bthl', q_abs, c_K) / math.sqrt(self.head_dim)
        attn = torch.softmax(scores, dim=-1)
        o_lat = torch.einsum('bthl,blL->bthL', attn, c_V)              # 仍在 latent 空间 (B,T,32,256)
        # V 的解压也吸收进 O:out = W_o · (W_v_up^T · o_lat)
        Wv_up_T = self.W_v_up.weight.view(self.n_heads, self.head_dim, self.d_latent)
        o = torch.einsum('bthL,hdl->bthd', o_lat, Wv_up_T)             # (B,T,32,128)
        return self.W_o(o.reshape(B,T,-1)), (c_K, c_V)             # 缓存仍是 latent
⭐ MLA 总结:DeepSeek 提出,DeepSeek-V4 继承并发扬。缓存 4MB(MHA 的 1/16)、投影 ÷16,且解压被吸收进 Q/O 后推理不再展开 K/V——是 1M+ 长上下文的关键支撑。一句话:GQA 省“个数”,MLA 省“尺寸”,两者可以叠加。
🧩 KDA · Kimi Delta Attention 线性注意力 —— 干脆不存 KV 列表
🔁 从 GQA/MLA 到这里,为什么是“换一条路”?
GQA 和 MLA 再怎么压,本质还是“按 token 存一份 K/V 列表”——列表长度 = L,内存 O(L)。KDA 的思路完全不同:

把 softmax 注意力换成“线性注意力”:用外积 v·k^T 把历史信息累加进一个固定大小的状态矩阵 S,新的历史不断写进去、旧的历史按遗忘门淡出。这样无论 L 多大,内存恒定 O(1),每步算力也恒定 O(1),不再随上下文增长。

这就回答了图 5-1 的最大疑问:QKV 在哪里? K/V 不再单独存成列表——每个新 token 的 k、v 算出来后立即写进状态 S(外积累加),Q 则作为读指针从 S 中取回压缩后的历史。Q、K、V 都在,只是“存储形态”从列表变成了状态机。
图 KDA-1 宏观 循环状态机:读 → 写 → 更新 → 输出
flowchart TD X["输入 x(当前 token)"] --> Q["q = W_q·x
(查询:从状态读取历史)"] X --> KV["k = W_k·x、v = W_v·x
(键/值:写入状态的内容)"] X --> G["δ = σ(W_δ·x)
(遗忘门:旧状态淡出多少)"] KV --> Upd["状态更新
S ← δ·S + v·k^T"] G --> Upd Q --> Read["读取
out = W_read(q · S)"] Upd --> State["状态 S(固定大小)
← 上一时刻的 S"] State --> Read Read --> Out["输出"] Note["对比 MHA/GQA/MLA:
它们存的是 KV 列表,长度=L,内存 O(L)
KDA 存的是固定大小状态 S,永远不随 L 变"] -.-> State
图 KDA-2 QKV 展开 Q=读指针、K=写地址、V=写内容、δ=遗忘门
flowchart TD X["输入 x"] --> Wq["W_q:4096 → 4096"] Wq --> Q["q:查询(读指针)
不写缓存,只读状态"] X --> Wk["W_k:4096 → 4096"] Wk --> K["k:键(写地址)
决定写进状态的哪个位置"] X --> Wv["W_v:4096 → 4096"] Wv --> V["v:值(写内容)
要写进状态的内容"] X --> Wd["W_δ:4096 → 4096"] Wd --> D["δ:遗忘门(0~1)
旧状态保留多少"] K --> Upd["S ← δ·S + v·k^T
(外积累加 = softmax 的线性近似)"] V --> Upd D --> Upd Q --> Read["out = W_read(q · S)
从状态读回压缩历史"] Upd --> State["状态 S(恒定大小)"] State --> Read Read --> Out["输出"] Note["Q/K/V 都在,只是形态变了:
不再按 token 存成列表,而是累加进 S"] -.-> Upd
图 KDA-3 数字 内存恒定 O(1)、算力恒定 O(1)
flowchart TD S0["状态 S 固定大小
2048×2048 ≈ 8MB"] --> S1["第 1 个 token:8MB"] S1 --> S2["第 4096 个 token:还是 8MB"] S2 --> S3["第 100 万个 token:还是 8MB"] S3 --> OK["无论 L 多大都不变 ✔"] C0["每步算力固定 O(1)
一次状态更新 + 一次读取"] --> C1["不随 L 增长 ✔"] style OK fill:#d1fae5,stroke:#10b981
显存占用
~8 MB 恒定
固定状态,与 L 完全无关
L=4096 对比
省 ~87.5%
MHA 64MB vs KDA 8MB
L=100k 对比
省 ~99.5%
MHA 1.6GB/层 vs KDA 仍是 8MB
每步解码算力
O(1)
MHA/GQA/MLA 是 O(L),长程扫描
代价:线性注意力是对 softmax 的近似(外积无法精确表达“指数归一化”),且有遗忘门,超长程信息可能被冲淡;目前质量一般不如 GQA/MLA。KDA 的价值在于把“存不下、算不完”这两个 O(L) 全部变成 O(1),理论可处理无限长文本。
class KDA(nn.Module):   # 对应 图KDA-1:完全替换图B蓝色框(线性注意力)
    def __init__(self, d_model=4096, s_dim=2048):
        self.s_dim = s_dim
        self.W_q   = nn.Linear(d_model, s_dim)     # 图KDA-2:q —— 读指针
        self.W_k   = nn.Linear(d_model, s_dim)     # 图KDA-2:k —— 写地址
        self.W_v   = nn.Linear(d_model, s_dim)     # 图KDA-2:v —— 写内容
        self.W_beta = nn.Linear(d_model, s_dim)    # 图KDA-2:δ —— 遗忘门(0~1)
        self.W_read = nn.Linear(s_dim, d_model)    # 图KDA-2:读状态 → 输出

    def forward(self, x, state=None):
        B = x.shape[0]
        # —— QKV 在哪里:Q=读、K=写地址、V=写内容,都在状态机内部 ——
        q = self.W_q(x).squeeze(1)                         # (B, s) 读取用
        k = self.W_k(x).squeeze(1)                         # (B, s) 写地址
        v = self.W_v(x).squeeze(1)                         # (B, s) 写内容
        decay = torch.sigmoid(self.W_beta(x)).squeeze(1)   # (B, s) 遗忘率 0~1
        if state is None:
            state = torch.zeros(B, self.s_dim, self.s_dim, device=x.device)
        # 图KDA-1 状态更新:S ← δ·S + v·k^T(外积累加,历史信息写进固定状态)
        state = decay.unsqueeze(1) * state + torch.einsum('bs,bt->bst', v, k)  # (B,s,s)
        # 图KDA-1 读取:out = W_read(q·S),从状态读回压缩后的全部历史
        out = self.W_read(torch.einsum('bs,bst->bt', q, state)).unsqueeze(1)   # (B,1,d_model)
        return out, state   # 状态大小恒定,与 L 无关 → O(1) 显存、O(1) 算力
⭐ KDA 总结彻底抛弃 KV 列表,改用固定大小状态机。显存与每步算力都 O(1),与上下文长度无关;代价是线性近似带来的质量折损。解决“长程算不完”,Kimi K3 的代表作。
🏁 五机制终极对比 统一基线:L=4096 · H=32 · d=128 · G=4 · d_latent=256
机制 每 token KV @4096 总缓存 相对 MHA 每步注意力 省内存靠 省算力靠 质量
MHA16KB64MB100%(基线) O(L) ≈33.6M⭐⭐⭐⭐⭐
MQA0.5KB2MB-97% O(L) ≈33.6M个数→1K/V投影 ÷32⭐⭐⭐
GQA2KB8MB-87.5% O(L) ≈33.6M个数→G=4K/V投影 ÷8 + 带宽 ÷8⭐⭐⭐⭐
MLA1KB4MB-93.75% O(L) 但省展开尺寸→d_latentK/V投影 ÷16 + 解压吸收⭐⭐⭐⭐⭐
KDA恒定 8MB8MB(恒定)与 L 无关 O(1)不存 KV 列表线性注意力 ÷L⭐⭐⭐⭐
🔍 五句话收尾
MHA:基线,缓存与算力都 O(L),两头爆炸。
MQA:所有头共享 1 份 K/V,省过头,质量掉。
GQA:分组共享(G 组),2026 年主流,缓存 ÷G、投影 ÷G。
MLA:不减个数、压尺寸,缓存 ÷16 且解压被吸收,质量几乎无损。
KDA:不存列表、改状态机,内存与算力双双 O(1),适合超长文本。

一个记忆框架:前四个都在回答“KV 缓存怎么存更省”(个数/尺寸),KDA 在回答“如果不存 KV 会怎样”(换算法)。
📊 本精读版 = 架构三层拆解(图 A/B/B→C/C)→ 图 B 蓝框桥接 → 四大旗舰速览 → 总览对比+演进路线 → 每个机制 3 张图(宏观/结构/推理数字)+ 源码逐行对应图部件 · 数字口径全程一致(16KB→0.5KB→2KB→1KB;64MB→2MB→8MB→4MB;KDA 恒定 8MB)