解密大模型的第一道关卡:深度剖析 Tokenization(BPE、WordPiece 与 SentencePiece)
当我们惊叹于 ChatGPT 的对答如流,或是感叹 LLaMA 能够吟诗作赋时,我们往往会将功劳归结于 Transformer 架构的精妙、海量训练数据的堆积以及 RLHF 等对齐技术的魔法。然而,在这些宏大的叙事之下,隐藏着一个极其基础却至关重要的环节——Tokenization(分词)。
如果把大语言模型比作一个正在阅读的人类,模型底层计算的是一个个冰冷的数字,而人类看到的是有意义的文本。Tokenization 就是连接这两个世界的“巴别塔”。它不仅决定了模型如何“阅读”文本,更直接影响了模型的推理速度、上下文窗口的实际长度,甚至是模型处理多语言和拼写错误的能力。
今天,我们就来深度剖析大模型背后的 Tokenization 技术,重点探讨业界最主流的三种算法:BPE、WordPiece 与 SentencePiece。
1. 为什么我们需要 Tokenization?
在深入算法之前,我们需要回答一个根本问题:为什么不直接把整段文本(字符序列)或者单个字符喂给模型,而要费尽心机去做分词?
1.1 词级别与字符级别的困境
早期的自然语言处理(NLP)通常采用词级别的切分。
- 优点:语义清晰。
- 缺点:词表会随着语料库的膨胀而无限扩大,遇到未登录词就完全束手无策。比如网络新词“绝绝子”,词表里没有,模型就只能输出
[UNK]。
为了解决 OOV 问题,人们转向了字符级别的切分(将英文切分为 a, b, c,中文切分为单字)。
- 优点:词表极小(英文只需 26 个字母加一些符号),永远不会有 OOV 问题。
- 缺点:字符本身缺乏语义(字母 “a” 没有具体含义),且把句子切分成字符会导致序列长度暴增。对于基于 Transformer 的模型来说,序列长度意味着计算复杂度的平方级增长(),这是不可接受的。
1.2 子词切分:寻找完美的平衡点
现代大模型普遍采用子词切分。它的核心理念是:
高频词保留,低频词拆分。
像 “apple” 这样的高频词,作为一个整体的 Token;而像 “unbelievably” 这样的低频词,则被拆分为 “un”, “believ”, “ably” 等具有一定语义的子词。这样一来:
- 词表大小适中:通常在几万到十几万之间。
- 无 OOV 问题:任何奇怪的词汇都可以被拆解为基本的字符或子词组合。
- 序列长度可控:在保留语义的同时,尽量缩短了输入序列的长度。
基于这个理念,业界衍生出了三大经典算法。
2. BPE (Byte-Pair Encoding):GPT 系列的基石
BPE 最早是一种数据压缩算法,在 2015 年被 Sennrich 等人引入到 NLP 领域。如今,它统治了绝大多数生成式大模型(GPT-2, GPT-3, GPT-4, LLaMA 系列均使用 BPE 或其变体)。
2.1 核心思想
BPE 的核心逻辑非常朴素:寻找语料中出现频率最高的一对相邻 Token,将它们合并为一个新的 Token,不断重复此过程,直到达到预设的词表大小。
2.2 训练过程详解
假设我们有一段极简的英文语料,经过预处理(加空格、转小写)后,我们首先按字符进行切分,并统计词频:
l o w </w>: 5次l o w e r </w>: 2次n e w e s t </w>: 6次w i d e r </w>: 3次
(注:</w> 通常被加在词尾,用来区分词内的子词和作为完整词的子词)
迭代 1:
统计相邻 Token 对的出现频率(受词频加权):
e和s在n e w e s t中出现了 6 次,是最高频对。- 动作:将
e s合并为es。 - 当前状态:
n es t </w>(6次)
迭代 2:
继续找最高频对。假设是 es 和 t,出现了 6 次。
- 动作:合并为
est。 - 当前状态:
n est </w>(6次)
迭代 N:
不断迭代,直到词表大小达到我们设定的目标(例如 32000 或 50000)。最终我们可能会得到诸如 low, lower, new, est 这样的高质量子词。
2.3 代码实战:使用 HuggingFace 体验 BPE
我们可以用 HuggingFace 的 tokenizers 库来直观地感受 BPE 的训练和切分过程。
1 | from tokenizers import Tokenizer |
输出解析:
GPT-2 的输出可能是这样的:['Un', 'believ', 'able', '!', ' Token', 'ization', ' is', ' magical', '.']
你可以清晰地看到,高频词 is 被完整保留,而 Unbelievable 被拆分成了有词缀意义的 Un, believ, able。而且,由于 GPT-2 使用了 Byte-level BPE (BBPE),它永远不会出现 [UNK]。
3. WordPiece:BERT 的选择
WordPiece 算法在 2012 年由 Google 提出,并在著名的 BERT、DistilBERT 等模型中被广泛使用。它看起来和 BPE 非常相似,但在合并的评判标准上有着本质的区别。
3.1 核心思想与 BPE 的区别
- BPE:选择语料中出现频率最高的相邻 Token 对进行合并。
- WordPiece:选择合并后能最大程度增加语言模型似然度 的相邻 Token 对。
在工程实现上,这个“似然度”通常被简化为一个巧妙的公式:
WordPiece 不会单纯因为 A 和 B 经常挨在一起就合并它们,而是要看合并 AB 后,相对于 A 和 B 单独出现的概率,其互信息(Mutual Information)有多大。
3.2 为什么 BERT 选择 WordPiece?
对于以理解为主的模型(如 BERT)来说,单纯的词频可能会导致误导。例如,在特定领域的语料中,某些无意义的字符组合可能频率极高,但它们合并在一起并不能带来更好的语义表示。WordPiece 的似然度评估机制,使得合并出来的子词在语言学上更具合理性。
3.3 直观的标记:##
WordPiece 最明显的特征是使用 ## 来标记非词首的子词。
例如单词 unwanted,在 BPE 中可能被切分为 un, want, ed。
但在 WordPiece 中,它会被切分为 un, ##want, ##ed。这告诉模型:##want 是一个后接片段,不能作为一个独立单词的开头。
3.4 代码实战:BERT 的 Tokenization
1 | from transformers import BertTokenizer |
输出解析:
切分结果类似于:['un', '##bel', '##ie', '##va', '##ble', '!', 'token', '##ization', 'is', 'magical', '.']
注意观察,BERT 把 unbelievable 切得更碎了,并且非首位的子词都带上了 ## 前缀。
4. SentencePiece:多语言大模型的终极武器
当我们从纯英文走向多语言(特别是中日韩等亚洲语言)时,BPE 和 WordPiece 都面临一个巨大的痛点:它们都依赖于预切分。
在英文中,我们可以简单地用空格把单词分开。但在中文里,“自然语言处理”这六个字之间是没有空格的。如果强行按字切分,序列会太长;如果调用结巴分词等外部工具,又引入了额外的依赖和误差。
SentencePiece 应运而生。它是 Google 开源的一个跨语言、纯数据驱动的 Tokenization 框架。LLaMA、ChatGLM、BLOOM 等现代大模型几乎都采用了它。
4.1 核心思想:把空格也当作一种字符
SentencePiece 的革命性在于,它完全抛弃了“空格即词边界”的传统假设。
它将输入的纯文本首先进行一种称为 Unicode NFKC 的规范化,然后把空格替换为一个特殊的可见字符(通常是 _ 或 <space>)。在这个阶段,所有语言都被统一看作是没有边界的连续字符流。
接着,它在底层使用一种名为 Unigram 的算法(或者 BPE)来进行子词切分。
4.2 Unigram 语言模型算法
虽然 SentencePiece 也支持 BPE,但它最经典的默认算法是 Unigram。与 BPE 自底向上的合并不同,Unigram 是自顶向下的。
- 初始化:用一个非常巨大的词表(比如包含所有可能的子串和字符)。
- 建模:计算每个子词在当前语料中的概率(通常使用期望最大化 EM 算法)。
- 剪枝:计算移除每个子词后对整体似然度的损失。移除那些损失最小(即最不重要)的子词。
- 重复:不断剪枝,直到词表缩小到目标大小。
优势:Unigram 能够为一个词输出多种切分可能性,并赋予它们不同的概率。在推理时,通常使用 Viterbi 算法寻找概率最大的那一条切分路径。
4.3 代码实战:LLaMA 的中文处理
1 | # 需要安装 sentencepiece: pip install sentencepiece |
5. 终极对决:BPE vs WordPiece vs SentencePiece
为了方便大家在实际工作和选型中做出决策,我总结了这三者的核心差异对比表:
| 特性 | Byte-Pair Encoding (BPE) | WordPiece | SentencePiece (Unigram) |
|---|---|---|---|
| 代表模型 | GPT 系列, LLaMA, RoBERTa | BERT, DistilBERT, XLNet | T5, ALBERT, ChatGLM, BLOOM |
| 核心算法 | 自底向上,频率驱动合并 | 自底向上,似然度驱动合并 | 自顶向下,概率EM算法剪枝 |
| 合并标准 | 最高频的相邻字符对 | 互信息最大(提升似然度最大) | 整体语料概率最优 |
| 依赖预分词? | 是 (依赖空格/外部工具) | 是 (依赖空格/外部工具) | 否 (空格视为普通字符 _) |
| 多语言支持 | 差(需 Byte-level 弥补) | 差 | 极佳 (语言无关的字节流处理) |
| 输出确定性 | 唯一确定的切分结果 | 唯一确定的切分结果 | 多种切分路径(推理时取最佳) |
选型建议:
- 如果你在做纯英文的生成式任务,直接上基于 Byte-level BPE 的 OpenAI 系列方案(如
tiktoken)。 - 如果你在做基于 BERT 架构的理解型任务,继续使用 WordPiece 即可。
- 如果你在做多语言混合或**中日韩(非空格分隔)**语言的大模型训练,SentencePiece 是几乎唯一的最优解。
6. Tokenization 对大模型的影响:为什么它比你想的更重要?
掌握了具体算法后,我们需要站在更高维度来看看 Tokenization 对当前大模型生态产生的深远影响。
6.1 上下文窗口的“通货膨胀”
我们常听宣传说“某模型支持 128K 上下文”。但请注意,这 128K 是 Token 数量,而不是字符数量。
由于不同语言和不同分词器的分词效率不同:
- 在 GPT-4 中,1 个英文单词通常约等于 1-1.3 个 Token。
- 但是,1 个中文字符通常需要 1.5 到 2 个 Token!
这意味着,相同的 128K 限制下,中文大模型实际能处理的文本长度要比英文短得多。这不仅关乎内存,更关乎训练和推理的成本。
6.2 为什么大模型不会拼写单词?(Spelling Issues)
你有没有发现,如果你让 GPT-4 “请把 Strawberry 这个单词倒序拼写”,它经常会出错(比如输出 “yrrabets” 而非 “yrrebarts”)?
原因就在于 BPE 分词。
在 GPT-4 的 Token 眼中,“Strawberry” 可能并不是 10 个字母的组合,而是一个单一的 Token ID 46823。模型在自注意力机制中看到的是这个 ID 及其上下文,并没有在底层特征中强化“它由哪些字母按什么顺序组成”的信息。这也是为什么现在很多新的研究开始呼吁回归字符级别的模型。
6.3 弱点与安全漏洞:Token 欺骗
由于分词器是独立于大模型主体训练的,有时它会成为安全漏洞的来源。
例如,在某些分词器中,特定的罕见字符组合可能会导致 Token 被异常切分,使得原本的安全过滤词(如 “kill”)被切分成 ki 和 ll,从而绕过基于字符匹配的安全审查系统。
7. 总结与展望
Tokenization 是连接人类语言与机器数字世界的桥梁。从最基础的频率统计(BPE),到引入概率语义模型,再到跨语言统一的 SentencePiece,分词技术的演进史实际上也是大语言模型走向成熟的一个缩影。
然而,Tokenization 并非完美的终局。它引入的边界模糊性、多语言的不公平性以及拼写推理能力的缺失,越来越成为模型进一步发展的掣肘。
在当前的前沿研究中,学术界已经开始探索无需 Tokenizer 的架构(如 Byte-level models,代表工作有 MegaByte 等),试图让模型直接从原始字节中学习。但在可预见的未来内,考虑到计算效率的极限,基于 BPE 和 SentencePiece 的子词分词器依然将是工业界大模型的主流配置。
理解 Tokenization,不仅是调参炼丹的第一步,更是真正看透大模型黑盒的敲门砖。希望这篇文章能为你在大模型领域的探索打下坚实的基础。