“分词开销降低近 500%。”这个表述我盯着看了一会儿。一个量怎么降低五个自身?如果某项开销是 100,降低 500% 就是负 400。不可能。但他们说的是和别的模型比的相对差距——基线模型需要 5 个 token,这个模型只需要 1 个,能推出“低了 500%”吗?只有把所有口径都搅在一起的人才会这么写。机制上到底发生了什么,一个数字说不清。我们搭一个最小的字节级 BPE 分词器,跑一遍阿萨姆语,看看这个 500% 在代码里对应的是哪一步。
先把最朴素的字节级分词器写出来,这是 BPE 的起点:
def tokenize_bytes(text):
"""最朴素的分词:按 UTF-8 字节切分,每个字节一个 token。"""
return [bytes([b]) for b in text.encode('utf-8')]
阿萨姆语用的是孟加拉文字系,一个字符在 UTF-8 里是三个字节。一个四字符的词,这样切出来就是 12 个 token。这就是通用词表没有阿萨姆语常见组合时的景象:每个字符都碎成三个字节 token。如果词表里已经有这些字符组合,那这个四字符词就可能被切成一个 token。同一个词,同一个意思,前面切出 12 块,后面切出 1 块。这就是分词器之间巨大差距的来源。
BPE 的操作规程很简单:从句子里反复找出现频率最高的相邻 token 对,合并成一个新的 token,再把所有这样的对都换掉:
def apply_merge(tokens, pair):
"""把相邻的一对 token 合并成一个 token。"""
out = []
i = 0
while i < len(tokens):
if i + 1 < len(tokens) and tokens[i] == pair[0] and tokens[i+1] == pair[1]:
out.append(pair[0] + pair[1])
i += 2
else:
out.append(tokens[i])
i += 1
return out
训练就是反复挑最高频的对,合并,再挑,再合并。每次合并都往词表里塞一个新条目。一条阿萨姆语文本,经过足够多次合并后,整个词可能变成一个 token。词表越大,token 越少,这是基本方向。但“减少”这个词在实际操作中有两种算法:一种是 (旧-新)/旧,得出词表增长后开销减少了百分之几十,最大只能是 100%;另一种是把两个模型硬比,甲模型切 12 个,乙模型切 1 个,有人说“乙模型的开销比甲低 1100%”。后一种说法在机制上已经错了,但它能告诉你作者脑子里是怎么想这个问题的——他在做比值,而且没管分母是谁。
用阿萨姆语举例跑一遍。注意这里的具体数字是我假设出来展示机制的,不是论文里提供的:
text = "অসমীয়া" # 阿萨姆语“阿萨姆语”
tokens = tokenize_bytes(text)
print(f"原始字节 token 数: {len(tokens)}") # 12
# 假设训练时合并过这几个字节对,每个孟加拉文字符变成一个 token
merges = [
(b'\xe0', b'\xa6'), # 前两个字节
(b'\xe0\xa6', b'\x85'), # 完整字符 'অ'
# ... 其余字符的合并类似
]
merged = tokens
for p in merges:
merged = apply_merge(merged, p)
print(f"合并后 token 数: {len(merged)}") # 可能降到 4 或更少
这里出来的数据就非常具体了:同一段文本,一个词表切 12 个 token,另一个切 4 个 token 甚至 1 个 token。如果只盯着 token 数,后者就是前者的三倍到十二倍之差。论文说的“近 500%”,如果是指“另一个模型要用五倍的 token”,那实际应该写成“降低了 80%”或“token 数降到原来的五分之一”。但它说的是“降低近 500%”。这个措辞本身说明作者在做机制描述时,没有把“降到五分之一”和“降低 80%”这两个不同的数学对象分开。
这个错误值得揪出来,不是因为它只是文字上的瑕疵——如果一个人对分词做了什么还用错误的口径来描述,会让读者误以为模型整体变强了五倍。分词器干了什么,是一回事;模型在这些 token 上能不能学会语言,是另一回事。分词器只管输入被切成多少块、每块长什么样,接下来的所有层决定这些块之间能不能组装成意义。
再往下挖一层,那个“近 500%”真正的含义可能只是:通用基线模型的字节级 BPE 词表从未为阿萨姆语做过合并,所以它遇到了字节级分词器独有的结构性惩罚。这个惩罚是什么?就是上面演示的,每个孟加拉文字符自动多付两到三个 token。对于一篇纯阿萨姆语文本,通用词表会切出比专门词表多好几倍的 token,这是字节级分词器的先天惩罚。阿萨姆语是低资源语言,所以通用词表几乎不包含阿萨姆语常见字节对。500% 这个夸张数字,本质上就是为一个从未被覆盖过的语言补上那几层合并之后,token 数的缩减幅度。但这个缩减完全在预期内,不是什么模型突破;它是分词器从零覆盖到局部覆盖的自然结果。
到这里就把这个“500%”看得差不多了:它大概率是 token 数比值的错误表述,实际的机制是词表从没有阿萨姆语合并到有阿萨姆语合并,让原本碎成字节的多字符序列变回单 token,于是 token 数降到了五分之一左右。这是一个真实、可复现的机制现象,但它是 tokenizer 层面的现象。想继续往下挖,可以做一件更硬的事:把这篇论文里提到的阿萨姆语评测文本加一个专门的分词层,再跑一遍通用模型,看看 token 数降下来之后 BPB 变成多少——如果 BPB 也等比例变好,那模型真正的语言能力就比词表扩展所暗示的还要可疑;如果 BPB 几乎不动,就证明分词器只是入口,语言能力在后面的所有层里。这个方向我不会在这篇里拦下任何人。