你的应用程序开启了一个包含 4 万 token 系统提示词和完整工具库的长对话。第一轮对话按写入费率为前缀付费,且提供商的 KV 缓存(KV cache)开始预热。第二轮对话在 90 秒后到来。你假设这会命中缓存。有时确实如此。但有时,同样的 4 万 token 会再次以未缓存的价格出现在你的账单上,而你的代码在第一轮和第二轮之间没有任何改动。
改变的是别人的流量。KV 缓存是共享基础设施。你的租户被分配到一个推理节点上,而在你的两轮对话之间的 90 秒内,该节点接纳了足够多的其他租户,从而将你的前缀从内存中驱逐。提供商的控制台会将其描述为“缓存压力(cache pressure)”。你的财务团队会将其描述为一项翻倍的支出项。这两种描述都是准确的,但原因都不在你的代码里。
大多数提示词缓存(prompt-caching)部署背后的架构假设是:缓存是一种你可以提前规划的折扣——注册相应费率,针对前缀稳定性优化你的提示词结构,然后节省的费用就会显现。但现实的版本是:缓存是一种你必须进行概率性预测的折扣,因为决定你是否能获得折扣的输入因素,是与你共享推理节点的陌生人的负载分布。
应该一致却互不相符的两本账
在任何提示词缓存系统中都有两本账,它们存在于一堵你无法看透的墙的两侧。
提供商的账本追踪的是“缓存命中率”——即匹配热前缀并以折扣读取费率提供服务的 token 比例。对于运行推理集群的人来说,这是一个有用的运营指标,大多数提供商都会公开一个针对每个响应的字段(OpenAI 上的 cached_tokens,Anthropic 上的 cache_read_input_tokens 字段),这样你也可以在自己这边看到。
你的账本就是你的发票。它追踪按写入费率计费的 token、按读取费率计费的 token,以及你月底应付的总金额。这两者理应是一致的:高缓存命中率、低写入费用、健康的账单。但它们往往不一致,而其中的差距正是无人预料到的部分。
差距之所以存在,是因为提供商向你展示的缓存命中率是单个响应的属性,而账单则是所有响应集合的属性。命中缓存的请求会报告较高的 cached_tokens 值并且成本低廉。未命中缓存的请求则报告零个缓存 token,并按写入费率支付完整的前缀费用。提供商的控制台显示的是平均值。而账单则是由那些未命中的请求主导的——因为这些请求的成本比命中的情况高出数倍。
如果在高流量工作负载下,你的命中率从 80% 下降到 60%,这听起来像是 25% 的性能退化。但那 20% 从命中转为未命中的请求,其成本并不是增加了 25%——它们大约贵了 10 倍,因为缓存读取的计费标准是基础输入费率的 0.1 倍,而缓存写入的计费标准是 1.25 倍。因此,一个看似温和的命中率变动在账单上却是乘数级的变动,而账单才是唯一需要真金白银支付的账本。
KV 缓存:你不拥有的基础设施
要理解为什么在你没做任何改动的情况下命中率会发生变化,你必须了解 KV 缓存究竟存放在哪里。
在现代 LLM 推理栈中——vLLM、TensorRT-LLM 以及各大提供商运行的私有分支——KV 缓存是被划分为固定大小块的 GPU 内存。每个请求在生成 token 时都会预留内存块,推理系统会在具有共同起始上下文的请求之间共享前缀块。当一个请求完成时,其非共享块会被释放。当推理节点承受压力时,驱逐策略(通常是 LRU 的变体)会从最近未被触及的缓存前缀中回收内存块。
这意味着从你的角度来看有三件事:
第一,缓存存在于特定节点的 GPU 内存中,而不是某个抽象的提供商范围的存储中。你的前缀在刚刚为你提供服务的节点上是热的,在其他地方则是冷的。如果提供商的负载均衡器将你的下一个请求路由到不同的节点——因为原节点繁忙、因为部署,或者因为流量模式转移——无论你最近使用缓存的频率如何,你都会经历冷启动。
第二,驱逐时限(eviction horizon)取决于你看不见的租户的流量。你的前缀占用有限数量的块。同一节点上的每个其他租户都在竞争同一块内存。在空闲的周末,你的前缀可能会在缓存中保持热态直到完整的 TTL。在繁忙的工作日,即使提供商的名义缓存 TTL 是 5 分钟或 60 分钟,同样的前缀也可能在不到一分钟内被驱逐。
第三,合同上的缓存 TTL 并不是实际操作中的缓存 TTL。Anthropic 公布的 5 分钟窗口是最小生命周期——条目在过期后会及时但非立即删除,且每次命中都会重置计时器。但反过来则没有保证:合同中没有任何条款规定你的条目在负载下能撑过完整的 TTL。在 2026 年早些时候,默认 TTL 行为的一次悄然转变导致许多团队的账单出现了有据可查的阶梯式变化,而这种变化只能通过阅读事后分析(post-mortems)和 dev.to 的文章才能发现,阅读代码或发布说明是看不出来的。
你无法控制这一切。提供商控制着它,而提供商的动机是最大化集群的总吞吐量,而不是为你的特定工作负载提供可预测的缓存留存。
无告警的故障模式
这种故障的形式很让人恼火,因为它没有报警。没有任何东西坏掉。没有延迟峰值超过阈值。没有错误率攀升。产品照常工作。模型照常回答。对话照常完成。
改变的是你“单次对话成本”曲线的斜率。同样的代码,调用同样的模型,使用同样的提示词结构,在繁忙时段的成本比空闲时段更高。这种波动不在你编写的任何测试中,因为你针对的是自己的代码编写测试,而波动却存在于别人的流量里。
这就是“嘈杂工作日”故障模式致命的地方:在账单寄到之前,它是隐形的。你全天的平均缓存命中率可能看起来依然可以接受。缓慢的偏移来自于长尾会话——那些带有长前缀的长对话,它们本该很便宜,但事实并非如此,因为这些会话最有可能跨越缓存驱逐事件。
一个有用的练习是度量每个会话的成本,而不是每次请求的成本。单次请求的平均值会掩盖问题;而会话成本的分布会告诉你,极少数的长上下文会话占据了大部分预算外支出,并且同一个会话在不同日期重新运行的成本是不同的。一旦你看清了这个分布,与财务部门的沟通就不再是“为什么账单波动这么大”,而是“哪些会话我们可以承受在共享缓存上运行,而哪些不能”。
缩小差距的模式
你无法修复底层的多租户机制。但你可以改变架构,使其对这种机制不那么敏感。
将单次对话成本视为一等指标。 大多数可观测性栈都在请求级别聚合成本——总 Token 数、平均命中率、单次调用美元。这种做法平均掉了故障模式。相反,应该按会话 ID 对成本进行分组,并查看每个会话中已缓存与未缓存 Token 混合的分布情况。那些在不同轮次中同一个前缀既显示为已缓存又显示为未缓存的会话,正是吞噬你账单的元凶。它们也是调查回报最快的地方。
压缩轮次之间的间隙。 缓存驱逐时限是影响命中率的主导因素。如果你的交互模式让轮次之间相隔数分钟——例如,一个 Agent 在 LLM 步骤之间停下来等待一个缓慢的工具调用——你就在最大化“吵闹邻居”将你驱逐出缓存的机会窗口。在产品允许的情况下,将相关的 LLM 调用紧凑地批量处理。预取下一个可能的轮次。如果你知道真正的轮次即将到来,可以针对相同的前缀发出一个低成本的“预热 Ping”。这些都不是免费的,但它们是用一笔小的、可预测的成本来换取消除那笔不可预测的成本。
将长上下文会话移出共享层。 对于无法承受驱逐波动的负载来说,这是个枯燥但正确的答案。预留吞吐量层级(Provisioned-throughput tiers)和专属容量选项的存在,正是因为当超过一定的前缀长度和会话长度后,共享缓存就不再具有经济优势。针对每个工作负载进行计算:在什么前缀规模下,在典型的轮次间隔内预期的驱逐概率,会导致专属层级的预期成本比包含重新计费的共享层级更便宜。这个交叉点就是你实际的决策边界,而它几乎从不位于公开的单 Token 价格所暗示的位置。
根据预估值而非命中率审计账单。 供应商的 cached_tokens 字段告诉你响应时发生了什么。账单告诉你计费了什么。每周核对一次。你发现的差额很少是供应商计费错误;它们往往是你的客户端代码因为前缀未变而预期命中,但实际响应显示零缓存 Token 的请求,因为前缀在轮次之间已被驱逐。将这种差额视为信号,而非噪声。
停止将缓存命中率视为仅在配置阶段关注的指标。 它不是“调整一次提示词后就束之高阁”的东西。它随供应商负载、集群拓扑、部署周期、当天的发布 TTL 以及陌生人的流量组合而变动。在生产环境中像监控 p99 延迟一样严肃地监控它。在高容量工作负载中,命中率 10% 的偏移就是一个预算事件。
应该更早进行的架构讨论
提示词缓存向严肃的 AI 工程团队提出的更深层次问题是:这种折扣是你赖以规划的东西,还是你预测的东西。
如果你基于它进行规划,你就会在架构中内置假设:Agent 循环假设 40k 的前缀在整个会话中摊销,产品团队定价时假设 70% 以上的命中率,与领导层的单位经济效益对话使用你从公开价目表中获取的数字。所有这些假设都是在赌别人数据中心里某块 GPU 内存的驻留情况,而这笔赌注在清闲的周日和繁忙的周二回报是不同的。
如果你对其进行预测,你就会将波动内置到模型中。成本预测是一个分布,而不是一个点估计。产品定价假设最差的四分之一会话成本将是中位数的数倍。架构为那些因驱逐敏感性而导致在共享层不具经济效益的工作负载提供了明确的后备方案。领导层看到的是一个成本区间,而不是一个成本数字,关于发布哪些功能的讨论是基于哪些功能可以承受该区间而进行的。
这是截然不同的组织姿态,大多数团队在从未进行过讨论的情况下,默认陷入了第一种。他们应该进行讨论的第一个暗示,通常是一张账单。
你在每一轮对话中都必须争取的折扣
提示词缓存(Prompt caching)带来的诱惑在于,人们很容易将其视为一种只需配置一次的设置。将系统提示词标记为可缓存,将工具结构化并置于上下文前端,设置缓存断点,然后发布。
但客观地看,这种折扣是你在每一轮对话中逐步争取到的。对话轮次之间的每一个间隔,都是驱逐策略(eviction policy)回收你前缀的机会。服务商做出的每一次路由决策,都有可能将你分配到冷节点上。进入你所在服务节点的每一个其他租户,都有可能竞占你前缀所占用的内存块。缓存并不是你拥有的状态 —— 它是服务商借给你的状态,其条款并未完全公开,且当服务商对内存有更好的用途时,它就会被回收。
这并不是放弃提示词缓存的理由。节省的成本是真实的,平均情况也是有利的,而另一种选择 —— 在每一轮对话中为每个前缀支付全额写入费用 —— 显然更糟。但这种折扣在你的架构和监控中应该被视为具有波动性的变量,而非系统的固有属性。在产品设计中按中位数情况进行规划,在预算编制中预测长尾效应。将实际命中率视为实时信号进行监测。并且要清楚哪些工作负载只有在共享缓存配合的情况下才具备经济性,因为正是这些负载会最先让你感到措手不及。
会员专享
余下内容仅对会员开放。
会员可读完整内容 —— 每一个公开发布的观点背后,那些框架、决策与推理的全貌。
- —完整文章,包含未公开存档的部分
- —可落地的工作框架,附带权衡与决策依据
- —新文章抢先看,先于公开发布
随时取消 · 一次订阅,畅读全部