一篇读懂 Prefix Caching:多轮对话省钱的另一半

站内之前写过《一篇读懂 KV Cache》——它解决的是一次请求内部的浪费:自回归生成时,历史 token 的 KV 状态不必重算。但多轮对话的真实负载是这样的:system prompt 两万 token、工具定义五千、对话历史越滚越长,每一轮请求都把这一切原样重发。模型每一次都老老实实把这些 token 重新过一遍 prefill——明明内容和上一轮一模一样。前缀缓存(Prefix Caching / Prompt Caching)就是为这个浪费准备的另一半答案:相同前缀的 KV 状态,算一遍就够,跨请求共享。它同时是自建推理引擎的默认开关和商业 API 的计价变量——读便宜十倍、写贵四分之一,用错场景会倒亏钱。

为什么能复用,又为什么必须是「前缀」

原理一句话:自回归注意力里,位置 i 的 KV 状态只取决于 token 0..i——前缀相同,则前缀的 KV 状态逐位相同,直接搬过来就能跳过 prefill 重算。这也解释了两个硬约束:

第一,必须是完整前缀,不能是任意片段。 中间任何一个 token 变了,它后面所有位置的 KV 全部作废——KV 依赖的是「它之前的全部上下文」,不是局部窗口。所以缓存的最小单位天然是「从第一个 token 开始的连续前缀」,工程上按 token 块切分、逐块算哈希。

第二,只加速 prefill,不加速 decode。 vLLM 文档对此有明确表述:输出很长的请求、或每次前缀都不同的请求,几乎拿不到收益。缓存省的是「读入上下文的计算」,不是「生成输出的计算」。

复用发生在哪一层,取决于你用的是谁的服务:商业 API 把它做成了计价项(命中部分按折扣计费),自建引擎把它做成了内存管理(命中部分免算)。

自建引擎:哈希链与 radix tree 两条路线

**vLLM 的 automatic prefix caching(APC)**用一条「块哈希链」组织缓存:每个块的哈希 = 哈希(父块哈希 + 本块 token + 附加项),父子相扣,本质是一棵隐式前缀树;只缓存满块,V0 引擎时代默认关闭,约 2025 年初 V1 引擎成为默认后转为默认开启。命中时把已计算块直接登记进 PagedAttention 的块表(引用计数 +1),只为差量后缀分配新块——APC 与 PagedAttention 是同一套内存的两层用法:PagedAttention 管「怎么分页」,APC 管「哪些页不用重算」。驱逐用 LRU,有个值得玩味的细节:请求结束释放块时按逆序放回空闲队列——越靠后的块包含 token 越多、复用概率越低,先驱逐它们。

SGLang 的 RadixAttention 则把前缀树显式建出来(论文 arXiv:2312.07104,2023-12):radix tree 的 key 是 token 序列、value 是 KV 张量,树的边可以挂任意长度的 token 序列,比逐字符的 trie 紧凑;KV 以分页布局放显存、树结构放 CPU 维护,驱逐时 LRU 递归摘叶子。相比哈希链,显式树的优势在分支共享:多个对话从同一个 system prompt 分叉时,公共前缀只存一份,分叉之后各自延伸。配套的 cache-aware 路由再把「缓存亲和」做进了负载均衡——按各副本的命中潜势分发请求,官方数据里 DeepSeek 场景解码吞吐最高 1.9x。缓存驱逐后重算代价太大,于是有了第三层:vLLM 生态的 HiCache 与独立的 LMCache 把缓存扩成「显存 → 主机内存 → 外部存储」的分级体系,逐出的 KV 不丢、跨实例复用——LMCache 已于 2025 年 10 月加入 PyTorch Foundation,成了生态级组件。

商业 API:显式断点与全自动两条路线

不自己管 GPU 的团队遇到的是另一套界面——四家主流 API 截至 2026-10 的机制对比:

触发方式 门槛 读价 写价 TTL
Anthropic 显式 cache_control 断点(≤4 个) 按模型 512–4096 token 0.1x 起,新模型低至 0.025x 1.25x(1h TTL 为 2x) 默认 5 分钟,读写都刷新
OpenAI 全自动(新模型另有显式断点) 1,024 token 新模型 0.1x,旧模型约 0.5x 新模型 1.25x,旧模型免费 新模型 30 分钟,复用刷新
Gemini 隐式自动 + 显式缓存对象 2,048 / 4,096 token 约 0.1x(上线时 0.25x) 无写溢价,显式缓存收存储费 隐式 ≤24h;显式默认 60 分钟
DeepSeek 自动(磁盘缓存) 按 prefix unit 整单元匹配 约为未命中的 1/50 无写溢价 数小时到数天

两条路线的哲学差异值得体会:Anthropic 给你断点、你负责规划——缓存写入只发生在 cache_control 标注的位置(最多 4 个断点,每个有 20 块的回溯窗口),tools → system → messages 的顺序是硬约束,任何一层变化即失效其后全部;规划得好还能玩出预热技巧——max_tokens 设为 0 发一个只写缓存的请求,用户进来之前就把最贵的部分备好。OpenAI 给你自动、你负责布局——无需参数,但命中与否取决于你有没有把静态内容放在前面、动态内容放在后面。DeepSeek 走得更极端,命中价压到未命中价的约五十分之一,代价是匹配要求最严格(SWA 架构下要整个单元对齐)。

算一笔账:78% 的节省是怎么来的

用一个可复现的例子把倍率落成钱。场景:100K token 的 system prompt + 每轮 1K 新消息,10 轮对话,每轮重发全量上下文:

  • 不用缓存:10 × 101K = 1,010K 等效输入 token;
  • 理想命中:首轮写 100K × 1.25 + 新消息 10K + 后 9 轮读 9 × 100K × 0.1 ≈ 225K 等效——省约 78%(与 OpenAI 官方文档的算例口径一致:写一次加九次全命中读,10 个请求的总成本是无缓存时的 2.15 倍,即省 78.5%);
  • TTL 大量过期:若 10 轮里只有 2 轮赶上 5 分钟 TTL(其余全量重写 4 次),成本约 655K 等效——只省 35%;若每轮都过期,写费溢价让总成本反比不用缓存贵 25%。

同一个系统,命中与不命中差出一个数量级——前缀缓存的全部工程学就是围绕「让第三行变成第二行」展开的。

命中率工程学:五条纪律

  1. 静态内容前置:工具定义 → system prompt → few-shot 示例 → 用户消息,稳定性递减、时效性递增。任何把时间戳、随机数放进前缀开头的写法都是在主动砸缓存。
  2. 对话历史只追加、不重写:追加式历史与上一轮只差最后一条消息,命中率接近满额;而压缩、摘要、重排历史都会打断复用链,等于每次全量重写。
  3. 工具定义是最大雷区:改一个描述、换一个顺序,Anthropic 侧全部缓存即失效。OpenAI 的官方建议很有代表性:暂时不用某工具时传 tool_choice: "none" 禁用,不要从 prompt 里删掉它。
  4. 并发与路由要留心:OpenAI 的缓存绑定单机,超过每分钟十几条请求就可能溢出路由到别的机器导致 miss(可用 prompt_cache_key 做亲和);Anthropic 侧首个响应返回前,其他并发请求都命不中这份缓存。多副本自建集群则用 cache-aware 路由解决。
  5. 盯监控指标:各家都吐命中 token 数(OpenAI 的 cached_tokens、Anthropic 的 cache_read_input_tokens、DeepSeek 的 prompt_cache_hit_tokens),官方推荐的 KPI 就是命中 token 占总输入 token 的比例——按会话和流量类型拆开看,钱在哪漏一目了然。两个监控口径的小陷阱:OpenAI 的隐藏系统 token 不计入 cached_tokens,旧模型上该字段向下取整到 128 的倍数——报表里的小额系统性偏差多半来自这里,不是缓存真坏了。
  6. 实验变量放尾部:A/B 测试改 system prompt 的一个字就是全量 miss。若必须实验,把变量作为断点之后的消息尾部注入,前缀本体保持不动——同理适用于按用户定制的任何场景:定制内容越靠后,公共前缀的复用面越大。

坑与边界

把前面没展开的坑归拢一下:写费溢价是押注——1.25x 的写入要靠至少一次命中摊回,官方文档自己给过算例:221 token 的短前缀、10 个请求、0.1x 读价,怎么都摊不回来,前缀太短或命中太少的场景开了就是净亏。TTL 语义有细节——Anthropic 的时钟从请求开始而非响应结束算起,4 分钟的流式响应会吃掉 5 分钟 TTL 的五分之四。省的是钱不是配额——OpenAI 明确命中 token 仍计入速率限制。共享缓存有侧信道——通过命中与未命中的时延差可以探测别人的 prompt 前缀,多租户场景要么用组织级隔离,要么像 vLLM 的 cache_salt 那样把租户标识掺进首块哈希。最后是几类几乎没有收益的场景:单轮短请求(门槛都够不到)、每次上下文全新的任务(如随机文档问答)、输出远长于输入的生成型任务(缓存只省 prefill)。

结语

前缀缓存与 KV Cache 是同一个原理(KV 状态由前缀唯一决定)在不同尺度上的两次应用:KV Cache 省一次请求内的时间维度,前缀缓存省跨请求的重复劳动。它把一个注意力机制的数学性质变成了计价表上的两个倍率——读 0.1x、写 1.25x——于是「prompt 怎么组织」从风格问题变成了成本问题:静态内容放前面、历史只追加、工具别乱动、TTL 心里有数。这四条纪律的成本差异,就是那个 78% 和负 25% 的差距。下次写 system prompt 时不妨多问一句:它的第一个 token,三个月后还会变吗?

参考资料

← 返回资讯列表

读者留言

COMMENTS 暂无
仅本站原创文章开放留言 · 请勿留下手机号、邮箱等个人信息

还没有留言,来说第一句?