跳到主要内容

Token 账单漂移:当你的追踪日志与供应商发票不一致时

· 阅读需 10 分钟
Tian Pan
Software Engineer

在每一家发布托管 LLM 功能的公司,通常在第四个月左右都会召开一次财务会议。工程团队一直在记录每次请求的 Token 数量。财务团队手里拿着供应商的账单。两边的数字对不上。有时差距是 5%,有时则是 30%。工程师说是账单错了,财务团队说是日志错了。从技术上讲,双方都是正确的,但谁也不负责对账。

这种偏差并不是欺诈。它是一个结构性的衡量问题,而且这种结构至少包含六种相互叠加的独立失效模式。一个不掌握这些失效模式的团队,接下来的一个季度都会在给 FP&A(财务计划与分析部门)写道歉信,解释为什么预测失准,而真实的情况是,工程侧根本没有人审计过自己日志中的 “Token” 到底是什么意思。

这件事比典型的 FinOps 偏差更重要的原因是,Token 是构建整个 AI 成本模型的计量单位。只有在记录的 Token 与计费 Token 能够对账的前提下,你才能预测支出、设置每个租户的预算、将成本归因于特定功能,以及协商大额折扣。一个无法将观察到的字节与计费字节对账的系统,并没有一个有效的成本模型。它有的只是一个猜测。

六个结构性的偏差来源

我所见过的每个认真审计此问题的团队,都能发现同样的六个影响因素,其量级也大致如下。

Tokenizer 版本偏移。 SDK 附带的 Tokenizer 只是一个快照。服务器运行的是当前版本。当 Anthropic 在 Opus 4.7 中发布新的 Tokenizer 时,对于同样的输入,它生成的 Token 数量比 Opus 4.6 多出 35%,所有使用旧版客户端 Tokenizer 估算成本的团队,在处理同样的英文文本时都少计了多达三分之一的费用。每个 Token 的单价没有变,但 Token 数量变了。如果你的 SDK 版本落后了六个月,你的成本模型也就过时了六个月。

缓存前缀计费。 Anthropic 和 OpenAI 现在都提供 Prompt 缓存,缓存读取大约有 90% 的折扣,但 Anthropic 还会根据 TTL(生存时间)对缓存写入收取基础输入价格 1.25 倍或 2 倍的费用。你的追踪日志可能记录了总输入 Token 和一个独立的 cache_read 计数,但供应商的账单会对这些 Token 的四个部分应用四种不同的价格:标准输入、1.25 倍的缓存写入、2 倍的缓存写入以及 0.1 倍的缓存读取。如果你的成本模型只是用总输入 Token 乘以标准费率,那么当缓存生效时你会大幅高估成本,而当缓存默默失效时你又会低估成本 —— 这两种情况都掩盖了同一个 Bug。

重试双重计数。 你的客户端会在遇到 429 错误和超时时进行重试。每次重试是否出现在账单上,取决于请求在失败前是否到达了推理层。在服务器收到 Prompt 之前就失败的网络超时不会被计费。而在 Prompt 完成 Token 化之后、但在生成结束之前的服务器超时通常是会被计费的。你的客户端对每次重试的记录方式都一样。供应商的用量 API 知道其中的区别。单次请求的差距很小,但累计起来就很大,尤其是在网络不稳定的日子。

流式传输用量帧。 当你流式传输响应时,请求的 Token 计数会在内容 Token 之后的最后一个用量帧中到达,紧接在流终止符之前。许多 SDK 需要显式设置选项(例如 OpenAI SDK 中的 stream_options={"include_usage": True};其他 SDK 也有类似设置)才能输出该信息。如果你在六个月前流式传输功能上线时忘记设置该选项,你的追踪日志在每次流式请求中记录的输出 Token 都是 0。而账单可不是。

供应商侧的隐藏 Token。 供应商会注入你从未编写过且无法看到的系统提示词,用于安全性、内容审核和工具调用的脚手架。有些供应商会对这些 Token 计费,有些则不会。有些仅在特定条件下计费(视觉输入、工具调用、结构化输出)。你的追踪日志看不到它看不见的东西,但账单能看到。

批处理和分级折扣。 两家主要的供应商都提供约 50% 的批处理模式折扣。如果你的日志层记录的是每次请求的按需价格,而不考虑它是如何提交的,并且你有一部分流量走的是批处理,那么你记录的成本将比账单高出 “批处理占比 × 50%”。反之亦然:如果一个你记录为批处理的请求由于容量回退实际上被路由到了按需处理,你就会低估成本。

以上任何一个因素可能只占百分之几。但六个因素加在一起,就能解释为什么一个团队第一次审视时,出现 30% 的差距并不罕见。

没人构建的对账原语

解决方法不是写一个更好的客户端 Tokenizer。Tokenizer 会不断偏移;服务器的视角永远是权威的。解决方法是将你记录的 Token 计数视为估计值,而将供应商报告的用量视为标准答案(ground truth),并将后者接入你的可观测性栈。

每个主流供应商都提供用量 API:Anthropic 的 Admin Usage API,OpenAI 的 /v1/organization/usage/completions,以及 AWS Bedrock 和 Google Vertex 的等效接口。这些 API 返回请求粒度(或每小时聚合,取决于级别)的 Token 计数和成本。对账原语是一个每日运行的任务,它从供应商处拉取前一天的用量数据,根据请求 ID 将其与你自己的请求日志进行关联,并将差异写入 cost_reconciliation 表中。

你需要从该表中获得的信息:

  • 每请求规范成本字段。 供应商报告的数值,而不是你客户端猜测的数值。这成为了成本归因、租户计费和预测的标准答案。
  • 偏差指标。 记录的 Token 减去计费的 Token,以百分比表示。这是你的 Tokenizer 版本健康信号。当它超过阈值(5%? 10%?)时,向相关人员发送告警。
  • 未归因成本。 账单上出现了但没有任何请求日志记录的 Token。这是隐藏系统提示词和重复计费检测信号的综合指标。
  • 已记录但未计费。 你的日志中有请求,但没有匹配的用量条目。这是重试双重计数和客户端 Tokenizer 误报的信号。

对账任务并不光鲜亮丽,也不发布新功能,这就是为什么除非迫不得已,否则没人会去构建它。拥有它的团队赢得了预测下个季度的权利;而没有它的团队在参加财务会议时只能带着两个数字,却无法解释其中的差距。

为什么财务和工程团队总是各执一词

这种组织失效模式比 LLM 还要久远。这与 2014 年破坏云成本核算以及 2008 年破坏 CDN 计费的模式如出一辙:一个团队将发票视为“唯一真相”(ground truth),因为那是公司实际支付的文件;而另一个团队将日志视为“唯一真相”,因为那是工程师实际产生的制品。没有团队负责对账,因此差距一直存在。

LLM 特有的版本是:工程团队通常根本没有意识到差距的存在,直到财务部门找上门。仪表板显示的是 Token 数量。但仪表板并不显示“如果我们询问,供应商会向我们收取的 Token 数量”。构建第二个仪表板才是真正的功夫所在。

一个有效的模式是:指定一个人——通常来自平台或基础架构团队——作为“成本对账”的指定负责人。他们的工作不是去修复每一个偏差来源,而是维护对账仪表板,在偏差超过阈值时发出警报,并成为那个拿着确切数字及其背后逻辑参加月度发票会议的人。如果没有明确的负责人,这项工作就会掉进 SRE、FinOps 和负责 AI 功能的应用团队之间的缝隙中,最终导致毫无进展。

分词器固定的回归测试

一个特别值得提倡的规范是:在 SDK 的 lockfile 中固定分词器(tokenizer)版本,并编写一个回归测试,当 lockfile 更新且没有配套的账单对账审查时,该测试将失败。

这听起来可能有些过头,直到你看到分词器的变更在某个 SDK 的次要版本发布中出现,并悄无声息地让每个请求的输入 Token 计数产生两位数的波动。固定分词器并配合回归式门禁的核心点在于:如果不在此 PR 中同步更新成本模型,就不允许该变更上线生产环境。测试不需要很复杂;它可以是一个快照测试,断言已知字符串会被分词为已知的计数。其价值在于这个门禁,而不在于断言有多巧妙。

必然的结果是:当你确实需要更新分词器时,请将其视为一次成本模型的迁移,而不仅仅是一个依赖项的升级。重新校准偏差指标。更新预测。在财务找你之前先告诉他们。

Token 就是字节;账单就是库存

我认为能打开这一局面认知框架是:Token 和字节一样,都是一种计量单位。一个无法将观察到的字节与计费字节对齐的存储系统,就是一个没有有效成本模型的系统——它可能便宜,也可能昂贵,但你无法断定。推理也是如此。

那些将 Token 视为比字节更模糊的东西(一个估计、一种感觉或仪表板上的一个数字)的团队,往往会在第五个月因为预算超支而措手不及,最终由财务主导成本谈判,因为工程团队无法提供合理的解释。而那些像对待字节一样严谨地对待 Token 的团队(版本化分词器、对账任务、偏差预警、明确的所有权),则能够掌握成本的话语权。

账单就是库存。你的追踪(trace)就是仓库日志。如果两者不匹配,你面临的不是计费问题,而是测量问题。而测量问题需要由你来解决。

References:Let's stay in touch and Follow me for more thoughts and updates