大模型的“原子”如何切分?深度解析 Tokenization 中的 BPE、WordPiece 与 SentencePiece

在如今的大模型(LLM)时代,当我们惊叹于 ChatGPT 的对答如流、惊叹于 LLaMA 的逻辑推理能力时,我们往往将目光聚焦于 Transformer 架构、注意力机制或是庞大的参数量。然而,在这些宏大的叙事背后,有一个常常被忽视却至关重要的“基石”——Tokenization(分词)

如果说大模型是一座巍峨的大厦,那么 Token 就是建造这座大厦的“砖块”,而 Tokenization 就是将现实世界连续的、无结构的文本,转化为模型能够理解的离散空间的“切割机”。大模型并不认识汉字或英文字母,它只认识数字。如何高效、无损地将文本转换为数字序列,就是 Tokenization 要解决的核心问题。

本文将带你深入大模型底层,详细剖析从传统的词空间到现代子词分割的演进,并深度解析三种主流 Tokenization 算法:BPE、WordPiece 与 SentencePiece


一、 为什么大模型需要复杂的 Tokenization?

在自然语言处理(NLP)的早期,分词策略相对简单,主要分为词级别字符级别。但它们在处理海量真实语料时,都遇到了致命的瓶颈:

1. 词级别的困境:OOV 与稀疏性

最直观的想法是把句子按空格切开,变成一个个单词。但这带来两个严重问题:

  • 词表过大:英语中加上时态、复数、各种专有名词,单词组合是无限的。为了覆盖日常语言,词表动辄几十万甚至上百万,导致 Embedding 层参数极其庞大。
  • OOV(Out-of-Vocabulary,未登录词)问题:遇到词表中没有的新词(比如新出现的网络流行语、错别字、专有药物名),模型只能无奈地将其标记为 <UNK>(Unknown),导致信息丢失。

2. 字符级别的困境:序列过长与语义丢失

既然词太多,那我们按字母或单个汉字切分总行了吧?

  • 序列长度爆炸:一句话 “I love natural language processing” 按词切是 5 个 Token,按字符切就是几十个 Token。注意力机制的计算复杂度是 O(N2)O(N^2),过长的序列会导致计算成本灾难。
  • 缺乏高级语义:单个字母 ‘t’ 或 ‘a’ 没有任何语义,模型需要耗费极大的算力去自己学习组合出 “apple” 的概念。

3. 破局之道:子词分割

为了平衡“词表大小”和“序列长度”,现代大模型普遍采用 子词分割 策略。
其核心思想是:高频词保留为整体,低频词拆分为有意义的子词。
例如,“unfriendly” 可能会被拆分为 “un”, “friend”, “ly”。这样既保留了词根的语义,又避免了 OOV 问题,因为无论什么新词,最终都可以拆解为最基本的字母。


二、 三大主流分词算法深度剖析

在子词分割的江湖中,有三大门派占据了统治地位。它们虽然目的相同,但在合并策略和底层逻辑上有着精妙的区别。

1. BPE (Byte Pair Encoding) —— GPT 系列的绝对主力

BPE 最初是一种简单的数据压缩算法,在 2015 年被 Sennrich 等人引入 NLP 领域。如今,GPT-2、GPT-3、GPT-4 以及 Meta 的 LLaMA 系列均采用此算法或其变体。

核心思想:频率至上

BPE 的训练过程非常朴素:不断找出语料中相邻出现频率最高的两个 Token,将它们合并成一个新的 Token,直到达到预设的词表大小。

算法步骤:

  1. 初始化:准备一个庞大的语料库,在单词末尾添加一个特殊的结束符 </w>(用来区分 “dog” 和 “dog<er” 中的 “dog”)。将所有单词拆分为字符序列。建立初始词表(即所有字符的集合)。
  2. 统计共现频率:统计语料中相邻两个字符(或子词)共同出现的频率。
  3. 合并:找出频率最高的一对(比如 th 共现最多),将它们合并为 th。更新词表和语料。
  4. 循环:重复步骤 2 和 3,直到词表大小达到预设值(例如 32000 或 50000)。

代码演示:从零实现一个简易版 BPE 训练

为了直观理解,我们用一段简短的 Python 代码来模拟 BPE 的合并过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import re
from collections import defaultdict

def format_word(word, end_of_word='</w>'):
return list(word) + [end_of_word]

def get_pairs(corpus):
pairs = defaultdict(int)
for word, freq in corpus.items():
symbols = word.split()
for i in range(len(symbols) - 1):
pairs[(symbols[i], symbols[i+1])] += freq
return pairs

def merge_vocab(pair, corpus):
new_corpus = {}
pattern = re.compile(r'(?<!\S)' + re.escape(' '.join(pair)) + r'(?!\S)')
for word in corpus:
# 将匹配到的相邻子词合并
new_word = pattern.sub(''.join(pair), word)
new_corpus[new_word] = corpus[word]
return new_corpus

# 初始语料库:词及其频率
corpus = {
'l o w </w>': 5,
'l o w e r </w>': 2,
'n e w e s t </w>': 6,
'g l a d </w>': 3
}

num_merges = 10
for i in range(num_merges):
pairs = get_pairs(corpus)
if not pairs:
break
# 找出频率最高的一对
best_pair = max(pairs, key=pairs.get)
# 合并
corpus = merge_vocab(best_pair, corpus)
print(f"Merge {i+1}: {best_pair} -> {''.join(best_pair)}")

输出结果分析
你会发现,程序首先合并了 es(因为 newest 中有),接着可能合并 est。这样,“newest” 就从单字符逐渐被压缩成了 newest 这样的高级子词。

2. WordPiece —— BERT 的“语言学大师”

WordPiece 算法在 2012 年由微软提出,并在著名的 BERT 模型中大放异彩。从宏观上看,它和 BPE 非常相似,也是通过不断合并子词来构建词表。

核心思想:似然度最大化

如果说 BPE 是个暴发户,只看频率;那么 WordPiece 就是个精算师,它看的是概率(似然度)
WordPiece 并不是简单地寻找频率最高的相邻子词对,而是寻找合并后能够最大程度增加整个语料库语言模型似然度的子词对

在实际工程中,计算完整的似然度非常昂贵。因此,WordPiece 采用了一个近似启发式方法:选择互信息最高的点进行合并。

  • 互信息:衡量两个变量之间的相关性。
  • 在这里,它指的是:如果两个子词经常在一起出现,而且很少和其他子词搭配,那么它们的互信息就很高。
  • 例如:th 的频率很高,he 的频率也很高。但 BPE 可能按绝对频率随意合并,而 WordPiece 会计算 P(th)/(P(t)×P(h))P(th) / (P(t) \times P(h))。如果这个值比 P(he)/(P(h)×P(e))P(he) / (P(h) \times P(e)) 高,才会优先合并 th

标志性特征:## 前缀

这是 WordPiece 最容易辨认的特征。在 BERT 的词表中,如果一个子词是在词的中间或结尾,它会被加上 ## 前缀。
例如,单词 “unwanted”:

  • WordPiece 可能会切分为:["un", "##want", "##ed"]
  • 这明确告诉模型:un 是词的开头,而 wanted 是后续部分,不是独立的单词。

3. SentencePiece —— 多语言大模型的终极武器

当你需要训练一个支持 100 种语言的大模型时,你会发现面临一个极其头疼的问题:空格不一致
英语是用空格分隔单词的,但中文、日文、韩文(CJK 字符)通常没有空格。如果你先对文本进行分词预处理,然后再进行 BPE,那么模型的表现就会严重依赖于预处理的分词质量。

为了解决这个痛点,Google 推出了 SentencePiece

核心思想:将文本视为纯字节流

SentencePiece 并不是一种全新的合并算法,它实际上是一个端到端的分词框架。在这个框架下,最常用的是一种叫做 Unigram 的算法(当然 SentencePiece 内部也支持 BPE)。

  • 不依赖预分词:SentencePiece 把输入的句子直接当作一串原始的 UTF-8 字节流。它不需要预先按空格切分单词,空格在 SentencePiece 中被特殊对待(通常被替换为一个特殊的视觉符号,如 ,即下划线符号)。

  • Unigram 语言模型:不同于 BPE 和 WordPiece 自底向上的合并策略,Unigram 采用的是自顶向下的拆解策略

    1. 它首先初始化一个巨大的词表(比如包含所有可能的子串和单字符)。
    2. 给定当前词表,使用期望最大化(EM)算法估计每个 Token 的概率。
    3. 计算移除每个 Token 对整体损失函数(似然度)的影响。
    4. 剔除那些对损失函数影响最小(即最不重要)的 Token,缩小词表。
    5. 重复上述过程,直到词表缩减到目标大小。
  • 概率化的多路径输出:因为 Unigram 是基于概率的模型,所以对于同一个输入句子,SentencePiece 可以输出多种不同的分词结果,并给出每种分词结果的概率。这在机器翻译等任务中非常有用。

代码演示:使用 SentencePiece 训练中文分词器

在实际开发中,我们通常使用 Google 官方的 sentencepiece 库。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import sentencepiece as spm

# 假设我们有一个纯文本语料库 'corpus.txt'
# 里面包含几十万行中文或混合语言文本

# 训练模型
# model_type 可以选择 'unigram' (默认) 或 'bpe'
spm.SentencePieceTrainer.train(
input='corpus.txt',
model_prefix='mymodel', # 输出模型前缀
vocab_size=32000, # 词表大小
model_type='unigram', # 使用 Unigram 算法
character_coverage=0.9995 # 字符覆盖率,对于中文通常设得很高
)

# 加载模型并进行分词
sp = spm.SentencePieceProcessor()
sp.load('mymodel.model')

text = "大模型的Tokenization非常有趣!"
print(f"原句: {text}")
print(f"分词: {sp.encode_as_pieces(text)}")
print(f"ID: {sp.encode_as_ids(text)}")

可能的输出结果

1
2
3
原句: 大模型的Tokenization非常有趣!
分词: ['▁大', '模型', '的', 'T', 'oken', 'ization', '非常', '有趣', '!']
ID: [345, 1205, 21, 56, 4003, 6702, 1890, 4502, 12]

注意看,中文没有空格,所以 ‘▁’ 标记了句子的开头;而英文 “Tokenization” 被按照 Unigram 算法切分成了 T、oken、ization 三个部分,因为这样在数学概率上是最优的。


三、 终极对比:BPE vs WordPiece vs SentencePiece

为了更直观地理解,我们可以从以下几个维度对三者进行总结对比:

特性 BPE (Byte-Pair Encoding) WordPiece SentencePiece (主要指 Unigram)
代表模型 GPT 系列, LLaMA, RoBERTa BERT, DistilBERT, Electra T5, ALBERT, XLNet, 多语言大模型
核心合并逻辑 频率最高 互信息最高 / 似然度增加最大 概率最高,不断丢弃低频子词
演进方向 自底向上合并 自底向上合并 自顶向下拆解
空格处理 依赖预分词,保留为独立 Token 依赖预分词,使用 ## 标识非词首 不依赖预分词,使用 (下划线) 标识词首
多语言支持 较弱(需配合特定的预分词器) 较弱(同上) 极强,语言无关,直接处理 Raw Text
确定性 唯一确定 唯一确定 可以输出概率化的多种分词结果

现代大模型的高级演变:BBPE (Byte-level BPE)

在 GPT-2 之后,大模型界开始流行一种叫做 Byte-level BPE (BBPE) 的技术。传统的 BPE 是基于字符的,遇到多字节的语言(如中文 UTF-8 编码)容易崩溃。
BBPE 的做法更加极致:它连字符都不看,直接看底层的字节!

无论你是英文、中文、还是 Emoji 表情,在计算机底层都是 0 和 1 的字节。UTF-8 编码最多只有 256 个不同的字节组合。BBPE 直接把这 256 个字节作为基础词表,然后用 BPE 算法去合并这些高频字节流。

  • 优势:真正的零 OOV。任何语言、任何特殊符号,模型都能完美吃下并解析,无需指定特定语言的规则。这也是 LLaMA 等先进模型能精通多国语言的关键所在。

四、 总结

大模型的 Tokenization 就像是一场精心编排的“原子切割手术”。从字符的堆砌到子词的巧夺天工,Tokenization 的演进折射出了 NLP 技术向海量、多模态、无边界数据妥协并征服的历程。

  • 如果你要处理像英文这样空格明确的语言,追求极致的压缩效率,BPE 是不二之选。
  • 如果你希望模型对前缀后缀有更好的语义感知,WordPiece 及其变体提供了优秀的思路。
  • 如果你立志打造一个跨语言、跨模态的全球级通用大模型,那么抛弃空格束缚的 SentencePiece(结合 Unigram 或 BBPE)则是你唯一的答案。

理解了 Tokenization,你就理解了 LLM 处理信息的“第一感官”。下一次,当你在对话框中敲下几个字,看到大模型一个 Token 接着一个 Token 地吐出长篇大论时,或许你会想到,在这背后,正是那些精密的算法在将人类的思想,重新编码成机器可以理解的数字星辰。