Tokenization的隐秘艺术——从BPE到SentencePiece的编码原理与模型影响
目录
一、Tokenization为何重要:底层设计如何重塑模型能力边界
在当今大语言模型(LLM)的军备竞赛中,模型架构(Transformer)、训练规模(Scaling Laws)、对齐技术(RLHF)占据了绝大多数研究者的注意力。然而,有一个处于模型流水线最底层的组件,其设计选择深远地影响着模型的所有下游能力——这就是Tokenization。Tokenization是文本进入模型的第一道关卡,它将原始字符串映射为模型可以处理的整数序列。这个看似简单的映射过程,实际上蕴含着一系列深刻的设计决策:词汇表的大小、子词分割的粒度、跨语言覆盖的均匀性,以及未知词(OOV)的处理策略。
1.1 Tokenization作为模型的"基因组"
如果将大语言模型比作一个生物体,Tokenization就是它的基因组——它编码了模型理解和生成语言的基础单元。正如基因序列中三联体密码子的编码方式决定了蛋白质合成的结果,Tokenization的分割粒度决定了模型在语义理解上的"分辨率"。过粗的分词(如整词级别)会导致词汇表巨大且稀疏,模型难以捕捉形态变化;过细的分割(如字符级别)则会显著增加序列长度,使Transformer的自注意力计算复杂度呈二次增长。更微妙的是,Tokenization还会在不经意间引入偏见:一个在英文语料上训练出的BPE分词器,对于中文、日文等非空格分隔语言往往会产生极度不均匀的Token分布,导致模型在这些语言上的表现显著下降。
1.2 被忽视的"瓶颈效应"
研究表明,Tokenization的质量直接影响模型的诸多能力:算术推理(数字是否被切割)、拼写意识(子词能否还原为字母序列)、多语言覆盖(低资源语言Token的分配数量)、乃至安全对齐(某些攻击向量正是利用了分词器对特定字符序列的处理缺陷)。本文将系统性地剖析从BPE到SentencePiece的各代Tokenization技术,揭示它们的设计哲学、内部运作机制以及对模型性能的多维影响。
深挖点
Tokenization的选择是模型预训练阶段最早做出的决策之一,且一旦确定就几乎无法在不重新训练的情况下更改。这意味着它锁定了模型能力的某些上限。例如,GPT-2使用BPE分词器导致不能可靠地处理字母计数任务("strawberry中有几个r?"),这正是因为分词器将"strawberry"编码为单个Token,使模型无法感知其内部的字母序列。
二、词袋与字符级编码的局限:为何需要子词分割
在子词分割技术成为主流之前,自然语言处理领域主要采用两种极端的编码策略:词袋模型(Bag of Words)和字符级编码(Character-level Encoding)。这两种策略分别处于粒度过粗和过细的两个端点,各自都有着根本性的局限。理解这些局限,是理解子词分割必要性的前提。
2.1 词袋模型(BoW)的三重困境
词袋模型将每个完整的词作为一个独立的Token,其词汇表大小直接对应于语料中不同词型的数量。对于一个中等规模的英文语料,词汇表通常在100K到1M的量级。这种编码方式面临三个根本性问题:第一,词汇量爆炸——语言中的形态变化(do, does, did, doing, done)为每个词根生成多个变体,大幅增加了词汇表的冗余度;第二,OOV(未登录词)问题——任何在训练语料中未出现的词都无法被编码,这在专业领域(医学术语、代码变量名)和新兴词汇中尤为致命;第三,语义稀疏性——"unbelievable"和"believable"被当作完全无关的向量,无法共享"believe"词根所携带的语义信息。
2.2 字符级编码的计算代价与信息瓶颈
字符级编码处于另一个极端。以UTF-8编码为基础,词汇表大小被压缩到256(字节级)或更少。这彻底解决了OOV问题,但引入了新的困境。Transformer的自注意力机制的计算复杂度为O(n²·d),其中n是序列长度。字符级编码使序列长度膨胀3-7倍(相对于子词分割),导致同样长度的文本需要数倍的算力。例如,一个含512个子词Token的句子在字符级可能需要3000+个Token。更严重的是,模型需要从头学习字符序列到语义单元的映射关系——这相当于让模型在不借助任何先验语言知识的情况下,从零开始发现"词汇"的概念。实验表明,字符级模型虽然理论上具有更强的泛化能力,但在实际训练预算下,其收敛速度远慢于子词级别模型,且长程依赖建模能力显著下降。
2.3 子词分割的黄金平衡点
子词分割(Subword Tokenization)在这两种极端之间找到了一个优雅的平衡点:频繁出现的完整词汇保留为独立Token以保持效率,不常见的词汇则被分解为更小的有意义的子词单元。这一理念的直观表达是:"never split frequent words, split rare words into frequent subwords"。通过这种方式,词汇表大小可以控制在合理范围内(通常是16K到128K),同时序列长度也远低于字符级编码。更重要的是,子词单元天然携带了形态学信息——"un"、"believe"、"able"这三个子词的组合可以重构出"unbelievable",使得模型能够有效地泛化到未见过的词。
| 编码策略 | 词汇表大小 | OOV处理 | 序列长度(相对) | 形态学感知 | 代表性模型 |
|---|---|---|---|---|---|
| 词袋级别 | 100K - 1M | ❌ 严重 | 1x(基准) | 弱 | Word2Vec, GloVe |
| 字符级别 | 50 - 256 | ✅ 完全解决 | 5x - 10x | 弱(需从头学习) | CharCNN, ByT5 |
| 子词级别 | 16K - 128K | ✅ 基本解决 | 1.2x - 2x | 强 | GPT, BERT, Llama |
深挖点
值得注意的是,子词分割在英语等形态丰富的语言中表现优异,但在汉语、日语等语言中遇到了新的挑战。这些语言没有天然的空格分隔,子词的边界定义变得模糊且依赖于统计频率而非语言学规则。SentencePiece正是在这种跨语言需求中诞生的解决方案。此外,近年来的研究表明,即便是子词分割,在面对代码生成、数学推理等需要精确字符级操作的任务时,仍然存在底层的信息损失——这催生了Byte-level模型的复兴。
三、BPE算法:从GPT到Llama的编码基石
字节对编码(Byte Pair Encoding, BPE)最初由Philip Gage于1994年提出,用于数据压缩领域。其核心思想简单而优雅:用最频繁出现的字节对替换为未使用的新字节,递归迭代直至达到目标压缩率。2016年,Rico Sennrich等人将其引入NLP领域,作为神经机器翻译的子词分割方法,从此开启了BPE在自然语言处理中的统治地位。今天,从GPT全线模型到Llama系列,BPE及其变体构成了大多数主流LLM的Tokenization基础。
3.1 BPE算法的核心原理
BPE是一种基于频率的贪心合并算法。其训练过程从字符级别的词汇表开始,逐轮统计当前词汇表中所有相邻符号对的频率,合并最频繁的一对,并将合并后的新符号加入词汇表。这一过程重复进行,直到词汇表达到预设的大小。在推理(编码)阶段,按照同样的合并优先级,将输入文本逐步合并为词汇表中的最长匹配子词。
以下是BPE训练过程的伪代码实现:
算法:BPE训练(BPE Training)
输入:
corpus: 训练语料(已按空格分词)
vocab_size: 目标词汇表大小
special_tokens: 特殊Token列表([UNK], [PAD]等)
输出:
merges: 有序合并规则列表
步骤:
1. 初始化词汇表 vocab = set(special_tokens)
2. 初始化字母表 alphabet = set()
3. 对每个word ∈ corpus:
word = word + "" // 添加词尾标记
chars = list(word) // 将词拆分为字符序列
alphabet.update(chars)
vocab.update(chars)
4. 初始化符号频率统计 symbol_freqs = {}
5. 对每个word ∈ corpus:
for each char c in word:
symbol_freqs[c] += count(word)
6. merges = []
7. while len(vocab) < vocab_size:
// 统计所有相邻pair的频率
pair_freqs = {}
for each word ∈ corpus:
for i in 0..len(word)-2:
pair = (word[i], word[i+1])
pair_freqs[pair] += count(word)
// 找到最频繁的pair
best_pair = argmax(pair_freqs)
best_freq = max(pair_freqs)
if best_freq == 0: break // 无可合并pair
// 合并best_pair为一个新符号
new_symbol = best_pair[0] + best_pair[1]
merges.append(best_pair → new_symbol)
vocab.add(new_symbol)
// 更新语料中所有出现该pair的位置
for each word ∈ corpus:
word = replace_pair(word, best_pair, new_symbol)
8. return merges
3.2 BPE合并过程的实例演示
下面通过一个简化的英文语料来说明BPE的合并过程。假设语料包含以下词及其频率:low(5), lower(2), newest(6), widest(3)。为简洁起见,我们先展示前6轮合并的进展。
| 合并轮次 | 最频繁Pair | 频率 | 合并后新符号 | 词汇表大小 |
|---|---|---|---|---|
| 1 | ('e', 's') | 9 | es | 30 |
| 2 | ('es', 't') | 9 | est | 31 |
| 3 | ('l', 'o') | 7 | lo | 32 |
| 4 | ('lo', 'w') | 7 | low | 33 |
| 5 | ('w', 'e') | 5 | we | 34 |
| 6 | ('low', 'e') | 2 | lowe | 35 |
通过这个实例可以看到,高频词(如"est"、"low")在早期就被合并为完整的子词单元,而低频词("lower"中的"lowe")需要在更晚的轮次才能被合并。最终,词汇表同时包含完整词汇(est, low)和组成更复杂词汇的基础构件。
3.3 BPE在GPT家族中的演进
GPT-2首次将BPE引入大规模语言模型预训练,采用40K词汇表大小,并创新地引入了字节级BPE(Byte-level BPE),使得词汇表覆盖所有UTF-8字节序列。GPT-3将词汇表扩展到50K,而GPT-4进一步将其提升到约100K。与此同时,Meta的Llama系列沿用了BPE,但采用了SentencePiece框架实现,并使用32K词汇表——这一相对较小的词汇表选择被认为有助于提升低资源语言的性能。值得注意的是,BPE在GPT家族中的长盛不衰并非因为它是理论最优的选择,而是因为它简单、高效、可复现,且在大规模实验中表现出稳定的性能。
深挖点
BPE的贪心本质意味着它存在一个被广泛忽视的问题:合并顺序的局部最优性。由于BPE每次只选择当前最频繁的pair进行合并,早期的一次合并决策可能影响后续所有合并的结果。例如,如果语料中"th"和"the"都频繁出现,BPE可能在合并"th"之后才发现"he"也应当合并,但此时"the"已经不可能通过合并"t"+"he"得到。这种序列依赖意味着BPE的最终词汇表可能不是全局最优的——不同的合并顺序会导致不同的子词划分。
四、WordPiece与BERT:最大似然驱动的子词选择
与BPE几乎同一时期,Google在BERT模型中引入了WordPiece分词器。WordPiece的思想与BPE类似,都采用自底向上的子词合并策略,但两者的合并标准有着本质区别:BPE基于符号对的频率,而WordPiece基于语言模型对合并后收益的量化评估。这一差异使得WordPiece能够更智能地选择"真正有意义的"子词边界。
4.1 WordPiece的核心算法
WordPiece同样从字符级词汇表开始,逐轮合并符号对。但它的合并选择标准不是简单的pair频率,而是评估合并该pair对语料整体似然度的提升程度。具体而言,WordPiece训练一个单字的语言模型来估计语料中每一个子词出现的概率,然后选择能使语言模型似然度增量最大的pair进行合并。合并的评分函数通常定义为:
算法:WordPiece训练
输入:
corpus: 训练语料
vocab_size: 目标词汇表大小
num_train_iter: 训练迭代次数(可选)
输出:
vocab: 最终词汇表
步骤:
1. 初始化词汇表 vocab = 所有单字符 + 特殊Token
2. 预处理语料:
for each word in corpus:
word = "[CLS] " + word + " [SEP]" // 添加BERT特殊标记
3. 对每个word ∈ corpus,初始分割为字符序列
4. for iter in 1..num_train_iter or until |vocab| >= vocab_size:
// 将当前分割下的语料视为一个序列 []
// 估计一个单字语言模型 P(token | context)
// 即:基于当前词汇表,学习每个子词的边际概率 P(token)
// 对每一对相邻子词(x, y),计算合并后的评分
for each adjacent pair (x, y):
// 评分 = 合并后语言模型似然度的提升
score(x,y) = log P(xy) - [log P(x) + log P(y)]
// 也可以用互信息形式:
// score(x,y) = PMI(x,y) = log[ P(xy) / (P(x) * P(y)) ]
// 选择评分最高的pair
best_pair = argmax score(x,y)
// 将best_pair合并为新子词
new_token = best_pair.x + best_pair.y
vocab.add(new_token)
// 更新所有序列中的分割
for each sequence in corpus:
merge_best_pair(sequence, best_pair)
// 重新估计语言模型
reestimate_unigram_model(vocab)
5. return vocab
4.2 BPE与WordPiece的本质差异
从信息论的角度来看,BPE和WordPiece的核心差异在于:BPE最大化的是合并后的频率增益,而WordPiece最大化的是合并后的信息增益。具体来说,如果两个符号经常一起出现(如"un"和"believable"),它们的PMI(点互信息)值会很高,合并不但不会损失信息,反而通过缩短序列长度提高了表示效率。反之,如果两个符号仅仅是因为语料中各自非常高频而经常相邻出现(如"a"和"the"),BPE仍然会合并它们(因为频率高),但WordPiece会正确识别出这种合并没有带来信息增益——因为"a"后面跟"the"的概率并不显著高于"a"后面跟其他词。
这种差异在实践中产生了显著的后果:WordPiece通常能生成更具语言学意义的子词单元,而BPE的词汇表中可能包含一些"噪音性的合并"(如"t"+"he"合并为"the"而不是保留"th"+"e"的分割,但实际上语料中的"the"应当被视为一个整体)。然而,WordPiece的评分计算需要维护一个概率模型,训练开销高于BPE。
| 特性 | BPE | WordPiece |
|---|---|---|
| 合并标准 | Pair频率 | 似然度增量 / PMI |
| 训练方式 | 贪心合并 | 语言模型驱动的贪心合并 |
| 词汇表起始 | 字符级 | 字符级 + 特殊Token |
| 特殊Token | 通常只有[UNK] | [CLS], [SEP], [MASK], [PAD], [UNK] |
| 输出标记 | 子词 + ""词尾标记 | 子词 + "##"前缀(非词首) |
| 训练复杂度 | O(V·N) 低 | O(V·N·E) 较高(需迭代重估LM) |
| 代表模型 | GPT系, Llama, RoBERTa | BERT, DistilBERT, ELECTRA |
| 语言学合理性 | 中等 | 较高 |
4.3 WordPiece的编码与解码流程
WordPiece的编码过程使用最长匹配优先的分词策略,辅以回退机制。第一步,在词汇表中查找输入词的最长前缀匹配;第二步,如果在词汇表中找到的匹配子词不是完整的原词,则对该子词添加"##"前缀标记,表示它是词中间或结尾的子词;第三步,对剩余部分递归进行最长前缀匹配;第四步,如果在任何一步出现无法匹配的字符,则将该字符作为未知Token处理(通常映射为[UNK])。解码过程则简单地将所有子词拼接在一起,忽略"##"标记。
深挖点
WordPiece的"##"前缀标记带来了一个有趣的副产品:它让模型能够隐式地区分词首和词中子词。例如,"playing"被分割为"play" + "##ing"时,模型知道"play"是一个词的开头而"##ing"不是。这种位置信息在BERT的NSP(Next Sentence Prediction)和MLM(Masked Language Model)任务中起到了微妙的作用。然而,Google的原始WordPiece实现从未公开发布完整的训练代码,HuggingFace的实现也是基于逆向工程实现的近似版本,这在一定程度上限制了社区对WordPiece深入研究的可能性。
五、Unigram LM:概率建模下的柔性词汇表
与BPE和WordPiece的"自底向上"合并策略不同,Unigram Language Model(Unigram LM)采取了截然相反的"自顶向下"剪枝策略。它从一个非常大的初始词汇表(通常包含语料中所有离散符号)开始,然后迭代地移除对整体似然度贡献最小的符号,直至词汇表缩减到目标大小。这种剪枝范式赋予了Unigram LM独特的灵活性:它可以在训练过程中自然地产生词汇表的概率分布,从而支持多候选分词等高级功能。
5.1 Unigram LM的数学基础
Unigram LM的核心假设是:句子中每个子词的出现是相互独立的。基于这一假设,一个给定句子X的分割序列S = (s₁, s₂, ..., sₖ)的似然度可以表示为各子词概率的乘积。其训练目标是通过最大化整个语料的似然度来学习最优的子词概率分布p(s),同时将词汇表缩减到目标大小。
算法:Unigram LM剪枝训练
输入:
corpus: 训练语料
vocab_size: 目标词汇表大小
corpus_iter: 语料中所有可能的子词序列
输出:
vocab: 最终词汇表(大小为vocab_size)
unigram_probs: 词汇表中每个子词的概率分布
步骤:
1. 构建种子词汇表 vocab_seed:
- 包含所有单字符
- 包含语料中出现频率 > min_freq 的所有子串
2. 初始化概率分布 p(s):
- 对s ∈ vocab_seed, 以频率估计 p(s) = freq(s) / ∑freq(s)
3. repeat until |vocab_seed| <= target_vocab_size:
// E步(Expectation): 使用Viterbi算法找到每个句子的最优分割
for each sentence ∈ corpus:
best_segmentation[sentence] = Viterbi(sentence, vocab_seed, p)
// M步(Maximization): 重估子词概率
for each token t ∈ vocab_seed:
p(t) = count(t in all best_segmentations) / total_tokens
// 剪枝: 计算移除每个token带来的似然度损失
for each token t ∈ vocab_seed:
L_original = sum_p log P(sentence | vocab_seed, p)
L_removed = sum_p log P(sentence | vocab_seed \ {t}, p_retrained)
loss[t] = L_original - L_removed
// 移除似然度损失最小的 token(比如移除序列中底部的10-20%)
tokens_to_remove = bottom_k_percent(loss, removal_rate)
vocab_seed = vocab_seed \ tokens_to_remove
// 重新归一化概率
renormalize(p)
4. // 最终几步的精细调整
5. return vocab_seed, p
5.2 多候选分词与Viterbi解码
Unigram LM的一个关键优势是它天然支持多候选分词:对于同一个输入字符串,可以基于子词概率计算出多个可行的分割方案。这对于训练过程中的噪声注入(noise injection)和数据增强尤为有用。在实现中,Unigram LM使用Viterbi算法找到给定输入的最优分割序列,也可以通过Forward-Backward算法采样多个候选分割。这种灵活性使Unigram LM成为SentencePiece框架中可选的分词策略之一,也是它区别于BPE和WordPiece的重要特征。
5.3 与BPE的对比分析
从信息论视角看,BPE和Unigram LM代表了两种不同的归纳偏置。BPE的贪心合并策略隐式假设了"高频共现即语义相关",这在线性统计上是合理的,但可能漏掉那些频率不高但语义关系密切的子词对。Unigram LM的全局剪枝策略则更能捕捉语义上的相关性,因为它评估的是整个词汇表的联合似然度而非局部频率。然而,Unigram LM的训练复杂度显著高于BPE——每次剪枝后都需要重新运行Viterbi解码以更新最优分割,对于大规模语料而言,这带来了数倍的计算开销。
深挖点
Unigram LM在训练中使用了Viterbi解码来找到每个句子的最优分割,但"最优"的定义完全依赖于当前的概率分布p(s)——而这个分布本身又在迭代中变化。这种自指性(self-referential)带来了一个理论问题:是否可能存在多个概率分布p(s)和词汇表V的组合都能产生相似的似然度?实验表明,Unigram LM的训练过程确实存在多个局部最优解,不同的初始化条件可能导致显著不同的最终词汇表——这既是Unigram LM的灵活性来源,也是其可复现性面临的挑战。
六、SentencePiece:跨语言的工业级Tokenization框架
前面的三种算法(BPE、WordPiece、Unigram LM)都面临一个共同的先决条件:输入文本需要预先进行空格分词(tokenization with pre-tokenizer)。这对于英语等使用空格分隔的语言是自然的,但对于中文、日文、泰文等语言,空格分词本身就是一项挑战。SentencePiece由Google于2018年提出,其核心理念是将原始文本直接作为输入,从根本上消除了对空格分词器的依赖。这一设计使得SentencePiece成为跨语言场景下最广泛使用的Tokenization框架。
6.1 SentencePiece的设计哲学
SentencePiece的核心创新是将输入视为原始的Unicode字符序列——空格被当作普通字符对待,而不作为分词边界。这意味着句子"I love NLP"和"我爱NLP"在SentencePiece中的处理流程是统一的。在这个框架下,用户可以选择BPE、Unigram LM或字符级编码作为底层算法。SentencePiece还引入了一个关键的可选功能:归一化(Normalization),它将Unicode NFKC规范化、大小写折叠和数字归一化整合到Tokenization流水线中。归一化处理在训练和推理之间必须保持一致,这是SentencePiece实践中容易出错的地方——训练时启用了某种归一化,但推理时忘记应用,会导致不一致的编码结果。
6.2 SentencePiece的流水线架构
SentencePiece的处理流程清晰地划分为三个阶段:归一化、分词训练和词汇表输出。下图为这个流水线的架构示意。
┌─────────────────────────────────────────────────────────────┐
│ SentencePiece 流水线架构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 原始输入文本 │
│ (Unicode字符流, 无空格预处理) │
│ │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ Stage 1: 归一化 │ Unicode NFKC / 大小写折叠 │
│ │ Normalizer │ / 自定义规则 │
│ └────────┬────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ Stage 2: 分词训练 │ BPE / Unigram LM / Char │
│ │ Trainer │ 可配置 vocab_size │
│ └────────┬────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ Stage 3: 编码器 │ 输出 Token ID 序列 │
│ │ Encoder │ 支持 byte-fallback │
│ └────────┬────────────┘ │
│ │ │
│ ▼ │
│ 输出: Token ID 序列 [1234, 5678, 9012, ...] │
│ │
└─────────────────────────────────────────────────────────────┘
6.3 SentencePiece在Llama中的应用
Meta的Llama系列模型全面采用了SentencePiece(底层使用BPE算法),并做出了几个关键的设计决策。第一,词汇表大小设定为32K——相对于GPT-3的50K和GPT-4的100K,这是一个偏小的选择。第二,Llama没有使用SentencePiece的预分词器(pretokenizer),而是直接对原始文本进行字符级处理。第三,Llama 2和Llama 3均使用byte-fallback机制来处理罕见字符。Llama 3更进一步,将词汇表扩展到128K,并通过优化的BPE训练流程大幅提升了对代码和数学表达式的编码效率。
| 特性 | SentencePiece (BPE) | GPT-2 BPE | WordPiece |
|---|---|---|---|
| 输入形式 | 原始Unicode序列 | 空格预分词后 | 空格预分词后 |
| 空格处理 | 作为普通字符("▁") | 保留为词界标记 | 保留为词界标记 |
| 跨语言支持 | ✅ 原生支持 | ⚠️ 需预处理 | ⚠️ 需预处理 |
| 双向性 | 无损编解码 ✅ | 无损编解码 ✅ | 无损编解码 ✅ |
| 底层算法 | BPE / Unigram LM / Char | BPE | PMI驱动的BPE变体 |
| 归一化内置 | ✅ 是 | ❌ 否 | ❌ 否 |
| byte-fallback | ✅ 支持 | ❌ 不支持 | ❌ 不支持 |
| 训练速度 | 中等 | 快 | 慢 |
| 代表模型 | Llama, T5, Gemma | GPT-2, GPT-3 | BERT, ALBERT |
深挖点
SentencePiece将空格作为普通字符处理看似简单,但带来了一个显著的影响:序列长度平均增加了10-15%,因为每个词前面的空格现在都需要一个单独的Token(或消耗子词中的部分容量)。例如,"I love NLP"在BPE中可能只需要3-4个Token,而在SentencePiece中可能变成5-6个。然而,这种代价换来了对中文、日文等语言的原生支持——在这些语言中,预分词器(如Jieba、MeCab)本身就可能引入错误传播。SentencePiece在一个统一的框架内解决了跨语言的Tokenization问题,使得多语言模型的训练变得可行。
七、Byte-level BPE与BBPE:超越Unicode的字节编码革命
传统的BPE和WordPiece依赖于Unicode字符作为基本单元,这意味着词汇表中包含所有训练语料出现的Unicode码点。然而,Unicode标准包含超过140,000个字符,且持续更新中。对于多语言模型而言,单纯覆盖所有Unicode字符就需要巨大的词汇表容量,这显然是不经济的。更关键的是,Unicode级别的编码无法处理包含未分配码点的输入序列(如损坏的HTTP请求或二进制编码的payload)。Byte-level BPE(BBPE)通过将基本单元从Unicode字符降级到UTF-8字节,彻底解决了这些问题。
7.1 从字符到字节的范式转换
BBPE的核心思想是:在UTF-8编码中,一个Unicode字符被编码为1到4个字节。BBPE将每个字节(0-255)作为基本符号,在这256个字节符号的基础上应用BPE算法。这意味着词汇表的基础大小只有256,任何Unicode字符都可以表示为1-4个字节的序列。一个中文字符"中"(U+4E2D)在UTF-8中被编码为0xE4 0xB8 0xAD三个字节,BPE算法可以根据这三字节序列在语料中的频率,决定是否将它们合并为一个子词。这种设计的最大优势在于:词汇表完全独立于Unicode版本——即使出现了新的Unicode字符,只要其UTF-8编码已知,BBPE就可以完美处理。
7.2 BBPE的信息论分析
从信息论角度看,字节级编码相比字符级编码引入了一个额外的编码层。一个Unicode字符平均需要2字节的UTF-8编码(实际根据语言不同,英语1字节/字符,中文3字节/字符)。这意味着同样的文本在BBPE中会以更长的初始序列开始——英文文本增长约2-3倍,中文增长约2-3倍。但BBPE带来的收益同样巨大:与字符级BPE需要覆盖数万个Unicode码点不同,BBPE的基础词汇表永远只有256个字节符号。经过BPE合并后,高频的Unicode字符(如英文字母、常见汉字)会在早期合并为完整的子词,最终序列长度仅略长于字符级BPE,而词汇表的总容量需求却大幅降低。
以下BBPE的训练流程展示了字节级别的基础操作:
算法:Byte-level BPE (BBPE) — GPT-2风格
输入:
corpus: 原始文本(二进制安全)
vocab_size: 目标词汇表大小(含256个基础字节)
输出:
merges: 字节级合并规则列表
步骤:
1. 初始化字节词汇表 byte_vocab = {0x00, 0x01, ..., 0xFF}
2. 对 corpus 中的每个字符c ∈ corpus:
将c编码为UTF-8字节序列 b₁b₂...bₙ
将b₁b₂...bₙ加入语料统计
3. 初始化合并大小 n = |byte_vocab| = 256
4. merges = []
5. while n < vocab_size:
统计所有相邻byte pair的频率
best_pair = argmax frequency(pair)
new_id = n // 新ID从256开始递增
merges.append(best_pair → new_id)
byte_vocab.add(new_id)
n += 1
更新语料:将所有 best_pair 替换为 new_id
可选:如果是GPT-2风格,当合并后的Token包含非ASCII字符时,
记录该Token的Unicode解码结果
6. 后处理:
计算每个词汇表条目的正则化频率(用于编码时的优先级排序)
构建前缀树(Trie)用于最长匹配编码
7. return merges, byte_vocab
GPT-2的BBPE实现有一个关键优化:它禁止合并跨越ASCII字符和非ASCII字符边界的"危险转移"(опасный переход, dangerous transition),以防止在编码时产生非utf-8兼容的字节序列。这一设计确保了对任意字节输入的处理都是安全的。
7.3 BBPE的空间效率权衡
BBPE在空间效率上引入了一个有趣的权衡。一方面,词汇表从字符级(通常几千到几万)缩小到256个字节,大幅减少了嵌入矩阵的参数量(对32K词汇量的嵌入层而言,参数量从32K×d_vocab降至256×d_vocab+合并后子词的表征数)。另一方面,序列长度增加了。假设一个典型的英文Token在Unicode级BPE中平均为2.3个字符,在字节级BPE中为4.8个字节。由于Transformer的注意力复杂度为O(n²),序列长度翻倍意味着注意力计算翻4倍。因此BBPE对推理效率的净影响取决于词汇表缩减带来的嵌入层节省是否能够抵消注意力计算的开销增加。
深挖点
BBPE有一个鲜为人知的副作用:它赋予了模型"字符间推理"的能力。由于字节级词汇表可以表示任意Unicode字符的重组,模型在理论上有能力对字符串进行字符级别的操作——例如拼写反转、字母计数、字符替换等。然而,实证研究表明,LLM在这些任务上表现仍然不佳,原因在于BPE的合并过程破坏了字符级别的信息结构:合并后的子词Token掩盖了下层的字节序列。解决这一问题的方向包括:在训练数据中注入字符级任务的样本、在模型架构中加入显式的编码器-解码器设计,或者采用Byte-level模型(如ByT5)直接操作字节序列。
八、Tokenization对模型性能的多维影响:效率、偏差与优化
Tokenization的选择不仅仅是一个预处理步骤——它通过多种机制深刻地影响着模型训练的效率和最终性能。本节将从计算效率、编码偏差、多语言公平性和安全问题四个维度,系统性地分析Tokenization对模型性能的影响。
8.1 计算效率:从Token压缩率到训练成本
Tokenization对计算效率最直接的影响体现在压缩率——即平均每个Token能编码的原始字符数。更高的压缩率意味着更短的序列、更低的注意力计算成本和更小的KV缓存。下面是一个对比分析:
| 语言 | BPE (英语优化, 50K vocab) | BPE (通用, 32K vocab) | SentencePiece (32K, Unigram) | 字符级 |
|---|---|---|---|---|
| 英语 | 3.7 字符/Token | 3.5 字符/Token | 4.0 字符/Token | 1.0 字符/Token |
| 中文 | 1.2 字符/Token | 1.5 字符/Token | 2.3 字符/Token | 1.0 字符/Token |
| 日文 | 1.0 字符/Token | 1.3 字符/Token | 1.8 字符/Token | 1.0 字符/Token |
| 阿拉伯文 | 2.5 字符/Token | 2.2 字符/Token | 2.8 字符/Token | 1.0 字符/Token |
| 代码 (Python) | 4.2 字符/Token | 3.8 字符/Token | 4.5 字符/Token | 1.0 字符/Token |
从上表可以清晰地看到,以英语为中心的BPE为英语提供了3.7字符/Token的优秀压缩率,但对中文(1.2)和日文(1.0)的压缩率极低,意味着同样的语义内容,中文文本需要英语文本3倍以上的Token数量。这不仅增加了推理成本,还意味着模型的上下文窗口对中文用户"缩水"了3倍。SentencePiece通过跨语言平衡训练,在一定程度上改善了这种不平等,但差距仍然显著。
8.2 Tokenization的语言偏差与文化偏见
Tokenization的语言偏差不仅仅是一个效率问题,它还深刻地影响着模型的能力分布。GPT-3的BPE分词器对希伯来语的Token平均花费是英语的2.5倍,这意味着在处理同样长度的文本时,希伯来语的有效上下文窗口远小于英语。更微妙的是,分词偏差可能导致模型在低资源语言上更频繁地产生不连贯的子词组合,影响生成质量和语言保真度。研究还发现,BPE词汇表中包含的大量英语形态变体(如复数形式、过去式、所有格)占据了宝贵的词汇表容量,而其他语言中同样多的形态变化则被压缩到更少的Token中,导致信息损失。
另一个被低估的偏差来源是数字和日期的编码。BPE可能将"2024"编码为一个Token(如果它在训练语料中高频出现),但将"2025"编码为多个子词的组合("20" + "25")。这意味着模型对熟悉数字的处理和对陌生数字的处理是异构的——这直接影响了模型在数学推理任务上的表现。GPT-3对4位十进制数的编码通常是1个Token,但对8位数的编码需要2-4个Token,这种数字长度与Token数量的非线性关系是模型算术性能下降的深层原因之一。
8.3 对齐安全中的Tokenization敏感性
Tokenization在安全对齐中也扮演着一个意想不到但至关重要的角色。最近的研究发现,某些对抗性提示(adversarial prompts)正是利用了分词器对特定字符序列的处理缺陷:例如,插入一个零宽空格(Zero Width Space, U+200B)可以使一个原本会被模型安全过滤器拦截的有害提示变成无害的字符序列被模型处理——因为分词器将零宽空格作为分隔符,改变了整个句子的Token边界。这种"Tokenization攻击"不需要任何模型内部的权重修改,完全利用了Tokenization作为预处理步骤的脆弱性。
深挖点
Tokenization的效率偏差已经催生了一个新的研究方向:Tokenization聚类校准(Tokenization-clustering Calibration)。其核心思想是在模型评估时,通过将不同语言对齐到相同的Token数量基准来消除Tokenization引发的性能偏差。具体做法是:计算每种语言每个Token的平均语义"载荷",然后基于Token数量而非字符数量的尺度来调整评估数据集。这一方法清晰地揭示了一个事实:当控制Token数量相同时,当前主流LLM在英语和中文上的性能差距大幅缩小,之前观察到的差距很大程度上来自Tokenization的不公平压缩率。
九、前沿演进:从静态词汇表到动态Tokenization
尽管BPE和SentencePiece已经成为当前LLM的事实标准,但学术界和工业界并未停止对Tokenization边界进行探索。随着模型能力的提升和对Tokenization底层机制的深入理解,一系列突破性的研究方向正在涌现:从自适应词汇表到动态Token化、从固定编码到与模型联合学习。这些前沿工作暗示着一个未来:Tokenization将不再是模型流水线中一个孤立的预处理步骤,而是与模型架构深度融合的可学习组件。
9.1 自适应Tokenization:根据输入动态调整
传统的BPE和SentencePiece在训练完成后就固化了词汇表和编码规则,对所有输入使用相同的分词策略。这种"一刀切"的做法忽视了一个基本事实:不同的语言、领域(医学、代码、法律)甚至同一语言的不同风格(正式写作 vs. 口头对话)具有完全不同的Token分布特征。自适应Tokenization(Adaptive Tokenization)的理念是:让模型根据输入内容动态调整分词策略。
一个实现方案是MoE-Tokenization(Mixture of Experts Tokenization),它维护多个专业化的分词器(每个适配一个领域或语言),并在推理时根据输入的特征分布,由门控网络(gating network)选择或组合最合适的分词器。初步实验显示,这种方案在代码和数学文本上实现了15-20%的Token压缩率提升。另一个方向是T-Fixup(Tokenization Fixup),它在输入序列前段加入一个可学习的"分词提示",引导模型使用某种特定的编码粒度。
9.2 端到端可学分词:从离散操作到可微分近似
Tokenization面临的一个根本性挑战是其离散性——合并、分割、映射到Token ID,这些操作都是不可微的,无法通过梯度反向传播进行优化。这意味着Tokenization方案的改进只能通过预训练实验来验证,耗时且昂贵。端到端可学分词(End-to-End Learnable Tokenization)试图打破这一壁垒。
几个代表性的工作包括:
- Differentiable BPE (DiffBPE):通过Gumbel-Softmax松弛将BPE的离散合并决策近似为连续可微的加权平均操作。DiffBPE允许词汇表的合并规则在预训练过程中通过梯度信号间接调整。
- CNNS (Controllable Neural Subword Segmentation):用一个基于LSTM的分割器网络替代固定的BPE规则,该分割器可以输出每个字符是否为词边界的概率。通过巧妙的概率链规则,实现了端到端的训练。
- Boundary-Aware Tokenization (BAT):在Transformer的词嵌入层和位置编码之间引入一个显式的子词感知机制,使得模型能够在Token级别上"穿透"子词边界进行操作。
9.3 无Token范式:字节级模型的复兴
如果Tokenization本身就是一个有问题的中间层,为什么不直接绕过它?这一思路催生了字节级模型的复兴。Google的ByT5(2021年)使用纯字节序列作为输入和输出,从根本上消除了Tokenization的必要性。ByT5在拼写任务和噪声数据处理上显著优于基于子词的T5,但在标准NLU基准上的表现略低于同等参数量的T5。Charformer(2021年)则采用了一种中间路线:它在字符输入的早期层通过一个局部注意力机制聚合相邻字符为"软子词",在保持Tokenization灵活性的同时避免了离散操作。
最新的趋势是MambaByte(2023年)等使用状态空间模型(SSM)直接处理字节序列的方法。SSM的线性复杂度O(n)使得字节序列的处理不再受Transformer二次复杂度的限制,这在计算层面为字节级模型扫清了障碍。初步结果表明,MambaByte在字节级文本建模任务上表现优于同等计算量的Transformer+BPE模型,特别是在需要细粒度字符操作的任务上。
深挖点
关于"去Tokenization"的争论实际上触及了一个更深层次的认知科学问题:人类的语言理解是否依赖于类似Token的离散语义单元?最新的神经科学证据表明,人类大脑在语言加工中确实表现出类似于"语义分块"(semantic chunking)的行为——高频词组被编码为整体单位。这与BPE的合并思想惊人地一致。这一发现暗示,Tokenization可能不是一个工程上的人为设计,而是语言处理系统固有特性的一种工程近似。未来的系统可能不是在"有无Tokenization"之间做选择,而是在Tokenization的可塑性和表示效率之间寻找更优的平衡点。
结语:Tokenization的未来——被看见的底层艺术
通过对BPE、WordPiece、Unigram LM、SentencePiece和Byte-level BPE的深入剖析,本文揭示了Tokenization作为LLM底层组件的复杂性和重要性。从BPE的贪心合并到WordPiece的似然度驱动,从Unigram LM的概率建模到SentencePiece的跨语言统一框架,再到BBPE的字节级革命,每一代Tokenization技术都在设计哲学上做出了关键的选择,这些选择深刻地影响着模型的能力边界、效率分布和安全特性。
对于工程师和研究者而言,Tokenization不应仅被视为一个"已解决"的预处理问题。相反,随着多语言、多领域、多任务AI系统的持续演进,Tokenization的设计决策将变得越来越重要。选择哪一种分词器、设置多大的词汇表、如何处理罕见字符、是否启用byte-fallback——这些看似微小的配置参数,正在以我们尚未完全理解的方式,塑造着下一代人工智能系统的能力图景。Tokenization这门隐秘的艺术,正逐渐走进聚光灯下。
| 维度 | 当前主流 | 新兴趋势 | 未来展望 |
|---|---|---|---|
| 基础单元 | 子词(Subword) | 字节(Byte) | 动态混合(Hybrid) |
| 词汇表 | 静态固定 | 领域自适应 | 端到端可学 |
| 多语言支持 | 以英语为中心 | 跨语言平衡 | 语言无关通用 |
| 底层架构 | Transformer (O(n²)) | 状态空间模型 (O(n)) | 神经符号融合 |
| 训练方式 | 与模型训练分离 | 联合损失引导 | 完全端到端 |
深挖点
如果只能带走一个结论,那应该是:Tokenization不是模型流水线上一个无关紧要的预处理步骤,而是一个对模型能力有着全局性影响的架构决策。在未来的论文中,建议每个模型公布两个关键数据:(1)在标准化测试集上的每种语言的Token数量分布;(2)不同词汇表大小选择对下游任务性能的影响曲线。这些数据将帮助社区建立对Tokenization效果的量化理解,推动这一领域的科学化进展。