大模型的算术差和代码里的低级错,有一部分锅在 tokenizer 头上。这不是玄学:Transformer 看到的不是"12345"这五个字符,而是若干个 id,id 的边界由语料频率决定、和人类认知的语义边界经常错位。数字和代码恰好是错位最严重的两类文本——本文分析错位在哪、伤害怎么传导、以及业界的修法。
数字:位值结构被随机边界切碎
十进制的核心是位值:12345 = 1\times10^4 + 2\times10^3 + \dots,每个数字的权重由位置决定,而位置是均匀的、可枚举的。BPE 对数字做的事:按频率把相邻数字合并成 token,比如 123 和 45,或 12 和 345——取决于语料里哪种切法高频。合并完成后,123 是一个原子 id,模型看不到它的内部是 1、2、3 三个数字。
伤害在两个层面发生。第一是进位链断裂。竖式加法 37 + 48 的进位发生在个位到十位之间;如果语料把 37 训成一个整体 token、进位操作必须在这个 token 的内部表示上"就地完成",模型就失去了一个可以逐步操作的符号接口。乘法更惨:123 × 456 需要逐位相乘再累加,token 边界和位边界不对齐时,每一步的中间结果都落在任意的子串上。多项对照研究(比较按位切分和整块切分的算术表现)结论一致:边界与位值对齐的切分明显更稳——这直接反映到正确率上。
第二是分布稀疏。所有可能的 $k$ 位数有 $9\times10^{k-1}$ 个,语料里出现的只是极小一角。训练集中见过的数字模式被整块记住(背诵),没见过的一律抓瞎——这正是"大模型算术靠记忆而非算法"的 token 化根源。
第三个隐患比较隐蔽:token 边界还随上下文漂移。带前导空格的 37 和裸的 37 是不同的 token,句中的 12345 和表头的 12345 切法可能不同。同一个数在不同位置有不同 id,位值的等价类被打散成多个互不相干的嵌入。
代码:结构性空白与标识符的拉锯
代码的 token 化障碍和数字不同源。代码的统计结构里,缩进和换行是骨架:def 后的换行加四个空格、if 后的八个空格,这些模式高频到必然被 BPE 合并成单 token——看起来是好事,模型一个 id 就是一个缩进层级。但问题在于粒度不稳定:四个空格的缩进是单 token,三个空格的就切成三个;同一个逻辑结构,视觉上只差一个空格,token 序列的结构就完全不同。
标识符的问题反过来:高频 API(self、request、__init__)大多整块成 token,低频的名字被切成碎片,模型记忆"库内 API 名"的成本天然低于记忆任意变量名。这本身还行,真正麻烦的是切分不一致——同一个标识符在不同 repo 风格的语料里被切成不同的碎片序列,表示不唯一,等价于同义词被随机打散。再有就是 FIM(fill-in-the-middle)场景:模型要在文件中间续写,但预训练语料里 # 注释\n def 这类"代码块开头"模式都被合并成巨型 token,中间插入点恰好落在 token 内部,前缀的最后一个 token 是不完整的——这就是代码模型常出现"续写从半个词开始"现象的来源之一。
修法:把边界还给结构
业界的修法可以归为三类。
按位对齐词表。 最干净的一招:让单个数字都是独立 token,合并只允许发生在固定长度的数字块上(如三位一块)。Llama 3 的词表把 0–9 做成独立条目并合并了部分固定长度的数字 run,就是这个思路。位值结构和 token 边界对齐后,进位、按位乘都在 token 粒度上可分解,算术数据上的 grokking(突然泛化)也更容易发生。
字符级或混合粒度探针。 研究侧常用 char-level 数字编码做对照实验:同架构下把数字部分换为逐字符 token,算术能力显著提升(数量级实验很多,结论一致)。这反过来证明了瓶颈在 token 化而不在模型容量。工程上全 char-level 代价太大,折中是数字和代码标识符走专门通道。
数据侧对齐。 一些工作不去改词表,而是在数据里统一数字格式(千分位、小数位数),减少同义变形;代码侧则统一缩进风格(pre-commit 级别的规范化),让 这类模式在语料里的形态收敛,BPE 学到的边界自然稳定。成本最低,但治标不治本——遇到格式之外的新数字照样碎。
一个佐证:上下文长度也被牵连
token 边界不稳定还有一个系统性后果:长度预算被浪费。数字密集的文本(财务报表、日志、科学计算)在坏切分下 token 密度暴涨,4096 的上下文装不下多少有效内容;而代码里被切碎的标识符让注意力在长距离上追踪"这是同一个变量"变得更贵。所谓"上下文长度",对这两类内容是有折扣的。
小结
数字和代码的问题同根:token 边界由语料频率决定,不由结构决定。数字的位值、代码的缩进和标识符,都是结构先于统计的对象,贪心子词切分必然把边界切在结构上错误的位置。修法也都围绕"把边界还给结构":按位对齐词表、混合粒度、数据规范化。下次评估一个模型的算术或代码能力,先查它的 tokenizer 怎么切数字——很多"能力短板"其实是输入编码的债。