背景问题:LLM 服务的重复 prefill 正在变贵
很多团队优化大模型推理服务时,第一反应是换更快的推理框架、做量化、搞连续批处理,或者拆分 prefill 和 decode 阶段。但在真实业务里,还有一类浪费更隐蔽:大量请求反复携带完全相同的前缀内容。
这些前缀可能是:
- 固定的 system prompt 和安全规则
- RAG 场景中重复查询的同一份长文档
- Agent 场景中稳定的工具列表、函数 schema、工作流说明
- 多轮对话里每一轮都会重新提交的历史消息
- 结构化输出场景中稳定的 JSON Schema 或格式约束
Transformer 推理通常分为 prefill 和 decode 两个阶段。prefill 处理输入 prompt,为每一层注意力计算 Key/Value 张量,也就是 KV Cache。输入越长,prefill 越重;而 RAG、Agent、长文档问答恰好会把大量上下文塞进 prompt。若每次请求都从头处理完全相同的前缀,就会浪费 GPU 计算,并拉高 TTFT(Time To First Token)。
Prefix Caching 的目标很直接:当新请求与历史请求共享相同 prompt 前缀时,直接复用已计算好的 KV Cache,跳过共享部分的 prefill 计算。
这不是”接口响应缓存”。响应缓存命中后可以直接返回旧答案;Prefix Caching 命中后,模型仍然会继续读取后续的动态输入并生成新答案。它复用的是中间计算结果,而不是最终文本。
核心原理:缓存的是前缀 KV,而不是回答
1. 什么是可复用前缀
在自回归 Transformer 中,每个 token 在 prefill 阶段会产生对应层的 Key 和 Value 张量。若两个请求的前 N 个 token 完全一致,那么这 N 个 token 对应的 KV 张量也可以复用。
一个典型 RAG 请求可以拆成两部分:
[固定系统指令 + 工具定义 + 文档上下文] + [本次用户问题]
只要左侧部分保持 token 级一致,后续请求就有机会命中缓存。vLLM 的 Automatic Prefix Caching(APC)文档明确说明:APC 会缓存已有查询的 KV Cache,当新查询与已有查询共享相同前缀时,可以复用这部分 KV Cache,跳过共享部分的计算。
2. 为什么必须强调”token 级一致”
Prefix Caching 对”看起来差不多”的文本没有天然容忍能力。多一个空格、换一种字段顺序、时间戳写在前面、工具 schema 顺序变化,都可能导致 token 序列不同,从而无法命中缓存。
因此工程落地时,关键不是简单打开一个参数,而是重构 prompt 组织方式:
| 静态内容(高复用) | 动态内容(低复用) |
|---|---|
| 系统提示词 | 用户问题 |
| 工具定义与 schema | 当前时间 |
| 输出格式约束 | 会话变量 |
| 长文档上下文 | 个性化参数 |
| few-shot 示例 | trace ID |
原则:静态内容稳定地放在 prompt 前部,动态内容尽量放在后部。OpenAI Prompt Caching 文档也建议把静态指令和示例放在开头,把用户特定信息放在结尾,并说明缓存命中依赖 exact prefix match。
3. Prefix Caching 优化的是 prefill,不是 decode
这一点容易被误解。vLLM 文档明确指出,APC 只减少 query processing(prefill 阶段)的时间,不减少生成新 token 的 decode 时间。
因此 Prefix Caching 在以下场景收益更明显:
- 输入很长、输出较短
- 同一长文档被反复问不同问题
- 多轮会话每轮都带上较长历史
- Agent 工具定义很大,但每轮只变少量任务参数
对输出特别长、且输入前缀不重复的场景,收益有限。
工程落地:从”打开缓存”到”设计可命中的 prompt”
1. vLLM 场景:先启用,再观测
vLLM 的启用方式很简洁,设置 enable_prefix_caching=True 即可:
from vllm import LLM, SamplingParams
llm = LLM(
model="meta-llama/Llama-3.1-8B-Instruct",
enable_prefix_caching=True,
)
sampling_params = SamplingParams(temperature=0.2, max_tokens=512)
shared_context = """You are a production support assistant.
Follow the company policy below...
[long static document or tool policy]"""
prompts = [
shared_context + "\nUser question: How should I handle case A?",
shared_context + "\nUser question: What about case B?",
]
outputs = llm.generate(prompts, sampling_params)
这只是最小示例。生产环境还需要同时观测:
- cache hit token 数量
- prefill latency / TTFT
- decode latency / TPOT
- GPU KV Cache 占用
- cache eviction 次数
- 不同业务、租户、模型、prompt 模板的命中率
如果只看整体 QPS,很容易把连续批处理、模型量化、请求长度变化造成的效果误判为 Prefix Caching 的收益。
2. OpenAI API 场景:让前缀稳定,并读取 cached_tokens
OpenAI Prompt Caching 是自动启用的,但并非所有 prompt 都会产生收益。官方文档说明,缓存对 ≥1024 token 的 prompt 可用,usage 字段中会返回 cached_tokens。
建议在日志里保留以下字段:
{
"model": "gpt-5.5",
"prompt_tokens": 2006,
"cached_tokens": 1920,
"ttft_ms": 430,
"total_latency_ms": 1850,
"prompt_template_version": "support-agent-v7"
}
业务侧真正要看的不是”有没有缓存”,而是:
cached_tokens / prompt_tokens
这个比例越高,说明重复 prefill 被复用得越充分。如果比例长期接近 0,通常不是模型问题,而是 prompt 结构或路由策略导致前缀无法稳定命中。
3. SGLang / RadixAttention:把公共前缀组织成树
SGLang 论文提出了 RadixAttention,用于跨多个生成调用复用 KV Cache。它的核心思想是用类似 radix tree 的结构组织共享前缀,让多个请求的公共 token 前缀可以共享已计算的中间状态。
这个方向对复杂 LLM 程序特别有价值,因为 Agent、RAG、多分支推理、few-shot 选择、结构化输出往往不是单次调用,而是一组有关联的调用。若运行时能识别这些调用之间的共享前缀,就能大幅减少重复计算。
4. Prompt 模板要做版本化
Prefix Caching 的稳定性高度依赖 prompt 模板。如果每次发布都调整系统提示词、工具 schema 排序、字段名称或示例顺序,缓存命中率会突然下降。
建议把 prompt 模板作为可观测、可回滚的工程对象:
prompt_template:
id: support-agent-v7
model: llama-3.1-8b-instruct
static_prefix_hash: sha256:...
tool_schema_version: tools-2026-06-30
output_schema_version: answer-json-v3
cache_strategy: prefix-cache-first
上线后按模板版本观察 TTFT 和 cached token 占比,避免”改了一个提示词,线上延迟突然升高”的问题。
适用场景:哪些业务最值得做
| 场景 | 收益机制 | 典型指标改善 |
|---|---|---|
| 长文档问答 | 合同/手册/保单作为固定前缀复用 | TTFT 下降 50-90% |
| 多轮对话 | 历史消息前缀稳定,后续轮次复用 | 第 2+ 轮延迟显著降低 |
| Agent 工具调用 | 工具 schema、权限说明、调用约束高度重复 | prefill 开销趋近 0 |
| 结构化输出 | JSON Schema、分类标签等固定前缀 | 结构化输出延迟下降 |
长文档问答
同一份合同、手册、保单、技术文档被反复查询时,长文档本身可以成为高复用前缀。vLLM 文档也将 long document query 列为 APC 的典型收益场景:同一长文档只需处理一次,未来请求复用其 KV Cache。
多轮对话
多轮会话里,新的请求通常包含之前的聊天历史。如果历史前缀保持稳定,后续轮次可以复用前面已经处理过的内容。需要注意的是,如果每轮都对历史做摘要、裁剪或重排,可能破坏前缀一致性。
Agent 工具调用
Agent 的工具定义、权限说明、调用约束、输出格式经常很长,但同一个 Agent 模板在多个请求之间高度相似。把工具 schema 稳定放在前缀位置,可以显著提升缓存命中概率。
结构化输出
JSON Schema、分类标签说明、字段约束等内容往往固定且可复用。OpenAI 文档也将 structured outputs schema 归入可缓存内容,因为 schema 会作为系统消息前缀的一部分参与缓存。
常见误区
误区一:把 Prefix Caching 当作语义缓存
Prefix Caching 不会判断两个问题语义是否相似,只看前缀 token 是否匹配。若想缓存”相似问题的答案”,那是 semantic cache 或 response cache 的范畴,风险和治理方式完全不同。
误区二:在 prompt 开头放动态字段
很多系统会把当前时间、用户 ID、trace ID、实验组、随机 nonce 放在 prompt 开头。这会直接破坏前缀复用。动态字段应尽量后置,日志字段则不应进入模型 prompt。
误区三:只打开参数,不改 prompt 编排
开启 enable_prefix_caching 只是第一步。真正决定效果的是 prompt 结构、路由策略、模板稳定性和缓存容量管理。
误区四:忽略缓存驱逐策略
GPU KV Cache 空间有限,缓存块会被驱逐。SAECache 论文指出,不同 token 类型的复用率差异很大——系统提示词、用户查询、工具输出、模型回复等 token 的缓存价值并不相同。生产系统至少要按业务类型、模板版本和缓存命中率做观测,避免高价值前缀被低价值长尾请求挤出。
上线检查清单
Prompt 结构
- 静态系统指令是否放在最前面
- 工具 schema 是否稳定排序
- few-shot 示例是否稳定
- 当前时间、用户变量、trace ID 是否后置或移出 prompt
- RAG 文档拼接顺序是否确定
- 模板版本是否可回滚
推理服务
- 是否启用 prefix caching
- 是否记录 cached token 数
- 是否区分 prefill latency 和 decode latency
- 是否监控 TTFT、TPOT、P95、P99
- 是否观察 KV Cache 命中率和驱逐率
- 是否按模型、租户、业务线、模板版本拆分指标
业务治理
- 是否避免跨租户共享敏感上下文
- 是否确认缓存策略符合数据保留要求
- 是否有模板发布灰度
- 是否在回滚时同步回滚 prompt 模板
- 是否为低命中模板设置优化任务
参考架构:把 Prefix Caching 做成一层能力
一个较稳妥的实现方式,是在 LLM Gateway 或 Inference Router 上增加 Prompt Assembly 与 Cache Metrics 两层:
业务请求
→ Prompt Assembly:固定前缀 + 动态后缀
→ Template Versioning:记录模板版本和前缀 hash
→ Inference Router:路由到支持 prefix caching 的推理后端
→ LLM Engine:vLLM / SGLang / API Provider
→ Metrics:cached_tokens、TTFT、TPOT、eviction、hit ratio
这样做有两个好处:
- 业务方不用理解底层 KV Cache 机制
- 平台团队可以通过统一指标判断哪些 prompt 模板值得优化
FAQ
Prefix Caching 会改变模型输出吗?
正常情况下不会。它复用的是相同前缀的 KV 中间表示,后续动态输入仍参与完整推理。OpenAI 文档也说明 Prompt Caching 不影响输出 token 生成或最终响应,命中缓存与否的输出应保持一致。
Prefix Caching 和 KV Cache Quantization 有什么关系?
两者解决的问题不同。Prefix Caching 关注复用相同前缀的 KV,减少重复 prefill;KV Cache Quantization 关注压缩 KV Cache 占用,降低显存压力。二者可以组合使用,但上线时要分别观察命中率、精度、延迟和显存。
为什么缓存命中率很低?
常见原因包括:动态字段被放在 prompt 开头、工具 schema 顺序不稳定、RAG 文档排序不确定、模板频繁改版、上下文裁剪破坏历史前缀、不同租户或请求被路由到不同缓存实例。
Prefix Caching 适合所有 Agent 吗?
不适合。若 Agent 每次都动态生成工具列表、动态改写系统提示词,或每轮上下文差异极大,命中率会很低。更适合工具集稳定、流程固定、上下文较长的生产型 Agent。
主要参考资料
- vLLM - Automatic Prefix Caching — docs.vllm.ai
- OpenAI API Docs - Prompt Caching — developers.openai.com
- SGLang: Efficient Execution of Structured Language Model Programs — arXiv:2312.07104
- ChunkAttention: Efficient Self-Attention with Prefix-Aware KV Cache and Two-Phase Partition — arXiv:2402.15220
- Not All Tokens Are Worth Caching: Learning Semantic-Aware Eviction for LLM Prefix Caches — arXiv:2605.18825
- LMCache: An Efficient KV Cache Layer for Enterprise-Scale LLM Inference — arXiv:2510.09665
- LLM Query Scheduling with Prefix Reuse and Latency Constraints — arXiv:2502.04677