站内之前写过《一篇读懂 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%。
同一个系统,命中与不命中差出一个数量级——前缀缓存的全部工程学就是围绕「让第三行变成第二行」展开的。
命中率工程学:五条纪律
- 静态内容前置:工具定义 → system prompt → few-shot 示例 → 用户消息,稳定性递减、时效性递增。任何把时间戳、随机数放进前缀开头的写法都是在主动砸缓存。
- 对话历史只追加、不重写:追加式历史与上一轮只差最后一条消息,命中率接近满额;而压缩、摘要、重排历史都会打断复用链,等于每次全量重写。
- 工具定义是最大雷区:改一个描述、换一个顺序,Anthropic 侧全部缓存即失效。OpenAI 的官方建议很有代表性:暂时不用某工具时传
tool_choice: "none"禁用,不要从 prompt 里删掉它。 - 并发与路由要留心:OpenAI 的缓存绑定单机,超过每分钟十几条请求就可能溢出路由到别的机器导致 miss(可用
prompt_cache_key做亲和);Anthropic 侧首个响应返回前,其他并发请求都命不中这份缓存。多副本自建集群则用 cache-aware 路由解决。 - 盯监控指标:各家都吐命中 token 数(OpenAI 的
cached_tokens、Anthropic 的cache_read_input_tokens、DeepSeek 的prompt_cache_hit_tokens),官方推荐的 KPI 就是命中 token 占总输入 token 的比例——按会话和流量类型拆开看,钱在哪漏一目了然。两个监控口径的小陷阱:OpenAI 的隐藏系统 token 不计入cached_tokens,旧模型上该字段向下取整到 128 的倍数——报表里的小额系统性偏差多半来自这里,不是缓存真坏了。 - 实验变量放尾部: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,三个月后还会变吗?
参考资料
- Prompt caching — Anthropic 官方文档:断点、定价倍率、TTL 刷新语义与失效规则(截至 2026-10)。
- Prompt caching — OpenAI 官方指南:自动缓存、最小长度与命中率最佳实践,含写费与官方算例。
- Automatic Prefix Caching 与 Prefix Caching 设计 — vLLM 文档:块哈希链、LRU 逆序驱逐与
cache_salt的一手说明。 - Fast and Expressive LLM Inference with RadixAttention — LMSYS 博客(2024-01):radix tree 缓存的设计与数据。
- SGLang: Efficient Execution of Structured Language Model Programs — arXiv:2312.07104:RadixAttention 原始论文。
- Context Caching on Disk — DeepSeek 官方指南:磁盘缓存机制与命中/未命中计量。
- Gemini API Context Caching — Google AI 文档:隐式/显式缓存的门槛与 TTL。
- 一篇读懂 KV Cache:大模型推理加速的第一课 — 本站:本文的姊妹篇,一次请求内部的 KV 复用。
读者留言
COMMENTS 暂无还没有留言,来说第一句?