Attention Architecture Evolution
导言
Attention 的发展并不是从 Full Attention 排成一条单向淘汰链。更准确的结构是三条并行路线:共享或压缩 KV cache、把历史压入固定状态、对历史 token 做稀疏选择。MQA、GQA 与 MLA 仍然读取全部历史;GDN 与 KDA 改写了记忆算法;DSA 则在 MLA 前增加轻量索引器,只让主注意力读取 top-k。
本文以首个公开论文或官方发布日为时间点,并把每种结构落到版本固定的开源代码:Q/K/V 从哪里产生、什么对象进入 cache、score 如何形成、复杂度到底被搬到了哪里。
结论¶
先给出最重要的判断:
- Full/MHA、MQA、GQA、MLA 是同一 dense softmax 家族中的不同 KV 组织方式。后三者主要减少 KV cache 或带宽,并不自动把全部 token 两两交互变成线性复杂度。
- GDN 与 KDA 属于递归状态路线。历史不再以逐 token K/V 保存,而是写入固定大小矩阵状态;KDA 把 GDN 的 head-wise 标量遗忘细化为 key-channel-wise 对角遗忘。
- DSA 属于 token 稀疏路线。Lightning Indexer 仍扫描历史并产生 top-k,主 MLA 才只读取选中位置,所以“主注意力稀疏”不等于整个模块没有历史扫描成本。
- 公开时间并不等于概念顺序。DSA 于 2025 年 9 月公开,KDA 于 2025 年 10 月公开;本文按真实时间线排列,而不是按缩写的相似度排列。
时间线¶
| 首次公开 | 结构 | 首个一手来源 | 主要变化 | 历史表示 |
|---|---|---|---|---|
| 2017-06-12 | Full Attention / MHA | Attention Is All You Need1 | 每个 Query head 拥有独立 K/V,读取全部历史 | 完整 K/V cache |
| 2019-11-06 | MQA | Fast Transformer Decoding: One Write-Head is All You Need2 | 多个 Q head 共享一组 K/V | 单组 K/V cache |
| 2023-05-22 | GQA | GQA: Training Generalized Multi-Query Transformer Models3 | Q head 分组共享 K/V | 多组 K/V cache |
| 2024-05-07 | MLA | DeepSeek-V24 | K/V 联合低秩压缩,解耦 RoPE | cKV + kR |
| 2024-12-09 | GDN | Gated Delta Networks5 | head-wise 衰减的 delta-rule 固定状态 | S ∈ R^(K×V) |
| 2025-09-29 | DSA | DeepSeek-V3.2-Exp6 | Indexer 选 top-k,主 MLA 稀疏读取 | MLA cache + Indexer K |
| 2025-10-30 | KDA | Kimi Linear7 | key-channel-wise 对角衰减的 delta-rule | S ∈ R^(K×V) |
Full Attention 与 MHA
Full Attention 描述 token 连通性:每个位置可读取全部允许的历史位置;MHA 描述 head 组织:每个 Query head 拥有独立 K/V head。自回归解码器通常同时使用 causal Full Attention 和 MHA,因此工程讨论里经常连写,但两者不是完全同义词。
三条演进路线¶
定义:序列长度为 T,Query head 数为 Hq,KV head 数为 Hkv,head 维度为 d,MLA 的 KV latent 维度为 r,DSA 选出的历史位置数为 k。
| 结构 | Token 连通性 | 随 T 增长的主要缓存 |
单步 decode 主计算 | 优化焦点 |
|---|---|---|---|---|
| Full/MHA | Dense | 2 × T × Hq × d |
O(T × Hq × d) |
基线 |
| MQA | Dense | 2 × T × d |
O(T × Hq × d) |
KV cache 与带宽 |
| GQA | Dense | 2 × T × Hkv × d |
O(T × Hq × d) |
质量与 KV 成本折中 |
| MLA | Dense | T × (r + d_rope) |
仍扫描全部历史 latent | 低秩 KV cache |
| GDN | Recurrent | 与 T 无关的 H × K × V 状态 |
O(H × K × V) |
固定状态 |
| KDA | Recurrent | 与 T 无关的 Hv × K × V 状态 |
O(Hv × K × V) |
更细粒度遗忘 |
| DSA | 主注意力 top-k | MLA cache + Indexer K | Indexer 扫描 T,主 MLA 读取 k |
长上下文主计算稀疏化 |
这张表解释了一个常见误区:KV cache 更小、token 连通性更稀疏、算法复杂度更低是三个不同命题。MQA/GQA 改第一个,DSA 改第二个,GDN/KDA 改第三个所依赖的历史表示。
代码阅读约定
下文代码块从固定 revision 的开源实现中摘取决定语义的关键行;为了聚焦 Q/K/V、cache 和状态更新,省略了异常处理、并行通信、shape 检查和部分无关参数。变量名与核心运算保持源码一致,脚注链接到完整文件和行号。
Dense Softmax 家族¶
Full Attention¶
对输入 X,每个 head 独立计算:
Qh = X WQh, Kh = X WKh, Vh = X WVh
Ah = softmax(Qh Kh^T / sqrt(d) + causal_mask)
Oh = Ah Vh
O = concat(O1, ..., OH) WO
Hugging Face 的 Llama eager 实现把投影、score、softmax 和 value 聚合完整暴露出来:8
query_states = self.q_proj(hidden_states).view(hidden_shape).transpose(1, 2)
key_states = self.k_proj(hidden_states).view(hidden_shape).transpose(1, 2)
value_states = self.v_proj(hidden_states).view(hidden_shape).transpose(1, 2)
attn_weights = torch.matmul(query, key_states.transpose(2, 3)) * scaling
attn_weights = nn.functional.softmax(attn_weights, dim=-1, dtype=torch.float32)
attn_output = torch.matmul(attn_weights, value_states)
瓶颈有两层:prefill 需要形成 T × T score;decode 虽然每步只有一个新 Query,却要读取越来越长的 K/V cache,常被显存带宽限制。
MQA¶
MQA 保留 Hq 个 Query head,但只生成一组 K/V:
Llama 的同一个实现通过 num_key_value_heads 表达 MHA、GQA 与 MQA。MQA 的配置边界是 num_key_value_heads = 1;repeat_kv 只创建与 Query head 对齐的计算视图,不代表 cache 中保存了 Hq 份 K/V:8
self.num_key_value_groups = (
config.num_attention_heads // config.num_key_value_heads
)
key_states = repeat_kv(key, module.num_key_value_groups)
value_states = repeat_kv(value, module.num_key_value_groups)
MQA 的优势集中在 decode cache 和内存读取。代价是所有 Query head 只能从同一份 K/V 表示读取信息,可能损失 head 多样性。
GQA¶
GQA 在 MHA 与 MQA 之间引入 Hkv 组 K/V,每组服务 Hq / Hkv 个 Query head:
对应的开源边界仍是 Llama 的两行投影和 repeat_kv:8
self.k_proj = nn.Linear(hidden_size, num_key_value_heads * head_dim)
self.v_proj = nn.Linear(hidden_size, num_key_value_heads * head_dim)
# Hkv = Hq: MHA; 1 < Hkv < Hq: GQA; Hkv = 1: MQA
key_states = repeat_kv(key, num_attention_heads // num_key_value_heads)
因此 GQA 的准确理解是:用一部分 head 多样性换取一部分 KV cache 和带宽收益。它仍然是对全部历史 token 的 dense attention。
MLA¶
MLA 不再以“减少 KV head 数”压缩 cache,而是把所有 head 的 K/V 联合投影到低维 latent:
DeepSeek-V2 的兼容实现直接展示了联合降投影和位置分量拆分:9
compressed_kv = self.kv_a_proj_with_mqa(hidden_states)
compressed_kv, k_pe = torch.split(
compressed_kv, [self.kv_lora_rank, self.qk_rope_head_dim], dim=-1
)
kv = self.kv_b_proj(self.kv_a_layernorm(compressed_kv))
k_nope, value_states = torch.split(
kv, [self.qk_nope_head_dim, self.v_head_dim], dim=-1
)
真正体现 memory-optimal decode 的是 DeepSeek 原生推理代码:cache 只分配 kv_lora_rank 和 qk_rope_head_dim;decode 时把 WUKV 的 K 部分吸收到 Query,把 V 部分吸收到输出路径:10
self.kv_cache = torch.zeros(max_batch, max_seq, self.kv_lora_rank)
self.pe_cache = torch.zeros(max_batch, max_seq, self.qk_rope_head_dim)
kv, k_pe = torch.split(self.wkv_a(x), [self.kv_lora_rank, self.qk_rope_head_dim], -1)
self.kv_cache[:bsz, start_pos:end_pos] = kv
self.pe_cache[:bsz, start_pos:end_pos] = k_pe.squeeze(2)
q_nope = torch.einsum("bshd,hdc->bshc", q_nope, wkv_b[:, :self.qk_nope_head_dim])
scores = torch.einsum("bshc,btc->bsht", q_nope, self.kv_cache[:bsz, :end_pos])
不要从兼容实现误判 cache
某些通用 Transformers 代码会先用 WUKV 展开完整 K/V,再交给标准 attention/cache 接口。这便于兼容,但会掩盖 MLA 的核心收益。判断实际 cache 必须看部署路径中保存的张量,而不只看数学等价的 forward。
MLA 压缩的是 每个 token 的历史表示,并没有选择更少的历史 token。没有 DSA 时,score 仍覆盖全部可见位置。
递归状态家族¶
GDN¶
GDN 仍产生 Q/K/V,但 K/V 不进入按 token 增长的 cache。它先经过短卷积与归一化,再把当前 token 写入固定状态 S:
其中 g_t 在 GDN 参考实现中是 [B,T,H],即每个 head 一个标量衰减;beta_t 控制 delta 写入强度。FLA 层代码展示 Q/K/V 的短卷积和 gate 输入:12
q = self.q_conv1d(x=self.q_proj(hidden_states), ...)[0]
k = self.k_conv1d(x=self.k_proj(hidden_states), ...)[0]
v = self.v_conv1d(x=self.v_proj(hidden_states), ...)[0]
beta = self.b_proj(hidden_states)
o, recurrent_state = chunk_gated_delta_rule(
q=q, k=k, v=v, g=self.a_proj(hidden_states), beta=beta,
initial_state=recurrent_state, use_qk_l2norm_in_kernel=True,
)
Naive recurrence 则把状态更新写得最清楚:13
b_q = q[:, :, i]
b_k = k[:, :, i]
b_v = v[:, :, i].clone()
h = h.clone() * g[:, :, i].exp()[..., None, None]
b_beta = beta[:, :, i]
b_v = b_v - (h.clone() * b_k[..., None]).sum(-2)
b_v = b_v * b_beta[..., None]
h = h.clone() + b_k.unsqueeze(-1) * b_v.unsqueeze(-2)
o[:, :, i] = torch.einsum("bhd,bhdm->bhm", b_q, h)
固定状态不是无损 KV 压缩。多个历史 token 会竞争同一矩阵容量,模型需要学习何时遗忘、何时覆盖。训练时可用 chunk/parallel 算法加速,但语义上仍是按时间递推。
KDA¶
KDA 延续 delta-rule,却把 g_t 从 [H] 扩展为 [Hv,K]。于是每个 value head 的每个 key channel 都有独立衰减:
Kimi Linear 模型实现先投影并短卷积 Q/K/V,再从两层低秩投影产生 per-dimension gate:14
q = self.q_conv1d(x=self.q_proj(hidden_states), ...)[0]
k = self.k_conv1d(x=self.k_proj(hidden_states), ...)[0]
v = self.v_conv1d(x=self.v_proj(hidden_states), ...)[0]
g = self.f_b_proj(self.f_a_proj(hidden_states))
g = fused_kda_gate(g, self.A_log, self.head_dim, g_bias=self.dt_bias)
beta = self.b_proj(hidden_states).float().sigmoid()
FLA naive KDA 明确记录 g: [B,T,HV,K],并在 key channel 维衰减状态:15
# g: [B, T, HV, K], S: [B, HV, K, V]
for i in range(0, T):
q_i, k_i, v_i = q[:, i], k[:, i], v[:, i]
g_i, b_i = g[:, i], beta[:, i]
S = S * g_i[..., None].exp()
S = S + torch.einsum(
"bhk,bhv->bhkv",
b_i[..., None] * k_i,
v_i - (k_i[..., None] * S).sum(-2),
)
o[:, i] = torch.einsum("bhk,bhkv->bhv", q_i, S)
更细粒度 gate 提高了状态表达能力,也增加 gate 生成和状态更新的复杂度。Kimi Linear 因此采用 KDA 与 MLA 混合层,而不是宣称固定状态可以完全替代 softmax attention。
Token 稀疏家族¶
DSA¶
DSA 建立在 MLA 上。Lightning Indexer 为 Query 生成多 head 索引向量,为每个历史 token 生成共享 Indexer K 和按 head 权重,再选择 top-k:
DeepSeek-V3.2-Exp 的开源 Indexer 使用 FP8 Q/K、独立权重和 topk:11
q = self.wq_b(qr).view(bsz, seqlen, self.n_heads, self.head_dim)
k = self.k_norm(self.wk(x))
q_fp8, q_scale = act_quant(q, block_size, self.scale_fmt)
k_fp8, k_scale = act_quant(k, block_size, self.scale_fmt)
weights = self.weights_proj(x.float()) * self.n_heads ** -0.5
index_score = fp8_index(q_fp8, weights, self.k_cache[:, :end_pos], self.k_scale_cache[:, :end_pos])
topk_indices = index_score.topk(min(self.index_topk, end_pos), dim=-1)[1]
主 MLA 用这些 indices 构造稀疏 mask;decode 仍从压缩 cKV cache gather,而不是回到完整 K/V:11
topk_indices = self.indexer(x, qr, start_pos, freqs_cis, mask)
index_mask = torch.full((bsz, 1, end_pos), float("-inf"), device=x.device)
index_mask = index_mask.scatter_(-1, topk_indices, 0)
scores += index_mask.unsqueeze(2)
scores = scores.softmax(dim=-1)
x = torch.einsum("bsht,btc->bshc", scores, self.kv_cache[:, :end_pos])
稀疏的边界
DSA 降低的是 主 MLA 的高维 score/value 计算。Indexer 本身仍需对历史位置打分,只是 head 维和精度更小。index_topk=2048 是 DeepSeek-V3.2-Exp 的具体配置,不是 DSA 定义中的固定常数。
混合架构¶
长上下文模型越来越少押注单一 Attention。线性或递归状态层负责大部分低成本历史传播,周期性 Full/MLA 层补充精确 token 检索,是更现实的质量—效率折中。
这种混合思想也适用于其他组合:GQA 降低 cache,DSA 降低主 attention 的 token 数,二者解决不同维度;MLA 与 DSA 更是明确的基座和索引关系。
选择框架¶
| 主要瓶颈 | 优先考察 | 原因 | 必验风险 |
|---|---|---|---|
| Decode KV cache / 带宽 | GQA、MQA、MLA | 直接减少每 token 历史表示 | 质量回退、kernel 与 cache layout |
| 极长上下文的固定内存 | GDN、KDA | recurrent state 不随 T 增长 |
状态容量、训练 kernel、跨序列边界 |
| 极长上下文主 attention FLOPs | DSA | 主 MLA 只读取 top-k | Indexer recall、top-k 通信、稀疏 kernel |
| 质量优先、上下文适中 | Full/MHA、GQA | 精确 dense retrieval,生态成熟 | cache 和带宽上限 |
| 质量与极长上下文同时要求 | Hybrid | 递归层传播、周期性 softmax 检索 | 层型比例、状态/cache 双系统 |
工程选型时至少测四件事:
- Prefill 与 decode 分开:二者的算力、带宽和并行特性完全不同。
- 峰值 cache 与实际吞吐同时测:更小的理论 cache 可能被不合适的 layout、dequant 或 gather 抵消。
- 固定准确率条件下比较:MQA/GQA group 数、MLA rank、DSA top-k、GDN/KDA state 维度都属于质量—成本旋钮。
- 按实现边界比较:FlashAttention、FlashMLA、chunk recurrence 与稀疏 gather 的 kernel 成熟度往往比大 O 公式更决定真实速度。
常见误区¶
- “MQA/GQA 是稀疏 attention”:错误。它们共享 K/V head,但仍读取所有历史 token。
- “MLA 把 attention 变成线性复杂度”:错误。MLA 压缩每个 token 的 K/V 表示,没有压缩 token 数。
- “GDN/KDA 只是更激进的 KV cache 压缩”:不准确。它们改变了历史记忆算法,用固定状态递推替代逐 token softmax 检索。
- “DSA 只算 top-k,所以模块没有全历史成本”:不完整。主 MLA 是 top-k,Indexer 仍扫描历史。
- “后出现的结构必然取代前者”:错误。真实模型广泛使用混合层,不同方法解决的是 cache、状态容量、token 连通性或 kernel 效率中的不同问题。
总结¶
Attention 的发展路径可以压缩成一句话:先共享 K/V,再联合压缩 K/V;另一条路线把历史写入固定状态;最新的稀疏路线先检索,再让昂贵的主注意力读取少数 token。
理解这些结构时,不要只背缩写。顺着代码追四个对象就足够:
Q/K/V如何投影、按多少 head 组织;- cache 保存完整 K/V、latent、Indexer K,还是固定状态;
- score 是对全部历史、top-k,还是根本不存在显式
T×T矩阵; - 被省掉的成本是否转移到了低秩重建、状态更新、索引器或稀疏 gather。
本文七张计算图均依据版本固定源码自绘,SVG 源文件与 1920×1080 PNG 同时保留,并通过结构、箭头、碰撞、语义几何与版式校验。
参考文献¶
-
Vaswani et al., Attention Is All You Need, first submitted 2017-06-12. ↩
-
Shazeer, Fast Transformer Decoding: One Write-Head is All You Need, first submitted 2019-11-06. ↩
-
Ainslie et al., GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints, first submitted 2023-05-22. ↩
-
DeepSeek-AI, DeepSeek-V2: A Strong, Economical, and Efficient Mixture-of-Experts Language Model, first submitted 2024-05-07. ↩
-
Yang et al., Gated Delta Networks: Improving Mamba2 with Delta Rule, first submitted 2024-12-09. ↩
-
DeepSeek-AI, DeepSeek-V3.2-Exp release, 2025-09-29; model and paper repository. ↩
-
Zhang et al., Kimi Linear: An Expressive, Efficient Attention Architecture, first submitted 2025-10-30. ↩
-
Hugging Face Transformers,
modeling_llama.py, commit29985e67. ↩↩↩ -
DeepSeek-AI,
modeling_deepseek.py, revision604d5664. ↩ -
DeepSeek-AI,
inference/model.pyMLA, commit87e509a2. ↩ -
DeepSeek-AI,
inference/model.py, commit87e509a2. ↩↩ -
FLA,
fla/layers/gated_deltanet.py, commitc70f11c5. ↩ -
FLA,
fla/ops/gated_delta_rule/naive.py, commitc70f11c5. ↩ -
Moonshot AI,
modeling_kimi.py, revisione1df551a. ↩ -
FLA,
fla/ops/kda/naive.py, commitc70f11c5. ↩









