For the complete documentation index, see llms.txt. This page is also available as Markdown.

10.2 KV 缓存:为什么能避免重复计算

KV 缓存把已经算过的 Key 和 Value 留在显存里,后续每步只追加、不重算。本节先讲这个机制为什么成立,再讲它换来的显存账单,以及沿着账单里不同因子压缩它的四条路线。10.1.2 列出 decode 每步的访存清单时,第二项「加载 KV 缓存」是被直接摆出来的——它为什么会随已生成长度线性增长、又凭什么值得占掉那份带宽,当时并没有交代。

10.2.1 重复计算的问题

在自回归解码中,生成第 tt 个词元时,需要计算它与之前所有 t1t-1 个词元的注意力。如果每次都从头计算所有词元的 Key 和 Value,计算量将随生成长度呈平方增长

10.2.2 KV 缓存的原理

KV 缓存的核心观察是:之前词元的 Key 和 Value 在后续步骤中不会改变(因为因果掩码确保它们不受未来词元的影响)。因此,只需在首次计算后将它们缓存到显存中,后续步骤直接复用即可。

每生成一个新词元,只需:

  1. 计算新词元的 Q、K、V

  2. 将新的 K、V 追加到缓存

  3. 用新的 Q 与所有缓存的 K 计算注意力权重

  4. 用权重对所有缓存的 V 加权求和

如果只看“历史 K/V 重新计算和新查询关注历史”的注意力子问题,KV 缓存把每步从按完整前缀重算降为新词元查询缓存,复杂度约从 O(t2d)O(t^2 \cdot d) 降至 O(td)O(t \cdot d)。完整 decoder layer 仍要为新词元计算 QKV 投影、MLP 和输出投影,因此每步不是常数成本。

下面这张流程图强调了 KV 缓存的关键点:历史词元的 K、V 只计算一次,之后每步只追加新块。

图 10-1:KV 缓存随解码步追加增长,历史 K/V 只需计算一次

10.2.3 KV 缓存的显存代价

KV 缓存虽然节省了计算,但引入了显著的显存开销。对于一个具有 LL 层、HkvH_{kv} 个 KV 头、head 维度 dhd_h、并发序列数为 BB 的模型,缓存 tt 个词元需要:

KV 缓存大小=2×B×L×Hkv×dh×t×bytes/element\text{KV 缓存大小} = 2 \times B \times L \times H_{kv} \times d_h \times t \times \text{bytes/element}

对于 Llama 2-70B(80 层、8 个 KV 头、128 维,使用 GQA),在 B=1B=1、4096 个词元、FP16 下,KV 缓存约 1.25 GiB。若使用标准 MHA(64 个独立 KV 头),则约 10 GiB。GQA 将 KV 缓存斜率减小了 8 倍。即便如此,在高并发场景下,多个用户请求的 KV 缓存仍会迅速填满显存。这正是 PagedAttention 等技术的优化目标。

配置
B
层数 L
KV 头 Hkv
头维 dh
词元 t
字节
KV 缓存 GiB

Llama 2-70B GQA

1

80

8

128

4096

2

1.25

Llama 2-70B MHA

1

80

64

128

4096

2

10.00

表 10-1:按 2BLHkvdhts2BLH_{kv}d_hts 逐列重算的 FP16 KV 缓存(GiB,s=2s=2 字节)。这里使用二进制 GiB;批量、层数、KV 头数、头维和上下文长度中的任一项翻倍,缓存也线性翻倍。

10.2.4 GQA 如何减小 KV 缓存

第二章介绍了多头注意力中每个头有独立的 Q、K、V 投影。分组查询注意力(Grouped-Query Attention,GQA)让多个查询头共享同一组 K 和 V 头。

例如,Llama 2-70B 使用 64 个查询头但只有 8 个 KV 头(每 8 个查询头共享一组 KV)。这将 KV 缓存减小为原始多头注意力的 1/8,且对模型质量的影响极小。

多头共享为什么不损性能

直觉上,减少 KV 头数量似乎必然导致表达能力下降。但实际上,GQA 之所以能够“几乎无损”,核心原因在于:

  1. KV 表征的冗余性:在标准 MHA 中,多个注意力头的 K 和 V 投影所承载的信息存在大量重叠。GQA 原论文的做法印证了这一点:把已训练模型的多个 KV 头做均值池化(mean-pooling)合并,再用约 5% 的预训练计算量稍加续训(uptrain),就能基本恢复原模型质量。这意味着独立的 KV 头之间存在大量冗余,共享并不会丢失太多独立信息。

  2. 查询的多样性足以补偿:GQA 保留了每个查询头的独立 Q 投影。即使多个查询头共享同一组 K 和 V,不同的 Q 投影仍然可以从共享的 KV 中“提取”不同的注意力模式。换言之,注意力机制的表达多样性主要由 Q 驱动,而非 KV。

  3. 实验验证:Google 在 GQA 的原论文中展示,在 T5-XXL(110 亿参数)上,使用 GQA(8 组)相比标准 MHA 在多个基准测试上的性能差距不到 0.5%,而推理速度接近仅保留单个 KV 头的 MQA。在主流解码器架构的实际部署中,GQA 通常能带来可观的端到端吞吐提升(下表给出量级示意,实际幅度取决于负载与实现)。后续 Llama 2-70B、Mistral 等模型进一步印证了这一权衡。

方案
KV 头数
KV 缓存大小(相对 MHA)
模型质量(相对 MHA)
推理吞吐量(相对 MHA)

MHA

HH(如 64)

基准

GQA

GG(如 8)

G/HG/H(如 1/8)

\approx 99.5%+

\approx 1.5-2×

MQA

1

1/H1/H(如 1/64)

\approx 97-99%

\approx 2-3×

GQA 是介于多头注意力(MHA,每个查询头一组独立 KV)和多查询注意力(MQA,所有查询头共享一组 KV)之间的折中方案。它在推理效率和模型质量之间取得了很好的平衡,成为现代大语言模型的标准配置——但它只减少了 KV 头的数量,10.2.3 那个乘积里的层数、每词元维度和词元数量,它一个也没碰。

10.2.5 前缀缓存:跨请求复用 KV 缓存

默认情况下,推理引擎仅在单个请求内部使用 KV 缓存——每个新请求都必须从头执行 Prefill。但如果两个请求共享相同的前缀(如相同的系统提示词),则第二个请求可以直接复用第一个请求已计算好的 KV 缓存,跳过共享前缀部分的 Prefill 计算。

这就是前缀缓存(Prefix Caching)的核心思想。商业 API(如 Anthropic、OpenAI)对缓存命中的输入词元收取更低的费用,正是因为复用缓存词元几乎不消耗计算资源。

前缀缓存在以下场景中收益显著:

  • 复杂系统提示词:智能体、RAG 管道、工具调用等场景通常在每次请求中携带相同的长系统提示词

  • 代码补全:代码生成和补全功能需要将相同的数千行代码作为共享上下文

  • 文档问答:文档摘要和检索问答在用户提问前反复传入相同的文档内容

  • 多轮对话:聊天模板会在每轮中重复之前的所有消息历史,随对话进行缓存收益递增

需要注意的是,前缀缓存从输入序列的起始位置开始匹配,直到遇到第一个不同的词元就终止。这意味着上下文的排列顺序直接决定了缓存效率——应将变化的内容(如用户输入)放在提示词的末尾,将不变的内容(如系统提示词、文档)放在前面,以最大化共享前缀的长度。

此外,对 KV 缓存进行量化(如使用 FP8 格式)可以在内存中存储更多的缓存条目,并加快缓存的读取速度,从而进一步提升前缀缓存和分离式推理架构的效果。

把 KV 缓存的线性增长、GQA 的斜率下降,以及前缀缓存的跨请求复用放在一张图里,会更容易看到三者解决的是不同层次的问题。

MHA 与 GQA 的 KV 缓存和前缀复用结构示意图

图 10-2:KV 缓存增长,以及 MHA、GQA 与前缀缓存的对比

10.2.6 PagedAttention:解决显存碎片化

虽然 GQA 降低了 KV 缓存的大小,但在高并发场景下,仍需更激进的显存管理策略。传统做法为每个请求预分配足够容纳最大生成长度的连续显存块,而实际生成长度通常远小于最大值,碎片化与过度预留造成的浪费可达 60-80%。PagedAttention(Kwon et al., 2023)借鉴操作系统虚拟内存的页式管理思想:用逻辑块表把每个请求“连续”的 KV 视图映射到按需分配的非连续物理页上,并支持跨请求共享前缀页面(写时复制),把浪费压低到最后一个块内的少量空洞(vLLM 报告实践中低于约 4%),等价于显著提高可用于批处理的有效显存。页大小、逻辑块表与共享机制的细节和图示见 11.2 节

10.2.7 新型 KV 缓存压缩:MLA(多头隐向量注意力)

随着模型规模增长,KV 缓存成本快速恶化。多头隐向量注意力(Multi-head Latent Attention,MLA)是 DeepSeek-V2/V3 等新模型的创新方向:与其为每个注意力头独立存储 K、V,MLA 将它们压缩为一个低维的隐向量,并在计算图中尽量保持压缩表示。

具体做法:

  • 压缩:将所有注意力头的 K、V 投影到一个紧凑的隐向量(DeepSeek-V2/V3 中该隐向量为 512 维,加上 64 维共享的 RoPE 键分量后共 576 维,约为全部注意力头 K、V 总维度 32768 的 1/57,相当于仅 2.25 组的 GQA)

  • 存储:在缓存中仅保存该隐向量,而非完整的 K、V 矩阵

  • 吸收投影:推理实现并不一定显式解压完整 K、V;DeepSeek 的 MLA 可把 key/value 的上投影分别吸收到 query 投影和输出投影中,在压缩域完成主要注意力计算,仅对需要 RoPE 的键分量单独处理

那 64 维「共享的 RoPE 键分量」不是凑数,而是被出来的——它正是上面「吸收投影」这一招与 RoPE 相冲突的产物,值得单独说清楚。

吸收投影的合法性依赖一个代数事实:qk=(xWQ)(cWUK)q^\top k = (xW^Q)^\top(c\,W^{UK}) 中,WUKW^{UK} 可以先与 WQW^Q 相乘、合并成一个矩阵,于是推理时永远不必把 cc 解压回完整的 kk。但 RoPE 是位置相关的旋转矩阵,它必须插在 qqkk 之间。DeepSeek-V2 论文把这个冲突讲得很直接:一旦对键施加 RoPE,WUKW^{UK} 就与一个位置相关的 RoPE 矩阵耦合在一起,WUKW^{UK} 再也无法被吸收进 WQW^Q——因为那个 RoPE 矩阵与当前正在生成的词元有关,它横在 WQW^QWUKW^{UK} 中间,而矩阵乘法不满足交换律。后果是推理时必须为所有前缀词元重算键,压缩带来的收益被彻底吃掉。

解法就是解耦:让位置相关的那一路走单独的、不压缩的分支——额外的多头查询 qt,iRq^R_{t,i} 配一个所有头共享的键 ktRk^R_t 专门承载 RoPE,压缩域那一路则完全不带位置信息。576 = 512 + 64 里的 64,就是这条旁路的宽度。这是一个典型的「不得不付的税」:为了保住吸收投影,MLA 必须多存一份不压缩的键;而之所以只多存 64 维、且所有头共享,正是为了把这笔税压到最小。

实际对比显示,DeepSeek-V3(671B)的单词元 KV 缓存仅需 70KB,而基于 GQA 的 Llama-3.1-405B 需 516KB、Qwen-2.5-72B 需 327KB(这两个模型本身已经用 8 KV 头 GQA 而非完整 MHA;若按完整 MHA 配置则会再放大约一个数量级),压缩比约为 5-7 倍。MLA 的收益不只是“少存再解压”,而是通过低维 KV 缓存和投影吸收减少生成阶段的显存带宽压力;在访存密集型的 decode 阶段,这通常比额外矩阵变换更关键。

10.2.8 沿时间轴压缩:面向百万词元的混合注意力

MLA 压缩的是每个词元的 KV 维度——词元数量不变,只是每个词元存得更小。当上下文推到百万词元级别时,这条轴很快就不够用了:即便每词元只有 70KB,一百万词元仍是数十 GB。于是出现了另一条正交的思路:沿时间轴压缩词元数量本身,再叠加稀疏选择。

DeepSeek-V4(见 13.3 节)的混合注意力是这条路线的代表,由两个部件组成:

  • 压缩稀疏注意力(Compressed Sparse Attention,CSA):先把每 mm 个词元的 KV 缓存合并成一个条目,再在这些压缩条目上做稀疏的 Top-k 选择(其内部使用 DeepSeek 稀疏注意力 DSA 进一步加速)

  • 高度压缩注意力(Heavily Compressed Attention,HCA):使用远大于 mm 的压缩率 mm',把每 mm' 个词元的 KV 合并为一个条目,但在合并后的条目上保持稠密注意力

两者分工明确:CSA 以适中压缩率保留细节、靠稀疏选择省算力;HCA 以极高压缩率提供一个粗粒度但覆盖全局的视野。官方技术报告给出的效果是,在百万词元上下文设置下,DeepSeek-V4-Pro 相比 DeepSeek-V3.2 只需要 27% 的单词元推理浮点运算量(以等效 FP8 浮点运算计)和 10% 的 KV 缓存

10.2.9 沿层轴共享:跨层复用 KV

前面几节压缩的分别是头的数量、每词元的维度和词元的数量。回看 10.2.3 的公式,还剩一个因子始终没被碰过:层数 LL。表 10-1 的图注已经点明“层数……中的任一项翻倍,缓存也线性翻倍”——那么反过来问:是否每一层都必须持有自己的那一份 KV?

跨层注意力(Cross-Layer Attention,CLA)正是沿这条轴做的:只让一部分层真正计算并缓存 K、V,其余层直接复用前面某个“产出层”已经算好的 K、V;每一层仍然计算自己的 Q,因此各层依旧能形成各自的注意力模式。公式中的 LL 于是被替换为“实际产出 KV 的层数”——若每两层共享一份,KV 缓存直接减半。

这与 GQA 是同一个想法用在不同维度上:GQA 让多个查询头共享一组 KV(头间共享),CLA 让多个层共享一组 KV(层间共享),两者正交因而可以叠加。代价也同源——被共享的层失去了独立的 KV 投影,模型容量随之下降,问题只在于这点损失是否换得划算。

CLA 的原始论文(Reducing Transformer Key-Value Cache Size with Cross-Layer Attention,Brandon 等,2024 年 5 月)在 MQA 基础上进一步共享相邻层的 K、V,报告在 1B 与 3B 规模从零训练的实验中,KV 缓存再降 2 倍、而准确率与未经修改的 MQA 基本持平,并在显存-准确率的权衡曲线上相对传统 MQA 取得帕累托改进。

分寸提示:这条轴的部署成熟度明显低于另外三条。GQA 已是现代解码器的标配,MLA 有 DeepSeek 系列的规模化生产验证,而层间共享目前主要停留在研究阶段与端侧小模型的探索中。另需注意,各家对“共享 KV”的措辞并不统一——某些厂商所称的 unified / shared Keys and Values 指的是同一层内部的合并,而非本节所说的跨层复用。判断某个具体模型是否采用了层间共享,应以其技术报告或模型卡的架构描述为准,不要仅凭产品页或第三方复现仓库的用词推断。

把四条轴放在一起看,KV 缓存的压缩空间就清楚了:它们各自咬住 10.2.3 那个乘积里的一个因子——GQA/MQA 减少头的数量 HkvH_{kv},CLA 类的层间共享减少产出 KV 的层数 LL,MLA 压缩每词元的表示维度 dhd_h,CSA/HCA 压缩词元的数量 tt 并引入内容相关的稀疏选择。正因为咬住的是不同因子,这四个方向相互正交、可以叠加——具体模型会选择其中一条或几条来组合,百万级上下文的服务成本正是沿着这些方向被压下来的。

最后更新于