跳到主要内容

前缀缓存

前缀缓存(prefix caching)(也称为提示词缓存(prompt caching)或上下文缓存(context caching))是在 LLM 推理中降低延迟和成本最有效的技术之一。它尤其适用于具有重复提示词结构的生产工作负载,例如聊天系统、AI 智能体(agent)和 RAG 流水线。

其思想很简单:通过缓存既有查询的 KV 缓存,共享相同前缀的新查询可以跳过对该部分提示词的重新计算,直接复用缓存结果。

前缀缓存不同于简单的语义缓存(semantic caching)。语义缓存将完整的输入和输出文本存储在数据库中,只有完全匹配(或相似)的查询才能命中缓存并立即返回结果。

前缀缓存是如何工作的?​

前缀缓存复用了模型已经计算出的注意力状态。

  1. 在预填充(prefill)阶段,模型对输入 token 执行一次前向传播,并构建键值(KV)缓存。
  2. 在解码(decode)阶段,模型利用预填充阶段缓存的状态逐个生成输出 token。注意力机制会计算一个 token 交互矩阵。每个 token 产生的 KV 对存储在 GPU 内存中。
  3. 当新请求到达时,模型找到从请求开头匹配的最长缓存 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)在键顺序和结构上是稳定的。非确定性的序列化会导致缓存未命中,即使内容在逻辑上相同。
  • 监控并分析缓存命中率:定期审查缓存性能,以发现优化的机会。

采用情况与性能收益​

在某些用例中,前缀缓存可以将计算量和延迟降低一个数量级。

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)的能力,以确保资源效率。

其他资源​