背景问题
在长上下文大模型应用里,很多请求并不是完全不同的。RAG 系统会反复携带同一份产品手册、合同条款或知识库片段;Agent 系统会在每轮调用里重复系统提示词、工具说明和安全策略;多轮对话也会不断把历史消息作为前缀重新提交给模型。
这些重复内容最直接的问题是:模型每次都要重新做 prefill。也就是说,即使前面 10,000 个 token 和上一轮完全一样,只要服务端没有复用能力,模型仍然要重新计算这些 token 对应的注意力状态。对于长上下文场景,这会显著推高 TTFT(Time To First Token)、GPU 计算成本和排队延迟。
Prefix Caching 的核心目标就是把这些重复前缀对应的 KV Cache 留下来,在后续请求命中相同前缀时直接复用,让模型只处理新增的后缀内容。它不是模型能力优化,也不是压缩上下文,而是推理服务层面的重复计算消除。
核心原理:从 KV Cache 到跨请求复用
在自回归生成中,模型每生成一个 token,都需要关注之前的 token。普通 KV Cache 会把当前请求中已经计算过的 key/value 张量缓存起来,避免每一步 decode 都重新计算完整历史。这解决的是”同一次生成内部”的重复计算。
Prefix Caching 向前推进了一步:如果两个请求拥有相同的 prompt prefix,那么这段 prefix 经过模型 prefill 得到的 KV Cache 是可以复用的。服务端只需要识别”相同前缀”,找到对应的 KV block,然后从命中位置继续计算后面的新增 token。
Request A: [System Prompt][Tool Schema][Long Document][User Question A]
└──────────── shared prefix ────────────┘
Request B: [System Prompt][Tool Schema][Long Document][User Question B]
└──────────── shared prefix ────────────┘
如果 A 已经完成 prefill,B 到来时就可以复用前面三段的 KV Cache,只对 User Question B 做增量 prefill。
为什么必须是”前缀”
大多数工程实现强调 exact prefix match,不是任意文本片段匹配。原因是 Transformer 的位置编码、注意力掩码和 token 顺序会影响 KV 状态。即使文本内容相同,只要出现在不同位置,直接复用也可能不安全或不可用。
这也是为什么 Prompt Layout 很重要:静态内容应尽量放在开头,动态内容放在末尾。如果把用户 ID、时间戳、随机 trace、动态工具结果放在系统提示词前面,就会在最早位置打断前缀,导致后面再长的公共内容也无法命中缓存。
自动前缀缓存与显式提示缓存
生产环境里常见两类形态:
| 类型 | 代表 | 特点 |
|---|---|---|
| 推理引擎内部 Automatic Prefix Caching | vLLM、SGLang | 引擎自动缓存已有请求的 KV Cache,新请求共享前缀时自动复用,应用侧需调整 prompt 结构 |
| API Provider 的 Prompt Caching | OpenAI、Anthropic | 对重复 prompt prefix 自动生效,通过 cached token 字段暴露命中情况,应用侧需使用稳定 cache key |
二者目标相似,但控制面不同。自托管推理更关注 KV block 管理、GPU 内存、调度和 eviction;API Provider 模式更关注 prompt 组织、cache key、请求频率、计费与合规边界。
工程落地
第一步:判断工作负载是否适合
Prefix Caching 并非万能。以下场景通常更有价值:
适合的场景:
- RAG 长文档问答:同一份长文档被多次查询,用户问题在末尾变化
- Agent 工作流:系统提示词、工具 schema、安全策略很长且稳定
- 多轮对话:前几轮历史基本不变,只追加新消息
- 批处理任务:大量请求共享相同模板,少量变量不同
- 结构化输出任务:JSON Schema 或输出约束较长且重复出现
不太适合的场景:
- prompt 很短,prefill 本来就不是瓶颈
- 每次请求前缀都不同,无法形成复用
- 输出特别长,decode 阶段占主要耗时
- 动态字段散落在 prompt 前部,导致缓存不断失效
- 多租户隔离要求很高但缓存 key 和权限边界尚未设计清楚
第二步:重构 Prompt Layout
Prefix Caching 的收益往往不是打开一个开关就能得到,而是需要重构请求格式。
推荐顺序:
[Stable system instructions]
[Stable policy / safety rules]
[Stable tool schemas]
[Stable examples]
[Stable retrieved document or shared context]
[Session history that rarely changes]
[Dynamic user-specific data]
[Current user question]
反例:
[request_id: random]
[current_time: every request changes]
[user profile]
[stable system instructions]
[stable tool schemas]
[long document]
[current question]
上面的反例会让前几个 token 就发生变化,导致后续公共内容无法作为 exact prefix 命中。
第三步:自托管推理中的接入方式
以 vLLM 为例,Automatic Prefix Caching 可以通过引擎参数开启。实际参数命名可能随版本变化,生产落地前应以当前部署版本文档为准。
from vllm import LLM, SamplingParams
llm = LLM(
model="your-model-name",
enable_prefix_caching=True,
)
sampling_params = SamplingParams(
temperature=0.2,
max_tokens=512,
)
outputs = llm.generate(
prompts=[
stable_system_prompt + stable_document + "\nQuestion: ..."
],
sampling_params=sampling_params,
)
工程上不要只看平均延迟。应该按路由、业务、租户、模型、prompt 模板版本分别统计缓存命中。否则很容易出现”整体均值没变化,但部分高价值长上下文接口已经明显受益”的误判。
第四步:API Provider 模式下的接入方式
如果使用带 Prompt Caching 的模型 API,重点是稳定前缀和监控 cached token。
from openai import OpenAI
client = OpenAI()
response = client.responses.create(
model="gpt-5.5",
prompt_cache_key="tenant-a-contract-review-v1",
prompt_cache_retention="24h",
input=[
{
"role": "system",
"content": "Stable policy, stable instructions, stable examples..."
},
{
"role": "user",
"content": "Dynamic user question placed at the end."
},
],
)
print(response.usage)
注意: cached token 仍然需要进入请求上下文和模型执行路径的约束体系。它可以降低部分延迟和成本,但不会让上下文窗口变大,也不会替代上下文压缩、检索质量控制或权限隔离。
关键指标
上线前建议建立一组专门的 Prefix Caching 指标,而不是只看 QPS 和平均延迟。
性能指标
| 指标 | 说明 |
|---|---|
| cache hit rate | 请求级命中率,表示有多少请求命中了缓存 |
| cached token ratio | token 级命中比例,通常比请求级命中率更有解释力 |
| TTFT p50/p95/p99 | Prefix Caching 最直接影响的是首 token 延迟 |
| prefill time | 需要单独拆出来,避免被 decode 时间掩盖 |
| TPOT / ITL | 确认缓存没有引入 decode 侧退化 |
| throughput | 同等 GPU 下单位时间完成的请求数或 token 数 |
资源指标
| 指标 | 说明 |
|---|---|
| KV Cache 内存占用 | 缓存不是免费的,会占用 GPU HBM、CPU 内存或外部存储 |
| eviction 次数 | 频繁驱逐说明缓存容量或请求模式不匹配 |
| cache thrashing | 不同前缀来回竞争缓存,导致命中率低且内存波动大 |
| prefix length distribution | 命中的前缀长度分布,判断是否真正命中了大块上下文 |
安全与隔离指标
| 指标 | 说明 |
|---|---|
| 按租户 cache key 统计 | 避免跨租户共享敏感前缀 |
| 权限边界命中检查 | 同一缓存条目不应被无权限用户复用 |
| 隐私与 retention 审计 | 确认缓存保留时间、存储位置和清理策略 |
| timing side-channel 风险评估 | 如果缓存命中会显著改变延迟,评估是否可能泄露 prompt 曾被处理过 |
适用场景
RAG:同文档多问题
RAG 最常见的问题是同一批文档片段被多次携带进 prompt。比如合同审查系统里,用户连续询问”违约条款""赔偿责任""自动续约”。如果每次都把同一合同全文放到上下文里,Prefix Caching 可以让合同正文部分尽量复用。
关键做法:把检索到的稳定文档放在用户问题之前,并避免在文档前面插入每次变化的字段。
Agent:稳定工具说明和策略
Agent 应用常常有很长的 system prompt、工具定义、MCP 工具说明、JSON Schema 和安全策略。这些内容天然适合放在 prompt 前部缓存。动态的工具执行结果、页面内容、用户临时输入则放在后部,避免破坏公共前缀。
多轮对话:复用历史前缀
多轮对话中,每一轮都包含之前的历史消息。Prefix Caching 可以减少重复处理历史消息的成本,但前提是历史消息序列保持稳定。如果应用每轮都会重写、摘要或重新排序历史,就会降低命中率。
批量任务:模板固定,变量后置
例如批量生成商品描述、批量审查简历、批量总结工单。只要模板、规则、示例固定,把变量放到末尾,就能让大量请求共享前缀。
常见误区
误区一:以为缓存可以提升所有请求
Prefix Caching 只对共享前缀有效。没有共享前缀,就没有复用。输出特别长时,decode 阶段仍然会消耗大量时间,缓存收益也会被稀释。
误区二:把动态字段放在开头
时间戳、request id、用户昵称、AB 实验参数、trace id 这类字段如果放在最前面,会让 exact prefix match 从第一段就失败。它们应该尽量放在后面,或者不要进入模型 prompt。
误区三:只看请求级命中率
一个请求命中 100 个 token 和命中 10,000 个 token 的价值完全不同。生产环境更应该看 cached token ratio 和 saved prefill tokens。
误区四:忽略多租户隔离
如果缓存 key 只按文本前缀计算,而没有结合租户、权限、模型版本、工具版本,就可能产生错误复用或隐私风险。缓存设计必须纳入安全评审。
误区五:频繁修改模板
每次发布都改 system prompt、工具 schema 或示例顺序,会导致缓存命中率突然下降。应对 prompt 模板做版本化,并把变更纳入性能回归。
上线检查清单
1. Prompt 结构
- 静态系统提示词是否放在最前?
- 工具 schema、JSON Schema、few-shot 示例是否稳定?
- 动态字段是否放在末尾?
- request id、时间戳、trace id 是否避免进入 prompt 前部?
- 模板是否有版本号,且版本变更可追踪?
2. 缓存策略
- 是否区分模型版本、tokenizer、租户、权限域?
- 是否明确缓存保留时间?
- 是否有 eviction 策略?
- 是否有手动或自动降级开关?
- 是否能按路由关闭 Prefix Caching?
3. 性能验证
- 是否建立无缓存基线?
- 是否分别统计 prefill、decode、TTFT?
- 是否覆盖 p95/p99 延迟,而不是只看平均值?
- 是否验证高并发下 cache thrashing?
- 是否验证模板发布后的命中率变化?
4. 质量验证
- 是否确认缓存不会改变模型输出语义?
- 是否覆盖 RAG、Agent、多轮对话等代表性任务?
- 是否有任务级评估集?
- 是否在缓存开启和关闭之间做 A/B 对比?
5. 安全验证
- 是否避免跨租户缓存共享?
- 是否对敏感 prompt 做隔离或禁用缓存?
- 是否评估 timing side-channel 风险?
- 是否符合组织的数据保留和区域合规要求?
- 是否记录缓存命中审计日志?
FAQ
Prefix Caching 会改变模型输出吗?
在正确实现下,它复用的是相同前缀对应的 KV 状态,目标是减少重复 prefill,而不是改变采样策略或模型权重。理论上不应改变模型输出。但生产环境仍需用固定评估集做回归测试,特别是涉及位置编码、模板变化、工具 schema 变化和多模型版本切换时。
Prefix Caching 和上下文压缩是什么关系?
二者解决的问题不同。Prefix Caching 复用重复前缀,适合”内容重复但仍需要完整上下文”的场景;上下文压缩是减少输入 token 数,适合上下文太长、窗口不足或噪声过多的场景。两者可以组合使用。
参考资料
- vLLM Documentation: Automatic Prefix Caching
- OpenAI API Documentation: Prompt caching
- Prompt Cache: Modular Attention Reuse for Low-Latency Inference
- SGLang: Efficient Execution of Structured Language Model Programs
- LMCache: An Efficient KV Cache Layer for Enterprise-Scale LLM Inference
- Don’t Break the Cache: An Evaluation of Prompt Caching for Long-Horizon Agentic Tasks
- Auditing Prompt Caching in Language Model APIs