跳到主要内容

Tokenizer 算术:生产环境中悄然作祟的隐藏层

· 阅读需 12 分钟
Tian Pan
Software Engineer

一个团队上线了一条 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%。但不对称性并未消失。

数字分词与算术失败

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