Token Counting

如何比较 OpenAI 兼容模型之间的 Token 使用量

比较 OpenAI 兼容模型之间的 Token 使用量

如何比较 OpenAI 兼容模型之间的 Token 使用量

一个 OpenAI 兼容端点可以接受相同的 JSON 请求,但仍然会统计出不同数量的 token。

这就是公平模型比较的第一条规则:API 兼容性不等于分词器兼容性

两个端点可能都支持 POST /v1/chat/completions、相同的 messages 数组,以及诸如 prompt_tokenscompletion_tokens 之类熟悉的 usage 字段。然而,在这个共享接口背后,它们可能使用不同的分词器、聊天模板、工具格式化方式、缓存规则或推理 token 计数方式。

如果你只比较最终的 total_tokens 数字,就很容易对提示词效率或成本得出错误结论。

本指南展示如何在 OpenAI 兼容模型之间运行一次苹果对苹果的 token 基准测试,规范化结果,并使用 TokenTest 检查每个端点的 usage 证据是否存在且合理。

简短答案

要公平地比较 token 使用量:

  1. 向每个端点发送相同的语义任务和相同的请求结构。
  2. 锁定 temperature、输出上限、tools、响应格式、流式模式和缓存状态。
  3. 分别记录输入、输出、总计、缓存和推理 token。
  4. 同时比较原始 token 数每单位有用工作所对应的 token 数
  5. 对每个测试重复执行,并标记缺失或内部不一致的 usage 字段。

不要期待计数相等。目标不是强迫每个模型返回相同的数字。目标是理解为什么这些数字不同,以及这些报告是否可信

为什么 OpenAI 兼容模型会以不同方式统计 token

“OpenAI compatible” 通常描述的是 API 表面。它表示某个提供商、网关或自托管服务器接受与 OpenAI API 请求相同形式的请求。

这并不保证后端使用的是 OpenAI 的分词器。

1. 分词器词表可能不同

分词器会把文本拆分为模型可读取的单元。不同模型家族使用不同的词表和切分规则,因此同一句话、JSON 对象、代码块或中文段落都可能产生不同的 token 数量。

这一点在以下内容上尤其明显:

  • 非英语文本;
  • 对空白字符敏感的代码;
  • 很长的标识符和 URL;
  • JSON 键和标点符号;
  • emoji 或不常见的 Unicode 字符。

OpenAI 自己的文档建议使用与你实际调用的模型相对应的分词器或 token 计数方法。来自另一模型家族的本地估算可能有助于规划,但对于第三方端点并不具有权威性。

2. 服务器可能应用不同的聊天模板

你的请求包含角色和消息内容。模型通常接收的是一个序列化后的序列,其中带有特殊控制 token,用来标记 system 消息、user 消息、assistant 回合或 tool 调用。

Hugging Face 的聊天模板文档解释说,即使对开发者来说对话看起来完全相同,模型也可能使用不同的控制 token。vLLM 的 OpenAI compatible 服务器在处理聊天请求时同样依赖模型聊天模板。

这意味着这个请求:

{
  "messages": [
    {"role": "system", "content": "简明回答。"},
    {"role": "user", "content": "解释 prompt 缓存。"}
  ]
}

在不同后端上,可能会变成不同的内部 token 序列。

3. 工具和结构化输出会增加隐藏的请求结构

即使可见的用户提示保持不变,工具定义、JSON Schema、响应格式指令以及提供方附加的包装也会增加输入使用量。

OpenAI 的 token 计数指南指出,请求结构很重要,包括工具和 schema。为了进行公平的基准测试,你必须在所有地方使用完全相同的工具 schema,或者单独运行仅文本测试。

4. 输出计费可能包含不止可见文本

某些模型会单独暴露推理或思考 token。某些模型会将不可见的模型内部工作计入输出用量。某些 OpenAI 兼容层会把详细用量简化为一个更简单的 completion_tokens 数值,而另一些则会完全省略这些细节。

因此,“答案是 80 个可见单词”并不意味着每个端点都应该报告相同的输出 token 数。

5. 缓存会改变重复运行的含义

某个端点可能会报告缓存输入 token、缓存读取或缓存创建 token。另一个端点可能支持缓存,但在其 OpenAI 兼容响应中省略这些细节。

如果一次运行是热缓存,而另一次是冷缓存,总 token 数可能看起来相近,但可计费或已处理 token 的行为不同。请将首次运行和重复运行的结果分开记录。

基准设置:先锁定这些变量

使用一个小型测试矩阵,而不是一个巨大的提示。每个测试都应该隔离一个 token 变化来源。

控制项 推荐设置 重要原因
提示内容 完全相同的文本和消息顺序 防止内容漂移
系统消息 完全相同或不使用 系统文本会影响输入用量
Temperature 在支持时设为 0 减少输出差异
输出上限 相同且受支持的上限 保持截断压力可比
工具 相同 schema,或禁用 工具 schema 可能增加大量输入 token
响应格式 相同 JSON/文本模式 结构化输出会增加指令
流式输出 全部使用相同模式 某些网关在流式传输中报告用量的方式不同
缓存状态 分别记录冷缓存和热缓存 避免混合缓存与非缓存行为
重试 每种情况至少运行 3 次 揭示不稳定的输出和报告

还要在可用时记录端点返回的确切模型 ID。一个友好的别名之后可能会静默指向不同的后端。

五项测试的 token 比较套件

以下提示词经过刻意压缩。你可以将它们粘贴到 TokenTest 中,并在多个已配置的端点上运行。

测试 1:简短英文指令

Summarize the following support issue in exactly three bullets. Each bullet must be under 12 words.

The customer upgraded yesterday. The dashboard still shows the old plan, but the invoice shows the new charge. They already signed out and back in.

测试内容:英文基础分词、指令开销,以及短输出约束。

测试 2:英文和中文语义对

英文:

Explain why prompt caching can reduce repeated-input processing. Use two sentences and no jargon.

中文:

请用两句话解释为什么提示词缓存可以减少重复输入的处理量,不要使用专业术语。

测试内容:与语言相关的分词器效率。先在同一模型内比较每种语言,再比较不同模型。

测试 3:JSON 提取

Extract customer_id, renewal_date, plan, and risk_level. Return valid JSON only.

Account AC-1049 is on the Pro annual plan. Renewal is 2026-08-15. The customer has opened three billing tickets and said they may cancel.

测试内容:标点、字段名、结构化输出约束,以及相对于报告的输出 token 的可见输出。

测试 4:长重复前缀

Policy reference:
- Refunds require an account identifier.
- Annual plans may be refunded within 14 days of renewal.
- Fraud claims must be escalated.
- Do not promise a refund before review.

Using only the policy above, draft a two-sentence reply to a customer requesting a refund 10 days after annual renewal.

在不更改任何内容的情况下运行两次。

测试内容:重复请求行为和缓存 token 证据。除非端点报告了证据,否则不要假定第二次运行使用了缓存。

测试 5:工具 schema 开销

将测试 3 运行一次,作为普通 JSON 输出;再运行一次,使用包含相同四个字段的函数/工具定义。

测试内容:端点为工具和 schema 增加了多少请求侧开销。

记录标准化结果,而不仅仅是总 token 数

对于每次运行,请收集以下工作表:

字段 示例
端点 provider-a.example/v1
请求的模型 model-alias
返回的模型 model-version-2026-07
测试 ID json-extraction
运行状态 coldrepeat
输入 / prompt tokens 142
缓存的输入 tokens 0
输出 / completion tokens 38
推理 tokens 0not reported
总 tokens 180
可见输出字符数 126
结束原因 stop
延迟 1.8 s
有效结果 yes

然后计算能够回答一个有用问题的指标:

input efficiency = input tokens / input characters
output density = visible output characters / output tokens
task efficiency = total tokens / valid completed task
cache share = cached input tokens / input tokens

这些比率并不是通用的质量分数。它们是诊断信号。

例如,如果模型未通过 JSON schema,那么较低的总 token 结果并不高效。如果更长的答案完成了更短答案遗漏的任务,那么更长的答案也不一定就是浪费。先标记任务是否有效,然后在有效输出之间比较 token 效率。

如何安全读取 usage 对象

OpenAI 风格的聊天响应通常会公开:

{
  "usage": {
    "prompt_tokens": 142,
    "completion_tokens": 38,
    "total_tokens": 180
  }
}

较新或特定于提供商的响应还可能公开用于缓存输入、推理、已接受的预测 tokens、音频 tokens 或其他模态的嵌套细节。

将这些字段规范化到你自己的内部 schema 中,但要保留原始响应。当某个字段缺失时,请使用 nullnot reported。不要把缺失的缓存或推理字段转换为 0,因为0 表示端点明确报告为无;缺失表示你不知道

当这些字段允许时,也要进行一次算术检查:

reported input + reported output ≈ reported total

精确关系可能取决于提供商的 schema,但无法解释的不匹配值得调查。

常见的比较错误

比较别名而不是固定模型

别名可能在不改变请求的情况下更换其后端。请保存请求的模型、返回的模型、测试日期和端点。

对每个端点都使用同一个本地 tokenizer

只有当本地 tokenizer 与已部署的模型和聊天模板匹配时,它才适合做粗略估算。对于权威规划,请在可用时使用提供商的原生计数器,并验证实时的 usage 响应。

混合文本专用请求和启用工具的请求

工具定义会实质性地改变输入使用量。应将工具调用视为单独的基准测试轨道。

将缺失细节视为零

如果推理或缓存字段缺失,请将其标记为 not reported。否则,你可能会因为端点隐藏了使用详情而错误地给它加分。

在不检查答案的情况下按总 token 数对模型排名

只有在输出通过任务要求之后,token 效率才有意义。在计算赢家之前,请先验证 JSON、指令遵从性、语言以及事实约束。

每个提示只运行一次

输出长度会有波动。至少运行三次重复测试,并报告中位数;如果方差具有意义,还要报告最小值和最大值。

TokenTest 的适用位置

TokenTest 旨在进行实时端点验证,而不仅仅是离线分词。

其当前产品界面包含以下检查:

  • usage 是否存在以及是否合理;
  • 输入、输出和缓存 token 的证据;
  • 总 token 一致性;
  • 随着提示变长,输入 token 是否单调增长;
  • 输出 token 是否合理;
  • 停止限制关联;
  • 流式 usage 一致性;
  • 重复调用中的缓存行为;
  • thinking 或 reasoning token 证据。

当你在比较直接提供商端点与网关、转售商、路由器或自托管的 OpenAI 兼容服务器时,这就很有用。

先从上面的五个提示开始,导出结果,并将原始 JSON 与你规范化后的工作表一并保存。TokenTest 说明不会存储 API 密钥,但你仍应遵循你所在组织的凭据和数据处理政策。

一个实用的决策规则

使用三层方法:

  1. 本地估算,用于快速编辑反馈。
  2. 提供商原生预检计数器,在可用时用于请求大小评估。
  3. 实时 TokenTest 运行,用于运行时 usage 完整性和跨端点比较。

这可以避免两个糟糕的捷径:假设一种分词器能代表所有兼容模型,或者假设每个 usage 对象都讲述了全部故事。

FAQ

两个 OpenAI 兼容模型是否应该报告相同的输入 token 数?

不应该。它们可以接受相同的请求 JSON,但使用不同的分词器、聊天模板、特殊 token 和服务端包装器。

token 更少的模型总是更便宜或更好吗?

不一定。定价可能不同,而且如果输出无法完成任务,低 token 输出也没有价值。请单独比较当前提供商定价,并且只对有效输出计算 token 效率。

我可以为每个 OpenAI 兼容端点都使用 tiktoken 吗?

除非端点确认它使用匹配的分词器和请求模板,否则只能将其视为估算。请使用原生计数或实时 usage 作为权威结果。

如果某个端点只报告总 token 数怎么办?

保留结果,但将缺失的输入/输出细节标记为不可用。与其他端点相比,这类端点为诊断和成本归因提供的证据更少。

我应该多久重新运行一次基准测试?

在模型版本变更、网关变更、聊天模板变更或 usage schema 变更后重新运行。还应按计划重新运行重要的生产路由,以免别名发生漂移却未被察觉。

最终检查清单

在你宣布 token 效率赢家之前,请确认你已经:

  • 使用了相同的语义输入;
  • 锁定了生成和工具设置;
  • 区分了冷启动和重复运行;
  • 记录了原始和归一化的使用字段;
  • 区分了 0 和未报告;
  • 在评分效率之前验证了输出;
  • 对每种情况都进行了重复测试;
  • 并保存了模型版本和测试日期。

然后在 TokenTest 中运行比较,并使用证据——而不只是 API 兼容性——来决定哪个端点适合你的生产工作负载。

来源

  • TokenTest 首页和实时产品行为:https://tokentest.io/
  • TokenTest 手册:https://tokentest.io/manual
  • OpenAI 计数 token 指南:https://developers.openai.com/api/docs/guides/token-counting
  • OpenAI API 参考:https://developers.openai.com/api/reference
  • Hugging Face 聊天模板:https://huggingface.co/docs/transformers/chat_templating
  • vLLM OpenAI 兼容服务器文档:https://docs.vllm.ai/en/latest/serving/openai_compatible_server.html