KV Cache 缓存机制的原理和应用:从 Transformer 推理到大模型服务优化
1. 文章背景:为什么 KV Cache 重要
在大模型应用开发中,很多人一开始关注的是 Prompt Engineering、RAG、Agent、向量数据库、Embedding、模型微调和推理部署,但真正进入线上服务阶段后,很快会遇到一个核心问题:大模型推理太慢、太贵、显存占用太高。
尤其是 ChatGPT 类对话系统、RAG 问答系统、代码助手、智能客服、Agent 工作流,都有一个共同特点:模型需要基于已有上下文不断生成下一个 token。生成过程不是一次性输出完整答案,而是自回归生成:
text
输入 Prompt → 生成第 1 个 token
输入 Prompt + 第 1 个 token → 生成第 2 个 token
输入 Prompt + 前 2 个 token → 生成第 3 个 token
...
如果每生成一个新 token,都重新计算前面所有 token 的注意力表示,计算量会非常浪费。KV Cache 就是为了解决这个问题出现的。
KV Cache 的核心思想非常简单:
在 Transformer 自回归推理中,把历史 token 的 Key 和 Value 缓存下来,后续生成新 token 时只计算当前 token 的 Query、Key、Value,并复用历史 Key/Value,从而避免重复计算。
在大模型推理系统中,KV Cache 几乎是必备机制。没有 KV Cache,长上下文生成速度会明显下降;有了 KV Cache,模型可以在生成阶段显著减少重复计算,提高 token 生成速度。
但是,KV Cache 不是"免费优化"。它会带来显存占用、缓存管理、批处理调度、长上下文压缩、多轮会话状态维护等一系列工程问题。对于企业级大模型服务来说,理解 KV Cache 不只是面试概念,而是推理优化、成本控制和系统稳定性的基础能力。
2. 核心问题:实际开发中会遇到什么问题
在真实的大模型系统中,KV Cache 通常会引出以下问题。
2.1 为什么生成越来越慢
用户在对话中不断追加上下文,Prompt 越来越长。如果没有合理使用 KV Cache,每生成一个 token 都要重新计算整段上下文,计算量会随着上下文长度增加而快速上升。
例如上下文长度为 8000 token,模型要继续生成 1000 token。如果每一步都重新处理 8000 多个历史 token,推理成本会非常高。
2.2 为什么显存突然爆了
KV Cache 会缓存每一层、每个注意力头、每个历史 token 的 Key 和 Value。上下文越长、batch 越大、模型层数越多,KV Cache 占用的显存越大。
很多线上推理服务不是模型参数放不下,而是:
text
模型参数 + 激活 + KV Cache + batch 调度开销
一起把显存打满。
2.3 为什么长上下文并发能力差
长上下文请求会占用大量 KV Cache。一个 32K 上下文请求可能比多个短请求更消耗显存。在线服务中,如果不做请求调度和缓存淘汰,很容易出现:
- 某些长请求拖慢整体吞吐;
- GPU 显存碎片化;
- batch 组不起来;
- 首 token 延迟和生成延迟不稳定;
- 多用户会话缓存难以管理。
2.4 RAG 和 Agent 为什么更依赖 KV Cache
RAG 系统一般会把检索到的文档片段拼进 Prompt,Agent 系统还会加入工具调用记录、任务规划、历史观察结果。它们的上下文通常比普通聊天更长,因此更依赖 KV Cache。
如果没有 KV Cache,RAG 和 Agent 的推理成本会明显上升,尤其是在多轮问答和工具链循环场景中。
3. 基础概念:用工程视角解释关键概念
3.1 Transformer 自注意力中的 Q、K、V
Transformer 的注意力机制中,每个 token 会被映射成 Query、Key、Value 三组向量。
可以简单理解为:
- Query:当前 token 想找什么信息;
- Key:每个历史 token 提供什么索引;
- Value:每个历史 token 实际携带什么内容。
注意力计算可以简化表示为:
text
Attention(Q, K, V) = softmax(QK^T / sqrt(d)) V
在自回归生成中,当前 token 只能关注自己和之前的 token,不能关注未来 token。因此第 t 个 token 生成时,需要访问 1 到 t 的历史信息。
3.2 Prefill 和 Decode
大模型推理通常分为两个阶段。
| 阶段 | 输入 | 主要工作 | 特点 |
|---|---|---|---|
| Prefill | 用户完整 Prompt | 一次性计算 Prompt 中所有 token 的 KV | 计算量大,但可并行 |
| Decode | 上一步生成的 token | 每次生成一个新 token,并追加 KV Cache | 单步计算小,但循环次数多 |
举例:
text
Prompt: "请解释 RAG 的原理"
模型先进入 Prefill 阶段,把这句话的所有 token 一次性处理,并缓存每一层的 K/V。然后进入 Decode 阶段,每生成一个新 token,就把新 token 的 K/V 追加到缓存中。
3.3 KV Cache 缓存的是什么
KV Cache 缓存的是每一层 Transformer attention 中历史 token 的 Key 和 Value。
假设模型有:
- L 层 Transformer;
- H 个注意力头;
- 每个 head 的维度为 D;
- 当前上下文长度为 T;
- batch size 为 B。
那么 KV Cache 大致需要存储:
text
K: [B, L, H, T, D]
V: [B, L, H, T, D]
实际框架中的维度排列可能不同,但本质上都是缓存所有历史 token 在每一层的 K/V 表示。
3.4 为什么只缓存 K/V,不缓存 Q
因为在生成下一个 token 时,当前 Query 只和当前 token 相关,每一步都会变化;而历史 token 的 Key 和 Value 在推理过程中不会变,所以可以复用。
换句话说:
- 历史 K/V:可复用;
- 当前 Q:必须重新算;
- 当前 K/V:算完后追加进缓存。
4. 系统设计:如何搭建完整 KV Cache 推理流程
一个支持 KV Cache 的大模型推理服务,可以拆成以下模块:
text
请求接入层
↓
Prompt 构造层
↓
Tokenizer
↓
Prefill 计算
↓
KV Cache Manager
↓
Decode 循环生成
↓
流式输出
↓
缓存释放 / 会话缓存复用
4.1 请求接入层
请求接入层负责接收用户请求,常见字段包括:
- 用户 ID;
- 会话 ID;
- Prompt;
- 历史消息;
- 最大生成长度;
- temperature;
- top_p;
- 是否开启流式输出;
- 是否允许复用历史 KV Cache。
在企业系统中,还需要加入权限校验、限流、审计日志和安全过滤。
4.2 Prompt 构造层
RAG 和 Agent 场景中,Prompt 通常由多个部分组成:
text
System Prompt
用户问题
历史对话
检索文档
工具调用记录
输出格式要求
Prompt 构造阶段要注意:上下文越长,KV Cache 越大。因此不能无限制拼接历史消息和检索结果。
4.3 Prefill 阶段
Prefill 阶段一次性处理完整输入 Prompt,生成初始 KV Cache。
工程特点:
- 更适合大 batch 并行;
- 计算密集;
- 对首 token 延迟影响大;
- Prompt 越长,Prefill 越慢;
- RAG 场景下 Prefill 成本通常较高。
4.4 Decode 阶段
Decode 阶段逐 token 生成,每一步只输入上一步生成的 token,并复用历史 KV Cache。
流程如下:
text
1. 输入当前 token
2. 读取历史 KV Cache
3. 计算当前 token 的 Q/K/V
4. 用当前 Q 和历史 K/V 做 attention
5. 预测下一个 token
6. 将当前 K/V 追加到 KV Cache
7. 重复直到结束
Decode 阶段对吞吐和延迟非常敏感,线上服务的 token/s 很大程度取决于 Decode 优化。
4.5 KV Cache Manager
KV Cache Manager 是推理系统中的关键模块,负责:
- 为请求分配 KV Cache 空间;
- 记录每个请求的缓存位置;
- 追加新 token 的 K/V;
- 支持 batch 内不同长度序列;
- 处理请求结束后的缓存释放;
- 管理长会话缓存复用;
- 避免显存碎片;
- 支持缓存换出和淘汰。
如果是自己实现推理框架,KV Cache Manager 是非常复杂的模块;如果使用 vLLM、TensorRT-LLM、SGLang 等推理框架,很多底层管理已经由框架完成。
5. 关键实现:核心模块、技术选型和实现细节
5.1 简化版 KV Cache 推理逻辑
下面用伪代码说明 KV Cache 的基本思想:
python
def generate(model, input_ids, max_new_tokens):
# Prefill:处理完整 Prompt,得到初始 past_key_values
outputs = model(
input_ids=input_ids,
use_cache=True
)
past_key_values = outputs.past_key_values
next_token = sample(outputs.logits[:, -1, :])
generated = [next_token]
# Decode:每次只输入最新 token,并复用 KV Cache
for _ in range(max_new_tokens - 1):
outputs = model(
input_ids=next_token,
past_key_values=past_key_values,
use_cache=True
)
past_key_values = outputs.past_key_values
next_token = sample(outputs.logits[:, -1, :])
generated.append(next_token)
if next_token == EOS_TOKEN_ID:
break
return generated
真实框架比这复杂得多,但核心思想就是:
text
第一次输入完整 Prompt
后续每次只输入一个新 token
历史 K/V 通过 past_key_values 复用
5.2 KV Cache 显存估算
KV Cache 的显存占用可以粗略估算为:
text
KV Cache ≈ batch_size × num_layers × 2 × seq_len × num_kv_heads × head_dim × bytes_per_element
其中:
- 2 表示 K 和 V;
- num_kv_heads 在 MHA、MQA、GQA 中不同;
- bytes_per_element 取决于 FP16、BF16、FP8 等精度。
例如模型层数越多、上下文越长、batch 越大,KV Cache 越容易成为显存瓶颈。
5.3 MHA、MQA、GQA 对 KV Cache 的影响
不同注意力结构对 KV Cache 显存影响很大。
| 机制 | 说明 | KV Cache 占用 |
|---|---|---|
| MHA | 每个 Query head 有独立 K/V head | 最大 |
| MQA | 多个 Query head 共享一个 K/V head | 最小 |
| GQA | 多组 Query head 共享 K/V head | 折中 |
工程上,很多新模型使用 GQA 或 MQA,就是为了减少 KV Cache 占用,提高长上下文推理效率。
5.4 PagedAttention
在高并发服务中,KV Cache 最大的问题之一是显存碎片和动态分配。PagedAttention 的思路类似操作系统分页,把 KV Cache 划分成固定大小的 block,再按需分配给不同请求。
它解决的问题包括:
- 不同请求长度不同,缓存空间难以连续分配;
- 长请求和短请求混合导致显存碎片;
- batch 动态变化时缓存管理复杂;
- 多轮会话缓存复用困难。
从系统设计角度看,PagedAttention 的价值不是改变注意力数学公式,而是让 KV Cache 的显存管理更适合在线推理服务。
5.5 Prefix Cache
Prefix Cache 是 KV Cache 的一个重要应用。很多请求有相同前缀,例如:
text
System Prompt
企业知识库回答规范
安全合规要求
输出格式要求
如果每个请求都重复计算这些固定前缀,会浪费大量 Prefill 计算。Prefix Cache 可以缓存公共前缀的 KV,后续请求直接复用。
适用场景:
- 企业客服系统有固定 System Prompt;
- RAG 系统有固定回答模板;
- Agent 有固定工具说明;
- 代码助手有固定规则;
- 多用户共享同一角色设定。
需要注意的是,Prefix Cache 对 Prompt 完全一致性比较敏感。只要 token 序列不一致,就不能直接复用。因此 Prompt 构造时要尽量把固定部分放前面,把动态部分放后面。
5.6 多轮对话中的 KV Cache 复用
多轮对话天然适合 KV Cache 复用。第一轮计算过的历史上下文,在第二轮中仍然是前缀。
但工程上不能无限保留每个会话的 KV Cache,否则显存会被长期占用。常见策略包括:
- 活跃会话保留 KV Cache;
- 空闲超过一定时间释放;
- 长会话只保留最近窗口;
- 重要会话可换出到 CPU;
- 低优先级会话重新 Prefill;
- 根据用户等级或业务类型设置不同缓存策略。
6. 常见问题:实际落地中的坑和解决方案
6.1 use_cache 开了但速度没明显提升
可能原因:
- 输入仍然每步传完整上下文;
- 框架没有正确传 past_key_values;
- batch 调度导致单请求收益不明显;
- Prefill 阶段占比过高;
- 生成长度太短,KV Cache 优势不明显;
- 使用了不支持高效 KV Cache 的模型结构或算子。
解决方案:
- 检查 Decode 阶段是否只输入最新 token;
- 打印每步 input_ids 长度;
- 分别统计 Prefill latency 和 Decode latency;
- 使用成熟推理框架;
- 对长生成任务单独测试 token/s。
6.2 显存被 KV Cache 占满
常见原因:
- max context length 设置过大;
- batch size 过大;
- 并发请求过多;
- 长会话缓存长期不释放;
- 使用 MHA 模型,KV head 数量较多;
- 没有分页式缓存管理。
解决方案:
- 限制单请求最大上下文;
- 限制最大生成长度;
- 使用 GQA/MQA 模型;
- 使用 PagedAttention 类框架;
- 设置会话缓存 TTL;
- 对超长请求进行降级;
- 使用 KV Cache 量化。
6.3 RAG Prompt 太长导致 Prefill 慢
RAG 系统经常把过多文档片段塞进 Prompt,导致首 token 延迟很高。
解决方案:
- 控制 top_k;
- 使用 rerank 减少无关片段;
- 对检索结果做摘要压缩;
- 对文档 chunk 去重;
- 只保留和问题强相关的段落;
- 将固定系统提示做 Prefix Cache;
- 将长文档问答改为分阶段检索和生成。
6.4 多用户会话缓存难以管理
每个用户都想保留上下文,但 GPU 显存有限。不能简单地为每个会话永久保留 KV Cache。
解决方案:
- 设计 Session Cache Manager;
- 按最近访问时间淘汰;
- 按用户优先级保留;
- 空闲会话释放 GPU KV;
- 必要时将 KV 换出到 CPU;
- 对长时间未活跃会话重新 Prefill;
- 对话历史做摘要,而不是无限追加。
6.5 Cache 和权限控制冲突
企业 RAG 系统中,不同用户权限不同。如果 Prefix Cache 或会话 Cache 没有隔离,可能出现数据安全问题。
例如某个用户的 Prompt 中包含了私有文档内容,如果 KV Cache 被错误复用给其他用户,就会造成严重泄漏。
解决方案:
- KV Cache 必须绑定 tenant、user、session、permission scope;
- 不同权限范围的 Prompt 不允许共享 Prefix Cache;
- 包含用户私有数据的上下文不要作为全局 prefix;
- 缓存 key 中必须包含权限相关字段;
- 会话结束后及时释放敏感缓存;
- 做缓存复用审计日志。
7. 效果评估:如何判断系统是否有效
KV Cache 优化不能只看"感觉快了",需要有指标体系。
7.1 核心性能指标
| 指标 | 含义 | 关注点 |
|---|---|---|
| TTFT | Time To First Token,首 token 延迟 | Prefill 优化效果 |
| TPOT | Time Per Output Token,每个输出 token 延迟 | Decode 效率 |
| tokens/s | 每秒生成 token 数 | 吞吐能力 |
| GPU Memory Usage | GPU 显存占用 | KV Cache 压力 |
| Cache Hit Rate | 缓存命中率 | Prefix/Session Cache 效果 |
| Batch Utilization | batch 利用率 | 调度效率 |
| OOM Rate | 显存溢出率 | 稳定性 |
| P95/P99 Latency | 尾延迟 | 线上体验 |
7.2 如何做压测
建议构造几类请求:
- 短 Prompt + 短生成;
- 短 Prompt + 长生成;
- 长 Prompt + 短生成;
- 长 Prompt + 长生成;
- RAG Prompt;
- 多轮对话 Prompt;
- 高并发混合请求。
这样可以区分瓶颈到底在 Prefill、Decode、KV Cache 显存还是调度层。
7.3 对比实验建议
至少做以下对比:
| 实验 | 目的 |
|---|---|
| use_cache=false vs true | 验证 KV Cache 基础收益 |
| 不同上下文长度 | 观察长上下文下的收益和显存增长 |
| 不同 batch size | 评估吞吐和显存平衡 |
| Prefix Cache 开关 | 评估公共前缀复用收益 |
| 不同 max_new_tokens | 区分 Prefill 和 Decode 占比 |
| 不同并发数 | 验证线上稳定性 |
8. 工程优化:性能、成本、稳定性和可维护性
8.1 性能优化
KV Cache 相关性能优化可以从几个方向入手:
- 使用支持高效 KV Cache 的推理框架;
- 使用 PagedAttention 降低显存碎片;
- 使用 FlashAttention 类高效注意力算子;
- 使用 GQA/MQA 模型减少 KV head;
- 开启 Prefix Cache;
- 做 continuous batching;
- 控制 Prompt 长度;
- 对长上下文做分层摘要;
- 将固定 Prompt 前缀标准化。
8.2 成本优化
成本主要来自 GPU 显存和推理时间。优化策略包括:
- 对不同业务使用不同模型规格;
- 长上下文请求单独限流;
- 高频固定前缀做缓存;
- RAG 检索结果控制长度;
- 对低价值请求降低 max_new_tokens;
- 使用量化降低模型参数和 KV Cache 占用;
- 对离线任务使用批处理,不占在线服务资源。
8.3 稳定性设计
KV Cache 系统必须支持降级:
text
Prefix Cache 不命中 → 正常 Prefill
GPU KV 空间不足 → 降低 batch 或排队
长会话缓存失效 → 重新 Prefill
缓存管理异常 → 清理 session cache
长请求超限 → 截断或摘要压缩
不要让缓存成为系统单点故障。缓存命中是优化,不应该是正确性的前提。
8.4 数据安全
企业落地要特别关注:
- 多租户缓存隔离;
- 用户会话缓存隔离;
- 权限变化后缓存失效;
- 私有文档 Prompt 不共享;
- 日志中不记录敏感 Prompt;
- 缓存释放策略可审计;
- 线上问题可追踪但不泄漏数据。
8.5 可维护性
建议将 KV Cache 相关配置显式化:
yaml
kv_cache:
enable: true
max_context_length: 8192
max_batch_tokens: 32768
session_cache_ttl_seconds: 600
enable_prefix_cache: true
enable_kv_quantization: false
max_gpu_memory_ratio: 0.9
同时要有监控面板,展示:
- 当前活跃请求数;
- KV Cache 使用量;
- Prefix Cache 命中率;
- OOM 次数;
- 平均上下文长度;
- P95/P99 延迟;
- 每个模型实例的 GPU 显存占用。
9. 实践建议:给开发者的落地建议
9.1 不要只优化模型,要优化推理链路
很多团队只关注模型选型,却忽略推理系统。实际线上体验往往由以下因素共同决定:
text
模型结构
Prompt 长度
KV Cache 管理
batch 调度
显存分配
流式输出
RAG 拼接策略
并发控制
KV Cache 是其中非常关键的一环。
9.2 RAG 系统要控制上下文长度
RAG 不是把文档塞得越多越好。上下文越长:
- Prefill 越慢;
- KV Cache 越大;
- 并发能力越差;
- 无关信息越容易干扰生成。
更好的做法是:
- 检索阶段提高召回质量;
- rerank 阶段减少无关 chunk;
- Prompt 中只放必要证据;
- 对历史对话做摘要;
- 对固定系统提示使用 Prefix Cache。
9.3 会话缓存要有生命周期
不要无限保存用户会话 KV Cache。建议按业务重要性设计不同策略:
| 会话类型 | 策略 |
|---|---|
| 普通聊天 | 短 TTL,空闲释放 |
| 企业客服 | 会话期间保留,结束释放 |
| Agent 长任务 | 任务内保留,任务后清理 |
| 高权限数据问答 | 严格隔离,尽快释放 |
| 低频用户 | 不保留 KV,重新 Prefill |
9.4 选模型时关注 KV Cache 成本
同样参数规模的模型,KV Cache 成本可能不同。选型时要关注:
- 最大上下文长度;
- 是否使用 GQA/MQA;
- num_layers;
- num_kv_heads;
- head_dim;
- 支持的推理框架;
- 是否支持量化 KV Cache;
- 长上下文性能曲线。
不要只看模型榜单指标。
9.5 线上必须拆分 Prefill 和 Decode 指标
如果只看总延迟,很难定位问题。建议分别记录:
text
prefill_time
decode_time
generated_tokens
prompt_tokens
tokens_per_second
kv_cache_allocated_blocks
cache_hit_rate
这样才能判断是 Prompt 太长、生成太慢、缓存命中低,还是 batch 调度不合理。
10. 总结:提炼核心观点
KV Cache 是大模型推理系统中的核心机制。它通过缓存历史 token 的 Key 和 Value,避免自回归生成中重复计算历史上下文,从而显著提升生成效率。
它解决的问题是:
- Decode 阶段重复计算;
- 长上下文生成慢;
- 多轮对话复用低;
- RAG 和 Agent 推理成本高。
但它也带来新的工程挑战:
- 显存占用增加;
- 长上下文并发能力下降;
- 多用户缓存隔离复杂;
- Prefix Cache 命中依赖 Prompt 稳定性;
- 会话缓存需要生命周期管理;
- 权限控制和数据安全必须严格设计。
从工程实践看,KV Cache 不是一个单点优化,而是大模型服务架构的一部分。它需要和以下模块协同设计:
- Prompt 构造;
- RAG 检索与 rerank;
- 推理框架;
- batch 调度;
- 显存管理;
- 会话管理;
- 权限控制;
- 监控告警。
一句话总结:
KV Cache 的本质是用显存换计算,用缓存换延迟;它能显著提升大模型生成效率,但必须配合合理的缓存管理、上下文控制和权限隔离,才能真正支撑企业级大模型应用落地。