跳到主要内容

KV Cache 驱逐:供应商称其为“缓存压力”,而你的账单则称其为“双倍前缀费用”

· 阅读需 13 分钟
Tian Pan
Software Engineer

你的应用程序开启了一个包含 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 的文章才能发现,阅读代码或发布说明是看不出来的。

你无法控制这一切。提供商控制着它,而提供商的动机是最大化集群的总吞吐量,而不是为你的特定工作负载提供可预测的缓存留存。

无告警的故障模式

加载中…
References:Let's stay in touch and Follow me for more thoughts and updates