KV Cache 与 Prompt Caching 彻底讲透
大模型为什么能"一个字一个字"地流畅生成,又为什么长上下文/多轮对话会很贵?答案藏在两个紧密相关又常被混淆的概念里:KV Cache(单次生成内的算力加速器)与 Prompt Caching(跨请求/跨轮复用的成本优化)。这篇用流程图、计算对比和一张交互图,把"它是什么、为什么这样设计、省在哪"讲清楚。
一句话区别:KV Cache 是一次生成过程中,把已经算过的 Key/Value 存下来给后续 token 复用;Prompt Caching 是多次请求之间,把相同前缀的 Key/Value 存下来给不同请求复用。前者解决"自回归重复计算",后者解决"重复前缀重复付费"。
一、先建立直觉:自回归生成里藏着大量"重复劳动"
Transformer 解码器是自回归的:它一次只产出一个 token,而每个新 token 的注意力必须"看到"之前所有 token(因果掩码)。问题是——这些"之前的 token"每生成一个新字,都得重新参与计算一次。
- 第 1 步:输入
t₁ t₂ t₃ t₄,算出 t₅。
- 第 2 步:输入
t₁ t₂ t₃ t₄ t₅,算出 t₆。
- 第 3 步:输入
t₁ … t₆,算出 t₇。
如果不加任何优化,每一步都要把整段上下文从头再过一遍网络。生成 N 个字,相当于把长度为 N 的序列前向计算了 N 次,总计算量约 O(N²)。绝大多数算力都花在了"重复算已经算过的东西"上。
关键洞察:每一步真正"新"的信息,其实只有最后那一个 token。前面所有 token 的表示并不会变——那为什么还要重算它们的 K、V?直接用缓存不就行了?这就是 KV Cache 的出发点。
二、KV Cache 是什么:把"历史的 K、V"存进内存
回忆注意力公式(单层、单头、省略缩放与 softmax 细节):
Attention(Q, K, V) = softmax( Q · Kᵀ / √d ) · V
要算位置 i 的输出,需要:
- Qᵢ:当前 token 自己的 Query("我要找什么");
- Kⱼ:所有
j ≤ i 的 Key("别人能被怎样匹配");
- Vⱼ:所有
j ≤ i 的 Value("别人真正要贡献的内容")。
KV Cache 的做法:在网络前向时,把每一层、每一个注意力头的 K 和 V 矩阵按序列长度方向"追加式"缓存起来。这样下一步只需计算新 token 自己的 Q、K、V,然后把它的 K、V 接到缓存末尾,用"新 Q"去和"缓存里的全部 K、V + 自己的 K、V"做注意力即可。
一句话定义:KV Cache = 在自回归生成中,缓存已经算过的 Key 与 Value 张量,使每个新 token 只做"自己的那一份"计算,而不必重算历史。它是推理引擎(vLLM、TensorRT-LLM、Ollama、llama.cpp 等)的标准配置,是长文本生成能跑得动的前提。
三、为什么只缓存 K 和 V,不缓存 Q?
这是高频面试题。答案来自"谁会被后续 token 反复需要":
| 张量 | 被谁需要 | 是否需要缓存 |
| Q(Query) | 只有"当前正在生成的那个 token"用它去查别人 | ❌ 不需要。过去的 Q 再也不会被任何未来 token 用到 |
| K(Key) | 未来每一个 token 都要拿自己的 Q 来和它对匹配度 | ✅ 必须缓存 |
| V(Value) | 匹配度算完后,要按权重从 V 里取内容 | ✅ 必须缓存 |
换句话说:Q 是"提问方",用过即弃;K 和 V 是"被查阅的资料",会被后来者反复翻看,所以只缓存 K、V。缓存 Q 既无必要,还会白白多吃一份显存。
注意:KV Cache 里"KV"指的就是注意力里的 Key 和 Value,跟键值数据库(Key-Value Store)没关系,名字撞了而已。
四、图文:一次生成里 KV Cache 怎样工作
看图就明白两件事:
- Prefill 阶段:把整段 Prompt 一次性前向,顺便把每一层的 K、V 全部写进 KV Cache(这一步省不了,但只做一次)。
- Decode 阶段:每来一个新 token,只需算它自己的 Q、K、V;它的 Q 去和"缓存里历史 K/V + 自己刚算的 K/V"做注意力,然后把自己的 K、V 也追加进缓存。历史 token 的 K、V 一次都没重算。
五、代价:它快了,但要吃显存
KV Cache 不是免费的。它要常驻显存,且随序列长度线性增长。每一层、每个注意力头的缓存形状为:
K, V 各为 [batch, num_heads, seq_len, head_dim]
每个 token 在每一层要存 2 × num_heads × head_dim 个数(K 一份 + V 一份)。以 fp16(2 字节)为例,单 token 单层的显存 ≈ 2 × num_heads × head_dim × 2 字节。
≈0.5 MB
Llama-2-7B 每 token 的 KV Cache(fp16)
32 层 × 32 头 × 128 维 × 2(K,V) × 2B
≈2 GB
4K 上下文时的 KV Cache 占用
所以"长上下文为什么贵"有两层含义:一是计算注意力本身随长度上升;二是KV Cache 的显存随长度线性膨胀——序列翻倍,KV Cache 显存也翻倍,这往往是长文本推理的硬瓶颈。工程上常用分页注意力(PagedAttention)、KV 量化(INT8/INT4)、分组查询(GQA/MQA)减少头数等手段来压它。
六、交互:有/无 KV Cache,算力差多少?
拖动滑块设定"已生成的 token 数 N"。左图蓝线为有 KV Cache时的累计计算量(每步只算 1 个新 token,累计 ∝ N);橙线为无 KV Cache时(每步要把整段重算一遍,累计 ∝ N²)。差距随 N 爆炸式拉大。
有 KV Cache:累计 ∝ N(线性)
无 KV Cache:累计 ∝ N²(平方)
在当前 N 下,无缓存的累计计算量约为有缓存的 20.5×;
若推到上下文长度 4096,这个倍数约为 2048×。
这就是为什么推理引擎默认必开 KV Cache:不是"优化项",而是"不这样根本跑不动长文本"的刚需。KV Cache 只解决单次生成内部的重复;如果很多不同请求共享同一段长前缀(系统提示、RAG 资料、多轮历史),还有一层浪费没解决——这正是下一节的 Prompt Caching。
七、Prompt Caching 原理:把"相同前缀的 KV"跨请求复用
KV Cache 的生命周期通常止于单次请求的结束。但现实中大量请求会复用同一段前缀:
- 聊天机器人的系统提示词(System Prompt)几十上百字,每次对话都一样;
- RAG 应用把同一份检索到的资料塞进很多用户的问题里;
- 多轮对话里,前几轮的历史对后面每一轮都是"相同前缀";
- 批量任务里成百上千条 prompt 共用同一段 few-shot 示例。
如果每次请求都把这段长前缀重新做一遍 Prefill,既慢(首 token 延迟 TTFT 高)又贵(按 token 计费重复付钱)。Prompt Caching 的做法:
核心思想:当新请求的前缀与已缓存请求的前缀逐 token 一致时,推理引擎直接复用那段前缀已经算好的 K、V,跳过对应的 Prefill,只对新出现的后缀做计算。本质就是"把 KV Cache 跨请求、跨多轮地持久化下来"。
它怎么知道"前缀相同"?——块级哈希
引擎不会傻傻逐字符比对,而是把 token 序列切成固定大小的块(block,常见 128 个 token),对每个块算哈希并建索引。新请求来时,按块匹配:已缓存的块直接命中(cache hit),缺失的块才现场计算并写入。这样既快又支持"前缀部分命中"。
八、图文:多轮对话里 Prompt Caching 怎样省
主流实现一览
| 提供方 | 名称 | 命中条件 / 特点 |
| Anthropic Claude | Prompt Caching | 显式设缓存断点;前缀 ≥1024 token;命中读取价约为写入的 1/10;默认 TTL 5 分钟(持续使用可到 1 小时) |
| OpenAI | Automatic Prompt Caching | 自动对 ≥1024 token 的前缀缓存;无需改代码;缓存输入 token 计费折扣(如 50% off) |
| Google Gemini | Context Caching | 显式创建带 TTL 的缓存;按存储时长 + token 分别计费 |
| 开源(vLLM 等) | Automatic Prefix Caching | 块级(如 128 token)哈希自动匹配;服务于本地/自部署推理 |
九、两者到底差在哪?(对比表)
| 维度 | KV Cache | Prompt Caching |
| 作用范围 | 单个请求内的多次 decode 步骤 | 跨请求 / 跨多轮对话共享前缀 |
| 复用对象 | 已生成 token 的 K、V | 相同前缀 token 的 K、V |
| 生命周期 | 一次生成过程(请求结束即释放) | 多次请求(带 TTL,如 5 分钟~1 小时) |
| 命中条件 | 必然命中(同一次生成内部) | 前缀 token 逐块一致(哈希匹配) |
| 主要目的 | 避免自回归重复计算,让生成本能跑 | 降低 TTFT 与算力/计费,省重复前缀 |
| 典型节省 | 每步计算 O(N) → O(1),总 O(N²) → O(N) | 长前缀 Prefill 免重算,读取价更低 |
| 实现层级 | 推理引擎必备(解码机制) | 引擎/API 提供的复用策略(可配置) |
关系一句话总结:Prompt Caching 是 KV Cache 思想的跨请求推广——把"一次请求里别重算历史"升级成"多个请求里别重算相同前缀"。两者缓存的都是注意力里的 K 和 V,区别只在作用的时间/空间尺度。
十、失效条件与注意事项
- 前缀必须逐 token 一致:哪怕差一个空格、换个 few-shot 顺序、改个标点,哈希就变了,直接 cache miss 全部重算。想命中,就把不变的内容放最前面、保持原样。
- 有 TTL / 容量上限:服务器不会无限缓存,长时间不用的前缀会被清掉;多轮对话要"连续命中"才划算。
- 它不是"记忆":缓存只是按精确文本前缀复用 KV,模型本身仍是无状态的。详见
大模型为什么没记忆。真正跨会话记忆要靠外部存储,而非 Prompt Caching。
- 缓存也要占资源:服务端要为每个前缀付出显存/存储,所以通常设最小长度门槛(如 ≥1024 token)才值得缓存。
- 并非所有请求都受益:如果每个请求前缀都不同(纯随机输入),Prompt Caching 帮不上忙,只有"大量共享前缀"的场景才显著。
十一、小结
- KV Cache:自回归生成里,把历史 token 的 Key、Value 按层/头/序列长度缓存,每个新 token 只算自己的 Q、K、V,从而把生成计算从 O(N²) 降到 O(N)。代价是显存随序列长度 O(N) 增长。
- 只缓存 K、V 不缓存 Q:因为未来的 token 只会反复查阅历史的 K、V,而历史 Q 再也不会被用到。
- Prompt Caching:把"相同前缀的 KV"跨请求/跨轮复用,靠块级哈希匹配;命中后跳过前缀 Prefill,降低 TTFT 与计费。它是 KV Cache 思想在更大尺度上的推广。
- 两者缓存的都是注意力里的 K 和 V,区别只在作用范围:一次生成内 vs 多次请求间。
相关阅读