> For the complete documentation index, see [llms.txt](https://yeasy.gitbook.io/llm_internals/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://yeasy.gitbook.io/llm_internals/di-san-bu-fen-tui-li-yu-bu-shu-pian/10_inference_optimization/10.2_kv_cache.md).

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

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

## 10.2.1 重复计算的问题

在自回归解码中，生成第 $$t$$ 个词元时，需要计算它与之前所有 $$t-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(t^2 \cdot d)$$ 降至 $$O(t \cdot d)$$。完整 decoder layer 仍要为新词元计算 QKV 投影、MLP 和输出投影，因此每步不是常数成本。

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

```mermaid
flowchart LR
    s1["step 1<br/>token 1: compute K1, V1"] --> c1["cache = [KV1]"]
    c1 --> s2["step 2<br/>token 2: compute Q2, K2, V2"]
    s2 --> a2["Q2 attends to [KV1, KV2]"]
    a2 --> c2["cache = [KV1, KV2]"]
    c2 --> s3["step 3<br/>token 3: compute Q3, K3, V3"]
    s3 --> a3["Q3 attends to [KV1, KV2, KV3]"]
    a3 --> c3["cache keeps growing by append only"]
```

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

## 10.2.3 KV 缓存的显存代价

KV 缓存虽然节省了计算，但引入了显著的**显存开销**。对于一个具有 $$L$$ 层、$$H\_{kv}$$ 个 KV 头、head 维度 $$d\_h$$、并发序列数为 $$B$$ 的模型，缓存 $$t$$ 个词元需要：

$$\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=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-2：按 $$2BLH\_{kv}d\_hts$$ 逐列重算的 FP16 KV 缓存（GiB，$$s=2$$ 字节）。这里使用二进制 GiB；批量、层数、KV 头数、头维和上下文长度中的任一项翻倍，缓存也线性翻倍。

## 10.2.4 GQA 如何减小 KV 缓存

[第二章](/llm_internals/di-yi-bu-fen-ji-chu-pian/02_attention/2.3_multi_head.md)介绍了多头注意力中每个头有独立的 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 | $$H$$（如 64） | 1×              | 基准                 | 1×                 |
| GQA | $$G$$（如 8）  | $$G/H$$（如 1/8）  | $$\approx$$ 99.5%+ | $$\approx$$ 1.5-2× |
| MQA | 1           | $$1/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 缓存和前缀复用结构示意图](/files/uFTldyKmXIGopghHTXZm)

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

## 10.2.6 PagedAttention：解决显存碎片化

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

## 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 相冲突的产物，值得单独说清楚。

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

解法就是**解耦**：让位置相关的那一路走单独的、不压缩的分支——额外的多头查询 $$q^R\_{t,i}$$ 配一个所有头共享的键 $$k^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 节](/llm_internals/di-si-bu-fen-mo-xing-yu-qian-yan-pian/13_decoder_models/13.3_deepseek_gemini.md)）的混合注意力是这条路线的代表，由两个部件组成：

* **压缩稀疏注意力**（Compressed Sparse Attention，CSA）：先把每 $$m$$ 个词元的 KV 缓存合并成一个条目，再在这些压缩条目上做稀疏的 Top-k 选择（其内部使用 DeepSeek 稀疏注意力 DSA 进一步加速）
* **高度压缩注意力**（Heavily Compressed Attention，HCA）：使用远大于 $$m$$ 的压缩率 $$m'$$，把每 $$m'$$ 个词元的 KV 合并为一个条目，但在合并后的条目上保持**稠密**注意力

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

## 10.2.9 沿层轴共享：跨层复用 KV

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

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

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

CLA 的原始论文（[Reducing Transformer Key-Value Cache Size with Cross-Layer Attention](https://arxiv.org/abs/2405.12981)，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 减少**头的数量** $$H\_{kv}$$，CLA 类的层间共享减少**产出 KV 的层数** $$L$$，MLA 压缩**每词元的表示维度** $$d\_h$$，CSA/HCA 压缩**词元的数量** $$t$$ 并引入内容相关的稀疏选择。正因为咬住的是不同因子，这四个方向相互正交、可以叠加——具体模型会选择其中一条或几条来组合，百万级上下文的服务成本正是沿着这些方向被压下来的。
