发布说明只有两行。“改进了多语言分词(Tokenization)。模型输出无破坏性变更。”一共不到二十个字。你的评估(Evals)确认了这一点:相同的提示词,相同的生成内容,相同的评分。你的平台团队在周五下午批准了升级。到了周二早上,你的缓存命中率从 80% 下降到 4%,每日推理费用翻了两番,而凌晨 6 点把你叫醒的轮值工程师在你的代码里找不到任何一行改动。
你的代码确实没有任何改动。但服务商发布了一个新的分词器,它对某个 Unicode 字符的一个字节划分与旧版本不同。你系统中每个缓存的前缀现在都是基于一个已不再存在的 Token 序列生成的指纹。模型的表现完全一致 —— 这确实是事实。但发布说明中未曾提及的缓存层,却为此付出了全额代价。
这种失效模式是提示词缓存(Prompt Caching)在架构上带来的风险:你所依赖的成本契约,并不是服务商测试的那个契约。服务商测试的是模型对相同的输入产生相同的输出。而你依赖的是模型对相同的输入产生相同的 Token,以相同的顺序、相同的字节边界,并哈希成相同的前缀指纹。这是两种截然不同的契约,而其中只有一种拥有回归测试套件。
缓存究竟是以什么为键的
目前各大服务商部署的提示词缓存本质上都是前缀缓存(Prefix Cache)。系统从第一个 Token 开始,按分块(Chunks)对 Token 序列进行哈希处理,并存储以这些哈希值为键的中间 KV 状态。当新请求到来时,缓存从头开始遍历,对 Token 进行哈希,直到发现一个与存储哈希不匹配的分块边界。在那之前的所有内容都算命中,之后的所有内容都要按原价重新计算。
关键的细节在于:缓存是对 Token 进行哈希,而不是字符。服务商在计算哈希之前会先对你的原始输入进行分词。如果分词器发生了变化 —— 即使相同的字符串现在产生了不同的 Token 序列 —— 那么所有已缓存的前缀都会变得不可访问。磁盘上的字符串没变,但由此衍生的指纹变了。
对于大多数提示词内容来说,这并不是问题,因为分词器对于绝大多数 ASCII 文本和成熟的多语言内容都是稳定的。危险潜伏在边缘地带:
- 可以通过不同规范化形式(NFC 与 NFD)表示的 Unicode 字符,例如
é 既可以是一个码点,也可以是两个
- 组合字符、带有零宽连字符(ZWJ)的表情符号序列以及区域指示符号
- 较冷门语种的字符,其分词器的 BPE 合并过程是针对不同语料库重新训练的
- 跨越新分词器划分的预分词边界的字节序列
分词器的“升级” —— 通常被描述为更好的多语言覆盖或更大的词表 —— 几乎总是会改变这些边缘情况。模型行为在设计上得到了保留:通过使用新分词器和足够的数据继续训练,使输出分布保持匹配。而缓存指纹的保留则纯属偶然,仅适用于那些在升级前后恰好落在相同 Token 边界上的内容。对于其他任何内容,前缀都会失效。
为什么在账单寄来之前它是隐形的
这种模式之所以会静默失效,是因为它保持了正确性。每个请求依然有效,每个生成结果依然正确。评估套件依然通过。延迟预算可能会稍微增加,因为缓存命中的返回速度比缓存未命中快,但缓存命中本身也存在波动,所以长尾端 20–40% 的延迟增加看起来就像正常的噪声,除非有人在盯着分布图。唯一崩坏的是成本曲线,而成本报告通常比流量增长预留了余量的预算延迟 24 小时。
标准的遥测堆栈抓不住这个问题。大多数团队在 LLM 调用上只监测三件事:延迟、错误率和总 Token 数。在分词器引发的缓存崩溃期间,这三者都不会有明显变化。原本只需支付 10% 费用的缓存前缀现在按 100% 计费,而暴露该细分项目的请求路径 —— cached_input_tokens 与 input_tokens —— 通常在仪表盘上被聚合成了单一的“输入 Token”指标。即使缓存完全失效,你可能也无法在轮值人员监控的图表中发现它。
真正会发生变化的是 cache_read_input_tokens 与总输入 Token 的比例(前提是服务商报告了该指标且你记录了它)。这就是“金丝雀”。一个健康的长上下文工作负载该比例应在 60–90% 之间。而崩溃的工作负载则是个位数。如果你没有按路由、按模型、按分钟绘制这个比例的图表,你将无法察觉它崩塌的瞬间。
发布说明的不对称性
更深层次的问题在于,服务商和消费者对“无破坏性变更”的定义不同。服务商的契约(显现在文档中,隐含在变更日志规范中)是关于模型输出的。给定相同的逻辑输入,模型是否产生相同的 Logits?如果是,则没有破坏性变更。对于他们暴露的 API 表面来说,这是一个合理的契约。
但提示词缓存是被作为一种成本优化手段销售的,而成本契约则完全是另一回事。它是一个关于 缓存指纹字节级稳定性 的契约,需要在客户无法感知的服务商侧基础设施变更中保持持续。没有任何服务商的文档承诺过这种契约。最接近的描述通常是“缓存键包含完整的提示词前缀”,这只是在描述现状,而非对未来的保证。
发布说明中写着“无破坏性变更”,这在技术上是正确的。但它也极具误导性,而这恰恰是导致你在凌晨 4 点被传呼的原因。服务商测试了他们交付的产品,但服务商没有测试你的账单。
三种缓解措施,按可行性排序
老实说,这在很大程度上超出了你的控制范围。分词器(Tokenizer)是服务器端的事物。你不拥有它,无法固定它,通常甚至不知道运行的是哪个版本。但你可以做三件事,按客户实际拥有的主动权递增排列。
将缓存命中率作为一级 SLO 进行监控。 这是唯一能在几分钟而非几天内捕捉到故障的缓解措施。计算 cache_read_input_tokens / (cache_read_input_tokens + input_tokens),并按路由、模型、供应商绘图,分辨率设为 5 分钟。当偏离基线超过两个标准差且持续 10 分钟时发出告警。将其整合进值班人员查看延迟和错误率的同一仪表盘中,因为由分词器驱动的缓存崩溃具有独特性:其他所有信号都保持绿色,唯独这一项断崖式下跌。大多数团队围绕 Prompt 缓存构建的仪表盘追踪的是总节省成本,这是一个滞后指标。而命中率则是领先指标。
固定模型版本,而非模型别名。 大多数供应商都会同时提供这两者——例如 claude-sonnet-latest 和 claude-sonnet-4-6-20260201,或者 gpt-4o 和 gpt-4o-2025-11-15。别名会根据供应商选择的节奏在你背后自动切换。固定版本理论上应该是不可变的。实际上,模型权重是固定的,但周边基础设施——分词器、归一化、批处理——有时并非如此,而这句话中的“有时”承担了很大的分量。固定版本依然有帮助:它切断了最激进的一类无声变更,即常规的别名轮换。它不能完全消除故障模式,但它要求供应商针对固定名称进行显式的发布,这比默认情况的门槛更高。
保留内容级金丝雀(Canary)。 维护一小组已知处于分词器边缘案例的内容 Prompt——例如 Unicode 密集的多语言文本、表情符号序列、ZWJ 组合、带有异常字符的代码——并每分钟针对缓存回放一次。将这种合成流量的缓存命中率与生产流量分开记录。真实的分词器变更会先在金丝雀上显现,然后才会反映在聚合指标中,因为合成流量正是针对那些容易导致损坏的内容选取的。为了早期预警,花这点钱是很划算的。
什么不是缓解措施:因此切换供应商。每个供应商面临这种故障模式的风险是相同的,因为每个供应商都针对他们生成的 Token 运行相同类型的前缀缓存(Prefix Cache)。还没发生过此类事件的供应商,只是还没宣布而已。在凌晨 4 点的事故中更换供应商的迁移成本,远高于等待 48 小时让缓存重新填满的成本。缓存重新填满才是分词器变更尘埃落定后的实际情况:你的前缀在新方案下被分词,命中率在一天内爬回基线,账单也随之恢复正常。昂贵的窗口期是你毫不知情的那个时段。
你并不拥有的契约
除了这一特定故障外,更普适的教训是:Prompt 缓存向你推销时说是 90% 的折扣,你买单时也以为是 90% 的折扣,但底层的契约并不是 90% 的折扣。契约的内容是“我们会在可行时缓存前缀”。“在可行时”这几个字才是关键,供应商重新定义“可行”的自由,就是他们在不改动 API 的情况下重新定义你成本线的自由。
这并非 LLM 所特有。每一个依赖于供应商内部状态的成本优化都具有这种特性:CDN 缓存命中率、数据库查询计划的稳定性、竞价实例(Spot Instance)的可用性。模式都是一样的——你的成本取决于供应商未作承诺的事情,而当他们做出改变时,你只能通过看账单来发现。LLM Prompt 缓存的独特之处在于量级。CDN 缓存未命中会让你多花 5 倍的钱。Prompt 缓存未命中会让你多花 10 倍。而且 Prompt 缓存的指纹取决于整个技术栈中最脆弱的组件:一个供应商正在积极重新训练的分词器。
未来的工作是将缓存视为一个需要关注的信号,而不是一个默认存在的折扣。发布说明会一直说“无破坏性变更”。有时他们是认真的。但墙上的图表才是告诉你真相的那个。
会员专享
余下内容仅对会员开放。
会员可读完整内容 —— 每一个公开发布的观点背后,那些框架、决策与推理的全貌。
- —完整文章,包含未公开存档的部分
- —可落地的工作框架,附带权衡与决策依据
- —新文章抢先看,先于公开发布
随时取消 · 一次订阅,畅读全部