Tokenizer 算术:生产环境中悄然作祟的隐藏层
一个团队上线了一条 JSON 提取流水线。在开发环境中运行完美:98% 的准确率、干净的结构化输出、可预测的 token 数量。他们推送到生产环境后,模型开始产生多余的空白字符,JSON 解析器开始报错,API 账单是原型阶段估算的 2.3 倍。模型没变。提示词没变。
是 tokenizer 变了——更准确地说,他们对它的假设从一开始就是错的。
分词(Tokenization)是你的输入经历的第一次转换,却是工程师在调试时最后才想到要检查的地方。大多数团队把它当作已解决的问题:文本进去,token 出来,模型完成其工作。但字节对编码(BPE,Byte Pair Encoding)——大多数生产级 LLM 背后的分词算法——在结构化输出生成、前缀缓存、成本估算和多语言部署中做出的决策,会产生连锁影响。一旦你知道该往哪里看,这些影响完全是可以预测的。
BPE 如何产生非均匀表示
BPE 的工作原理是:反复将训练语料库中出现频率最高的相邻字符对合并为复合 token。对于英文文本,这套词汇表效率很高——大约每个 token 对应 4 个字符——但这个压缩比并不普遍适用。tokenizer 从其训练数据中学习,如果那些数据严重偏向英文,生成的词汇表也会如此。
GPT-4 的 cl100k_base tokenizer 拥有 10 万个 token 的词汇表。GPT-4o 的 o200k_base 扩展到了 20 万个,扩展的主要驱动力是为非拉丁文字提供更多 token 空间。但根本性的不对称依然存在:常见的英文单词被压缩为单个 token,而其他语言和专业符号则做不到这一点。
在生产环境中,真正危险的不是压缩比本身——而是隐藏在看似均匀输出背后的不确定性。
两个语义上等价的字符串,可能被分词为不同的 token ID 序列。对模型来说看起来相同的 token 序列,解码后的字符串对字符串比较函数来说可能略有不同。而你为了清理输出而添加或删除的空白字符,可能会完全改变模型所走的 token 路径。
工程师最先踩中的四个故障模式
结构化标识符的边界切割
BPE 基于训练语料库中的频率模式进行分词。作为一个整体频繁出现的字符串会获得单个 token;否则就会被切碎。
"GPT-4"在自然文本中并不频繁地作为单个字符串出现,因此会被拆分:"GP"、"T"、"-"、"4"——或者根据 tokenizer 版本有类似的碎片化处理。版本号、电子邮件地址、API 密钥和技术标识符都面临同样的问题。模型将这些作为断裂的碎片来处理,而非统一的标识符。
这对结构化输出产生了实际影响。当你要求模型从文本中提取版本号并以 JSON 字符串值的形式返回时,模型必须重构一个其解码器从未表示为整体的字符串。准确率下降,格式不一致出现,你为处理干净版本号而编写的解析器开始抛出异常。
一个具体的诊断方法:在调试提取流水线为何在某些输入上失败之前,先将这些输入通过 tokenizer 运行,观察分词的边界。如果你试图提取的实体被碎片化了,这不是提示词工程问题——这是一个 tokenizer 问题,需要不同的提取策略。
空白字符敏感性与精确匹配缓存
单词前面的空格会改变所选的 token。在 cl100k_base 中," the"和"the"是不同的 token,具有不同的嵌入表示。前缀空格的变体在训练数据中出现的频率比无空格变体高 2.5 到 2.7 倍,因此模型对输出哪种形式有强烈的位置先验。
这在两个地方造成了影响:
结构化输出生成: 当语法约束解码通过在每一步屏蔽无效 token 来强制执行有效的 JSON 语法时,该约束可能会迫使模型走上一条空白字符模式出乎意料的 token 路径。对语法约束解码的研究记录了至少八种可追溯到空白边界偏移的不同故障类型——包括"空白分离"(whitespace detachment,空格与它所属的单词分离)和"词内重新分割"(intra-word resegmentation,已知单词在不该有的词素边界处被切分)。
前缀缓存: 大多数提供商端的提示词缓存以 128 个 token 的粒度进行精确前缀匹配。缓存前缀起始处哪怕一个字符的差异,都会使整个缓存命中失效。如果你的提示词组装有时会在末尾添加空格,有时不会——因为你是从不同空白字符的模板片段拼接提示词的——那么你为每个本该命中缓存的请求付了全价。这可不是小代价:对于高并发应用来说,缓存命中率低于 60% 意味着你支付的费用是你的架构所暗示的 1.6 到 2 倍。
语言 Token 差异
对于多语言产品,token 倍增效应是一个直接的成本和上下文窗口问题:
- 普通话中文:等价内容约为英文的 1.76 倍 token
- 日语:平均约 2.12 倍(最坏情况下可达 8 倍)
- 韩语:约 2.36 倍
单个汉字如"猫"在 cl100k_base 中编码为 3 个 token。这是一个单独的 Unicode 码点,却变成了三个 token,没有一个代表该字符作为语义单元的含义。
实际后果是:你的上下文窗口预算并不固定。为英文文本在 16K token 上下文内运行而设计的应用,可能会超出等效日文内容的限制。你在英文原型阶段的成本估算无法移植。你在多语言产品中的每用户成本,因用户使用的语言而异——大多数产品成本模型并未将这一差异纳入考 量。
GPT-4o 的 o200k_base 在这方面有了显著改善:需要长 token 表示的中文句子比例从约 80% 降至约 45%。但不对称性并未消失。
数字分词与算术失败
数字的分词方式不一致,能直接预测算术失败的模式。
在 cl100k_base 中,多位数字从左到右以三位为一组进行分块,但分块方式取决于数字的位数。"480"成为单个 token;"481"可能被拆分为两个。相邻的整数分词结果不对称。像"3.14159"这样的浮点数会被碎片化为四个独立的块:"3"、"."、"14"、"159"。
以算术任务作为诊断工具的研究发现,当加法题的答案比任一输入的位数都多时——即长度不匹配——从左到右的分词导致准确率崩溃至 8.25%。模型在算术上并不差;它面对的是一种在该特定位数配置下未经训练的 token 序列结构。
GPT-3.5 呈现出一个系统性规律:它能正确得出计算结果的前三位数字,并在第四位数字上出现稳定的失败。这是一个 tokenizer 对齐问题,而非模型能力问题。如果你在构建需要可靠多位算术的应用,理解这一规律能告诉你在哪里添加验证,以及何时应该使用工具调用(tool-calling)而非期望模型在其补全中完成计算。
生产调试清单
当流水线开始产生微妙错误的输出,而模型似乎就是问题所在时,在修改提示词之前,先逐项检查以下内容:
1. 显式地对输入进行分词。 使用实际的 tokenizer 库(OpenAI 模型用 tiktoken,其他模型用等效工具)在 token 边界级别检查输入的样貌。重点检查你试图提取或生成的标识符、结构化字符串和数字。
2. 检查提示词模板中的空白字符。 注入变量边界处的任何空白字符都可能改变相邻内容的分词。对每个模板片段运行两个版本——有末尾空格和没有末尾空格——并比较 token 序列。如果两者不同,你的缓存就是碎片化的。
3. 审计跨语言的 token 数量。 如果你的应用处理非英文输入,请在真实输入的代表性样本上测量实际 token 数量,而非合成的英文测试用例。倍增效应是真实且稳定的。
4. 按请求类型统计上下文窗口使用情况。 追踪各请求类型在第 95 百分位的已用 token 与上下文限制的比值。如果某些请求类型持续接近限制而其他类型不然,差异通常来自语言或内容类型驱动的 token 膨胀。
5. 在分词后的输入上运行结构化提取。 对于你的提取流水线所针对的每个字段,检查目标值是否在值的中间跨越了 token 边界。如果跨越了,该提取目标需要不同的提示词策略或后处理规范化步骤。
为什么开发阶段的 Token 计数无法迁移到生产环境
开发阶段的 token 计数之所以出错,有三个 叠加的原因。
第一,你用英文测试。你的用户用他们自己的语言写作。两个群体的字符/token 比率不同。
第二,你用干净、格式规范的输入测试。生产输入有不一致的空白字符、复制粘贴的格式痕迹、混合文字和特殊字符。每一项都以叠加的方式改变分词结果。
第三,你的 token 数量估算是静态的。模型实际消耗的 token 取决于它生成的内容,而它生成的内容取决于它走的 token 路径,后者又取决于输入的分词结果。在开发环境中稳定生成 50 个 token 补全的提示词,在生产流量中可能生成 150 个 token 的补全,因为某些输入的分词将模型推上了不同的生成路径。
你的开发成本估算与生产账单之间的差距,主要不是由更高的请求量或不同的请求模式来解释的。它是由对你的真实用户实际发送的输入和内容类型的 token 数量系统性低估造成的。
改进型 Tokenizer 改变了什么(以及没改变什么)
2024-2025 年的趋势是更大的词汇表和更好的多语言覆盖。GPT-4o 将词汇表扩大到 20 万,有意义地提升了 CJK 效率。BoundlessBPE(2025)等研究提议放宽预分词边界约束,以允许" of the"这样的短语级 token——实现了 20% 的每 token 字节压缩率提升和超过 97% 的词汇表利用率。
Meta 的 Byte Latent Transformer 探索了完全不同的方向:使用基于熵的动态分块处理原始字节,而非固定的分词方式,以少至 50% 的推理 FLOPs 实现了与 Llama 3 相 当的效果。
这些改进都无法消除运行中应用的核心问题。你无法对现有生产提示词重新分词。你的缓存前缀绑定在特定的 tokenizer 版本上。你的上下文窗口假设已被烘焙进你的数据流水线。改变底层 tokenizer 的模型升级,需要对所有这些内容重新审计。
最重要的运营意义:当提供商将模型更新到使用不同底层 tokenizer 的版本时,将其视为基础设施变更,而非模型变更。在此前后重新运行 token 数量基准测试,重新验证上下文窗口预算,并检查缓存命中率。
可操作的总结
大多数团队在该责怪 tokenizer 时却责怪了模型。在将生产故障升级为提示词工程或模型评估问题之前:
- 检查失败的输入是否包含以意外方式分词的标识符、数字或非英文文本。
- 检查输入之间的空白字符差异是否能解释你的缓存未命中率。
- 检查你的 token 数量估算是否基于英文测试数据,以及你的生产流量实际上是否是英文。
Tokenizer 是将你的意图转化为模型现实的那一层。搞错了它不会产生明显的错误——它会产生微妙的、持续的、难以归因的质量下降,看起来像模型问题,行为上也像。
但它不是。
- https://arxiv.org/html/2405.17067v2
- https://arxiv.org/html/2402.14903v1
- https://arxiv.org/html/2504.00178v1
- https://arxiv.org/html/2601.14658v1
- https://arxiv.org/html/2502.14969v2
- https://tonybaloney.github.io/posts/cjk-chinese-japanese-korean-llm-ai-best-practices.html
- https://blog.matt-rickard.com/p/the-problems-with-tokenization-in
