文章

Prefix Caching 生产实战:用 KV Cache 复用降低长上下文 TTFT

系统讲解 Prefix Caching 在大模型推理服务中的原理、适用场景、工程接入、指标监控、安全边界与常见误区,帮助 RAG、Agent 和多轮对话应用降低 TTFT、减少重复预填充计算并提升资源利用率。

背景问题

在长上下文大模型应用里,很多请求并不是完全不同的。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 CachingvLLM、SGLang引擎自动缓存已有请求的 KV Cache,新请求共享前缀时自动复用,应用侧需调整 prompt 结构
API Provider 的 Prompt CachingOpenAI、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 ratiotoken 级命中比例,通常比请求级命中率更有解释力
TTFT p50/p95/p99Prefix 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 ratiosaved 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 数,适合上下文太长、窗口不足或噪声过多的场景。两者可以组合使用。

参考资料

  1. vLLM Documentation: Automatic Prefix Caching
  2. OpenAI API Documentation: Prompt caching
  3. Prompt Cache: Modular Attention Reuse for Low-Latency Inference
  4. SGLang: Efficient Execution of Structured Language Model Programs
  5. LMCache: An Efficient KV Cache Layer for Enterprise-Scale LLM Inference
  6. Don’t Break the Cache: An Evaluation of Prompt Caching for Long-Horizon Agentic Tasks
  7. Auditing Prompt Caching in Language Model APIs

常见问题

Prefix Caching 和普通 KV Cache 有什么区别?
普通 KV Cache 主要复用同一次生成过程中已经计算过的历史 token;Prefix Caching 则尝试在不同请求、不同轮对话或不同任务之间复用相同前缀对应的 KV Cache,从而减少重复 prefill。
Prefix Caching 一定能降低延迟吗?
不一定。它主要降低 prefill 阶段的重复计算。如果输出很长、decode 阶段占主导,或者请求之间没有稳定相同前缀,收益会明显下降。
生产环境最应该监控哪些指标?
建议至少监控 cached_tokens、cache hit rate、TTFT p95/p99、prefill time、KV Cache 内存占用、eviction 次数、cache thrashing、按租户或路由划分的命中率。
为什么命中率很高,但延迟没有明显下降?
常见原因有三类:第一,命中的 token 很少,请求级命中率高但 token 级收益低;第二,输出很长,主要耗时在 decode;第三,缓存读取、KV 传输或调度开销抵消了节省的 prefill 时间。