跳到主要内容

破坏生产级 LLM 系统的分词器盲点

· 阅读需 11 分钟
Tian Pan
Software Engineer

大多数构建 LLM 的工程师最终都会学到一个粗略的换算比例:1 个 Token 大约等于 0.75 个英文单词,因此 4,000 个 Token 的上下文窗口大约可以容纳 3,000 个单词。当你的输入是日常英文文本时,这个数字用于粗略估算还可以。但在其他任何地方,它都是悄无声息地错误——而事实证明,“其他任何地方”涵盖了大多数有趣的生产环境负载。

Token 计算错误不会大声报错。它们表现为与任何账单项目都不匹配的成本超支、上下文窗口悄悄截断了文档的最后几段,或者是多语言流水线在英文测试中表现良好,但在遇到真实流量的第一周就超出了 4 倍预算。当你追溯到 Tokenizer 分词问题时,损失已经造成。

这篇文章将探讨那些会坑害生产系统的特定故障模式——不是为了研究 Tokenizer 内部原理本身,而是为了揭示将 Tokenizer 视为黑盒会让你在金钱、可靠性或两者上付出代价的地方。

“每 1000 个 Token 换算 750 个单词”的迷思及其破灭

0.75 的比例是使用 OpenAI 的 cl100k_base Tokenizer 针对英文散文进行校准的。对于干净的英文新闻文章和博客文章,它保持得相当好。但在以下四种常见情况下,它会失效。

不同模型的分词方式不同。 句子 "Artificial intelligence is transforming industries" 在 GPT-4 中产生 6 个 Token,在 Claude 3 中产生 7 个,在 Llama 2 中产生 8 个。如果你针对一个模型估算 Token 数量并在另一个模型上运行,你已经错了。当模型更新发布新的 Tokenizer 时,相同的工作负载每字符成本可能会在一夜之间增加 2 倍以上——这是一个让工程团队措手不及的真实案例。

代码的 Token 密度比散文高 1.5–2.5 倍。 语法字符(括号、分号、方括号)每个都会消耗 Token。驼峰式 (CamelCase) 和蛇形 (snake_case) 标识符不像重复的自然语言模式那样可以高效压缩。在许多 Tokenizer 中,缩进也被计为 Token。一个提取 20 个代码片段的 RAG 系统平均比等量的文档散文多消耗大约 40% 的 Token——这足以让调优良好的上下文预算超出限制。

JSON 和结构化格式带来了沉重的开销。 每个大括号、引号、冒号和逗号都是一个 Token 或其一部分。与相同表格数据的 CSV 编码相比,JSON 使用的 Token 多 30–60%。如果你的流水线检索结构化数据并在注入上下文之前将其格式化为 JSON,那么你在为每次请求支付高额溢价。数字的碎片化也很严重:"3.14159" 经常被拆分为多个 Token,使得数值密集型格式的成本高得不成比例。

输出 Token 的成本高于输入 Token。 大多数供应商对输出 Token 的收费是输入 Token 的 2–3 倍。生成 1,000 个输出 Token 的成本不是 1,000 个输入 Token 的 2 倍——通常是 3 倍或更多。基于单一 Token 费率建立的成本模型,其低估程度会随着输出字数的增加而扩大。

多语言 Token 膨胀不容忽视

英语与非拉丁语系语言在 Token 效率上的巨大差异,已经大到足以成为产品层面的关注点,而不仅仅是一个奇闻。

以下是主要语系相对于英语的近似 Token 倍数:

语言近似倍数
西班牙语、法语1.0–1.2x
俄语、希伯来语~1.5x
简体中文~1.8x
日语~2.1x
韩语~2.4x
阿拉伯语~2.5x
印地语~4.7x
泰米尔语~7.2x

这些数字来自于主要针对英文文本(在网络爬虫数据集中占主导地位)训练的 Tokenizer。像“猫”这样的汉字在 cl100k_base 中需要 2–3 个 Token,因为 BPE 词汇表是基于一个中日韩(CJK)字符代表性不足的语料库构建的。英语受益于高效的子词压缩;而非拉丁语系通常按字符或以小的字节级分块进行分词。

生产环境的影响是巨大的。一家为全球用户构建 AI 功能的 SaaS 公司,其英文流量的经济效益可能尚可,但在泰米尔语的同等交互中成本会激增 7 倍。由于这种成本并不对应于可见的功能或服务,它往往表现为基础设施支出中的异常账单,而不是产品漏洞——这意味着它通常会被忽视数月之久。

对于多语言工作负载,唯一可靠的方法是在发布前测量真实流量样本中每种语言的 Token 数量。根据你的语言组合,0.75 规则会导致你的容量规划出现 2–7 倍的偏差。

结构化输出中的 BPE 边界效应

字节对编码(BPE)的工作原理是迭代地将训练语料库中最频繁出现的相邻字节对合并为单个 Token。这创建了一个常用子词词汇表,可以高效地压缩自然语言。但当你要求模型生成的输出结构包含训练语料库中罕见或缺失的模式时,就会产生问题。

数字格式化是一个持续的弱点。 在自然语言文本上训练的 Tokenizer 无法为任意数字字符串构建高效的表示。像 "1234567" 这样的数字可能会被拆分为三到四个独立的 Token。在财务数据或科学结果中,这意味着数值密集的输出会消耗更多上下文,并且更有可能出现相对于模型注意力机制跟踪内容而言位置尴尬的数字边界。

空格处理在不同 Tokenizer 系列中各不相同。 Tiktoken 风格的 Tokenizer 实际上是无损的——空格被精确保留。使用 标记单词边界的 SentencePiece Tokenizer 可能会以丢失语义信息的方式合并多个空格。代码缩进(在 Python 中具有结构意义,在其他语言中也很有意义)可能会受到影响。

结构化输出失败会产生复合影响。 当你提示模型返回 JSON 时,模型并不是在生成文本然后再对其进行序列化——它是在生成 Token,而这些 Token 在解码时恰好形成了有效的 JSON。如果字段值的边界恰好落在 BPE 合并树中尴尬的位置,模型对于逻辑上相同但序列化方式略有不同的输出可能会有不同的“自然”补全。这就是从业者在生成结构化输出时注意到的连贯性问题的原因:相同的逻辑结果在不同调用之间并不总能实现完全一致的序列化。

针对结构化输出的实际解决方案是使用提供商原生的结构化输出 API(函数调用、工具调用、约束生成),而不是依赖基于 Prompt 的 JSON 生成。这些方法在解码层限制了 Token 生成空间,从而彻底避免了边界效应问题。

上下文溢出:不会自报家门的 Bug

上下文窗口溢出是 LLM 系统中最隐蔽的生产环境 bug 之一,因为它会静默失败。旧模型和某些框架只是简单地截断输入而不返回错误。你会注意到输出质量下降,但没有明显的错误可以追溯。现代 API 处理得更好 —— Claude Sonnet 3.7 及更高版本在溢出时会返回明确的验证错误 —— 但许多生产系统仍然建立在早期行为的假设之上。

比硬性溢出更微妙的是上下文衰减 (Context rot) 问题:在上下文窗口填满之前,性能就已经开始出现可衡量的下降。对包括 GPT-4.1、Claude Opus 4 和 Gemini 2.5 在内的前沿模型的研究发现,随着上下文长度的增加,即使输入长度远低于最大限制,性能也会出现一致的下降。其机制是注意力稀释 (Attention dilution):在 100K token 时,Transformer 会跟踪大约 100 亿个成对的 token 关系。随着 Softmax 归一化分布在更多 token 上,每个单独的 token 接收到的注意力信号就会变弱。

此外还存在位置效应。模型表现出 U 型注意力曲线:位于上下文窗口最开始和最末尾的信息比埋在中间的信息获得更强的注意力。对于将大量检索到的分块注入长上下文中间的 RAG 系统,注入事实的预期召回率可能比放置在边缘附近的信息低 30%。

实际结果是:对于相同的任务,128K 的上下文窗口并不比 64K 的上下文窗口可靠 2 倍。如果你的架构假设你可以无限期地扩展上下文来处理更长的任务,那么随着任务运行时间的延长,你会看到智能体 (Agent) 的可靠性下降。一种编程智能体的失败模式已通过经验测量:任务成功率大致随着任务时长的翻倍而减半。

如何准确计算 token 是避免这一切的前提条件。正确的方法是针对你正在部署的具体模型,使用供应商提供的 token 计数 API,包括完整的请求体:系统提示词 (System prompt)、所有对话轮次、工具定义和检索到的上下文。对于 Anthropic 的 API,client.messages.countTokens() 是免费调用的,并且考虑了所有请求组件。对于 OpenAI,tiktoken 提供了与生产行为一致的 O(n) 计数。本地启发式方法 —— 单词数、字符数、0.75 规则 —— 不能替代生产环境中的实际 token 计数。

构建 Token 感知系统

将 Token 化视为实现细节会导致成本模型失效和上下文预算意外溢出。另一种选择是在系统设计中将 token 预算作为一个显式约束。

显式分配 token 预算。 在 100K 的上下文窗口中,预先决定每个组件获得多少 token:系统提示词(通常为 1–3K)、对话历史(10–20K)、检索到的上下文(50–60K)以及输出留白(10–20K)。在生产指标中跟踪每个组件的实际消耗。当任何组件持续接近其预算时,这就是优化的信号 —— 精简系统提示词、缩短检索到的分块、对对话历史进行摘要。

考虑每条消息的额外开销。 对话 API 在每一轮都会增加 3–4 个 token 的框架开销(角色标记、分隔符)。在 20 轮对话中,这就是 60–80 个 token 的隐形成本。孤立来看很小,但在任何精确的预算计算中都值得包含在内。

在发布前测量多语言膨胀。 如果你的系统将服务于非英语用户,在确定容量计划之前,请先通过 token 计数器运行代表性的流量。在传输相同信息量的情况下,英语中每日 2M token 的预算在泰米尔语中大约相当于每日 300K token 的预算。

监控分词器偏移 (Tokenizer drift)。 当供应商更新模型时,token 化的行为可能会发生变化。在生产监控中跟踪每个请求的平均 token 数。如果该数字在输入量没有相应变化的情况下突然发生位移,则表明 token 化行为已发生变化,这将影响成本和上下文预算计算。

审慎选择格式编码。 如果你正在向上下文中注入结构化数据,请考虑 JSON 是否是合适的格式。对于表格数据,CSV 或紧凑的键值格式可以在传输相同信息的情况下减少 30–60% 的 token 使用量。对于大型检索语料库,在注入前进行语义去重可以在不降低检索质量的情况下减少 token 体积。

今天该做什么

如果你还没有这样做,请将 token 计数添加到你的生产请求流水线中。将输入 token 数、输出 token 数和组件细分(系统提示词、用户消息、检索到的上下文)记录为结构化指标。在供应商 API 的查询时间内,这不消耗任何成本,并能为你提供可观测性,以便在成本漂移和上下文预算违规演变成事故之前捕获它们。

0.75 的比例对于向非技术利益相关者解释 LLM 定价很有用。它不应该出现在你的生产成本模型、你的上下文预算分配或你的容量规划电子表格中的任何地方。Token 化是 LLM 系统中的一等约束 —— 工程纪律就是将其视为一个约束。

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