前缀缓存
前缀缓存(prefix caching)(也称为提示词缓存(prompt caching)或上下文缓存(context caching))是在 LLM 推理中降低延迟和成本最有效的技术之一。它尤其适用于具有重复提示词结构的生产工作负载,例如聊天系统、AI 智能体(agent)和 RAG 流水线。
其思想很简单:通过缓存既有查询的 KV 缓存,共享相同前缀的新查询可以跳过对该部分提示词的重新计算,直接复用缓存结果。
前缀缓存不同于简单的语义缓存(semantic caching)。语义缓存将完整的输入和输出文本存储在数据库中,只有完全匹配(或相似)的查询才能命中缓存并立即返回结果。
前缀缓存是如何工作的?
前缀缓存复用了模型已经计算出的注意力状态。
- 在预填充(prefill)阶段,模型对输入 token 执行一次前向传播,并构建键值(KV)缓存。
- 在解码(decode)阶段,模型利用预填充阶段缓存的状态逐个生成输出 token。注意力机制会计算一个 token 交互矩阵。每个 token 产生的 KV 对存储在 GPU 内存中。
- 当新请求到达时,模型找到从请求开头匹配的最长缓存 token 序列,加载这些 KV 状态,并只对剩余 token 运行预填充。
这只有在前缀完全一致时才有效,包括空格和格式。即使只有一个字符不同,也会破坏缓存。考虑下面三个提示词:
Prompt 1: Summarize this incident report in one paragraph.
Prompt 2: Summarize this incident report in three bullet points.
Prompt 3: Write a one-paragraph summary of this incident report.
提示词 2 可以复用共享开头 Summarize this incident report in 的 KV 状态。模型只需要处理两个提示词分叉之后的剩余部分。提示词 3 请求的是类似的结果,并且包含与提示词 1 相同的许多词,但它不以相同的 token 序列开头。因此,它无法从一开始就复用提示词 1 的缓存。
这就是前缀缓存不同于语义缓存的原因。两个提示词可能含义相同,却仍然无法命中前缀缓存;而两个最终问题不同的请求,却可能共享大部分缓存的计算。
以下是前缀缓存的两种常见用例:
复用静态系统提示词
一个常见的可缓存前缀是许多请求共享的系统提示词:
You are a helpful AI writer. Please write in a professional manner.
如果该提示词及其序列化格式保持不变,模型只需计算一次 KV 状态,即可在多次对话中复用。这样每个请求只需对紧随其后的用户特定内容运行预填充。
复用不断增长的对话历史
多轮聊天正是收益更明显的地方。假设第一轮是:
User: Why did the checkout API slow down after deployment?
Assistant: Trace data shows that repeated inventory database lookups added most of the latency.
然后用户问:
User: Which lookup should we optimize first?
模型得到的并不只是最新的问题。应用会将之前的消息重新发送,作为上下文窗口的一部分,这样模型才知道用户指的是哪些查询。因此,序列化后的第二个请求大致如下:
User: Why did the checkout API slow down after deployment?
Assistant: Trace data shows that repeated inventory database lookups added most of the latency.
User: Which lookup should we optimize first?
前两条消息是模型已经处理过的精确前缀。如果它们的 KV 状态仍然可用,模型就可以复用它们,只对新用户消息做预填充。如果没有前缀缓存,模型必须重新处理整个对话。
随着对话变长,可复用的前缀也随之增长。避免重复预填充可以降低 GPU 计算量,并使 TTFT 不会随轮次增加而快速上升。请注意,命中缓存仍然取决于服务引擎保留该条目、将请求路由到可访问该缓存的 worker,以及以完全一致的方式序列化之前的消息。
KV 缓存与前缀缓存有什么区别?
KV 缓存用于在 GPU 内存中存储每个 token 的中间注意力状态。它最初用于描述单个推理请求内的缓存,尤其对于加速解码阶段至关重要。
LLM 在解码时以自回归方式工作,根据先前生成的 token 输出下一个新 token(即复用它们的 KV 缓存)。如果没有 KV 缓存,模型需要在每一步解码中重新计算先前 token 的所有内容(而上下文每步都在增长),这将造成巨大的资源浪费。
当把这种缓存概念扩展到多个请求时,更准确的叫法是前缀缓存。由于 KV 缓存的计算只依赖于所有先前的 token,具有相同前缀的不同请求可以复用相同的前缀 token 缓存,从而避免重新计算。
如何组织提示词以最大化缓存命中率
前缀缓存只有在提示词保持一致时才有用。以下是一些最大化缓存命中率的最佳实践:
- 前置静态内容:将恒定或很少变化的信息放在提示词开头。这可以包括跨多次查询保持不变的系统消息、上下文或指令。将动态的或用户特定的内容移到提示词末尾。
- 将相似请求分批:将共享相同前缀的查询(尤其是在服务多个用户或智能体时)分组,以便高效复用缓存结果。
- 避免在前缀中加入动态元素:不要在提示词早期插入时间戳、请求 ID 或任何其他与请求相关的变量。这会降低你的缓存命中率。
- 使用确定性的序列化:确保你的上下文或记忆序列化(例如 JSON)在键顺序和结构上是稳定的。非确定性的序列化会导致缓存未命中,即使内容在逻辑上相同。
- 监控并分析缓存命中率:定期审查缓存性能,以发现优化的机会。
采用情况与性能收益
在某些用例中,前缀缓存可以将计算量和延迟降低一个数量级。
- Anthropic Claude Sonnet 提供提示词缓存,对于长提示词最高可节省 90% 的成本并降低 85% 的延迟。
- Google Gemini 对缓存 token 给予折扣,并单独收取存储费用。
vLLM、SGLang 和 MAX 等框架为不同的开源 LLM 支持前缀缓存。在这些引擎中,前缀缓存与 KV 缓存的分配方式相关联,通常默认开启。你实际能控制的是是否禁用它,以及缓存 token 的匹配粒度:
MAX
max serve --model google/gemma-3-27b-it \
--enable-prefix-caching \
--kv-cache-page-size 256
MAX 中前缀缓存默认开启。使用 --no-enable-prefix-caching 禁用它。页大小必须是 128 的倍数。
vLLM
vllm serve --model google/gemma-3-27b-it \
--enable-prefix-caching \
--block-size 16
在 vLLM 中使用 --no-enable-prefix-caching 禁用它。
SGLang
sglang serve --model-path google/gemma-3-27b-it
SGLang 以 RadixAttention 的方式实现前缀缓存,即在 KV 缓存之上建立基数树(radix tree),并默认启用。它以 token 粒度匹配,而不是固定的块(--page-size 默认为 1)。使用 --disable-radix-cache 禁用它。
禁用前缀缓存通常并不是一种性能优化。它主要用于建立无缓存基线。
在智能体工作流中,收益甚至更为显著。某些用例的输入输出 token 比例高达 100:1,使重新处理大型提示词的成本高得不成比例。
局限性
对于具有长且重复提示词的应用,前缀缓存可以显著降低延迟和成本。然而,随着时间推移,你的 KV 缓存规模可能变得相当庞大。GPU 内存是有限的,在众多用户之间存储长前缀会很快耗尽空间。你需要缓存逐出(eviction)策略或内存分层(tiering)。
开源社区正在积极研究分布式服务策略。详见推理路由。
另一个实际局限是特性组合。当模型只有一个标准的全注意力 KV 缓存时,前缀缓存很容易理解。较新的服务栈可能需要同时管理多种缓存类状态:用于投机解码的草稿模型与目标模型缓存、VLM 的图像编码器状态、量化 KV 缓存的缩放元数据,或混合注意力层的独立缓存。
对于这些模型,共享的文本前缀并不总是意味着每种缓存状态都能以相同方式复用。例如,滑动窗口注意力只保留一个有界的近期窗口,因此缓存管理器必须知道哪些 token 仍然有效。在生产环境中,应将前缀缓存命中率视为一个与工作负载相关的指标,而不是一个单一的全局数字,并验证你的推理框架能否将前缀缓存与你启用的其他优化组合使用。
优化 LLM 前缀缓存需要你的 LLM 服务与基础设施栈具备灵活的可定制能力。我们致力于提供用于专用和可定制 LLM 部署的基础设施,具备快速自动扩缩容和缩容至零(scaling-to-zero)的能力,以确保资源效率。