# KV Cache 与前缀缓存：大模型推理为什么越聊越快

从 QKV 概念出发，讲清推理时为什么旧 token 的 K、V 不用重算，以及多个请求如何共享同一段公共前缀

> KV cache · 前缀缓存 · 推理优化 · 约 3 分钟 · 08 月 06 日

## 本篇要点

1. 推理时每生成一个词都要走一次前向计算，最朴素的做法会把整段序列的 K、V 和注意力矩阵全部重算，但因果掩码决定了旧 token 的表示永远不会变——重算是纯浪费。
2. KV cache 把每个 token 每层的 K、V 存起来，decode 时每步只算新 token 的 q、k、v，用新 q 与缓存中的 K、V 做注意力；Q 用完即弃，所以只缓存 K、V，不缓存 Q。
3. 生成被分成 prefill（并行处理整段 prompt、填充缓存）和 decode（逐 token 生成）两阶段；KV cache 消除的是重复投影，注意力扫描仍随序列长度线性增长。
4. 前缀缓存把多个请求共享的公共前缀（system prompt、对话历史、few-shot）对应的 KV 缓存复用：vLLM 用块级哈希匹配，SGLang 用 radix 树组织共享前缀。
5. 前缀缓存只省 prefill、不省 decode，收益来自请求间确实存在重复前缀——对话越长、system prompt 越长，收益越大。

---

前几篇我们把注意力怎么算、Transformer 怎么搭都拆开了，但你有没有想过一个问题：模型在训练时是一次性处理整段序列，可到了生成阶段，它是**一个词一个词往外蹦**的——你每次打开 ChatGPT，答案都是流式吐出来的。那每蹦一个词，模型是不是要把整段输入从头到尾重算一遍？今天这篇就回答这个：KV cache 如何消除推理中的重复计算，前缀缓存又如何让多个请求共享同一段开头。

## 本篇问题边界

只讲推理阶段的两个提速机制：KV cache 与前缀缓存。FlashAttention、GQA、PagedAttention 这些更深的工程优化只点到名字，放在「下一步」。

## 生成一个词，为什么是重复劳动

回顾 QKV 那篇：每个 token 都算出自己的 $q$、$k$、$v$，用 $q$ 去点积所有 $k$ 得到权重，再加权所有 $v$。推理时，模型把新 token 接到序列末尾，走一遍前向，取最后一个位置的输出作为下一个词。

关键在因果掩码：新 token 只能看它前面的 token。所以**旧 token 的表示不会因为新 token 的到来而改变**——它们由自身和更早的内容决定。但最朴素的实现每步都把整段序列重新过一遍：所有 $k$、$v$ 重算，整张注意力矩阵重算，只为取最后一行。算过的全被扔掉。

![追加一个新 token 后，QKV 矩阵和注意力矩阵只多出一行，旧行完全不变](https://peterchng.com/blog/2024/06/11/what-is-the-transformer-kv-cache/20240608094623.png)

看这张图：追加 token 后，旧行一个都没动 [2]。

## KV cache：把算过的 K、V 存起来

既然旧 token 的 $k$、$v$ 永远不变，就别重算——算一次，存起来。这就是 KV cache [1][2]。生成因此分成两阶段 [3]：

- **prefill（预填充）**：把整段 prompt 并行算一遍，把每个 token 每层的 $k$、$v$ 写进缓存；
- **decode（解码）**：每步只算新 token 的 $q$、$k$、$v$，把 $k$、$v$ 追加进缓存，再用新 $q$ 与缓存里全部 $K$、$V$ 做注意力。

注意只有 $K$、$V$ 被缓存，$Q$ 不缓存——$Q$ 只服务于当前这一步，用完即弃 [2]。代价是显存：$K$、$V$ 要一直存到序列结束，长上下文时它的体积会超过模型权重本身。

## 计算量对比：一个直觉数字

假设 prompt 有 1000 个 token，还要生成 100 个新词。朴素实现第 $t$ 步要重新处理约 $1000+t$ 个位置，100 步合计约 10 万次 token 计算；用 KV cache 只需算 100 个新 token，投影部分省了约千倍 [4]。但注意：每一步新 $q$ 仍要和缓存里全部 $K$ 做点积，这一步扫描成本随序列变长线性增长——**KV cache 消除了重复投影，没有消除注意力扫描** [4]。

## 前缀缓存：多个请求共享同一段开头

KV cache 是在一个请求内部省重复。再看请求之间：很多请求的开头完全一样——同一个 system prompt、同一段对话历史、同一批 few-shot 示例。同样一串 token 用同样的权重，算出的 $k$、$v$ 必然逐位相同，重复算就是纯浪费。

前缀缓存（prefix caching）就是干这个的：把算过的 KV 按前缀组织起来，新请求先匹配公共前缀，命中的部分直接复用，只算新增的部分 [5][6]。两个主流实现：

- **vLLM**：把 KV cache 切成固定大小的块（如 16 个 token 一块），每块用「块内 token + 前面所有 token」的哈希做标识，多个请求共享同一哈希的块就指向同一块内存 [5]；
- **SGLang**：用 radix tree（基数树）按 token 粒度组织共享前缀，支持更细的分支复用 [6]。

![vLLM 前缀缓存设计文档中的示意图](https://docs.vllm.ai/en/stable/assets/design/prefix_caching/overview.png)

类比：就像盖楼时共享地基——新楼不必重新挖地基。对话越长、system prompt 越长，缓存价值越大。注意前缀缓存只省 prefill、不省 decode [5]，生成新词的部分该算还得算。

## 下一步

KV cache 引出了推理阶段最值钱的两个问题：注意力扫描怎么加速（FlashAttention）、缓存怎么省显存（GQA、PagedAttention）。这是「大模型如何跑得快」的下一站。

## 来源

1. [Hugging Face 官方文档：KV cache 是什么、缓存了哪些矩阵](https://huggingface.co/docs/transformers/main/en/cache_explanation)
2. [KV cache 原理详解，含注意力矩阵追加 token 前后的可视化](https://peterchng.com/blog/2024/06/11/what-is-the-transformer-kv-cache/)
3. [JAX 团队推理手册：prefill 与 decode 两阶段划分](https://jax-ml.github.io/scaling-book/inference/)
4. [KV cache 与 FlashAttention 交互式走查，含朴素实现与缓存实现的计算量对比](https://kvcache.cobanov.dev/)
5. [vLLM 自动前缀缓存设计文档（块级哈希与缓存管理）](https://docs.vllm.ai/en/stable/design/prefix_caching/)
6. [SGLang radix cache（基数树前缀缓存）源码实现](https://github.com/sgl-project/sglang/blob/main/python/sglang/srt/mem_cache/radix_cache.py)

---

原文：https://pangzhengboyin.com/articles/kv-cache-and-prefix-caching-7d7f5578

> **庞征博引** · 想学的，慢慢都会
>
> 庞征博引是把想学的东西写成连载的 AI 学习工具。说出想学什么，它会先了解你的基础，再把主题写成一篇篇 5–10 分钟能读完的文章；边读边问，接下来学什么跟着你走。这篇就是这样写出来的。
>
> 开始你自己的连载 → https://pangzhengboyin.com
