DEEP DIVE · INFERENCE SYSTEMS参考综述 ↗
2026 / LONG-FORM TECHNICAL GUIDE

KV Cache 的前世今生

从 Attention 的历史记忆,到长上下文推理系统的架构选择。

6 大部分24 个技术章节交互图示与估算器

模型权重已经放进 GPU,为什么长上下文推理仍然会 OOM?

假设一个标准 MHA 模型拥有 32 层、32 个 KV 头、128 维头维度,并用 BF16 保存历史 K/V。每处理一个 Token,所有层合计需要存储 512 KiB;当序列增长到 128 Ki Token,单条请求的理论 KV Cache 就达到 64 GiB,尚未计入模型权重、运行时激活和分配开销。

如果将 KV 头数从 32 降至 8,理论缓存可降至 16 GiB;如果再改变缓存表示、量化精度、可见历史范围,数字还能变化。但缓存变小不一定意味着请求更快:历史 KV 的读取带宽、并发调度、复用与传输,仍可能决定系统性能。

MHA · 32 KV heads64 GiB
GQA · 8 KV heads16 GiB
MQA · 1 KV head2 GiB
统一假设:32 层、每头 128 维、BF16、单序列 128 Ki Token。纯理论对照,不代表某个模型支持该长度,也不代表完整推理显存。
优化维度

不同方法改变的不是同一个变量

优化路径改变的对象代表方案适用边界
结构与表示每 Token 的历史状态GQA、MLA、CLA减少存储量,不等于相同比例加速
数值缓存元素的位宽KV Quantization需要计入 scale、反量化及精度损失
保留与访问哪些历史位置可见SWA、淘汰、稀疏注意力可能改变可用历史信息和模型质量
计算方式如何计算 Attention / 状态FlashAttention、线性注意力前者优化 IO;后者改变序列状态机制
内存系统缓存如何分配、复用、存放PagedAttention、Prefix Cache、Offloading逻辑 KV 大小与物理占用不同
服务调度何时计算与搬运 KVChunked Prefill、P/D 分离关注尾延迟、吞吐、网络与资源利用率
PART 01 / 06

为什么 KV Cache 会成为瓶颈?

从第一个 Token 到 128K 上下文:看懂计算复用、显存容量与读取带宽。

1

从 Transformer 说起

先回答:历史为什么要复用,以及缓存如何从优化变成瓶颈。

2017 年 6 月,Google 的研究人员发表了论文 《Attention Is All You Need》,提出了 Transformer。它当时是一种新的序列建模架构;从今天回头看,它也是理解 KV Cache 及其内存问题的起点。

在 Transformer 出现之前,RNN 通过逐步传递隐藏状态处理序列,长距离信息保留和并行计算都面临挑战。Transformer 则通过自注意力直接建模位置之间的关系,让训练时的序列计算更易并行,也更适合 GPU。

Transformer 此后成为 BERT、GPT、Llama、Qwen、DeepSeek 等模型的基础。随着应用扩展到长文档、代码库和多轮对话,模型在生成新内容时需要反复利用已有上下文。为了避免重复计算历史状态,推理系统引入了 KV Cache;但随着上下文变长、并发请求增多,缓存的显存占用和读取成本也随之增长。

时期变化与 KV 的关系
2017Transformer 提出自注意力。每层产生 Q、K、V;生成阶段出现重复历史计算。
2018–2022预训练与模型规模快速增长。参数、批量和序列长度共同推高推理成本。
2023–2024GQA、PagedAttention、MLA 和 KV 量化等路线发展。结构、分配、表示精度开始分别优化。
2025–2026更长上下文、混合架构和 Agent 服务。缓存的共享、生命周期、传输与调度更加重要。
2

理解 Query、Key 和 Value

2.1 从一个招聘场景说起

假设你是一名技术团队负责人,现在需要招聘一位擅长大模型分布式训练的工程师。你的招聘需求是:

熟悉 Megatron-LM,理解 TP、PP、EP 等分布式并行策略,能够解决大模型训练中的性能问题。

此时,有三位候选人投递简历:

  • 候选人 A:熟悉 Megatron-LM、TP、PP,擅长训练框架开发和显存优化。
  • 候选人 B:熟悉 EP、MoE、NCCL,擅长通信优化与大规模集群性能调优。
  • 候选人 C:熟悉 PyTorch、Transformers,擅长模型微调和数据处理。

接下来,你会如何评估这些候选人?很自然,你会先判断每个人与岗位的匹配程度,再结合他们的实际能力进行综合评估。这个过程与 Attention 的计算思路非常相似。

2.2 Query:我需要什么?

你需要明确招聘需求:“我想找一位熟悉 Megatron、TP、PP、EP 的工程师。”这就相当于 Attention 中的 Query(查询向量),表示当前需要寻找什么样的信息。在 Transformer 中,每个 Token 都可以生成自己的 Query,用来判断上下文中哪些位置的信息对自己有帮助。

2.3 Key:谁符合我的需求?

有了招聘需求,下一步就是查看候选人的技能标签。例如:

候选人Key:技能标签
AMegatron、TP、PP
BEP、MoE、NCCL
CPyTorch、Transformers

在 Attention 中,这对应 Query 和 Key 的匹配计算。

模型将 Query 与各个 Key 计算点积,得到匹配分数,再通过 Softmax 转换为注意力权重。

假设最终得到:

  • 候选人 A:50%
  • 候选人 B:40%
  • 候选人 C:10%

这些比例表示当前查询对不同候选人的关注程度。

2.4 Value:他们究竟能提供什么?

知道候选人与岗位有多匹配,还不等于知道他们能为团队贡献什么。例如,A 熟悉 Megatron、TP、PP,能够承担训练框架开发和显存优化;B 熟悉 EP、MoE、NCCL,能够优化跨节点通信和 MoE 性能;C 则可以贡献模型微调和数据处理能力。

这里就引出了 Value(价值向量)。可以把 Key 理解为“用于判断是否匹配的技能标签”,而把 Value 理解为“真正能够提供的能力和信息”。Key 决定哪些信息值得关注,Value 决定这些位置能够提供什么信息。

2.5 Attention:将不同来源的信息融合起来

Query我要找什么
对比
Key候选位置是否匹配
得到权重
Value按权重汇总信息

根据岗位需求,你已经判断出三位候选人的匹配程度,接下来可以结合他们各自的能力,形成对人才供给的整体认识。

标准 Softmax Attention 的做法与此类似:先用 Query 和 Key 计算注意力权重,再根据这些权重,对各个可见位置的 Value 加权求和。

假设三位候选人的权重分别为 50%、40% 和 10%,对应的 Value 为 V_A、V_B、V_C,那么:

Attention Output = 0.5 × V_A + 0.4 × V_B + 0.1 × V_C

得到的输出向量融合了不同来源的信息。这个类比并不是说招聘时要把三个人合并成一个人,而是说明:Attention 的输出是多个 Value 向量的加权组合,不只是取出匹配分数最高的 Value。

2.6 回到 Transformer 的数学计算

招聘需求、技能标签和实际能力,在 Transformer 中都对应数值向量。下面的代码只展示单个 Query 的 Attention 数学过程,假设直接从整个可见上下文计算 K、V;它不是启用 KV Cache 后的增量执行过程:

# 当前 Token 的 Query
q = x_current @ W_Q

# 可见上下文的 Key 和 Value(包含历史及当前位置)
k = x_context @ W_K
v = x_context @ W_V

# 第一步:计算匹配分数
scores = q @ k.T / sqrt(d_k)

# 第二步:转换为注意力权重
weights = softmax(scores, dim=-1)

# 第三步:聚合 Value
output = weights @ v

这里的上下文已经限定为当前 Token 可见的位置,因此示意代码没有另写因果 mask。启用 KV Cache 后,Decode 阶段复用每层已保存的历史 K、V,只为当前 Token 计算并追加新的 K、V,再用当前 Query 参与注意力计算:

# 单层、单 Token 的简化增量计算
q = x_current @ W_Q
k_new, v_new = x_current @ W_K, x_current @ W_V
k, v = append(k_cache, k_new), append(v_cache, v_new)
output = softmax(q @ k.T / sqrt(d_k), dim=-1) @ v

其中 k_cache、v_cache 是历史 Token 在当前层已计算并缓存的张量;W_Q、W_K、W_V 是模型权重。实际实现还需处理位置编码、多头、批次和缓存更新。

3

什么是 KV Cache,为什么需要它?

推理通常分为 Prefill 与 Decode。Prefill 处理输入 Prompt,在各注意力层建立已处理位置的 K、V,并能从最后位置的输出分布得到首个生成 Token。随后 Decode 将已生成 Token 作为新输入继续前向,计算该输入位置的 Q/K/V 并将 K/V 追加到缓存,用 Q 访问当前可见的历史。这里要区分“本轮作为输入计算的 Token”和“本轮刚采样出来、将在下一轮处理的 Token”。

输入 prompt→逐层建立 KV→生成新 token→追加 KV 并读取历史↻

没有缓存时,简单实现会在后续生成步骤中反复计算旧 token 的状态;有缓存则用显存换取对历史投影的复用。Q 一般只在当前步骤使用,因此通常不需要像 K、V 一样持久保存。

标准 KV 字节数 = 2 × L × Hkv × D × T × B × S

L:层数;Hkv:KV 头数;D:头维度;T:缓存 token 数;B:等长序列数;S:每个元素字节数。2 对应 K 和 V。

例如 L=32、Hkv=8、D=128、FP16:每 token 需要 128 KiB;128 Ki token 的单条序列约 16 GiB。公式假设每层相同且 K/V 维度相同;MLA、混合注意力、量化元数据和分页开销需要另算。

INTERACTIVE / 估算器

调整参数,观察缓存规模

16.00 GiB

理想数据体积;不含量化 scale、对齐、分页尾部浪费、权重、激活及框架开销。GiB = 2³⁰ 字节。

适用范围:此估算器适用于逐 Token 存储 K、V 的等形状 MHA/MQA/GQA。MLA、混合线性/全注意力、共享 K=V、跨层共享及量化元数据需单独建模。

适用范围:这一公式对应每层 K/V 维度相同的逐 Token MHA/MQA/GQA。MLA、局部/全局混合层及递归状态,需要单独按层计算。

逐步生成时发生了什么?

prefill: [A B C] → 缓存 [Kₐ Kᵦ K꜀] + [Vₐ Vᵦ V꜀]
decode: D → Qᴅ,追加 Kᴅ / Vᴅ → 注意力读取 [A B C D]

示意因果自注意力;具体实现的输出 token 与缓存更新时序可能不同。

一条请求的缓存一般包含 prompt 与已处理的生成 token。输出越长,缓存越长;多个请求各自持有状态,除非满足前缀共享条件。缓存释放时间还受会话复用、调度和框架策略影响。

容量对照:同样 128 Ki token

示例假设每 token单序列
MHA 基线32 层 / 32 KV 头 / 128 维 / FP16512 KiB64 GiB
GQA 示例32 层 / 8 KV 头 / 128 维 / FP16128 KiB16 GiB
MQA 示例32 层 / 1 KV 头 / 128 维 / FP1616 KiB2 GiB

只比较公式中的数据体积;不同模型、有效上下文、实际 kernel 和额外内存并未等同。

交互:缓存如何随 token 增长

示意 32 层、8 KV 头、128 维、FP16 的单请求数据量;1K = 1,024 token。
4

问题:当内存成为瓶颈

KV Cache 用显存换取对历史计算结果的复用。随着上下文变长、并发请求增多,它也会带来三方面的压力:

  • 显存容量:每个请求都需要保存自己的缓存。缓存随序列长度、层数和并发增长,可能挤占权重和其他运行时数据的空间。
  • 读取带宽:在全注意力下,生成新 Token 时需要读取可见的历史 K、V。上下文越长,读取的数据通常越多;实际瓶颈还取决于模型、批量、硬件和实现。
  • 内存分配:不同请求的长度和结束时间不同,预留空间与实际使用量之间的差异会造成浪费,限制可服务的并发数。

缓存避免了历史状态的重复投影,但不会让全注意力的单步 decode 与上下文长度无关:新的 Query 仍需与可见历史进行注意力计算。

内存碎片:预留了空间,却没有用上

例如,为请求预留 4,096 个 Token 的缓存,实际只用 200 个,其余空间就可能被闲置。此外,分散的空闲空间也可能无法满足连续内存分配。PagedAttention 论文对当时所研究的已有服务系统报告,KV Cache 内存中约有 60%–80% 因碎片和预留等原因未被有效利用。

应对 KV Cache 的五类策略

策略核心思路代表方法
减少 KV 表示减少共享之外需要保存的独立 KV 头,或用更紧凑的潜变量表示历史。MQA、GQA、MLA
改进内存管理按需分配和管理缓存块,减少预留浪费与碎片。PagedAttention
降低数值精度用更少的位数存储缓存中的元素。KIVI、KVQuant、TurboQuant
减少缓存 Token在有限预算下选择保留的重要位置,淘汰部分历史。H₂O、SnapKV、StreamingLLM
限制注意力范围让部分或全部注意力层只访问局部窗口,或混合使用局部与全局层。滑动窗口、局部—全局注意力
PART 02 / 06

如何让每个 Token 的缓存更小?

从独立 KV 头到潜变量、跨层共享和数值压缩:改变每个 Token 保存什么。

5

多头注意力 MHA:昂贵的基线

多头注意力(Multi-Head Attention,MHA)把注意力计算分成多个头。每个头使用自己的 Query、Key 和 Value,在不同的表示子空间中计算注意力;各头的结果随后被拼接并投影,形成这一层的输出。

在标准 MHA 中,每个 Query 头都有各自对应的 Key 头和 Value 头。因此,32 个 Query 头对应 32 个 Key 头和 32 个 Value 头,三者是一一对应的关系。

MHA:每个头都有独立的 K 和 V以单层、单个 Token 的 32 头结构为例
多头注意力的 Query、Key 和 Value 对应关系32 个 Query 头分别对应独立的 32 个 Key 头和 32 个 Value 头。每层中历史 Token 的 Key 和 Value 保存到 KV Cache。QUERYKEYVALUEQ₁Q₂Q₃Q₃₂K₁K₂K₃K₃₂V₁V₂V₃V₃₂………虚线框:历史 Token 的 K、V 会保存在 KV Cache 中32 个 K 向量+ 32 个 V 向量每个向量 128 维FP16:每元素 2 字节

为什么 MHA 的缓存开销大?

每来一个 Token,每一层都要为每个头保存一组 K 和 V。假设模型有 32 层、32 个注意力头、每头 128 维,缓存使用 FP16(每个元素 2 字节),那么:

单层、单 Token:2(K 和 V)× 32(头)× 128(维)× 2(字节)= 16 KiB

32 层、单 Token:16 KiB × 32 = 512 KiB

这里的第一个 2 表示 K 和 V 两组向量;最后的 2 字节表示 FP16 的元素大小。

缓存长度单条序列的理论 KV 大小计算
1 Token512 KiB32 层合计
1,024 Token512 MiB512 KiB × 1,024
4,096 Token2 GiB512 KiB × 4,096
131,072 Token64 GiB512 KiB × 131,072

这是 Llama 2 7B 的头数、层数和维度对应的 FP16 数据量示例;不包含权重、激活、分页及其他运行时开销。128 Ki Token 仅用于展示线性增长,不表示该模型原生支持这一上下文长度。

6

多查询注意力 MQA:共享一套记忆

在 MHA 中,每个 Query 头都有独立的 Key 和 Value。多查询注意力(Multi-Query Attention,MQA)保留多个 Query 头,让它们共享同一个 Key 头和一个 Value 头。

不同的 Query 头仍然可以计算不同的注意力权重,但它们读取的是同一组 K、V。这样,模型不必为每个 Query 头分别保存一份历史 K、V。MQA 论文 ↗

MQA:多个 Query 头共享一组 K、V以单层、单个 Token 的 32 个 Query 头为例
多查询注意力的共享结构32 个 Query 头共同使用一个 Key 头和一个 Value 头;虚线框中的 K 和 V 是该层该 Token 需要缓存的向量。QUERYQ₁Q₂Q₃…Q₃₂KV虚线框:该 Token 的共享 K、V32 个 Query 头共享 1 个 K 和 1 个 V每层、每 Token:0.5 KiB

MQA 的缓存大小

仍使用第 5 节的示例:32 层、32 个 Query 头、每头 128 维,缓存采用 FP16,每个元素占 2 字节。MQA 只有 1 个 KV 头,因此:

每 Token 的 KV Cache = 2(K 和 V)× 层数 × 1(KV 头)× 头维度 × 每元素字节数

单层、单 Token:2 × 1 × 128 × 2 = 512 字节 = 0.5 KiB

32 层、单 Token:512 字节 × 32 = 16 KiB

序列中每个 Token 都有自己的共享 K、V;32 个 Query 头共享的是同一位置的 KV 头,而不是所有 Token 共用一对向量。

对照KV 头数单层 / Token32 层 / Token
MHA3216 KiB512 KiB
MQA10.5 KiB16 KiB

在层数、头维度、缓存精度和 Token 数相同的条件下,该 MQA 示例的理论 KV 数据量是 MHA 的 1/32。这是缓存大小的比较,实际速度提升还取决于 kernel、硬件和工作负载。

7

分组查询注意力 GQA:中间路线

GQA(Grouped-Query Attention)将 Query 头分组,每组共享一个 Key 头和一个 Value 头。它在每个头各自拥有 K、V 的 MHA,与所有头共享一组 K、V 的 MQA 之间提供了一种折中方案。

例如,32 个 Query 头分成 8 组,每组有 4 个 Query 头,共享 1 个 K 头和 1 个 V 头。因此,每层、每个 Token 需要保存 8 个 K 向量和 8 个 V 向量。

GQA:每 4 个 Query 头共享一组 K、V32 个 Query 头 ÷ 8 个 KV 组 = 每组 4 个 Query 头
GQA 分组共享结构32 个 Query 头分为 8 组,每组的 4 个 Query 头共享一个 Key 头和一个 Value 头。图中展示第 1、2、8 组。第 1 组Q1Q2Q3Q4K1V1第 2 组Q5Q6Q7Q8K2V2第 8 组Q29Q30Q31Q32K8V8…8 组 ×(1 个 K + 1 个 V)= 每层、每 Token 缓存 16 个向量

GQA 的缓存大小

每 Token 的 KV Cache = 2(K 和 V)× 层数 × G(KV 头数)× 头维度 × 每元素字节数

G 是 KV 头数:MHA 中 G 等于 Query 头数;MQA 中 G = 1。公式假设各层结构相同、K 和 V 维度相同。

沿用前两节的示例:32 层、32 个 Query 头、8 个 KV 头、头维度 128,缓存使用 FP16:

单层、单 Token:2 × 8 × 128 × 2 = 4 KiB

32 层、单 Token:4 KiB × 32 = 128 KiB
注意力类型Query 头KV 头32 层 / Token相对 MHA
MHA3232512 KiB基线
GQA328128 KiB缓存为 1/4
MQA32116 KiB缓存为 1/32

以上比较固定层数、头维度、精度和 Token 数。GQA 的理论 KV 数据量是该 MHA 配置的四分之一,实际性能还取决于实现与工作负载。

GQA 在模型中的应用

GQA 已用于 Llama、Mistral、Qwen 和 Gemma 等模型,但不同规格的头数各不相同。

模型年份注意力类型Query 头KV 头相对同配置 MHA
Llama 2 7B / 13B2023MHA32 / 4032 / 401×
Llama 2 70B2023GQA6488×
Llama 3.1 8B / 70B / 405B2024GQA32 / 64 / 1288 / 8 / 84× / 8× / 16×
Mistral 7B2023GQA3284×
Qwen2 / Gemma 22024GQA依规格而定依规格而定依配置计算

表中倍数表示其他条件相同时,由 KV 头数减少得到的理论压缩比,并非不同模型的实际显存或速度对比。GQA 论文 ↗ · Llama 2 ↗ · Llama 3 ↗

8

多头潜在注意力 MLA:换一种表示

MQA 和 GQA 通过共享来减少 KV 头数。DeepSeek-V2 的多头潜在注意力(MLA)换了一个思路:不缓存展开后的全部多头 K、V,而是为每个 Token 保存一个更紧凑的 KV 联合潜变量,并单独处理位置编码相关部分。

抓住 MLA 的两步:存储时压缩成潜变量;计算时利用线性变换的可结合性做矩阵吸收,尽量避免在 Decode 中显式恢复所有展开的历史 K/V。压缩比例需固定 MHA 基线才有意义。

第一步:把输入表示投影到低维空间

cₜ = W_DKV × hₜ
  • hₜ:第 t 个 Token 在当前层的输入表示,是一个向量。
  • W_DKV:模型训练得到的下投影矩阵,将输入表示映射到较低维度。
  • cₜ:投影得到的 KV 联合潜向量,包含后续计算 Key 和 Value 所需的压缩信息。

以 DeepSeek-V2 为例,隐藏维度是 5,120,KV 压缩维度是 512。按照列向量的写法:

W_DKV [512, 5120] × hₜ [5120, 1] = cₜ [512, 1]

一个 5,120 维输入向量经过下投影,得到一个 512 维潜向量。

W_DKV 是模型权重,不需要为每个 Token 单独保存一份;每个 Token 则会计算并缓存自己的 cₜ。cₜ 是向量,不是一张新的低秩 KV 矩阵。

第二步:用同一个潜向量计算 K 和 V

概念上,同一个 cₜ 可以通过两个不同的上投影矩阵,分别得到 Key 的内容部分和 Value:

K 的内容部分 = W_UK × cₜ
V      = W_UV × cₜ

W_UK 和 W_UV 也是模型权重。推理实现可利用矩阵吸收,避免显式展开全部多头 K、V。

因此,cₜ 不是 K 或 V 本身,而是供两者共同使用的低维中间表示。“低秩”指这组投影存在低维瓶颈:例如 Key 内容部分的有效映射可以写成 W_UK × W_DKV,其中间维度是 512,映射的秩受到这个维度的约束。

关键推导 · 为什么解码时不必展开历史多头 KV?

矩阵吸收:把对历史 Key 的大投影移到当前 Query 一侧

仅仅把 K、V 压缩后存起来并不够:如果每一步 Decode 都要把所有历史潜变量重新展开为多头 K/V,读取与计算仍会很重。MLA 的关键是利用线性变换的结合律。先只看某个头的内容分量,暂时不写 RoPE、缩放和 Softmax。

kₜ = Wᵁᴷ · cₜ
qᵀ · kₜ = qᵀ · Wᵁᴷ · cₜ
      = ( (Wᵁᴷ)ᵀ · q )ᵀ · cₜ

令 q′ = (Wᵁᴷ)ᵀ q。对于当前 Query,只做一次 q → q′ 的变换,后续就可以用 q′ 与历史缓存中的低维 c₁、c₂、… 计算分数,不必先把每个旧 cₜ 都展开成宽的 Key。

Value 也能利用线性性:同一注意力头的权重 aₜ 确定后,先对低维潜变量加权求和,再上投影到 Value 空间。

Σₜ aₜ vₜ = Σₜ aₜ (Wᵁⱽ cₜ)
      = Wᵁⱽ (Σₜ aₜ cₜ)

“先对低维历史加权求和,再投影”与“先逐项上投影,再加权求和”在理想线性计算中等价。实现中还会结合多头输出投影做相应融合。

存储cₜ + 位置 Key
→
匹配当前 q 投影后读取 cₜ
→
聚合先聚合 latent 再投影

边界:以上仅推导内容分支。DeepSeek MLA 的解耦 RoPE 位置分量需要单独参加打分;Softmax 仍对完整分数执行;跨头融合与实际 Kernel 细节也不能从这两行公式直接省略。矩阵吸收降低的是展开与读写开销,不是把模型变成无损的任意 MHA 转换。

除了 cₜ,还缓存什么?

DeepSeek-V2 将 RoPE 的位置相关部分与内容部分解耦。每层、每个 Token 除了缓存 512 维的 cₜ,还要缓存 64 维的位置相关 Key,共 576 个元素。这是一种经过训练的注意力结构,不能理解为对任意既有 MHA 模型启用的无损压缩开关。DeepSeek-V2 论文 ↗

把潜变量代入:V2 的结构示例

原文使用的 V2 配置中,低秩 KV 潜变量维度为 512,解耦位置相关 Key 维度为 64;于是每层每 token 保存 576 个元素。若把 128 个头、每头 128 维的标准 MHA 作为假设对照,K/V 一共需要 2×128×128 = 32,768 个元素,约为 576 的 56.9 倍。

56.9× 是与上述假设的 128 头标准 MHA 在同层、同数据类型下的元素数比较,并不是相对任意 GQA 模型、任意上下文或实际推理显存的压缩倍数;权重、投影计算、RoPE 分量和运行时开销应分别计算。

隐藏状态 hₜ → 下投影 → cₜ(缓存)
cₜ + 解耦位置分量 → 注意力计算 → 输出

“缓存什么”与“kernel 如何计算”是两个问题;矩阵吸收允许避免完整展开。

V2 的论文结果与 V3 的扩展

DeepSeek-V2 论文报告:相较 DeepSeek 67B,V2 的 KV Cache 减少 93.3%;在使用实际服务输入和输出长度分布、单节点 8 张 H800 的测试中,其最大生成吞吐达到对照模型的 5.76 倍。这是两个完整模型在论文设置下的对比,吞吐差异不能全部归因于 MLA。DeepSeek-V2 论文 ↗

DeepSeek-V3 继续采用 MLA,并使用 61 个 Transformer 层。在每层缓存 512 维潜变量和 64 维位置相关 Key、每个元素 2 字节的假设下:

61 × (512 + 64) × 2 = 70,272 字节 ≈ 68.6 KiB / Token
模型注意力总参数每 Token 的 KV 估算
Llama 3.1 405BGQA,8 个 KV 头405B504 KiB
Qwen2.5 72BGQA72B320 KiB
DeepSeek-V3MLA671B(约 37B 激活)约 68.6 KiB

这些是按各模型配置、16 位缓存估算的理论数据量;不同模型的层数、结构和参数规模不同,不能仅凭此表判断质量或实际速度。DeepSeek-V3 技术报告 ↗

交互:对比不同缓存结构

统一假设 32 层、每头 128 维、16-bit;MLA 仅为按 576 元素示意,并非同等质量或实际模型的性能对比。
9

跨层注意力 CLA:沿层数方向共享

MQA/GQA 在同一层的 Query 头之间共享 K、V;跨层注意力(Cross-Layer Attention,CLA)则让相邻的 Transformer 层共享 K、V,从层数方向减少独立缓存。

标准注意力与跨层共享

标准注意力中,每一层通常都为历史 Token 计算并保存自己的 K、V。CLA 可以将相邻层分组:组内某一层生成 K、V,其他层使用这组缓存,同时仍计算各自的 Query。

示例:4 层模型,每两层共享一组 KV每个缓存块表示该层或该组为历史 Token 保存的 K、V
标准逐层缓存与跨层共享缓存对照标准方式中四个层分别持有四组 KV 缓存;示例 CLA 中第 1、2 层共享一组,第 3、4 层共享一组,共两组缓存。标准:每层一组 KVCLA:相邻两层共享第 1 层第 2 层第 3 层第 4 层KV ₁KV ₂KV ₃KV ₄第 1 层第 2 层第 3 层第 4 层共享 KV ₁共享 KV ₂4 组 KV 缓存2 组 KV 缓存

图中表示的是缓存的共享关系,并不意味着第 1、2 层变成同一层:它们仍有各自的计算与 Query。实际哪些层生成或共享 KV,取决于具体模型设计。

缓存量如何变化?

假设每层的 KV 头数、头维度和精度相同,且 32 层中每两层共享一组 KV,那么需要保存的独立 KV 组从 32 组降至 16 组。

标准方式:2 × 32 层 × KV 头数 × 头维度 × 每元素字节数

两层共享:2 × 16 组 × KV 头数 × 头维度 × 每元素字节数

以上为每个 Token 的理论数据量;第一个 2 表示 K 和 V。

例如,每层有 8 个 KV 头、每头 128 维,使用 FP16 时,标准方式为 128 KiB/Token;每两层共享后,理论值为 64 KiB/Token。

CLA 与 GQA 的区别

方法共享方向示例
GQA同一层内,Query 头之间共享4 个 Query 头共享 1 组 KV 头
CLA相邻层之间共享2 层共享 1 组 KV 缓存

两种方法作用于不同维度,可以在相应模型设计中组合。缓存比例是理论估算;质量和实际性能需要结合共享布局、训练方式与推理实现评估。CLA 论文 ↗

10

KV Cache 量化:让每个数更小

量化不必改变需要保留的历史位置,而是降低缓存元素的位宽。与 MQA、GQA、MLA、CLA 主要改变头数、表示和共享方式不同,KV 量化直接减少每个数的存储字节。

1. 量化是什么?

量化把原本较精细的数值映射到有限的离散取值。例如,一个简化的 2-bit 量化器只能用 4 个编号表示 4 个量化级别;原始值通常要近似到其中一个级别,因此会产生误差。

量化:q = round(x / scale) + zero_point
近似还原:x̂ = (q − zero_point) × scale

均匀仿射量化示意;实际还需把 q 限制在可表示范围内。scale 控制步长,zero_point 对齐零值;不同格式不一定采用相同公式。

例如,将 0 到 1 均匀映射到 4 个级别(0、1/3、2/3、1),原值 0.70 会被表示为最接近的 2/3,产生约 0.033 的误差。低位宽节省空间,但不一定保留原值的全部细节。

交互:位宽、近似与理论体积

数值示例采用 [0,1] 区间的均匀量化器,展示 0.70 的近似;16-bit 一行仅作均匀量化示意,并非模拟 FP16 编码。体积只计算原始元素位数。

相对 16-bit 元素,8、4、2 bit 的理想元素数据量分别是 1/2、1/4、1/8。但实际缓存还可能包含 scale、零点、分组信息、未量化的近期缓存和对齐开销;总显存还包含模型权重、激活等,所以不会按同一比例下降。

2. 为什么 KV 量化不容易?

K 用来与 Query 计算匹配分数,K 的误差可能改变注意力权重;V 是被加权汇总的内容,V 的误差会影响输出表示。如果少数异常值远大于其他数值,它们会扩大一个量化组的取值范围,让其他数值可用的精度变粗。

K 和 V 的异常值分布示意行表示 Token,列表示向量通道;高亮仅表示示例中的较大值
K 和 V 的异常值分布与量化粒度左侧 Key 矩阵的异常值集中在同一个通道,KIVI 对 Key 按通道分组;右侧 Value 矩阵的异常值分布在某些 Token,KIVI 对 Value 按 Token 分组。图为示意,不表示所有模型的固定分布。Key:通道差异Value:Token 差异Token ↓Token ↓通道 →通道 →按通道确定量化参数按 Token 确定量化参数

图是帮助理解量化粒度的简化示意:KIVI 在其研究的模型中观察到 K 的异常值倾向于特定通道,而 V 的分布更适合逐 Token 处理。实际数值分布和分组方式取决于模型与实现。

3. 三种方法分别做了什么?

KIVI:针对 K 和 V 使用不同量化粒度

KIVI 对 Key 采用逐通道量化,对 Value 采用逐 Token 量化,以适应两者不同的分布。它在生成过程中保留一个近期高精度残差缓冲,再按方法的分组方式量化历史缓存;因此实际内存不仅由 2-bit 元素决定。

论文在 Llama、Falcon、Mistral 等模型的实验中报告:采用 2-bit KV 后,包括模型权重在内的峰值显存约减少到对照的 1/2.6;更大的可用 batch 带来了 2.35–3.47 倍吞吐。这里的峰值显存与吞吐不是单独 KV 元素的理论 8 倍压缩比。KIVI 论文 ↗

KVQuant:处理 RoPE、分布和异常值

KVQuant 将 Key 的量化放在 RoPE 之前,以减轻位置旋转对量化的影响;同时采用逐通道 Key 量化、适应数据分布的非均匀量化,并把异常值与主要数值分开处理,避免少量极端值拉大量化范围。

论文报告,在所测 LLaMA、Llama 2/3 和 Mistral 模型上,3-bit KV 量化在 WikiText-2 和 C4 上的困惑度增加低于 0.1;其定制 CUDA kernel 在 LLaMA-7B 的矩阵—向量乘测试中,相对 FP16 基线最高约快 1.7 倍。这些分别是质量和 kernel 指标,不是整机吞吐的普遍保证。KVQuant 论文 ↗

TurboQuant:随机旋转与向量量化

TurboQuant 先对向量进行随机旋转,使信息在坐标间更均匀地分布,再对坐标进行标量量化。针对内积估计,论文还提出对量化残差使用 1-bit 量化变换的两阶段方法,以减轻内积估计偏差。

论文在所测 KV Cache 量化任务中报告,约 3.5 bit/通道时质量指标与基线持平,约 2.5 bit/通道时出现轻微下降;结果取决于具体模型、任务和实现,不能理解为所有场景都没有质量损失。TurboQuant 论文 ↗

4. 方法对比

方法主要思路论文讨论的位宽实际部署还需关注
KIVIKey 逐通道、Value 逐 Token;近期残差缓冲2-bit K/V分组参数、残差缓存、反量化开销
KVQuantPre-RoPE Key、非均匀量化与异常值处理3-bit 等低位配置量化参数、异常值存储与 kernel 支持
TurboQuant随机旋转、标量量化及可选的残差估计论文示例约 2.5 / 3.5 bit/通道旋转、残差、有效位宽与 kernel 成本

三种方法的实验设置和报告指标不同;不能直接把它们的压缩率、困惑度和吞吐倍数并列排名,也不能假设不同技术的收益可以直接相乘。

低位宽缓存的收益边界

低精度 KV 可以减少容量和传输字节数,但读取时还可能涉及 scale/zero-point、反量化、数值格式转换、额外 kernel、block 对齐与不规则布局。只有在实际受 KV 带宽制约、且低精度 Kernel 能高效处理该格式时,容量收益才可能转化为显著的 TPOT 改善。建议分开测量 KV 占用、Attention kernel 时间、端到端 TPOT 和质量。

5. 权重量化与 KV 量化:两笔独立的内存

模型权重是加载时使用的参数;KV Cache 是推理时由上下文计算出的运行状态。权重使用低位宽,不会自动让 KV Cache 使用同样的位宽。原文以 Bonsai 8B 与 Q4 KV 的本地实验说明这一点:

配置权重KV Cache32K KV总驻留内存
Bonsai 基线低位权重FP164,608 MiB6,011 MiB
同模型 + Q4 KV低位权重Q41,296 MiB2,699 MiB

这组数字转述自参考原文引用的本地日志;原文所链实验仓库目前无法访问,因此未能独立核对日志。它只说明该实验配置的结果,不能推广为所有模型或设备的表现。

PART 03 / 06

如何让历史访问更少、更高效?

窗口、保留策略、IO-aware Attention 与递归状态:改变读取历史的方式。

11

滑动窗口与局部—全局注意力

滑动窗口注意力(Sliding Window Attention,SWA)限制每层直接可访问的历史范围:每次只看最近 W 个 Token。窗口之外的位置不再参与该层当前步的直接注意力计算,因此在适当的缓存策略下可以减少保留与读取的数据。

滑动窗口如何移动?

假设窗口大小为 4。当新 Token 到来时,窗口向前移动一格:最新位置进入窗口,最早的位置离开窗口。对于只依赖该局部窗口的层,离开窗口的 KV 可以从该层缓存中移除。

窗口大小 W = 4颜色表示当前层可直接关注的位置
滑动窗口随新 Token 移动第一步关注 T1 至 T4,第二步新 Token T5 到来后关注 T2 至 T5,T1 移出窗口。当前:T4新 Token:T5T1T2T3T4T1T2T3T4T5移出窗口进入窗口

窗口长度决定该层的直接可见范围:旧 Token 退出后,当前层不能再直接读取它的 K、V。信息可能通过层间表示间接传播,但这不等于能精确找回所有窗口外的细节。

缓存何时封顶?

如果一个局部层最多保留 W 个 Token 的 KV,那么当序列长度 T 超过 W 后,该层的缓存数据量不再继续随 T 增长:

局部层保留的 Token 数 = min(T, W)

局部层 KV 字节数 = 2 × KV 头数 × 头维度 × min(T, W) × 每元素字节数

这里假设 K、V 维度相同;能否回收窗口外缓存还取决于具体实现。

例如,单层使用 8 个 KV 头、每头 128 维、FP16,窗口为 4,096 Token,则该层的理论 KV 大小封顶约为 16 MiB。若 32 层都使用相同窗口并能回收旧缓存,合计约 512 MiB;这只是 KV 数据量,不包含模型权重和其他开销。Mistral 7B ↗

局部—全局注意力:不同层看不同范围

如果所有层都只使用局部窗口,远处信息不能被当前层直接访问。另一种架构是让一部分层使用局部窗口,另一部分层使用全注意力:局部层控制缓存增长,全局层则保留访问完整可见历史的路径。

示例:局部层与全局层交替同一个当前 Token,在不同层可见的历史范围不同
局部层和全局层的可见范围局部层只关注最近四个位置,全局层关注所有可见历史位置;两层使用不同的缓存范围。局部层全局层T1T2T3T4T5T6T7T8T1T2T3T4T5T6T7T8灰色:该局部层窗口外  绿色:当前层可直接关注

例如,Gemma 3 在部分模型配置中采用 5 个局部层搭配 1 个全局层的模式。实际层数、窗口和 KV 头配置需以具体模型为准。Gemma 3 技术报告 ↗

总缓存 ≈ 局部层数 × 单层每 Token 大小 × min(T, W)
    +全局层数 × 单层每 Token 大小 × T

简化示意:若不同层的 KV 头数或维度不同,应分别计算后求和。

因此,纯局部窗口层的缓存可以封顶;只要架构中仍有全局层,总缓存通常仍随上下文长度增长,只是增长速度较慢。

12

KV Cache 淘汰:只保留部分历史

在生成过程中,模型对历史 Token 的关注通常并不均匀:有些位置反复获得较高注意力,有些位置受到的关注较少。KV Cache 淘汰方法利用这一观察,在有限的缓存预算内选择保留部分历史,以减少显存和读取开销。

关键问题是:哪些位置应该留下?不同方法使用不同的判断依据,也在不同阶段做选择。

三种保留方式:同一条 Token 序列的示意数字表示位置;灰色表示未保留;颜色仅用于说明选择思路,并非论文中的固定保留比例
三种 KV 缓存保留策略H2O 保留历史重要位置与最近窗口;StreamingLLM 保留开头的 attention sinks 和最近窗口;SnapKV 使用 prompt 末尾观察窗口选择重要前缀位置,同时保留观察窗口。H₂O123456789101112StreamingLLM123456789101112SnapKV123456789101112选中的重要位置近期窗口初始 Sink观察窗口

H₂O:保留重要位置和近期位置

H₂O 在生成过程中统计历史位置获得的累计注意力,将经常受到关注的位置称为 heavy hitters。当缓存达到预算时,它优先保留这些重要位置,同时保留一部分最近的 Token,以维持对近期上下文的访问。H₂O 论文 ↗

Scissorhands:重要性可能持续

Scissorhands 基于一个观察:在某些生成步骤中重要的 Token,之后也可能继续重要。它利用这一“重要性持续”假设,在固定预算内以更高概率保留关键位置;重要性并非对所有任务和后续问题都保证不变。Scissorhands 论文 ↗

StreamingLLM:保留开头与最近窗口

研究发现,一些模型会对序列开头的少量 Token 分配较多注意力;这些位置被称为 attention sinks(注意力汇)。StreamingLLM 保留初始 sink 和近期窗口,让模型在固定缓存预算下持续处理流式输入。已经移除的中间内容不能被直接检索,因此持续运行不等于记住全部历史。StreamingLLM 论文 ↗

SnapKV:在生成前筛选 Prompt

SnapKV 利用 Prompt 末尾的观察窗口,查看这些位置对前面内容的注意力,并为不同注意力头选择重要的前缀 KV 位置。它在 Prompt 处理后、生成前压缩缓存;观察窗口本身也会保留。SnapKV 论文 ↗

PyramidInfer 与 PyramidKV:不同层分配不同预算

PyramidInfer 根据跨层注意力的重要性观察,逐层保留关键上下文,研究如何减少计算及缓存。PyramidKV 观察到某些模型中低层注意力较分散、高层更集中,据此为低层分配更多缓存、为高层分配较少缓存。两者是分别提出的方法,不代表所有模型的层都遵循相同模式。PyramidInfer ↗ · PyramidKV ↗

方法对比

方法何时 / 依据什么选择主要保留
H₂O生成中,根据累计注意力重要历史位置 + 最近位置
Scissorhands生成中,利用重要性持续的假设更可能持续重要的位置
StreamingLLM流式生成时,采用固定保留策略初始 sink + 最近窗口
SnapKVPrompt 处理后,依据末尾观察窗口选中的前缀位置 + 观察窗口
PyramidInfer依据注意力及层间重要性差异各层选出的关键上下文
PyramidKV按层调整缓存预算低层通常较多,高层较少

这些策略减少了可供未来注意力访问的历史状态。过去关注较少的内容仍可能与后续问题有关,因此应同时评估缓存节省和目标任务质量。

13

FlashAttention:更高效地执行注意力计算

FlashAttention 主要优化注意力计算中的数据搬运,而不是持久 KV Cache 的结构或容量。它利用分块计算和片上存储,减少中间结果在 HBM 和 SRAM 之间反复读写。

为什么注意力计算需要关注内存访问?

标准注意力可以分为计算分数、Softmax 归一化和聚合 Value 三步:

Scores = QKᵀ / √dₖ → Weights = softmax(Scores) → Output = Weights × V

因果注意力还需对不可见的位置应用 mask。

朴素实现可能先计算完整的 Score 矩阵,并将它和后续的注意力权重写入 GPU 的高带宽显存(HBM),再读回来完成下一步。序列越长,中间矩阵越大,反复读写也越昂贵;并非所有非 FlashAttention 实现都会完整物化这些矩阵。

下面的完整 N×N Score 矩阵是整段 Prefill 的朴素实现对比,不是单 Token Decode 会产生的中间矩阵。Decode 对单个 Query 的分数长度通常随可见历史线性增长。

下面的 256 GiB Score 矩阵 是完整 Prefill 的朴素密集物化示例,不是每生成一个 Token 都要保存的矩阵。单 Token Decode 的 Score 形状与对历史 KV 的读带宽应另行讨论。

例如,序列长度为 32,768、批量为 1、128 个头,若每个头的完整 Score 都用 BF16 保存:一个头需要 32,768² × 2 字节,即 2 GiB;128 个头合计 256 GiB。这只是假设完整、密集物化 Score 的体积,不包含权重和其他张量,也不代表实际推理一定会分配这么多。

分块计算:减少 HBM 与片上存储之间的数据搬运

GPU 的 HBM 容量较大,而片上共享内存等高速存储容量较小。FlashAttention 将 Q、K、V 划分为块,把当前需要的数据送入片上存储,融合计算并累积结果,避免将完整的 Score 和概率矩阵写回 HBM。

两种注意力计算路径示意中间数据如何在显存与片上存储之间流动
朴素注意力与 FlashAttention 的数据流对比朴素实现可能把完整分数和权重写入显存后再读取;FlashAttention 分块载入 Q、K、V,在片上更新统计量和输出,避免物化完整注意力矩阵。朴素实现Q × KᵀScore 写入 HBMSoftmax / 读写× V → 输出FlashAttentionQ / K / V 分块片上计算 + 在线 Softmax更新统计量与输出累积写出结果

Online Softmax:分块后怎样得到正确结果?

普通 Softmax 需要一整行分数的归一化结果,而分块计算一次只看到一部分分数。Online Softmax 在扫描各块时,为每个 Query 维护三个状态:已处理分数的最大值 m、经过最大值平移后的指数和 ℓ,以及对应 Value 的加权累积 u。

读取 KV 块 1→更新 m、ℓ、u→读取 KV 块 2→重新缩放并更新→处理完所有块,输出 u / ℓ

如果新块产生更大的最大值,之前的指数和与加权累积也会按新最大值重新缩放,因此无需保存整行 Score,也能正确合并各块。FlashAttention 在数学上计算的是精确的标准注意力;浮点计算顺序不同,结果可能有微小数值差异,不保证逐位一致。FlashAttention 论文 ↗

降低的是哪部分显存?

在固定其他维度时,FlashAttention 避免物化完整的 N × N 注意力中间矩阵,使注意力计算的辅助内存从朴素实现的二次规模降低到线性规模。它没有把标准全注意力的计算量从 O(N²) 降到 O(N),也不会单独减少 KV Cache 中需要保存的元素数。

FlashAttention-2:改进 GPU 内部的并行执行

FlashAttention-2 在减少 HBM 访问的基础上,进一步减少非矩阵乘法操作,让同一个注意力头的计算可分配给更多线程块,并调整线程块内 Warp 的任务划分,减少不必要的共享内存通信。

论文在 A100 的实验中报告,部分测试相较 FlashAttention 约有 2 倍加速;其特定 GPT 风格模型的端到端训练实验最高达到 72% 的模型 FLOPs 利用率(MFU)。该 MFU 是端到端训练指标,不应直接当作注意力 kernel 的利用率。FlashAttention-2 论文 ↗

与 KV Cache 的关系:Prefill 和 Decode 不一样

对比Prefill:处理 PromptDecode:逐 Token 生成
Query / KV 长度通常分别为 N / N单 Token 解码时通常为 1 / N
Score 形状N × N1 × N
典型关注点计算量、临时显存和 HBM IO历史 KV 读取带宽和短 Query 的并行度
优化重点分块避免完整中间矩阵物化高效读取 KV,并利用适合解码的并行策略

这些是典型情况,实际瓶颈会随批量、上下文、硬件和 kernel 改变。部分面向解码的实现采用 Split-KV:将较长的 KV 序列分给多个线程块并行处理,再合并局部结果,以改善短 Query 下的并行度。

与其他 KV 技术如何配合?

技术主要优化对象
MQA / GQA减少需要缓存和读取的 KV 头数
MLA使用更紧凑的 KV 表示
KV 量化减少缓存中每个元素的存储位数
PagedAttention改善 KV 块的分配和共享
FlashAttention优化注意力计算的数据访问和执行方式

这些技术针对不同层次,可以组合,但具体兼容性与收益取决于模型和推理框架。FlashAttention 官方实现 ↗

14

线性注意力与状态空间模型:当历史状态不再随 Token 增长

标准全注意力为历史中的每个 Token 保留 K/V。GQA、量化和窗口能降低开销,但仍然以逐位置状态为中心。线性注意力和部分递归架构采用另一种方式:将历史汇聚到持续更新的固定形状状态,而不是为每个历史位置都保存独立 K/V。

固定状态并不意味着无限记忆

递归状态的形状可以固定,不随历史 Token 数增长,但这不意味着拥有无限、精确、可随机访问的记忆。它需要将历史压入有限状态。全注意力则保留逐位置 K/V,允许每个 Query 再对各位置评分。两种设计各有得失。

14.1 两种历史:逐位置账本与持续更新的状态

标准全注意力像逐页保存档案:要查历史时,可以按 Query 与各页 Key 的相关性读取 Value。递归架构更像维护一份会被不断修订的工作摘要:新内容进来后更新状态,旧 Token 不再都以独立 K/V 的形式存在。

逐位置缓存与递归状态示意单层处理同一条序列;方块大小不代表实际字节数
标准 KV 缓存与固定大小递归状态的对照标准注意力为 Token 1、2、3 逐个添加 KV 缓存;递归模型依次接收 Token 并更新同一个固定形状的状态。标准注意力:逐位置保存K₁ + V₁K₂ + V₂K₃ + V₃…Token 增加 → 缓存继续增长递归状态:持续更新状态 S₁状态 S₂状态 S₃+ Token 2+ Token 3状态的内容改变,形状保持固定

两者的运行时成本不能只看“有没有 KV Cache”。一个矩阵状态即使维度固定,也可能很大;如果系统还包含全注意力层、短卷积或扫描状态,还必须把这些开销一起计入。

14.2 Linear Attention:用乘法结合律先汇总历史

先从一个刻意简化的计算开始。标准 Attention 含 Softmax;我们暂时去掉 Softmax 和缩放,只考察点积加权和是否可以重排。统一使用列向量 q、k ∈ ℝdₖ,v ∈ ℝdᵥ,状态 S ∈ ℝdₖ×dᵥ,后面不再改变维度约定。

逐位置形式:oₜ = Σᵢ₌₁..ₜ (qₜᵀ kᵢ) vᵢ

定义状态:Sₜ = Σᵢ₌₁..ₜ kᵢ vᵢᵀ
递归更新:Sₜ = Sₜ₋₁ + kₜ vₜᵀ
读取结果:oₜ = Sₜᵀ qₜ

kᵢ vᵢᵀ 是外积,得到 dₖ×dᵥ 矩阵;Sₜᵀ qₜ 是 dᵥ 维输出。对固定的 dₖ 和 dᵥ,状态尺寸与历史长度无关。

14.3 手算两个 Token:为什么只存一个矩阵也能读取历史?

取一个只有两个通道的玩具例子。第一个 Token:k₁=[1,0]ᵀ、v₁=[2,1]ᵀ;第二个:k₂=[0,1]ᵀ、v₂=[1,3]ᵀ。两个外积按行累加后:

k₁v₁ᵀ = [[2,1], [0,0]]
k₂v₂ᵀ = [[0,0], [1,3]]
S₂ = [[2,1], [1,3]]

新的 q=[1,1]ᵀ:
S₂ᵀ q = [3,4]ᵀ

它与逐项计算 (qᵀk₁)v₁ + (qᵀk₂)v₂ = [2,1]+[1,3]=[3,4] 相同。历史信息被折叠到矩阵 S,而不是按 Token 存两套 KV。

但这不是标准 Softmax Attention 的无损替代。 标准 Softmax 的指数和归一化无法简单地通过这次线性重排消失。某些核化线性注意力通过特征映射 φ 建立可分解的相似度,并额外保存归一化状态。

Sₜ = Sₜ₋₁ + φ(kₜ) vₜᵀ
zₜ = zₜ₋₁ + φ(kₜ)
oₜ = Sₜᵀ φ(qₜ) / (φ(qₜ)ᵀ zₜ)

忽略分母接近零时的数值保护。φ 和归一化规则决定实际模型性质,不能把所有 Linear Attention 都解释成“把 Softmax 去掉”。

14.4 简单累加还不够:Delta Rule 如何修正旧关联?

如果状态只会增加,新信息和旧信息可能不断叠加。例如已经把“某个键”对应到旧值,现在希望用新值修正它。Delta Rule 先读取旧状态对当前 Key 的预测,再把差值写回。沿用前面的 S ∈ ℝdₖ×dᵥ 约定:

旧预测:v̂ₜ = Sₜ₋₁ᵀ kₜ
差值: eₜ = vₜ − v̂ₜ
更新: Sₜ = Sₜ₋₁ + βₜ kₜ eₜᵀ

βₜ 控制写入强度。为便于手算,下面使用单位范数的 one-hot Key,并取 β=1;实际模型需要相应的规范化与稳定性设计。

沿用上面的 S₂,假设又出现 k₃=[1,0]ᵀ,但新的 Value 是 [0,2]ᵀ:旧预测为 [2,1],误差为 [-2,1]。把差值写回第一行,得到:

S₂ = [[2,1], [1,3]]
e₃ = [0,2] − [2,1] = [-2,1]
S₃ = S₂ + k₃ e₃ᵀ = [[0,2], [1,3]]

原来与这个 Key 对应的 [2,1] 被修正成 [0,2],而第二行保留。这只是说明更新方向的玩具例子,神经网络状态并不是可直接编辑的自然语言事实表。

14.5 Gated DeltaNet:为什么在“纠错”外还要加门控?

Delta Rule 决定怎样针对当前 Key 修正状态,但模型还需要控制旧信息保留多久。门控让状态先衰减或保留,再计算更新误差:

预衰减:S̃ₜ = αₜ Sₜ₋₁
旧预测:v̂ₜ = S̃ₜᵀ kₜ
更新: Sₜ = S̃ₜ + βₜ kₜ (vₜ − v̂ₜ)ᵀ

αₜ 决定旧信息的保留强度,βₜ 决定新信息写入强度;这是概念性 DeltaNet 更新式,省略了真实网络的投影、归一化、卷积与并行算法。它说明为什么 Gated DeltaNet 不只是“把所有旧 KV 相加”。

工程上,递归公式并不等于 Prefill 必须 Python for-loop 串行执行。Chunkwise 算法、扫描和矩阵化计算可以提高 Prefill 并行性;Decode 则侧重低延迟状态更新。

14.6 与 Softmax Attention、Mamba、RWKV 的关系

路线历史形式需要注意的代价
全注意力逐位置 K/V长上下文的缓存和读取随长度增加
线性注意力 / DeltaNet固定形状矩阵状态有限状态表达、更新算子与数值稳定性
Mamba / SSM选择性递归状态状态演化规则不同,不能套用 KV 外积式
RWKV / RetNet各自定义的递归或 retention 状态训练并行性与推理算子的实现差异

它们的共同点是增量推理可以用状态承载历史,但并非一组相同算法。固定状态对历史长度可以是 O(1) 空间,但对模型维度、批次和状态精度仍有依赖。

14.7 混合架构:为什么保留少数全注意力层?

有限状态难以像全注意力那样对任意旧位置直接寻址,因此新架构可能采用“多数递归层 + 少数全注意力层”的折中:递归层提供稳定的历史状态预算,全注意力层保留逐位置精细匹配能力。总状态应当按层相加,而不能只数传统 KV。

混合架构的状态示意以下排列仅为说明,并非某个具体模型的真实层序
注意力与递归层混合架构示意混合模型交替包含递归层和注意力层;递归层维护固定形状状态,注意力层维护随可见历史增长的 KV 缓存。递归层注意力层递归层注意力层递归层递归层:固定形状状态注意力层:逐位置 KV总状态 = 各递归层状态 + 各注意力层 KV + 其他开销

14.8 Qwen3.5-9B:为什么总缓存不能只数全注意力层?

根据公开的 Qwen3.5-9B 配置,文本主干共 32 层,按“3 层线性注意力 + 1 层全注意力”的模式安排:24 层使用 Gated DeltaNet 路线,8 层使用全注意力。假设这 8 层的 KV 头数为 4、头维度为 256、缓存精度 BF16:

每 Token 的全注意力 KV
= 2 × 8 层 × 4 KV 头 × 256 维 × 2 B
= 32,768 B = 32 KiB

128 Ki Token × 32 KiB = 4 GiB

这个 4 GiB 只计算 8 个全注意力层的 KV。24 个递归层的 DeltaNet 状态、短卷积状态、量化元数据和内存分配都另计。

作对照:假设全部 32 层都使用相同 KV 头数与维度的全注意力,则理论 KV 为 16 GiB。但这是一组同条件构造,不是直接对两个真实 checkpoint 的实测显存比较。

14.9 适用条件与代价

优势:递归层可以让每步 Decode 的历史相关工作量不直接随 Token 数增长。边界:历史被汇聚到有限状态,不保证任意位置都能被准确找回;混合架构仍含逐位置 KV;并且缓存恢复、前缀复用、状态检查点和批处理 Kernel 都比纯 GQA 系统更复杂。

PART 04 / 06

如何把 KV Cache 管好、用好?

分页、前缀复用、分层存储和 P/D 分离:让缓存进入服务系统。

15

PagedAttention:为 KV Cache 引入分页管理

即使单个 Token 的 KV Cache 已经通过 GQA、MLA 或量化缩小,不同请求的长度和结束时间仍会造成显存预留浪费和碎片。PagedAttention 解决的是如何按需分配和复用缓存块,而不是改变每个 Token 的数学表示。

一句话区分:GQA/MLA 让每个 Token 的 KV 逻辑字节数变小;PagedAttention 把请求的逻辑 KV 块映射到可分散分配的物理块,主要改善分配碎片、预留浪费与共享。

问题:请求长度不同,内存却可能提前预留

生成开始时,系统通常不知道每个请求最终会有多长。如果提前为请求分配一整块连续空间,按最多 4,096 个 Token 预留,而实际只用 200 个,就会有大量空间暂时闲置。不同请求同时运行时,空闲内存还可能被切分成无法满足连续分配的片段。

INTERNAL FRAGMENTATION

内部碎片

已经分配给请求的空间中,有一部分未被使用。

EXTERNAL FRAGMENTATION

外部碎片

空闲空间分散,虽然总量足够,却不一定能满足连续分配。

RESERVATION

预留浪费

为尚未生成的 Token 预留空间,但请求可能提前结束。

内部碎片和预留浪费可能重叠:这里分别强调“已分配空间未用”和“为未来增长提前保留”。PagedAttention 论文报告,在当时评估的系统中,KV Cache 内存有 60%–80% 因碎片或预留等原因未得到有效利用。

解决方法:像虚拟内存一样管理 KV 块

这里的“分页”与模型是否采用 GQA/MLA 无关:它解决的是逻辑 Token 位置到物理 KV 块的映射。改变分配方式不能直接改变每个 Token 的逻辑 KV 元素数。

操作系统的虚拟内存把地址空间划分为页,通过页表将连续的逻辑地址映射到不必连续的物理位置。PagedAttention 借鉴这一思路,将每个请求的 KV Cache 切成固定大小的逻辑块,再按需映射到 GPU 上的物理块。

逻辑块连续,物理块可以分散两个请求的 KV 块通过各自的块表映射到 GPU 显存
PagedAttention 逻辑块与物理块的映射请求 A 的逻辑块 A0、A1、A2 分别映射到物理块 8、1、6;请求 B 的 B0、B1 映射到物理块 0、3。物理块在显存中不要求连续。请求的逻辑块GPU 物理块请求 A请求 BA0A1A2B0B1B0A1B1A2A0块表 A:A0 → 8 A1 → 1 A2 → 6块表 B:B0 → 0 B1 → 3物理块编号按行从 0 开始

例如,每块可以容纳 16 个 Token;32 个 Token 的块也可用于说明。请求到来时按需分配块,序列增长时再申请新块。逻辑上相邻的块无需在物理显存中相邻,注意力 kernel 通过块表找到对应的 K、V。

请求到达→按需分配 KV 块→块表记录映射→序列增长时追加块→请求结束后回收

这样不必为每个请求一次性预留最大长度的连续空间。固定块也有权衡:最后一块可能未填满,块表与访问映射也会带来一定开销。

块共享:公共前缀不必重复保存

多个请求如果具有完全相同且状态兼容的前缀,例如相同的系统提示词,可以让它们的块表指向同一批 KV 物理块。生成分叉时,只有各自新增或修改的部分需要独立保存;部分场景可使用写时复制。

请求 A:共享前缀块 → A 的后续块
请求 B:共享前缀块 → B 的后续块

能否共享取决于模型、Token 前缀、位置和其他相关状态是否兼容,也取决于服务框架的缓存策略。

PagedAttention 不会改变每个 Token 的 K、V 理论元素数,也不能消除所有内存开销;它主要减少分配与预留浪费。实际收益随请求长度分布、并发和系统实现变化。TensorRT-LLM、SGLang 等推理系统也提供分页式 KV 管理或相关缓存管理机制。

16

前缀缓存与 RadixAttention:让多个请求共享 KV Cache

假设我们构建了一个基于大语言模型的文档问答系统。用户 A 和用户 B 使用同一份约 8K Token 的技术文档,但分别询问“核心观点是什么”和“有哪些性能优化方法”。两个请求的问题不同,前面的系统提示词和文档却相同。

如果没有复用,模型需要分别处理两个请求的全部输入,即使公共前缀完全相同,也要重复执行其中的 Attention、MLP 等计算。Prefix Caching(前缀缓存)要解决的就是:能否让第二个请求复用第一个请求已经计算过的公共前缀?

为什么历史 KV 可以复用?

在因果注意力中,每个 Token 只能关注自己及之前的位置,不能看到未来 Token。假设两个请求的前缀完全相同:

请求 A:系统提示词 + 共享文档 + 问题 A
请求 B:系统提示词 + 共享文档 + 问题 B

公共前缀中的 Token 不受后续问题 A 或 B 的影响。因此,在模型权重、位置及其他相关计算状态兼容的条件下,公共前缀对应的逐层 K、V 可以复用。

请求 A

计算并保存

处理公共前缀和问题 A,并按缓存策略保存可复用的前缀 KV。

请求 B

匹配并复用

找到命中的公共前缀,复用其 KV,只对未命中的后缀执行 Prefill。

仍需计算

处理新问题

问题 B 的 Query 仍需关注公共前缀的 K,并聚合对应的 V。

如果 8K Token 的公共前缀完整命中,问题 B 有 200 Token,第二个请求可以跳过前缀 Token 的重复前向计算,只对新增的 200 Token 执行 Prefill。这不意味着新增 Token 无需访问历史,也不意味着 Decode 可以跳过。

RadixAttention:如何管理共享前缀?

实际系统中,请求可能共享系统提示词、共享文档,也可能只共享更短的前缀。SGLang 提出的 RadixAttention 使用压缩前缀树(Radix Tree)组织 Token 前缀与对应的 KV 缓存位置。SGLang 论文 ↗

示例:系统提示词下的不同文档和问题
RadixAttention 共享前缀树示意根节点下为公共系统提示词,之后分成文档 A 和文档 B;文档 A 分成问题 A 和问题 B,文档 B 后接问题 C。根节点系统提示词文档 A文档 B问题 A问题 B问题 C

图中每条从根开始的路径表示一个 Token 前缀;具体节点、边和缓存块的组织取决于实现。

  • 相同前缀对应的 KV 可以共享;Token 开始不同的位置形成分叉。
  • 新请求到来时,系统从根开始查找最长可复用前缀,复用命中部分,只计算未命中的后缀。
  • 连续且没有分叉的多个 Token 可以合并为一个节点或一条边上的片段,减少树的管理开销。

例如,请求 A 结束后相关缓存仍在,且请求 B 的状态与之兼容时,B 可以匹配“系统提示词 + 文档 A”,复用其 KV,再处理问题 B。树用于索引前缀及其缓存位置;模型权重不存放在这棵树中,KV 的物理布局也由具体缓存管理器决定。

缓存不是无限的:匹配、插入、保护与淘汰

环节做什么
匹配 Match查找新请求的最长可复用前缀,确定可以跳过哪些前缀计算。
插入 Insert新 Token 完成计算后,按缓存策略将可复用的前缀及 KV 位置加入树中。
引用保护正在运行的请求引用公共前缀时,通过引用计数等机制防止相关缓存被回收。
淘汰 Eviction显存不足时,回收不再被活跃请求使用的缓存。原始 RadixAttention 使用基于 LRU 的叶节点淘汰策略。

优先淘汰较久未使用、没有活跃引用的叶节点,有助于保留仍能被其他分支共享的公共前缀;缓存也不是永久保留,命中与否取决于它是否仍在对应的缓存池中。

前缀缓存能带来多大收益?

复用的收益依赖前缀的精确 Token 序列、位置、模型版本、KV 表示以及缓存命中率。命中后主要跳过相应的重复 Prefill;之后的 Decode 仍需依据模型注意力规则访问历史,不能据此宣称单步 Decode 成本消失。

前缀命中主要减少重复的 Prefill 计算,通常有助于降低首 Token 延迟(TTFT)。但“8K 前缀 + 200 Token 新问题”并不意味着整个请求加速 40 倍:新问题仍需关注前缀,缓存查找、调度、显存带宽和后续 Decode 都会影响耗时。

  • 长公共前缀、多次复用:文档问答、多轮对话和 Agent 工作流通常更有机会受益。
  • 短前缀或命中率低:能够省去的前向计算较少,收益也可能有限。
  • 输出很长:即使 Prefill 加快,Decode 仍会持续执行,整体用时不一定同比降低。

多 GPU 场景还需要考虑缓存所在位置:前缀 KV 若在 GPU A,而请求被分配到没有该缓存的 GPU B,则不能直接命中 B 的本地缓存;系统需要根据架构选择重新计算、跨设备传输或进行缓存感知路由。

工程边界:什么时候不能复用?

相同文本本身不足以保证可复用。系统通常需要检查分词后的 Token 前缀,以及模型权重、位置编码、LoRA Adapter 和其他影响计算结果的状态是否兼容。按固定块管理的实现还可能只复用完整命中的块。

多租户场景也需要考虑缓存隔离:命中带来的延迟差异可能泄露其他请求是否使用过某段前缀。隔离策略会影响可共享范围和收益。

Prefix Caching 避免重复计算相同前缀;RadixAttention 进一步组织前缀的查找、共享、保护和淘汰。它们不改变注意力的数学公式,而是改善推理系统对已有 KV 的管理与复用。

SGLang 论文 ↗ · SGLang RadixCache 源码 ↗ · vLLM 前缀缓存文档 ↗

17

KV Cache 卸载:用 CPU 和 NVMe 扩展缓存容量

当上下文很长或并发请求很多时,即使使用 GQA、MLA 或 KV 量化,GPU 显存也可能不够。KV Cache Offloading(卸载)的思路是:将一部分缓存放到容量更大的 CPU 内存或存储层,需要时再加载回来。

它用较慢层级的容量缓解 GPU 显存压力,但也引出了新的问题:搬运缓存的代价,能否被节省的显存和计算抵消?

17.1 从 GPU 显存到多级存储

GPU HBM 带宽高、容量有限;CPU DRAM 通常容量更大;NVMe SSD 可以提供更大但通常更慢的存储。具体容量、带宽和访问路径取决于设备与部署方式。

KV Cache 的分层存储数据根据缓存策略在不同层级间移动
GPU HBM、CPU DRAM 和 NVMe 存储层级GPU HBM、CPU 内存和 NVMe 存储层按容量与访问速度构成层级;KV 可以根据策略在各层间写入或加载。GPU HBM:当前计算所需的 KV带宽高 · 容量有限CPU DRAM:暂存或复用的 KV容量较大 · 经设备互连传输NVMe / 外部存储:更大规模的缓存容量更大 · 需要存储 I/O按策略写入 / 需要时读取;各层级并非每个部署都启用

常见流程是:GPU 计算 KV → 根据缓存和内存策略写入 CPU 或其他层级 → 后续需要时加载到计算位置。对于在 GPU 执行的常规 Attention,卸载到 CPU 或磁盘的数据不会自动参与 GPU 计算,相关状态仍需通过实现支持的路径读取或搬回。

纯粹改变存放位置不会缩小 KV 的逻辑数据量;如果同时量化或选择性保留,数据量才会随之改变。

17.2 为什么 Decode 时的卸载困难?

标准全注意力的每个 Decode 步骤通常都要访问当前可见历史。假设历史 KV 共 8 GB,其中 6 GB 在 CPU;若每步计算都需要将这 6 GB 搬回 GPU,那么生成每个 Token 都可能发生大量传输。是否真的每步传输这么多,取决于缓存驻留、层级调度、稀疏访问和具体实现。

新 Token 的 Query→读取可见历史 KV→Attention 计算→输出新 Token↻

卸载可以让原本放不下的请求运行,但如果反复传输历史 KV,单 Token 延迟可能显著上升。预取、分批与计算—传输重叠能够隐藏部分开销,最终效果仍需实测。

17.3 FlexGen:分层放置与批量调度

FlexGen 面向 GPU 显存有限、能够容忍较高延迟的生成任务,联合规划权重、KV Cache 和激活在 GPU、CPU 与磁盘上的存放和访问。它通过优化放置策略、分块调度和较大的有效 batch 提高数据复用,并研究权重与缓存量化。

其目标侧重容量受限条件下的吞吐;较大 batch 和多层级搬运可能带来较高的单请求等待时间,不必然适合交互式低延迟服务。FlexGen 论文 ↗

17.4 InfiniGen:选择性预取重要 KV

InfiniGen 研究在卸载系统中减少从主机内存获取的数据。它利用当前层输入及下一层部分信息,轻量预测下一层可能重要的历史位置,预取对应 KV,再进行注意力计算。

CPU 中的历史 KV→预测下一层重要位置→选择性预取→GPU 注意力计算

其代价包括预测本身的开销和可能遗漏重要位置带来的质量影响。因为它选择性访问历史,不能简单视为只改变存储位置、同时完整执行原始全注意力的方案。InfiniGen 论文 ↗

17.5 现代服务:分层前缀缓存

第 16 节介绍了 RadixAttention 如何查找和共享前缀。分层缓存进一步让部分可复用 KV 从 GPU 扩展到 CPU 或其他存储层:GPU 上没有命中时,如果较慢层仍保留兼容的缓存,系统可以加载它,避免从头计算整个前缀。

SGLang HiCache 提供 GPU、CPU 与可配置外部存储的分层缓存;vLLM KV Offloading 的 OffloadingConnector 可将完成的 KV 块放到 CPU 等较慢层级,命中后再按需加载。实际可用后端、策略和配置以所用版本为准。SGLang HiCache ↗ · vLLM KV Offloading ↗

场景缓存何时加载?主要目标
跨请求复用前缀后续请求命中较慢层中的前缀时用加载代替重复 Prefill;比较加载与重新计算成本
活跃长序列 Decode生成过程中需要访问被卸载的历史时缓解 GPU 容量压力;控制反复传输的延迟

这两种场景都涉及搬运 KV,但访问频率和优化目标不同;不能把前缀缓存的加载开销直接套用到每个 Decode 步骤,反过来也一样。

17.6 四种方案对比

方案核心思路主要优化目标关键代价
FlexGenGPU、CPU、磁盘协同放置与调度权重、激活和 KV显存受限条件下的推理吞吐数据传输与较高延迟
InfiniGen预测并选择性预取重要 KV减少 Decode 的主机到设备传输预测开销、遗漏重要信息的风险
SGLang HiCacheGPU、CPU、可配置存储后端分层管理扩大可复用前缀缓存容量缓存加载、存储 I/O 与管理开销
vLLM KV Offloading将可复用 KV 块卸载至较慢层级,命中时加载扩大前缀缓存容量与复用能力数据搬运和缓存管理开销

17.7 如何估算搬运代价?

可以用数据量除以有效带宽,估算一次传输的理想时间下界:

传输时间 ≥ 本次传输的 KV 数据量 ÷ 有效传输带宽

例如 1 GB ÷ 64 GB/s ≈ 15.6 ms;这里 GB 与 GB/s 均采用十进制单位。

这不包括数据准备、同步、调度及其他系统开销;若传输与计算重叠,增加的端到端延迟也不一定等于完整传输时间。判断是否值得卸载,应一起测量命中率、TTFT、每 Token 延迟、吞吐、GPU/CPU 内存和数据传输量。

KV 卸载的核心权衡是:用更大容量的存储层容纳或复用历史状态,同时承担数据搬运与调度成本。

18

Prefill 与 Decode 分离:为什么一次推理要交给两组 GPU?

用户 A 正在等待模型逐 Token 输出;此时用户 B 提交了一份长达 32K Token 的文档。若长 Prompt 的 Prefill 与 A 的 Decode 在同一组 GPU 上竞争资源,A 的输出间隔就可能出现波动。

Prefill/Decode Disaggregation(PD 分离)的思路是:由不同的 GPU 或 GPU 集群分别处理 Prefill 和 Decode,并在阶段之间交接 KV Cache 等状态。

18.1 为什么两个阶段容易相互干扰?

Prefill 处理输入 Prompt,计算各层状态并建立历史 KV;大量输入位置可以并行计算。Decode 基于已有上下文逐步生成,常见配置下每轮每个请求推进一个 Token。实际的首 Token 采样与输出位置取决于服务实现。

特征PrefillDecode
处理方式并行处理较多输入 Token多轮增量生成
常见关注点计算吞吐、Prompt 长度权重与 KV 读取、显存带宽、调度延迟
典型指标首 Token 延迟 TTFT输出 Token 间延迟 ITL / 平均 TPOT
主要目标尽快处理输入稳定、持续地产生输出

这些是常见特征,不是固定规律;批量、模型、上下文长度和硬件都会影响瓶颈。Continuous Batching 与 Chunked Prefill 可以在混合部署中缓解干扰,但仍需权衡两种工作的调度。

18.2 PD 分离:让两组资源各司其职

一次请求如何在两个阶段之间流转图为概念流程;首 Token 输出位置与状态交接方式取决于具体实现
Prefill 与 Decode 分离部署用户请求由调度器送往 Prefill 集群处理 Prompt 并建立 KV Cache;KV 和请求状态传给 Decode 集群,后者继续生成输出。用户请求调度与路由Prefill 集群处理 Prompt / 建立 KVDecode 集群加载状态 / 增量生成输出结果持续返回 Token阶段交接:传输所需 KV Cache 与请求状态两组资源可分别配置和扩容
  1. 调度器将请求送到 Prefill 节点,处理输入并生成所需状态。
  2. 系统把后续生成需要的 KV Cache 和相关请求状态交给 Decode 节点。
  3. Decode 节点利用这些状态继续自回归生成。
  4. 两组节点可以按各自负载选择并行配置和资源规模。

阶段分离降低了两类计算在同一 GPU 上的直接干扰,但它们仍可能竞争网络、存储或其他共享资源。

18.3 为什么 KV Cache 是交接的关键?

Decode 节点若要利用 Prefill 已经完成的结果,通常需要获取各注意力层对应的历史 KV。对于常规 MHA/GQA,单次交接的数据量可用下式估算:

M_KV = 2 × B × S × L × H_KV × D × b

B:序列数;S:缓存长度;L:注意力层数;H_KV:KV 头数;D:头维度;b:每元素字节数。公式假设各层配置相同、K/V 维度相同。

例如,单请求 16 Ki Token、32 层、8 个 KV 头、每头 128 维,使用 BF16:

2 × 1 × 16,384 × 32 × 8 × 128 × 2 字节 = 2 GiB

若该请求的完整缓存都需跨节点交接,理论数据量约为 2 GiB;实际传输会受已命中前缀、分片和精度等因素影响。MLA 等结构需要按其实际缓存表示计算,混合架构还可能需要传输其他状态。

18.4 Splitwise、DistServe 与 Mooncake

系统核心关注点
Splitwise按阶段特点分离部署,优化硬件选择、资源利用和成本。
DistServe减少 Prefill/Decode 干扰,联合配置资源与并行策略,提高满足 TTFT、TPOT 约束的有效吞吐。
Mooncake以 KV Cache 为中心,结合分离计算、跨节点缓存、复用、传输与调度。

Mooncake 的分布式缓存还可以让后续请求复用兼容的公共前缀,减少重复 Prefill。Splitwise ↗ · DistServe ↗ · Mooncake ↗

18.5 为什么分离不一定更快?

P/D 分离前应先做 Chunked Prefill 基线:只有阶段干扰显著、KV 交接/网络成本可接受、两种资源池利用率适配时,分离才可能改善尾延迟或有效吞吐。它不是对单请求 TTFT 必然加速的开关。

分离减少了 GPU 上的阶段干扰,也增加了 KV 传输、阶段交接、排队和协调成本。短 Prompt 的 Prefill 本来就较短时,额外开销可能抵消隔离带来的收益;两阶段处理能力不匹配时,也可能在交接处排队。

如果实现必须在 KV 传输和交接完成后才能返回首 Token,那么 TTFT 可以粗略拆成队列、Prefill、传输和交接等耗时;如果 Prefill 节点先返回首 Token,交接则可能更多影响首个 Token 之后的间隔。实际流程还可能重叠,不能统一把传输时间简单加到 TTFT 上。

  • TTFT:从请求到首 Token 返回的延迟。
  • TPOT / ITL:每输出 Token 的平均耗时与相邻 Token 到达间隔。
  • Goodput:满足服务延迟目标的有效吞吐,具体统计口径应以系统定义为准。
  • 资源与网络:KV 数据量、带宽、传输延迟、GPU 数量和利用率。

长 Prompt、高并发且对输出稳定性敏感的负载值得评估 PD 分离;短 Prompt、低请求量或 GPU 资源紧张时,混合部署配合合适的调度也可能更经济。vLLM 分离式 Prefill 文档 ↗

18.6 补充:Ring Attention 解决的是另一类问题

当序列过长,单个设备无法容纳所需状态时,Ring Attention 可将序列和分块注意力计算分布到多个设备,并在设备之间传递 KV 块。它关注如何跨设备处理一条长序列;PD 分离关注如何安排不同请求的 Prefill 和 Decode 阶段。二者解决的问题不同,是否组合取决于系统实现。Ring Attention 论文 ↗

PD 分离不直接缩小 KV Cache,而是用额外的状态交接与调度成本,换取阶段隔离和资源配置的灵活性。

PART 05 / 06

2026 年的新模型和工作负载

从新一代模型看三条变化:历史的表示方式、生成的执行路径,以及 Agent 请求的上下文生命周期。

DeepSeek V4 改变长历史的保存与访问方式;DFlash / DFlare 优化生成的串行验证;Kimi 的 KDA 和 Agent Swarm 分别影响模型状态与多任务上下文。MoE 的稀疏性主要在专家计算侧,不能直接等同于 KV Cache 压缩。

模型内部历史状态如何保存、检索MLA · KDA · CSA/HCA
生成过程多少轮串行前向DFlash · DFlare
应用和服务上下文如何拆分、复用Agent Swarm · Prefix Cache
19

逐模型比较:技术如何组合

跨模型比较 KV Cache,不能只看总参数量或是否使用 Attention。需要具体看哪些层保存逐位置状态、每个位置保存多少数据、历史可见范围,以及推理框架采用什么缓存布局。下面选四条代表性的模型路线对比。

代表路线核心状态主要减少的成本需要额外关注
Llama 3.1 / GQA每位置保留少量 KV 头与 MHA 相比,缩小逐 Token KV历史长度仍直接影响 Decode 读取
DeepSeek-V3 / MLAKV 联合潜变量 + 位置分量避免展开式多头历史存储矩阵吸收与位置处理、Kernel 复杂性
Qwen3.5 / 混合递归状态 + 部分全注意力 KV多数层不保存逐 Token 标准 KV混合状态管理与质量、算子支持
Gemma 4 / 局部+全局局部窗口状态 + 全局层历史局部层历史长度封顶层级头维不同,K=V 物理存储另核算

注意:这里的“代表路线”不是性能排名。每项技术改变不同的计算与存储变量,不能只比较模型总参数量或相对 MHA 的缓存压缩率。

核验示范:Gemma 4 31B 的缓存到底怎么算?

Gemma 4 31B 官方配置标明 60 层,按 5 层局部注意力 + 1 层全局注意力循环,即 50 层局部、10 层全局。局部层 Hkv=16、head_dim=256、窗口=1024;全局层 Hkv=4、global_head_dim=512;配置还标注 attention_k_eq_v=true。

通用逐层估算:M = Σₗ Tₗ × (Hₖ,ₗDₖ,ₗ + Hᵥ,ₗDᵥ,ₗ) × 字节数

假设 128 Ki Token、BF16、K/V 各存一份:
局部层:50 × 1024 × 2 × 16 × 256 × 2 B = 0.78125 GiB
全局层:10 × 131072 × 2 × 4 × 512 × 2 B = 10 GiB
合计:10.78125 GiB(假定 K/V 分别存一份)

其中 K/V 的 2 倍来自“假设分别物化”。若所有相关层在物理上确实只保存一份 K=V 张量,则上述主体数据减半,为 5.390625 GiB;是否如此必须检查具体缓存实现、位置编码、归一化和实际 block 布局,不能由 attention_k_eq_v 字段直接推断得到一个确定的物理 GiB 数字。

因此,原文的“128K 约 5.4 GB”只有在明确一份共享状态、GiB 单位等假设时才可能接近理论值,不能不带条件地作为所有框架的缓存结论。上述 10.78125 GiB 也只是明示假设的算例,不代表任何框架的实测峰值显存。局部层因保留窗口的边界、分页与缓存策略可能有额外开销。

完整跨代参数:作为可核查的参考附表

下面保留原文整理的参数线索,供按模型查阅。“原文列出上下文”不是官方模型原生长度,表格中的 KV 值多为特定形状与精度下的理论估算;具体部署请核验 checkpoint 配置。

展开:2023–2024 基础模型参数表
模型年份参数注意力Q / KV 头原文列出上下文16-bit KV / Token
Llama 2 7B ↗20237BMHA32 / 324K512 KiB
Llama 2 70B ↗202370BGQA64 / 84K320 KiB
Mistral 7B ↗20237BGQA + 滑动窗口32 / 832K;窗口 4K128 KiB,窗口内
Gemma 1 7B ↗20247BMHA16 / 168K448 KiB
Llama 3.1 8B ↗20248BGQA32 / 8128K128 KiB
Llama 3.1 405B ↗2024405BGQA128 / 8128K504 KiB
Qwen2 72B ↗202472BGQA64 / 8128K320 KiB
Gemma 2 27B ↗202427BGQA + 局部/全局32 / 168K;局部 4K随长度及层类型变化
Phi-4 14B ↗202414BGQA40 / 1016K200 KiB
DeepSeek-V2 ↗2024236B;约 21B 激活MLA + MoE128 / 潜变量128K按 MLA 结构计算
DeepSeek-V3 ↗2024671B;约 37B 激活MLA + MoE128 / 潜变量128K约 68.6 KiB
展开:2025–2026 跨代模型参数表
模型参数注意力 / KV 设计Q / KV 头原文列出上下文关注点
Gemma 3 27B ↗27BGQA + 局部/全局32 / 依层配置128K;局部 1K局部与全局层约 5:1
Qwen3 0.6B ↗0.6BGQA16 / 832KQ 与 KV 头数 2:1
Qwen3 4B ↗4BGQA32 / 832K36 层,Q 与 KV 头数 4:1
Qwen3 8B ↗8.2BGQA32 / 8原文列 128K支持思考模式;上下文扩展方式需查具体模型卡
Qwen3 32B ↗32.8BGQA64 / 8原文列 128K64 层,Q 与 KV 头数 8:1
Qwen3-235B-A22B ↗235B;约 22B 激活GQA + MoE64 / 4原文列 128K16:1 Q/KV 头数
Qwen3.5 4B ↗4B全注意力 + 线性注意力混合见官方配置原文列 262K混合结构应分别计算 KV 与递归状态
Llama 4 Scout ↗109B;约 17B 激活长上下文 + MoE见官方配置10M模型卡的上下文上限不等同有效检索能力
Llama 4 Maverick ↗400B;约 17B 激活长上下文 + MoE见官方配置1M多模态模型;缓存布局需看实现
Kimi K2 ↗约 1T;约 32B 激活MLA + MoE64 / 潜变量原文列 128K潜变量缓存与总参数量并不直接对应
Gemma 4 E2B ↗约 5.1B;有效约 2.3BGQA + 局部/全局 + KV 层共享8 / 1128K不同层的窗口和共享方式影响总缓存
Gemma 4 E4B ↗约 8B;有效约 4.5BGQA + 局部/全局 + KV 层共享8 / 2128K42 层;局部窗口 512;部分层共享 KV
Gemma 4 26B-A4B ↗26B;约 3.8B 激活GQA + 局部/全局 + MoEKV:局部 8 / 全局 2256K按层类型分别估算
Gemma 4 31B ↗31BGQA + 局部/全局KV:局部 16 / 全局 4256K混合头维度、窗口和共享 K/V 表示
Bonsai 8B约 8.2B原文列为低位权重 + GQA原文未列原文列 128K权重位宽不自动决定 KV 位宽;实验来源参见第 10 节;未独立复现

容量对照:统一 BF16 假设

模型统一 BF16 假设下的单 Token 数据边界
Qwen3-32B256 KiB64×8×128;只计标准 KV
DeepSeek-V3约 68.6 KiB61 层 × (512+64) 元素,潜变量+位置分量
Qwen3.5-9B全注意力部分 32 KiB另有 24 层递归/卷积等状态
Gemma 4 31B不能用单一常数局部层窗口、全局层长度、维度与共享布局不同
20

DeepSeek V4:用不同粒度处理长上下文

上下文很长时,不同距离的信息未必需要用相同粒度保存。DeepSeek V4 的注意力设计结合短滑动窗口、压缩稀疏注意力(CSA)和高压缩注意力(HCA):近期保留较细的状态,较远历史则压缩后参与注意力计算。

不同层类型 · 数据流对照
CSA · c4a8 Token 加权窗口 / stride 4

历史 → 压缩池 → 索引器 Top-K → 与本地窗口合并 → Attention

长历史先稀疏选择;索引器有额外状态与计算
HCA · c128a128 Token 非重叠窗口 / stride 128

历史 → 强压缩池 → 直接访问全部压缩条目 → Attention

不走同样的 Top-K 索引器
纯滑动窗口层局部 128 Token

最近历史 → 窗口内注意力

不读取远程压缩池

示意为不同层类型,不是每一层同时执行三条历史分支;CSA/HCA 层自身还可结合局部未压缩窗口。参考 vLLM 的 DeepSeek V4 实现分析及 Transformers 模型文档。

看图前先区分层类型:CSA(c4a)、HCA(c128a)和纯 Sliding Window 在模型中是不同注意力层的配置;图中并排展示它们的处理逻辑,不表示单层同时执行全部路径。Transformers 官方结构说明 ↗

V4 还让 Key 和 Value 共享同一组向量,进一步减少需要存储的状态;这项设计与沿序列压缩是不同的节省来源。为正确处理共享表示中的位置编码,注意力输出还需要经过逆旋转位置编码(inverse RoPE)。vLLM 注意力机制说明 ↗

近期信息:短滑动窗口

每种压缩路径都与短窗口配合:窗口保留近期未压缩的 KV,让当前 Query 能直接访问附近位置。特别是当新 Token 尚未形成完整的压缩组时,窗口为它提供近期上下文;窗口之外的远处信息则通过压缩路径访问。

CSA:先压缩,再选择相关条目

CSA(Compressed Sparse Attention,压缩稀疏注意力)对应 vLLM 实现中的 c4a。它先沿序列维度把历史信息压缩为较少的 KV 条目,再由索引器针对当前 Query 选择其中一部分条目,最后把选中的压缩条目与近期窗口一起用于注意力计算。

历史 KV→约 4 倍序列压缩→索引器选取相关条目→与近期窗口一起计算注意力

根据 vLLM 的实现说明,c4a 的一个压缩条目对 8 个原始位置做加权汇聚,步长为 4,因此相邻压缩窗口会重叠,条目数量约为原序列的四分之一。压缩后仍可针对不同 Query 选择不同条目,但选中的条目已经是压缩表示,并非原始逐 Token KV。

HCA:压缩得更强,直接关注压缩历史

HCA(Heavily Compressed Attention,高压缩注意力)对应 c128a。它以 128 个原始位置、步长 128 形成一个压缩条目,相比 CSA 产生更少的远程历史条目。由于压缩后的条目数量较少,vLLM 所述实现可直接对可见的压缩条目做注意力,不需要像 c4a 那样用索引器筛选 top-k。

历史 KV→约 128 倍序列压缩→关注可见的压缩条目→结合近期窗口

更强的压缩意味着缓存条目更少,也意味着更长的一段历史被汇聚到一个表示中。它不是把原始内容无损压缩后随时逐 Token 还原;模型需要学习如何利用这些压缩表示。

对比CSA / c4aHCA / c128a
压缩方式8 个位置加权汇聚,步长 4,窗口重叠128 个位置汇聚,步长 128
远程条目数量约为原始位置数的 1/4约为原始位置数的 1/128
如何使用远程条目索引器选择部分条目,再计算注意力对可见压缩条目计算注意力
近期细节配合短滑动窗口配合短滑动窗口

这里的 4 与 128 是序列维度上的压缩步长,不能直接当作整个模型的显存缩减倍数;索引器、窗口、压缩状态及缓存精度都会影响总量。vLLM 实现说明 ↗

动态示意:CSA 与 HCA 如何处理历史

点击播放或拖动步骤,观察历史压缩、CSA 的稀疏选择,以及两种路径如何结合近期窗口。

原始历史
CSA / c4a
HCA / c128a
近期窗口

图中用 16 个方块抽象历史,仅演示相对粒度和选择过程;CSA 的实际重叠窗口、HCA 的 128 个 Token、索引器打分、因果边界及具体层布局未按比例模拟。选中条目只是示例,并非真实模型输出。

模型与服务实现

DeepSeek V4-Pro 和 V4-Flash 采用不同规模与层配置。vLLM 对 V4-Pro 的一组估算中,单条 1,048,576 Token 序列使用 BF16 缓存约 9.62 GiB;对照的 61 层 V3.2 式结构约 83.9 GiB。比较包含文章所列的压缩缓存与索引器假设,不是模型权重或整个服务进程的总显存。查看 vLLM 对比与计算依据 ↗

在服务系统中,不同压缩率的层还需要兼容分页、前缀缓存与状态传输。vLLM 使用统一的逻辑块单位,并通过 kernel 融合等方式降低管理与计算开销。DeepSeek V4 模型卡 ↗

不同层的缓存布局需要统一管理

不同注意力层使用不同的压缩率,同一个逻辑 Token Block 在物理内存中可能对应不同数量的缓存条目。vLLM 将相关缓存组织到少量页面大小类别,并融合压缩、归一化、写入等操作,以降低管理和搬运开销。vLLM 实现说明 ↗

21

DFlash:块扩散式推测解码

DFlash 不是 KV Cache 压缩方法。它通过块扩散草稿模型一次提出多个候选 Token,再由目标模型验证,尝试减少自回归生成需要的串行验证轮次。 DFlash 论文 ↗

它为何出现在 KV 文章中?DFlash 重点减少目标模型的串行验证轮次,不是通用的 KV 压缩算法;草稿 K/V、目标 KV、验证接受率都会影响真实性能。

为什么需要推测解码?

普通自回归解码中,目标模型每次生成下一个 Token,再进入下一轮。推测解码先让较轻量的草稿模型提出一串候选,目标模型再并行计算验证所需的各位置结果。如果多个候选被接受,一轮目标模型验证就能推进多个 Token。

方式候选如何产生目标模型如何参与
普通自回归不使用草稿;逐 Token 生成逐步生成下一个 Token
自回归草稿的推测解码草稿模型通常逐个提出候选一次前向并行验证候选位置
DFlash块扩散草稿模型一次前向并行预测候选块并行验证候选,接受有效前缀

并非所有推测解码方法都使用独立、逐 Token 的草稿模型;表格展示的是便于理解的典型对照。DFlash 缩短草稿阶段的串行路径,但仍有草稿、验证和状态管理开销。

DFlash 如何把目标模型的信息交给草稿模型?

目标模型先处理 Prompt。DFlash 从目标模型选取多个深度的隐藏状态,将它们拼接、融合并投影为上下文特征;随后,这些特征被投影到草稿模型各层的 Key、Value 中,供草稿阶段使用。

交互:逐步查看 DFlash 流程

拖动滑块,或使用键盘方向键切换步骤。

1 目标模型 Prefill2 提取多层特征3 融合与投影4 注入草稿 KV5 并行生成草稿6 目标模型验证

注入的是草稿模型各层自己的 KV 表示:目标隐藏状态先经过相应投影,不能理解成直接复制目标模型的 KV Cache。

一个验证例子

假设草稿模型一次提出 4 个候选:A、B、C、D。以贪心验证为例,如果 A、B 与目标模型的选择一致,而 C 是第一个不一致的位置,那么接受 A、B;C 处使用目标模型给出的结果,后面的 D 不再直接采纳。随后从已确定的前缀继续下一轮。

交互:一次候选块验证

绿色表示被接受的候选;拒绝后如何校正取决于验证与采样算法。

目标模型验证候选时可能临时产生相应 KV。若某些候选未被接受,后续生成只能基于已确认前缀;具体系统会丢弃或回退无效候选对应的临时状态,并维护草稿模型与目标模型各自的缓存。随机采样时,是否保持目标模型分布取决于验证算法,不能简单等同于贪心逐项匹配。

为什么接受长度和草稿成本都重要?

平均每 Token 延迟 ≈ (草稿时间 + 验证时间)÷ 每轮平均产出 Token 数

简化示意;一次验证还可能产生目标模型给出的额外 Token,具体统计口径依算法而定。

如果候选经常被拒绝,草稿和验证成本只能摊到少数 Token 上;如果草稿模型太慢,即使接受较多候选也未必划算。DFlash 的设计同时尝试降低草稿延迟,并通过目标模型特征帮助草稿模型提出更容易被接受的候选。

与 KV Cache 优化有什么关系?

GQA、MLA 等方法主要减少每个 Token 需要保存或读取多少 KV 状态;DFlash 则尝试让一次目标模型验证推进更多 Token,减少串行验证轮次。两者可以组合,但 DFlash 本身不是目标模型 KV 压缩方法,还会引入草稿模型和草稿缓存的额外开销。

论文中的实验结果

DFlash 论文报告,在其评测的模型、任务和实现设置下,部分结果相对自回归基线达到 6 倍以上加速,部分比较相对 EAGLE-3 的加速比进一步提高;这些数字受模型、硬件、并发、生成长度和接受长度影响,不能直接当作所有部署的收益。查看 DFlash 论文 ↗

2026 后续:DFlare 把草稿层级的条件信息做得更丰富

DFlash 的块扩散草稿机制减少了部分串行生成轮次,但草稿层的条件信息若过于相似,会限制深层草稿模型的表达能力。2026 年 EMNLP 的 DFlare 让不同草稿层从更广的目标模型层中学习各自的条件组合,以提高候选块的质量。论文在多个模型上报告了优于 DFlash 的端到端加速,属于论文设定下的测量,不能直接相乘推广到其他工作负载。

22

从 Kimi K2.5 Agent Swarm 到 K3.1:模型状态与上下文编排

普通对话中,一次请求可能是“输入问题 → 生成回答”。Agent 则可能反复搜索、读取网页、运行工具、分析结果,再决定下一步。这些工具结果和中间输出会不断进入上下文,也会带来相应的 KV Cache。

重要边界:Agent Swarm 的首要价值是任务拆分、并行协作与上下文隔离,并不保证所有子 Agent 的 KV 总量下降。需要区分峰值驻留、总 Token 数、任务延迟与可复用前缀。

问题:一个任务为什么会产生很长的上下文?

假设用户要求“调研三个模型的推理优化方案并写报告”。Agent 需要查询资料、读取论文、记录结论和交叉验证。如果所有过程都堆进同一个会话,即使其中一些工具输出只在某一步有用,它们也可能留在上下文中,随着后续生成继续占用缓存。

用户任务→搜索资料→读取长文→工具输出进入上下文→继续搜索与推理

因此,Agent 的问题不只是“单个 Token 的 KV 有多大”,还包括:哪些上下文需要一直保留?哪些工作可以隔离?任务完成后缓存何时能回收?

Agent Swarm:分任务,也分上下文

Kimi K2.5 论文描述了 Agent Swarm:由协调器(Orchestrator)拆解任务、安排子 Agent 并汇总结果。每个子 Agent 在自己的工作上下文中处理子任务;完成后,把与主任务相关的结果返回给协调器,而不是默认把完整工具调用过程全部塞回主上下文。

示例:调研任务的上下文流转每条支线代表一个独立工作上下文,KV Cache 与该上下文对应
Agent Swarm 中任务、上下文与缓存的流转主 Agent 拆分任务到三个子 Agent。每个子 Agent 独立搜索处理并维护局部上下文和缓存;它们将相关结论返回主 Agent,任务结束后局部缓存可以按系统策略释放或复用。主 Agent:拆解调研任务维护自己的上下文和 KV子 Agent A:模型架构子 Agent B:推理系统子 Agent C:实验结果搜索 → 阅读 → 分析搜索 → 阅读 → 分析搜索 → 阅读 → 分析局部上下文 + KV Cache局部上下文 + KV Cache局部上下文 + KV Cache返回相关结论返回相关结论返回相关结论主 Agent:汇总与写作使用返回的结果,而非全部过程子任务结束后,局部缓存可按服务策略释放或保留复用

这是一种上下文分片:主 Agent 聚合相关结果,子 Agent 处理各自的原始资料。分片并不自动保证正确性;子 Agent 返回的结论如果遗漏细节,主 Agent 可能需要追问或重新读取来源。

多份上下文对显存意味着什么?

单 Agent多个子 Agent
大量工具输出可能积累在同一个上下文中。每个子任务有独立上下文;主上下文主要接收相关结果。
一条较长序列对应一份较大的缓存。多条较短序列分别有缓存;并行运行时可能同时占用显存。
执行过程较容易集中追踪。可并行探索,但需要调度、汇总和跨任务核验。

例如,假设每个 Token 的 KV 理论数据量为 64 KiB:三条各 8,192 Token 的子任务如果同时运行,合计约需 1.5 GiB KV;一条 24,576 Token 的序列在相同假设下也是约 1.5 GiB。这个算术例子没有计入各任务重复的系统提示词、协调器、权重、分页和运行时开销。

任务完成后,局部 KV 是否立即释放、保留用于前缀复用或转移到其他存储,取决于服务系统。缩短单条上下文和减少总显存是两个不同问题。

PARL:学习何时并行

Agent Swarm 不只是把任务机械地平均分成多份。论文提出 PARL(Parallel-Agent Reinforcement Learning),训练协调器学习何时创建和安排子 Agent。训练信号同时关注任务表现,以及是否形成有效并行、子任务能否完成等因素,目的是避免始终串行或无意义地创建大量分支。

其他上下文管理方式

Discard-all:论文在 BrowseComp 的上下文管理实验中,引用了达到 Token 阈值后截断历史的策略。它可以限制已积累的历史,但具体保留哪些信息、是否重新计算或释放缓存取决于实现;丢弃内容也可能包含后续需要的证据。

Toggle:论文还讨论一种训练方法,在有 Token 预算的优化阶段与常规推理扩展阶段之间交替,以减少冗余输出。报告中的 25%–30% 平均输出 Token 减少来自对 Kimi K2 Thinking 的实验,不能直接当作 K2.5 在所有任务中的固定收益。

论文报告的性能结果

在论文的 WideSearch 测试中,Agent Swarm 达到指定任务表现时,相比单 Agent 基线报告约 3–4.5 倍的执行时间改善。这是特定评测条件下的墙钟时间结果,不代表 GPU 总成本或 KV 峰值显存也按同样比例降低。Kimi K2.5 技术报告 ↗

从 K2.5 到 K3 / K3.1:把模型层和 Agent 层分开看

前面讨论的 Agent Swarm 属于应用工作负载层:任务分解可缩短关键路径、隔离多个子任务的上下文,却不保证所有请求的 KV 总字节数下降。后来 Kimi K3 系列又在模型架构层改变历史表示,这是另一条技术线,二者可以同时影响服务设计。

阶段模型/应用层变化与 KV 或状态的关系
K2.5 · Agent Swarm任务拆分、多 Agent 并行与协调改变请求数、上下文重叠、存活时间和缓存复用机会
K393 层:69 KDA + 24 Gated MLA大部分层使用递归状态,部分层保留潜变量历史
K3.1 Pro93 层:69 KDA + 24 Gated DSA全注意力分支改为门控稀疏注意力,更强调长上下文访问效率

因此不能把 K3 的“多数层使用 KDA”理解成整个模型没有逐位置状态,也不能把 Agent Swarm 的任务并发收益解释成 KV 压缩。大规模部署需要同时估算单 Agent 状态、并发 Agent 数、共享前缀命中、调度与 KV 迁移。K3.1 官方模型卡 ↗

23

MoE 与 KV Cache:计算更稀疏,缓存会更小吗?

MoE(Mixture of Experts,混合专家模型)通过稀疏激活,让模型拥有大量参数,却不必让每个 Token 执行全部专家计算。例如,DeepSeek-V3 约有 671B 总参数,每个 Token 约激活 37B 参数。

那么,每次只激活少量参数,是否意味着 KV Cache 也会大幅减少?不会自动减少。MoE 主要改变专家网络的计算;KV Cache 的理论大小主要由注意力结构和缓存配置决定。

23.1 MoE 改变 Transformer 的哪一部分?

典型 Transformer Block 的 Attention 处理上下文关系,FFN 对 Token 特征进行非线性变换。MoE 通常用多个专家网络替换部分 FFN,并通过 Router 为每个 Token 选择少量专家。Attention 的 K、V 状态仍由相应注意力层维护。

示意:Attention 与 MoE FFN 的位置当前 Token 被路由到专家 1 和专家 3;图中省略残差、归一化和可能存在的共享专家
Transformer Block 中 Attention、KV Cache 和专家路由的关系输入经注意力层,注意力产生的 KV 状态写入缓存;之后路由器选择专家 1 和 3,专家 2 未选中,两个专家输出经加权合并。输入 Hidden StatesAttentionKV CacheRouter:选择专家专家 1 · 选中专家 2 · 未选中专家 3 · 选中加权合并输出缓存 K、V →

例如,一层有 64 个专家,每个 Token 选中其中 2 个。Dense FFN 为每个 Token 执行同一个 FFN;MoE 执行被选中的专家,而不是全部 64 个专家。节省的是相对于执行全部专家的计算量,不能仅凭专家数量断言它一定比任意 Dense FFN 更省计算。

无论 Token 被路由到哪个专家,注意力层仍按照自己的结构计算和保存所需历史状态。仅将 Dense FFN 替换为 MoE FFN,不会自动改变该注意力层的理论 KV 大小。

23.2 为什么 MoE 不直接决定 KV Cache 大小?

对于各层结构相同、K/V 维度相同的常规 MHA 或 GQA,等长序列的理论 KV 大小为:

M_KV = 2 × B × S × L × H_KV × D × b

B:并发序列数;S:每条序列的缓存长度;L:注意力层数;H_KV:KV 头数;D:每头维度;b:每元素字节数。第一个 2 对应 K 和 V。

公式中没有专家数或 Top-K 参数:在注意力配置、序列长度和缓存精度保持不变时,只改变 FFN 为 Dense 或 MoE,不会改变这个逻辑缓存估算。MLA、混合窗口或线性注意力需要依据实际状态另行计算。

23.3 DeepSeek-V3:MoE 与 MLA 分别解决什么?

技术主要作用对 KV Cache 的直接影响
MoE路由并执行部分 FFN 专家,在较大总参数规模下控制活跃专家计算不直接减少注意力 KV
MLA将 KV 信息压缩为潜变量,并单独处理位置相关 Key改变注意力缓存表示,减少理论数据量
MoE + MLA同时设计专家计算与注意力状态分别作用于不同开销

DeepSeek-V3 采用 MoE 和 MLA:前者负责专家网络的稀疏激活;后者让推理时可以缓存压缩 KV 潜变量和解耦的位置 Key。其 KV Cache 的压缩来自注意力设计,不能简单归因于 MoE。DeepSeek-V3 技术报告 ↗

23.4 MoE 不直接减少 KV,为什么仍影响显存?

GPU 显存还要容纳模型权重和运行时开销:

GPU 显存占用 ≈ 权重 + KV Cache + 激活、通信缓冲与其他运行时开销

每个 Token 只激活少量专家,不代表其他专家权重不占空间。系统仍需保证可能被路由到的专家可以访问;如果大量专家权重驻留在 GPU 上,留给 KV Cache 的容量就会减少,可能限制长上下文请求的并发。

专家并行(EP)可以把专家分布在不同设备;量化和权重卸载也可能降低单卡权重占用,但可能引入通信、数据搬运和调度开销。实际显存分布取决于部署方式。vLLM 专家并行文档 ↗

23.5 专家负载不均如何间接影响 KV?

Decode 批次中的 Token 经过 Router 后,可能集中到少数热门专家。在分布式专家并行中,这可能让部分设备处理更多计算与通信,其他设备等待较慢的分支完成:

Token 路由集中→专家负载不均→计算或通信等待→请求完成变慢→KV 驻留时间可能变长

单个 Token 的 KV 大小没有因此改变,但较长的请求驻留时间可能影响可用缓存容量的周转和系统吞吐。因此,实际 MoE 服务还需要关注专家负载均衡、跨设备通信与调度。vLLM 大规模 DeepSeek 推理 ↗

理解 MoE 与 KV Cache 的关系,关键是区分专家计算量、模型权重占用和历史状态缓存:三者会在系统中相互影响,但不是同一个指标。

PART 06 / 06

从技术原理走向工程选型

先算理论下限、再用 Profiler 找瓶颈,最后对齐 TTFT、TPOT 与质量。

WORKED EXAMPLE · 从官方配置出发

实战推演:Qwen3-32B,32K 上下文到底占多少 KV?

以 Qwen3-32B 官方配置为准:64 层、64 个 Query 头、8 个 KV 头、head_dim=128,缓存假设 BF16(2 字节)。这里的 32K 约定为 32,768 个已缓存 Token,不是在配置内强行声称超出原生支持长度。

每层每 Token = 2 × 8 × 128 × 2 = 4096 B = 4 KiB
全模型每 Token = 64 × 4 KiB = 256 KiB
32 Ki Token 单请求 = 256 KiB × 32768 = 8 GiB

只计算逻辑 KV 数据,不包括权重、激活、碎片、CUDA Graph 与其他运行时开销。

单请求 · 32K8 GiBBF16 理论 KV
4 条独立请求32 GiB等长、无共享
8 条独立请求64 GiB等长、无共享

容量只是第一张账单:再估算 Decode 历史读取量

在单 Token、常规全注意力的理想化下,满 32K 时每生成一步需要访问大约 8 GiB 历史 K/V(加上当前 Token 的少量数据)。这不是总 HBM 流量,也不是模型一步只需要读取这么多数据:权重、激活、Kernel 实现、缓存命中以及并行部署都会改变实际访存。

如果仅将 8 GiB 除以假设的 3 TB/s 有效带宽,会得到约 2.7 ms 的理想读带宽下界;不能把它当作真实 TPOT 预测,因为有效带宽、硬件、并发、调度与其他模型工作可能完全不同。

选型前,先回答这五个工程问题

  1. 容量是否真的卡住?记录剩余 KV block、峰值显存、preemption 和请求排队。
  2. 是 Prefill 还是 Decode 慢?分别看 TTFT、TPOT、Prefill 吞吐、Decode 批大小。
  3. 瓶颈是不是 KV 带宽?通过 Profiler 对比 Attention 读带宽、权重 GEMM、通信和 CPU 调度开销。
  4. 流量有多少重复前缀?比较冷/热缓存 TTFT、命中率、可共享的实际 Token 数。
  5. 优化是否损害质量或尾延迟?在固定输入/输出分布下对比 P50/P95/P99、Goodput、任务正确率与成本。
该案例为基于公开配置的 容量与理论下界推演,不是实际 GPU 基准测试。落地时必须固定框架版本、KV 数据类型、TP/PP、最大上下文、缓存块大小以及并发负载。
24

从技术原理走向工程选型:先诊断,再组合

同一个模型在不同负载下,瓶颈可能完全不同。以下固定 Qwen3-32B、32K 上下文等假设,分别分析显存容量、Decode 带宽和共享前缀,再据此选择优化策略。

统一案例 · Qwen3-32B · Text Only

先固定可复现的模型与场景

以 Qwen3-32B 官方配置为例:64 层、64 个 Query 头、8 个 KV 头、head_dim=128。只考虑32K(32,768)已处理 Token、BF16 KV、等长请求,暂不考虑分页元数据、对齐和张量并行的实际布局。

单 Token:2(K/V) × 64层 × 8 KV头 × 128维 × 2字节
    = 262,144 B = 256 KiB

单条 32K:256 KiB × 32,768 = 8 GiB

8 GiB 是整个模型的一条逻辑请求的 KV 数据量,不等于某一 TP Rank 必须独占 8 GiB,也不包括模型权重。

1 条 · 32K8 GiB逻辑 BF16 KV
4 条 · 32K32 GiB互不共享
8 条 · 32K64 GiB互不共享

场景 A:显存不够——先分清“逻辑 KV”与“真实占用”

并发从 1 → 4 → 8 增加,理想逻辑 KV 从 8 → 32 → 64 GiB 增加。这不意味着一块 80GB GPU 跑 8 条一定可行:权重、激活、CUDA Graph、块管理、临时缓冲与碎片都要占空间,还要区分 TP/PP/DP 后每张卡实际持有哪些层和头。

核对项可观测量下一步可能动作
理论 KV 与实际 block已用块、保留块、碎片/未使用页调整块配置、容量预留、请求预算
模型权重及其他显存GPU 峰值、KV 可用内存、工作区权重精度、分布式切分、并发上限
KV 占主导相同负载下分精度的显存变化测试 KV 量化;注意质量与 Kernel

选型思路:如果模型可以切换架构,可考虑 GQA/MLA/混合架构;模型结构固定时,优先评估 KV 精度、并发和缓存块策略,再考虑窗口或卸载的质量/延迟代价。不要直接把“理论 KV 小了 2×”写成“峰值显存少了 2×”。

场景 B:显存够,Decode 仍慢——用带宽模型建立下界,再用 Profile 验证

对于单请求,若理想化地假设每个 Decode 步都需要从 HBM 读取完整的 8 GiB 历史 KV,并且有效读取带宽达到 3 TB/s(十进制):

理想 KV 读取下界 = 8 × 2³⁰ B / (3 × 10¹² B/s)
         ≈ 0.002863 s = 2.86 ms / Token

这是一个只计 KV 读取的理想化下界,不是实际 TPOT 预测。多卡 TP 时每个 Rank 只持有相应分片,带宽需要按 GPU/Rank、设备互联与实际 kernel 布局评估;GPU 峰值带宽也不等于真实有效带宽。

还应同时考虑权重流量。例如模型约 32.8B 参数、BF16 权重的数量级约为 65.6 GB(十进制),batch 很小时权重读取可能同样重要;随着批次变大,权重可在更多 token 之间摊销,但 KV 读取通常随活跃请求的上下文增加。最终应分辨是权重带宽、KV 带宽、Attention kernel、通信还是调度限制了 TPOT。

① 理论

算每 Rank KV、权重字节与带宽下界

② 采样

固定输入/输出长度与并发,采 TTFT、TPOT、吞吐

③ Profiler

拆分 GEMM、Attention、通信、CPU 调度

④ 验证

一次只改一个策略,核对质量与 E2E

关键判断:如果 Profile 显示瓶颈主要在权重或调度,即便 KV 容量减半,单请求 TPOT 也可能不会显著改善。此时优先优化 batch/调度、kernel 或权重路径;不要预设 KV 量化一定是最佳选择。

场景 C:多个请求引用同一个 16K 文档——前缀缓存能省多少?

假设 8 条请求,每条总计 32K Token,其中前 16K 的 Token 序列和位置完全相同,后 16K 各不相同。不共享时理论 KV 是 8 × 8 = 64 GiB。如果服务端通过引用计数和块共享真正实现“只保存一份相同前缀”,则:

共享部分:16K × 256 KiB = 4 GiB
每条独有后缀:16K × 256 KiB = 4 GiB
8 条共享后的理想 KV:4 GiB + 8×4 GiB = 36 GiB
相比互不共享的 64 GiB,理论节省 28 GiB

前提是请求的前缀 Token IDs、位置、模型及相关执行配置一致,且缓存块仍驻留、真正支持多请求共享。实际还受块粒度、映射、淘汰策略与缓存命中率影响。

性能收益的主战场是重复 Prefill。命中前缀后,新请求可以跳过已有前缀的重复计算;但生成后续 Token 时,仍需要在注意力规则允许的范围内使用这些前缀状态。若 16K 文档反复出现,重点关注 prefix hit rate、命中 Token 数、TTFT 和 Prefill GPU 时间,不能只看“节省了多少 GiB”。

基于瓶颈选择优化方案

FIELD GUIDE · DECISION FLOW
容量、TTFT、TPOT 或 Goodput 不满足 SLO
↓ 找到主要受限资源
容量 / 并发不足

每 Rank KV + 权重 + 工作区 → 块利用率 → 精度、并发、卸载策略。

Decode TPOT 高

Profiler 区分权重、KV、kernel、通信及 host;对真正瓶颈做优化。

重复 Prefill 高

看 Token 级前缀重复、命中率、缓存路由及驻留时间。

Prefill 干扰 Decode

先测 Chunked Prefill;仍存在尾延迟问题再评估 P/D 分离与 KV 传输。

↓ 固定模型 / 负载 / 硬件,做 A/B 测量
真实峰值显存 · TTFT · TPOT / ITL · P95/P99 · Goodput · 质量 · 成本

组合原则:内存收益可计算,性能收益不能直接相乘

例如同形状条件下,MHA 32 KV 头 → GQA 8 KV 头令理论 KV 字节数变为 1/4;再将 BF16 改为 FP8,在忽略元数据的前提下可到 1/8。这只是容量比,不是 TPS、TPOT 或模型质量的提升倍数。 GQA 改了结构,不能对训练完成的任意 MHA 模型无损切换;量化则需要对应 Kernel 与质量验证。

优化轴典型技术主要验证指标
状态形状 / 表示GQA、MLA、CLA、KDA每 Token 字节、长文本质量、kernel
数值与历史范围KV 量化、SWA、淘汰缓存与带宽、检索质量
内存管理与复用PagedAttention、RadixAttention实际块利用率、Prefix hit、TTFT
存储 / 网络Offloading、HiCache、P/D 分离传输时间、尾延迟、Goodput
注意力执行 / 生成FlashAttention、DFlashKernel 时间、每轮接受数、E2E

混合架构的状态管理

现代混合模型可能同时包含逐位置 KV、强压缩历史池、索引器状态、短局部窗口、递归状态和短卷积缓冲。因此“KV Cache 管理器”正在演变为更加一般的推理状态资源管理器:需要按层类型追踪生命周期,按请求路由复用,按存储层级迁移,并保证不同策略下的数值质量与恢复正确性。

结语

KV Cache 优化不是“把缓存越做越小”,而是先明确存什么、读多少、放哪里、何时算、能否复用,再用相同负载下的显存、延迟、吞吐、任务质量与成本验证系统级收益。

APPENDIX

参考资料与核验入口

论文、模型配置与推理框架说明的链接尽量保持可追溯;不同来源的数值请按其测试条件阅读。

来源口径:模型参数以指定官方 checkpoint 配置为准;论文数值仅在其测试设置下成立;算例以文中明确列出的维度、字节数与条件计算;实际性能需要独立测量。本文是基于原始 KV Cache 研究综述扩展的中文技术解读,并非原文逐句翻译。

25

参考资料

以下链接包括原始综述、研究论文、官方模型配置及推理框架实现。