模型权重已经放进 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 的读取带宽、并发调度、复用与传输,仍可能决定系统性能。
六部分内容
可按顺序阅读,也可从关心的问题开始查阅。
不同方法改变的不是同一个变量
| 优化路径 | 改变的对象 | 代表方案 | 适用边界 |
|---|---|---|---|
| 结构与表示 | 每 Token 的历史状态 | GQA、MLA、CLA | 减少存储量,不等于相同比例加速 |
| 数值 | 缓存元素的位宽 | KV Quantization | 需要计入 scale、反量化及精度损失 |
| 保留与访问 | 哪些历史位置可见 | SWA、淘汰、稀疏注意力 | 可能改变可用历史信息和模型质量 |
| 计算方式 | 如何计算 Attention / 状态 | FlashAttention、线性注意力 | 前者优化 IO;后者改变序列状态机制 |
| 内存系统 | 缓存如何分配、复用、存放 | PagedAttention、Prefix Cache、Offloading | 逻辑 KV 大小与物理占用不同 |
| 服务调度 | 何时计算与搬运 KV | Chunked Prefill、P/D 分离 | 关注尾延迟、吞吐、网络与资源利用率 |
为什么 KV Cache 会成为瓶颈?
从第一个 Token 到 128K 上下文:看懂计算复用、显存容量与读取带宽。
从 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 的关系 |
|---|---|---|
| 2017 | Transformer 提出自注意力。 | 每层产生 Q、K、V;生成阶段出现重复历史计算。 |
| 2018–2022 | 预训练与模型规模快速增长。 | 参数、批量和序列长度共同推高推理成本。 |
| 2023–2024 | GQA、PagedAttention、MLA 和 KV 量化等路线发展。 | 结构、分配、表示精度开始分别优化。 |
| 2025–2026 | 更长上下文、混合架构和 Agent 服务。 | 缓存的共享、生命周期、传输与调度更加重要。 |
理解 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:技能标签 |
|---|---|
| A | Megatron、TP、PP |
| B | EP、MoE、NCCL |
| C | PyTorch、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:将不同来源的信息融合起来
根据岗位需求,你已经判断出三位候选人的匹配程度,接下来可以结合他们各自的能力,形成对人才供给的整体认识。
标准 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 是模型权重。实际实现还需处理位置编码、多头、批次和缓存更新。
什么是 KV Cache,为什么需要它?
推理通常分为 Prefill 与 Decode。Prefill 处理输入 Prompt,在各注意力层建立已处理位置的 K、V,并能从最后位置的输出分布得到首个生成 Token。随后 Decode 将已生成 Token 作为新输入继续前向,计算该输入位置的 Q/K/V 并将 K/V 追加到缓存,用 Q 访问当前可见的历史。这里要区分“本轮作为输入计算的 Token”和“本轮刚采样出来、将在下一轮处理的 Token”。
没有缓存时,简单实现会在后续生成步骤中反复计算旧 token 的状态;有缓存则用显存换取对历史投影的复用。Q 一般只在当前步骤使用,因此通常不需要像 K、V 一样持久保存。
标准 KV 字节数 = 2 × L × Hkv × D × T × B × SL:层数;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、混合注意力、量化元数据和分页开销需要另算。
调整参数,观察缓存规模
理想数据体积;不含量化 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 维 / FP16 | 512 KiB | 64 GiB |
| GQA 示例 | 32 层 / 8 KV 头 / 128 维 / FP16 | 128 KiB | 16 GiB |
| MQA 示例 | 32 层 / 1 KV 头 / 128 维 / FP16 | 16 KiB | 2 GiB |
只比较公式中的数据体积;不同模型、有效上下文、实际 kernel 和额外内存并未等同。
交互:缓存如何随 token 增长
问题:当内存成为瓶颈
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 |
| 限制注意力范围 | 让部分或全部注意力层只访问局部窗口,或混合使用局部与全局层。 | 滑动窗口、局部—全局注意力 |
如何让每个 Token 的缓存更小?
从独立 KV 头到潜变量、跨层共享和数值压缩:改变每个 Token 保存什么。
多头注意力 MHA:昂贵的基线
多头注意力(Multi-Head Attention,MHA)把注意力计算分成多个头。每个头使用自己的 Query、Key 和 Value,在不同的表示子空间中计算注意力;各头的结果随后被拼接并投影,形成这一层的输出。
在标准 MHA 中,每个 Query 头都有各自对应的 Key 头和 Value 头。因此,32 个 Query 头对应 32 个 Key 头和 32 个 Value 头,三者是一一对应的关系。
为什么 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 Token | 512 KiB | 32 层合计 |
| 1,024 Token | 512 MiB | 512 KiB × 1,024 |
| 4,096 Token | 2 GiB | 512 KiB × 4,096 |
| 131,072 Token | 64 GiB | 512 KiB × 131,072 |
这是 Llama 2 7B 的头数、层数和维度对应的 FP16 数据量示例;不包含权重、激活、分页及其他运行时开销。128 Ki Token 仅用于展示线性增长,不表示该模型原生支持这一上下文长度。
多查询注意力 MQA:共享一套记忆
在 MHA 中,每个 Query 头都有独立的 Key 和 Value。多查询注意力(Multi-Query Attention,MQA)保留多个 Query 头,让它们共享同一个 Key 头和一个 Value 头。
不同的 Query 头仍然可以计算不同的注意力权重,但它们读取的是同一组 K、V。这样,模型不必为每个 Query 头分别保存一份历史 K、V。MQA 论文 ↗
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 头数 | 单层 / Token | 32 层 / Token |
|---|---|---|---|
| MHA | 32 | 16 KiB | 512 KiB |
| MQA | 1 | 0.5 KiB | 16 KiB |
在层数、头维度、缓存精度和 Token 数相同的条件下,该 MQA 示例的理论 KV 数据量是 MHA 的 1/32。这是缓存大小的比较,实际速度提升还取决于 kernel、硬件和工作负载。
分组查询注意力 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 的缓存大小
每 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 |
|---|---|---|---|---|
| MHA | 32 | 32 | 512 KiB | 基线 |
| GQA | 32 | 8 | 128 KiB | 缓存为 1/4 |
| MQA | 32 | 1 | 16 KiB | 缓存为 1/32 |
以上比较固定层数、头维度、精度和 Token 数。GQA 的理论 KV 数据量是该 MHA 配置的四分之一,实际性能还取决于实现与工作负载。
GQA 在模型中的应用
GQA 已用于 Llama、Mistral、Qwen 和 Gemma 等模型,但不同规格的头数各不相同。
| 模型 | 年份 | 注意力类型 | Query 头 | KV 头 | 相对同配置 MHA |
|---|---|---|---|---|---|
| Llama 2 7B / 13B | 2023 | MHA | 32 / 40 | 32 / 40 | 1× |
| Llama 2 70B | 2023 | GQA | 64 | 8 | 8× |
| Llama 3.1 8B / 70B / 405B | 2024 | GQA | 32 / 64 / 128 | 8 / 8 / 8 | 4× / 8× / 16× |
| Mistral 7B | 2023 | GQA | 32 | 8 | 4× |
| Qwen2 / Gemma 2 | 2024 | GQA | 依规格而定 | 依规格而定 | 依配置计算 |
表中倍数表示其他条件相同时,由 KV 头数减少得到的理论压缩比,并非不同模型的实际显存或速度对比。GQA 论文 ↗ · Llama 2 ↗ · Llama 3 ↗
多头潜在注意力 MLA:换一种表示
MQA 和 GQA 通过共享来减少 KV 头数。DeepSeek-V2 的多头潜在注意力(MLA)换了一个思路:不缓存展开后的全部多头 K、V,而是为每个 Token 保存一个更紧凑的 KV 联合潜变量,并单独处理位置编码相关部分。
第一步:把输入表示投影到低维空间
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,映射的秩受到这个维度的约束。
矩阵吸收:把对历史 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ₜ)“先对低维历史加权求和,再投影”与“先逐项上投影,再加权求和”在理想线性计算中等价。实现中还会结合多头输出投影做相应融合。
边界:以上仅推导内容分支。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 405B | GQA,8 个 KV 头 | 405B | 504 KiB |
| Qwen2.5 72B | GQA | 72B | 320 KiB |
| DeepSeek-V3 | MLA | 671B(约 37B 激活) | 约 68.6 KiB |
这些是按各模型配置、16 位缓存估算的理论数据量;不同模型的层数、结构和参数规模不同,不能仅凭此表判断质量或实际速度。DeepSeek-V3 技术报告 ↗
交互:对比不同缓存结构
跨层注意力 CLA:沿层数方向共享
MQA/GQA 在同一层的 Query 头之间共享 K、V;跨层注意力(Cross-Layer Attention,CLA)则让相邻的 Transformer 层共享 K、V,从层数方向减少独立缓存。
标准注意力与跨层共享
标准注意力中,每一层通常都为历史 Token 计算并保存自己的 K、V。CLA 可以将相邻层分组:组内某一层生成 K、V,其他层使用这组缓存,同时仍计算各自的 Query。
图中表示的是缓存的共享关系,并不意味着第 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 论文 ↗
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 的误差。低位宽节省空间,但不一定保留原值的全部细节。
交互:位宽、近似与理论体积
相对 16-bit 元素,8、4、2 bit 的理想元素数据量分别是 1/2、1/4、1/8。但实际缓存还可能包含 scale、零点、分组信息、未量化的近期缓存和对齐开销;总显存还包含模型权重、激活等,所以不会按同一比例下降。
2. 为什么 KV 量化不容易?
K 用来与 Query 计算匹配分数,K 的误差可能改变注意力权重;V 是被加权汇总的内容,V 的误差会影响输出表示。如果少数异常值远大于其他数值,它们会扩大一个量化组的取值范围,让其他数值可用的精度变粗。
图是帮助理解量化粒度的简化示意: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. 方法对比
| 方法 | 主要思路 | 论文讨论的位宽 | 实际部署还需关注 |
|---|---|---|---|
| KIVI | Key 逐通道、Value 逐 Token;近期残差缓冲 | 2-bit K/V | 分组参数、残差缓存、反量化开销 |
| KVQuant | Pre-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 Cache | 32K KV | 总驻留内存 |
|---|---|---|---|---|
| Bonsai 基线 | 低位权重 | FP16 | 4,608 MiB | 6,011 MiB |
| 同模型 + Q4 KV | 低位权重 | Q4 | 1,296 MiB | 2,699 MiB |
这组数字转述自参考原文引用的本地日志;原文所链实验仓库目前无法访问,因此未能独立核对日志。它只说明该实验配置的结果,不能推广为所有模型或设备的表现。
如何让历史访问更少、更高效?
窗口、保留策略、IO-aware Attention 与递归状态:改变读取历史的方式。
滑动窗口与局部—全局注意力
滑动窗口注意力(Sliding Window Attention,SWA)限制每层直接可访问的历史范围:每次只看最近 W 个 Token。窗口之外的位置不再参与该层当前步的直接注意力计算,因此在适当的缓存策略下可以减少保留与读取的数据。
滑动窗口如何移动?
假设窗口大小为 4。当新 Token 到来时,窗口向前移动一格:最新位置进入窗口,最早的位置离开窗口。对于只依赖该局部窗口的层,离开窗口的 KV 可以从该层缓存中移除。
窗口长度决定该层的直接可见范围:旧 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 ↗
局部—全局注意力:不同层看不同范围
如果所有层都只使用局部窗口,远处信息不能被当前层直接访问。另一种架构是让一部分层使用局部窗口,另一部分层使用全注意力:局部层控制缓存增长,全局层则保留访问完整可见历史的路径。
例如,Gemma 3 在部分模型配置中采用 5 个局部层搭配 1 个全局层的模式。实际层数、窗口和 KV 头配置需以具体模型为准。Gemma 3 技术报告 ↗
总缓存 ≈ 局部层数 × 单层每 Token 大小 × min(T, W)
+全局层数 × 单层每 Token 大小 × T简化示意:若不同层的 KV 头数或维度不同,应分别计算后求和。
因此,纯局部窗口层的缓存可以封顶;只要架构中仍有全局层,总缓存通常仍随上下文长度增长,只是增长速度较慢。
KV Cache 淘汰:只保留部分历史
在生成过程中,模型对历史 Token 的关注通常并不均匀:有些位置反复获得较高注意力,有些位置受到的关注较少。KV Cache 淘汰方法利用这一观察,在有限的缓存预算内选择保留部分历史,以减少显存和读取开销。
关键问题是:哪些位置应该留下?不同方法使用不同的判断依据,也在不同阶段做选择。
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 + 最近窗口 |
| SnapKV | Prompt 处理后,依据末尾观察窗口 | 选中的前缀位置 + 观察窗口 |
| PyramidInfer | 依据注意力及层间重要性差异 | 各层选出的关键上下文 |
| PyramidKV | 按层调整缓存预算 | 低层通常较多,高层较少 |
这些策略减少了可供未来注意力访问的历史状态。过去关注较少的内容仍可能与后续问题有关,因此应同时评估缓存节省和目标任务质量。
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。
Online Softmax:分块后怎样得到正确结果?
普通 Softmax 需要一整行分数的归一化结果,而分块计算一次只看到一部分分数。Online Softmax 在扫描各块时,为每个 Query 维护三个状态:已处理分数的最大值 m、经过最大值平移后的指数和 ℓ,以及对应 Value 的加权累积 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:处理 Prompt | Decode:逐 Token 生成 |
|---|---|---|
| Query / KV 长度 | 通常分别为 N / N | 单 Token 解码时通常为 1 / N |
| Score 形状 | N × N | 1 × N |
| 典型关注点 | 计算量、临时显存和 HBM IO | 历史 KV 读取带宽和短 Query 的并行度 |
| 优化重点 | 分块避免完整中间矩阵物化 | 高效读取 KV,并利用适合解码的并行策略 |
这些是典型情况,实际瓶颈会随批量、上下文、硬件和 kernel 改变。部分面向解码的实现采用 Split-KV:将较长的 KV 序列分给多个线程块并行处理,再合并局部结果,以改善短 Query 下的并行度。
与其他 KV 技术如何配合?
| 技术 | 主要优化对象 |
|---|---|
| MQA / GQA | 减少需要缓存和读取的 KV 头数 |
| MLA | 使用更紧凑的 KV 表示 |
| KV 量化 | 减少缓存中每个元素的存储位数 |
| PagedAttention | 改善 KV 块的分配和共享 |
| FlashAttention | 优化注意力计算的数据访问和执行方式 |
这些技术针对不同层次,可以组合,但具体兼容性与收益取决于模型和推理框架。FlashAttention 官方实现 ↗
线性注意力与状态空间模型:当历史状态不再随 Token 增长
标准全注意力为历史中的每个 Token 保留 K/V。GQA、量化和窗口能降低开销,但仍然以逐位置状态为中心。线性注意力和部分递归架构采用另一种方式:将历史汇聚到持续更新的固定形状状态,而不是为每个历史位置都保存独立 K/V。
递归状态的形状可以固定,不随历史 Token 数增长,但这不意味着拥有无限、精确、可随机访问的记忆。它需要将历史压入有限状态。全注意力则保留逐位置 K/V,允许每个 Query 再对各位置评分。两种设计各有得失。
14.1 两种历史:逐位置账本与持续更新的状态
标准全注意力像逐页保存档案:要查历史时,可以按 Query 与各页 Key 的相关性读取 Value。递归架构更像维护一份会被不断修订的工作摘要:新内容进来后更新状态,旧 Token 不再都以独立 K/V 的形式存在。
两者的运行时成本不能只看“有没有 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。
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 系统更复杂。
如何把 KV Cache 管好、用好?
分页、前缀复用、分层存储和 P/D 分离:让缓存进入服务系统。
PagedAttention:为 KV Cache 引入分页管理
即使单个 Token 的 KV Cache 已经通过 GQA、MLA 或量化缩小,不同请求的长度和结束时间仍会造成显存预留浪费和碎片。PagedAttention 解决的是如何按需分配和复用缓存块,而不是改变每个 Token 的数学表示。
问题:请求长度不同,内存却可能提前预留
生成开始时,系统通常不知道每个请求最终会有多长。如果提前为请求分配一整块连续空间,按最多 4,096 个 Token 预留,而实际只用 200 个,就会有大量空间暂时闲置。不同请求同时运行时,空闲内存还可能被切分成无法满足连续分配的片段。
内部碎片
已经分配给请求的空间中,有一部分未被使用。
外部碎片
空闲空间分散,虽然总量足够,却不一定能满足连续分配。
预留浪费
为尚未生成的 Token 预留空间,但请求可能提前结束。
内部碎片和预留浪费可能重叠:这里分别强调“已分配空间未用”和“为未来增长提前保留”。PagedAttention 论文报告,在当时评估的系统中,KV Cache 内存有 60%–80% 因碎片或预留等原因未得到有效利用。
解决方法:像虚拟内存一样管理 KV 块
这里的“分页”与模型是否采用 GQA/MLA 无关:它解决的是逻辑 Token 位置到物理 KV 块的映射。改变分配方式不能直接改变每个 Token 的逻辑 KV 元素数。
操作系统的虚拟内存把地址空间划分为页,通过页表将连续的逻辑地址映射到不必连续的物理位置。PagedAttention 借鉴这一思路,将每个请求的 KV Cache 切成固定大小的逻辑块,再按需映射到 GPU 上的物理块。
例如,每块可以容纳 16 个 Token;32 个 Token 的块也可用于说明。请求到来时按需分配块,序列增长时再申请新块。逻辑上相邻的块无需在物理显存中相邻,注意力 kernel 通过块表找到对应的 K、V。
这样不必为每个请求一次性预留最大长度的连续空间。固定块也有权衡:最后一块可能未填满,块表与访问映射也会带来一定开销。
块共享:公共前缀不必重复保存
多个请求如果具有完全相同且状态兼容的前缀,例如相同的系统提示词,可以让它们的块表指向同一批 KV 物理块。生成分叉时,只有各自新增或修改的部分需要独立保存;部分场景可使用写时复制。
请求 A:共享前缀块 → A 的后续块
请求 B:共享前缀块 → B 的后续块能否共享取决于模型、Token 前缀、位置和其他相关状态是否兼容,也取决于服务框架的缓存策略。
PagedAttention 不会改变每个 Token 的 K、V 理论元素数,也不能消除所有内存开销;它主要减少分配与预留浪费。实际收益随请求长度分布、并发和系统实现变化。TensorRT-LLM、SGLang 等推理系统也提供分页式 KV 管理或相关缓存管理机制。
前缀缓存与 RadixAttention:让多个请求共享 KV Cache
假设我们构建了一个基于大语言模型的文档问答系统。用户 A 和用户 B 使用同一份约 8K Token 的技术文档,但分别询问“核心观点是什么”和“有哪些性能优化方法”。两个请求的问题不同,前面的系统提示词和文档却相同。
如果没有复用,模型需要分别处理两个请求的全部输入,即使公共前缀完全相同,也要重复执行其中的 Attention、MLP 等计算。Prefix Caching(前缀缓存)要解决的就是:能否让第二个请求复用第一个请求已经计算过的公共前缀?
为什么历史 KV 可以复用?
在因果注意力中,每个 Token 只能关注自己及之前的位置,不能看到未来 Token。假设两个请求的前缀完全相同:
请求 A:系统提示词 + 共享文档 + 问题 A
请求 B:系统提示词 + 共享文档 + 问题 B公共前缀中的 Token 不受后续问题 A 或 B 的影响。因此,在模型权重、位置及其他相关计算状态兼容的条件下,公共前缀对应的逐层 K、V 可以复用。
计算并保存
处理公共前缀和问题 A,并按缓存策略保存可复用的前缀 KV。
匹配并复用
找到命中的公共前缀,复用其 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 论文 ↗
图中每条从根开始的路径表示一个 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 的管理与复用。
KV Cache 卸载:用 CPU 和 NVMe 扩展缓存容量
当上下文很长或并发请求很多时,即使使用 GQA、MLA 或 KV 量化,GPU 显存也可能不够。KV Cache Offloading(卸载)的思路是:将一部分缓存放到容量更大的 CPU 内存或存储层,需要时再加载回来。
它用较慢层级的容量缓解 GPU 显存压力,但也引出了新的问题:搬运缓存的代价,能否被节省的显存和计算抵消?
17.1 从 GPU 显存到多级存储
GPU HBM 带宽高、容量有限;CPU DRAM 通常容量更大;NVMe SSD 可以提供更大但通常更慢的存储。具体容量、带宽和访问路径取决于设备与部署方式。
常见流程是:GPU 计算 KV → 根据缓存和内存策略写入 CPU 或其他层级 → 后续需要时加载到计算位置。对于在 GPU 执行的常规 Attention,卸载到 CPU 或磁盘的数据不会自动参与 GPU 计算,相关状态仍需通过实现支持的路径读取或搬回。
纯粹改变存放位置不会缩小 KV 的逻辑数据量;如果同时量化或选择性保留,数据量才会随之改变。
17.2 为什么 Decode 时的卸载困难?
标准全注意力的每个 Decode 步骤通常都要访问当前可见历史。假设历史 KV 共 8 GB,其中 6 GB 在 CPU;若每步计算都需要将这 6 GB 搬回 GPU,那么生成每个 Token 都可能发生大量传输。是否真的每步传输这么多,取决于缓存驻留、层级调度、稀疏访问和具体实现。
卸载可以让原本放不下的请求运行,但如果反复传输历史 KV,单 Token 延迟可能显著上升。预取、分批与计算—传输重叠能够隐藏部分开销,最终效果仍需实测。
17.3 FlexGen:分层放置与批量调度
FlexGen 面向 GPU 显存有限、能够容忍较高延迟的生成任务,联合规划权重、KV Cache 和激活在 GPU、CPU 与磁盘上的存放和访问。它通过优化放置策略、分块调度和较大的有效 batch 提高数据复用,并研究权重与缓存量化。
其目标侧重容量受限条件下的吞吐;较大 batch 和多层级搬运可能带来较高的单请求等待时间,不必然适合交互式低延迟服务。FlexGen 论文 ↗
17.4 InfiniGen:选择性预取重要 KV
InfiniGen 研究在卸载系统中减少从主机内存获取的数据。它利用当前层输入及下一层部分信息,轻量预测下一层可能重要的历史位置,预取对应 KV,再进行注意力计算。
其代价包括预测本身的开销和可能遗漏重要位置带来的质量影响。因为它选择性访问历史,不能简单视为只改变存储位置、同时完整执行原始全注意力的方案。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 四种方案对比
| 方案 | 核心思路 | 主要优化目标 | 关键代价 |
|---|---|---|---|
| FlexGen | GPU、CPU、磁盘协同放置与调度权重、激活和 KV | 显存受限条件下的推理吞吐 | 数据传输与较高延迟 |
| InfiniGen | 预测并选择性预取重要 KV | 减少 Decode 的主机到设备传输 | 预测开销、遗漏重要信息的风险 |
| SGLang HiCache | GPU、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 卸载的核心权衡是:用更大容量的存储层容纳或复用历史状态,同时承担数据搬运与调度成本。
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 采样与输出位置取决于服务实现。
| 特征 | Prefill | Decode |
|---|---|---|
| 处理方式 | 并行处理较多输入 Token | 多轮增量生成 |
| 常见关注点 | 计算吞吐、Prompt 长度 | 权重与 KV 读取、显存带宽、调度延迟 |
| 典型指标 | 首 Token 延迟 TTFT | 输出 Token 间延迟 ITL / 平均 TPOT |
| 主要目标 | 尽快处理输入 | 稳定、持续地产生输出 |
这些是常见特征,不是固定规律;批量、模型、上下文长度和硬件都会影响瓶颈。Continuous Batching 与 Chunked Prefill 可以在混合部署中缓解干扰,但仍需权衡两种工作的调度。
18.2 PD 分离:让两组资源各司其职
- 调度器将请求送到 Prefill 节点,处理输入并生成所需状态。
- 系统把后续生成需要的 KV Cache 和相关请求状态交给 Decode 节点。
- Decode 节点利用这些状态继续自回归生成。
- 两组节点可以按各自负载选择并行配置和资源规模。
阶段分离降低了两类计算在同一 GPU 上的直接干扰,但它们仍可能竞争网络、存储或其他共享资源。
18.3 为什么 KV Cache 是交接的关键?
Decode 节点若要利用 Prefill 已经完成的结果,通常需要获取各注意力层对应的历史 KV。对于常规 MHA/GQA,单次交接的数据量可用下式估算:
M_KV = 2 × B × S × L × H_KV × D × bB:序列数;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,而是用额外的状态交接与调度成本,换取阶段隔离和资源配置的灵活性。
2026 年的新模型和工作负载
从新一代模型看三条变化:历史的表示方式、生成的执行路径,以及 Agent 请求的上下文生命周期。
DeepSeek V4 改变长历史的保存与访问方式;DFlash / DFlare 优化生成的串行验证;Kimi 的 KDA 和 Agent Swarm 分别影响模型状态与多任务上下文。MoE 的稀疏性主要在专家计算侧,不能直接等同于 KV Cache 压缩。
逐模型比较:技术如何组合
跨模型比较 KV Cache,不能只看总参数量或是否使用 Attention。需要具体看哪些层保存逐位置状态、每个位置保存多少数据、历史可见范围,以及推理框架采用什么缓存布局。下面选四条代表性的模型路线对比。
| 代表路线 | 核心状态 | 主要减少的成本 | 需要额外关注 |
|---|---|---|---|
| Llama 3.1 / GQA | 每位置保留少量 KV 头 | 与 MHA 相比,缩小逐 Token KV | 历史长度仍直接影响 Decode 读取 |
| DeepSeek-V3 / MLA | KV 联合潜变量 + 位置分量 | 避免展开式多头历史存储 | 矩阵吸收与位置处理、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 ↗ | 2023 | 7B | MHA | 32 / 32 | 4K | 512 KiB |
| Llama 2 70B ↗ | 2023 | 70B | GQA | 64 / 8 | 4K | 320 KiB |
| Mistral 7B ↗ | 2023 | 7B | GQA + 滑动窗口 | 32 / 8 | 32K;窗口 4K | 128 KiB,窗口内 |
| Gemma 1 7B ↗ | 2024 | 7B | MHA | 16 / 16 | 8K | 448 KiB |
| Llama 3.1 8B ↗ | 2024 | 8B | GQA | 32 / 8 | 128K | 128 KiB |
| Llama 3.1 405B ↗ | 2024 | 405B | GQA | 128 / 8 | 128K | 504 KiB |
| Qwen2 72B ↗ | 2024 | 72B | GQA | 64 / 8 | 128K | 320 KiB |
| Gemma 2 27B ↗ | 2024 | 27B | GQA + 局部/全局 | 32 / 16 | 8K;局部 4K | 随长度及层类型变化 |
| Phi-4 14B ↗ | 2024 | 14B | GQA | 40 / 10 | 16K | 200 KiB |
| DeepSeek-V2 ↗ | 2024 | 236B;约 21B 激活 | MLA + MoE | 128 / 潜变量 | 128K | 按 MLA 结构计算 |
| DeepSeek-V3 ↗ | 2024 | 671B;约 37B 激活 | MLA + MoE | 128 / 潜变量 | 128K | 约 68.6 KiB |
展开:2025–2026 跨代模型参数表
| 模型 | 参数 | 注意力 / KV 设计 | Q / KV 头 | 原文列出上下文 | 关注点 |
|---|---|---|---|---|---|
| Gemma 3 27B ↗ | 27B | GQA + 局部/全局 | 32 / 依层配置 | 128K;局部 1K | 局部与全局层约 5:1 |
| Qwen3 0.6B ↗ | 0.6B | GQA | 16 / 8 | 32K | Q 与 KV 头数 2:1 |
| Qwen3 4B ↗ | 4B | GQA | 32 / 8 | 32K | 36 层,Q 与 KV 头数 4:1 |
| Qwen3 8B ↗ | 8.2B | GQA | 32 / 8 | 原文列 128K | 支持思考模式;上下文扩展方式需查具体模型卡 |
| Qwen3 32B ↗ | 32.8B | GQA | 64 / 8 | 原文列 128K | 64 层,Q 与 KV 头数 8:1 |
| Qwen3-235B-A22B ↗ | 235B;约 22B 激活 | GQA + MoE | 64 / 4 | 原文列 128K | 16: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 + MoE | 64 / 潜变量 | 原文列 128K | 潜变量缓存与总参数量并不直接对应 |
| Gemma 4 E2B ↗ | 约 5.1B;有效约 2.3B | GQA + 局部/全局 + KV 层共享 | 8 / 1 | 128K | 不同层的窗口和共享方式影响总缓存 |
| Gemma 4 E4B ↗ | 约 8B;有效约 4.5B | GQA + 局部/全局 + KV 层共享 | 8 / 2 | 128K | 42 层;局部窗口 512;部分层共享 KV |
| Gemma 4 26B-A4B ↗ | 26B;约 3.8B 激活 | GQA + 局部/全局 + MoE | KV:局部 8 / 全局 2 | 256K | 按层类型分别估算 |
| Gemma 4 31B ↗ | 31B | GQA + 局部/全局 | KV:局部 16 / 全局 4 | 256K | 混合头维度、窗口和共享 K/V 表示 |
| Bonsai 8B | 约 8.2B | 原文列为低位权重 + GQA | 原文未列 | 原文列 128K | 权重位宽不自动决定 KV 位宽;实验来源参见第 10 节;未独立复现 |
容量对照:统一 BF16 假设
| 模型 | 统一 BF16 假设下的单 Token 数据 | 边界 |
|---|---|---|
| Qwen3-32B | 256 KiB | 64×8×128;只计标准 KV |
| DeepSeek-V3 | 约 68.6 KiB | 61 层 × (512+64) 元素,潜变量+位置分量 |
| Qwen3.5-9B | 全注意力部分 32 KiB | 另有 24 层递归/卷积等状态 |
| Gemma 4 31B | 不能用单一常数 | 局部层窗口、全局层长度、维度与共享布局不同 |
DeepSeek V4:用不同粒度处理长上下文
上下文很长时,不同距离的信息未必需要用相同粒度保存。DeepSeek V4 的注意力设计结合短滑动窗口、压缩稀疏注意力(CSA)和高压缩注意力(HCA):近期保留较细的状态,较远历史则压缩后参与注意力计算。
历史 → 压缩池 → 索引器 Top-K → 与本地窗口合并 → Attention
长历史先稀疏选择;索引器有额外状态与计算历史 → 强压缩池 → 直接访问全部压缩条目 → Attention
不走同样的 Top-K 索引器最近历史 → 窗口内注意力
不读取远程压缩池示意为不同层类型,不是每一层同时执行三条历史分支;CSA/HCA 层自身还可结合局部未压缩窗口。参考 vLLM 的 DeepSeek V4 实现分析及 Transformers 模型文档。
V4 还让 Key 和 Value 共享同一组向量,进一步减少需要存储的状态;这项设计与沿序列压缩是不同的节省来源。为正确处理共享表示中的位置编码,注意力输出还需要经过逆旋转位置编码(inverse RoPE)。vLLM 注意力机制说明 ↗
近期信息:短滑动窗口
每种压缩路径都与短窗口配合:窗口保留近期未压缩的 KV,让当前 Query 能直接访问附近位置。特别是当新 Token 尚未形成完整的压缩组时,窗口为它提供近期上下文;窗口之外的远处信息则通过压缩路径访问。
CSA:先压缩,再选择相关条目
CSA(Compressed Sparse Attention,压缩稀疏注意力)对应 vLLM 实现中的 c4a。它先沿序列维度把历史信息压缩为较少的 KV 条目,再由索引器针对当前 Query 选择其中一部分条目,最后把选中的压缩条目与近期窗口一起用于注意力计算。
根据 vLLM 的实现说明,c4a 的一个压缩条目对 8 个原始位置做加权汇聚,步长为 4,因此相邻压缩窗口会重叠,条目数量约为原序列的四分之一。压缩后仍可针对不同 Query 选择不同条目,但选中的条目已经是压缩表示,并非原始逐 Token KV。
HCA:压缩得更强,直接关注压缩历史
HCA(Heavily Compressed Attention,高压缩注意力)对应 c128a。它以 128 个原始位置、步长 128 形成一个压缩条目,相比 CSA 产生更少的远程历史条目。由于压缩后的条目数量较少,vLLM 所述实现可直接对可见的压缩条目做注意力,不需要像 c4a 那样用索引器筛选 top-k。
更强的压缩意味着缓存条目更少,也意味着更长的一段历史被汇聚到一个表示中。它不是把原始内容无损压缩后随时逐 Token 还原;模型需要学习如何利用这些压缩表示。
| 对比 | CSA / c4a | HCA / c128a |
|---|---|---|
| 压缩方式 | 8 个位置加权汇聚,步长 4,窗口重叠 | 128 个位置汇聚,步长 128 |
| 远程条目数量 | 约为原始位置数的 1/4 | 约为原始位置数的 1/128 |
| 如何使用远程条目 | 索引器选择部分条目,再计算注意力 | 对可见压缩条目计算注意力 |
| 近期细节 | 配合短滑动窗口 | 配合短滑动窗口 |
这里的 4 与 128 是序列维度上的压缩步长,不能直接当作整个模型的显存缩减倍数;索引器、窗口、压缩状态及缓存精度都会影响总量。vLLM 实现说明 ↗
动态示意:CSA 与 HCA 如何处理历史
点击播放或拖动步骤,观察历史压缩、CSA 的稀疏选择,以及两种路径如何结合近期窗口。
图中用 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 实现说明 ↗
DFlash:块扩散式推测解码
DFlash 不是 KV Cache 压缩方法。它通过块扩散草稿模型一次提出多个候选 Token,再由目标模型验证,尝试减少自回归生成需要的串行验证轮次。 DFlash 论文 ↗
为什么需要推测解码?
普通自回归解码中,目标模型每次生成下一个 Token,再进入下一轮。推测解码先让较轻量的草稿模型提出一串候选,目标模型再并行计算验证所需的各位置结果。如果多个候选被接受,一轮目标模型验证就能推进多个 Token。
| 方式 | 候选如何产生 | 目标模型如何参与 |
|---|---|---|
| 普通自回归 | 不使用草稿;逐 Token 生成 | 逐步生成下一个 Token |
| 自回归草稿的推测解码 | 草稿模型通常逐个提出候选 | 一次前向并行验证候选位置 |
| DFlash | 块扩散草稿模型一次前向并行预测候选块 | 并行验证候选,接受有效前缀 |
并非所有推测解码方法都使用独立、逐 Token 的草稿模型;表格展示的是便于理解的典型对照。DFlash 缩短草稿阶段的串行路径,但仍有草稿、验证和状态管理开销。
DFlash 如何把目标模型的信息交给草稿模型?
目标模型先处理 Prompt。DFlash 从目标模型选取多个深度的隐藏状态,将它们拼接、融合并投影为上下文特征;随后,这些特征被投影到草稿模型各层的 Key、Value 中,供草稿阶段使用。
交互:逐步查看 DFlash 流程
拖动滑块,或使用键盘方向键切换步骤。
注入的是草稿模型各层自己的 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 的端到端加速,属于论文设定下的测量,不能直接相乘推广到其他工作负载。
从 Kimi K2.5 Agent Swarm 到 K3.1:模型状态与上下文编排
普通对话中,一次请求可能是“输入问题 → 生成回答”。Agent 则可能反复搜索、读取网页、运行工具、分析结果,再决定下一步。这些工具结果和中间输出会不断进入上下文,也会带来相应的 KV Cache。
问题:一个任务为什么会产生很长的上下文?
假设用户要求“调研三个模型的推理优化方案并写报告”。Agent 需要查询资料、读取论文、记录结论和交叉验证。如果所有过程都堆进同一个会话,即使其中一些工具输出只在某一步有用,它们也可能留在上下文中,随着后续生成继续占用缓存。
因此,Agent 的问题不只是“单个 Token 的 KV 有多大”,还包括:哪些上下文需要一直保留?哪些工作可以隔离?任务完成后缓存何时能回收?
Agent Swarm:分任务,也分上下文
Kimi K2.5 论文描述了 Agent Swarm:由协调器(Orchestrator)拆解任务、安排子 Agent 并汇总结果。每个子 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 并行与协调 | 改变请求数、上下文重叠、存活时间和缓存复用机会 |
| K3 | 93 层:69 KDA + 24 Gated MLA | 大部分层使用递归状态,部分层保留潜变量历史 |
| K3.1 Pro | 93 层:69 KDA + 24 Gated DSA | 全注意力分支改为门控稀疏注意力,更强调长上下文访问效率 |
因此不能把 K3 的“多数层使用 KDA”理解成整个模型没有逐位置状态,也不能把 Agent Swarm 的任务并发收益解释成 KV 压缩。大规模部署需要同时估算单 Agent 状态、并发 Agent 数、共享前缀命中、调度与 KV 迁移。K3.1 官方模型卡 ↗
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 状态仍由相应注意力层维护。
例如,一层有 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 × bB:并发序列数;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 大小没有因此改变,但较长的请求驻留时间可能影响可用缓存容量的周转和系统吞吐。因此,实际 MoE 服务还需要关注专家负载均衡、跨设备通信与调度。vLLM 大规模 DeepSeek 推理 ↗
理解 MoE 与 KV Cache 的关系,关键是区分专家计算量、模型权重占用和历史状态缓存:三者会在系统中相互影响,但不是同一个指标。
从技术原理走向工程选型
先算理论下限、再用 Profiler 找瓶颈,最后对齐 TTFT、TPOT 与质量。
实战推演: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 与其他运行时开销。
容量只是第一张账单:再估算 Decode 历史读取量
在单 Token、常规全注意力的理想化下,满 32K 时每生成一步需要访问大约 8 GiB 历史 K/V(加上当前 Token 的少量数据)。这不是总 HBM 流量,也不是模型一步只需要读取这么多数据:权重、激活、Kernel 实现、缓存命中以及并行部署都会改变实际访存。
如果仅将 8 GiB 除以假设的 3 TB/s 有效带宽,会得到约 2.7 ms 的理想读带宽下界;不能把它当作真实 TPOT 预测,因为有效带宽、硬件、并发、调度与其他模型工作可能完全不同。
选型前,先回答这五个工程问题
- 容量是否真的卡住?记录剩余 KV block、峰值显存、preemption 和请求排队。
- 是 Prefill 还是 Decode 慢?分别看 TTFT、TPOT、Prefill 吞吐、Decode 批大小。
- 瓶颈是不是 KV 带宽?通过 Profiler 对比 Attention 读带宽、权重 GEMM、通信和 CPU 调度开销。
- 流量有多少重复前缀?比较冷/热缓存 TTFT、命中率、可共享的实际 Token 数。
- 优化是否损害质量或尾延迟?在固定输入/输出分布下对比 P50/P95/P99、Goodput、任务正确率与成本。
从技术原理走向工程选型:先诊断,再组合
同一个模型在不同负载下,瓶颈可能完全不同。以下固定 Qwen3-32B、32K 上下文等假设,分别分析显存容量、Decode 带宽和共享前缀,再据此选择优化策略。
先固定可复现的模型与场景
以 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 GiB8 GiB 是整个模型的一条逻辑请求的 KV 数据量,不等于某一 TP Rank 必须独占 8 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、吞吐
拆分 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”。
基于瓶颈选择优化方案
每 Rank KV + 权重 + 工作区 → 块利用率 → 精度、并发、卸载策略。
Profiler 区分权重、KV、kernel、通信及 host;对真正瓶颈做优化。
看 Token 级前缀重复、命中率、缓存路由及驻留时间。
先测 Chunked Prefill;仍存在尾延迟问题再评估 P/D 分离与 KV 传输。
组合原则:内存收益可计算,性能收益不能直接相乘
例如同形状条件下,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、DFlash | Kernel 时间、每轮接受数、E2E |
混合架构的状态管理
现代混合模型可能同时包含逐位置 KV、强压缩历史池、索引器状态、短局部窗口、递归状态和短卷积缓冲。因此“KV Cache 管理器”正在演变为更加一般的推理状态资源管理器:需要按层类型追踪生命周期,按请求路由复用,按存储层级迁移,并保证不同策略下的数值质量与恢复正确性。
KV Cache 优化不是“把缓存越做越小”,而是先明确存什么、读多少、放哪里、何时算、能否复用,再用相同负载下的显存、延迟、吞吐、任务质量与成本验证系统级收益。
参考资料与核验入口
论文、模型配置与推理框架说明的链接尽量保持可追溯;不同来源的数值请按其测试条件阅读。
来源口径:模型参数以指定官方 checkpoint 配置为准;论文数值仅在其测试设置下成立;算例以文中明确列出的维度、字节数与条件计算;实际性能需要独立测量。本文是基于原始 KV Cache 研究综述扩展的中文技术解读,并非原文逐句翻译。
参考资料
以下链接包括原始综述、研究论文、官方模型配置及推理框架实现。
注意力架构与模型
- Attention Is All You NeedTransformer
- Fast Transformer Decoding: One Write-Head is All You NeedMQA
- GQA: Training Generalized Multi-Query Transformer ModelsGQA
- Reducing Transformer Key-Value Cache Size with Cross-Layer AttentionCLA
- Mistral 7BGQA 与滑动窗口
- Llama 2: Open Foundation and Fine-Tuned Chat Models模型配置
- The Llama 3 Herd of Models模型配置
- DeepSeek-V2MLA
- DeepSeek-V3 Technical ReportMLA 与 MoE
- DeepSeek V4 模型卡CSA / HCA
KV 缓存管理与压缩
- Efficient Memory Management for LLM Serving with PagedAttention分页管理
- H₂O: Heavy-Hitter Oracle缓存淘汰
- Scissorhands: Persistence of Importance缓存淘汰
- Efficient Streaming Language Models with Attention SinksStreamingLLM
- SnapKV: LLM Knows What You are Looking for Before Generation缓存筛选
- PyramidInfer: Pyramid KV Cache Compression逐层缓存压缩
- PyramidKV: Dynamic KV Cache Compression逐层缓存预算
- KIVI: A Tuning-Free Asymmetric 2bit Quantization for KV CacheKV 量化
- KVQuant: Towards 10 Million Context Length LLM InferenceKV 量化
- TurboQuant: Online Vector Quantization向量量化
计算优化与替代架构
- FlashAttention: Fast and Memory-Efficient Exact Attention分块与 IO 优化
- FlashAttention-2: Faster Attention with Better Parallelism并行与任务划分
- DFlash: Block Diffusion for Flash Speculative Decoding推测解码
- Mamba: Linear-Time Sequence Modeling with Selective State Spaces状态空间模型
- RWKV: Reinventing RNNs for the Transformer Era递归序列模型
- Retentive Network: A Successor to TransformerRetention
- Gated Delta Networks: Improving Mamba2 with Delta Rule门控与 Delta Rule
- Jamba: A Hybrid Transformer-Mamba Language Model混合架构
- Zamba: A Compact 7B SSM Hybrid Model混合架构
推理服务与系统实现
- SGLang: Efficient Execution of Structured Language Model ProgramsRadixAttention
- FlexGen: High-Throughput Generative Inference分层存储与调度
- InfiniGen: Dynamic KV Cache Management选择性预取
- Splitwise: Efficient Generative LLM Inference Using Phase SplittingPrefill/Decode 分离
- DistServe: Disaggregating Prefill and DecodingGoodput 与阶段分离
- Mooncake: A KVCache-centric Disaggregated Architecture集群级 KV 管理
- Ring Attention with Blockwise Transformers序列并行
- Kimi K2.5: Visual Agentic IntelligenceAgent 与上下文管理
配置与工程文档
- Qwen3.5-9B 模型配置混合层配置
- DeepSeek V4 in vLLMCSA / HCA 实现
- SGLang HiCache 文档分层缓存
- vLLM KV Offloading 使用指南KV 卸载
- vLLM Expert Parallel Deployment专家并行
- vLLM Disaggregated Prefilling阶段分离