【大模型】- 分词器:BPE、WordPiece、SentencePiece
分词器:BPE、WordPiece、SentencePiece
你的 LLM 不会读英语。它读整数。分词器决定了这些整数是携带意义还是浪费意义。
类型: 学习 | 语言: Python | 🏷 前置:《第 05 阶段(NLP 基础)》(本课程体系其他阶段)
学习目标
- 从零实现 BPE、WordPiece 和 Unigram 分词算法并比较它们的合并策略
- 解释词汇表大小如何影响模型效率:太小产生长序列,太大浪费embedding 参数
- 跨语言和代码分析分词伪影,识别特定分词器在哪里崩溃
- 使用 tiktoken 和 sentencepiece 库分词文本并检查生成的 token ID
问题
你的 LLM 不会读英语。它不读任何语言。它读数字。
“Hello, world!” 和 [15496, 11, 995, 0] 之间的差距就是分词器。每个词、每个空格、每个标点符号都必须转换为整数,模型才能处理。这种转换不是中性的。它将假设烘焙到模型中,以后无法撤销。
搞错了,你的模型就会浪费容量用多个 token 编码常见词。”unfortunately” 变成四个 token 而不是一个。你的 128K 上下文窗口对于多音节词密集的文本缩小了 75%。搞对了,同样的上下文窗口能容纳两倍的意义。”这个模型擅长处理代码”和”这个模型在 Python 上卡住”之间的区别通常归结为分词器的训练方式。
你对 GPT-4 或 Claude 的每次 API 调用都按 token 计费。模型生成的每个 token 都消耗计算。表示输出所需的 token 越少,端到端推理就越快。分词不是预处理。它是架构。
概念
三种失败的方法(和一种成功的方法)
有三种明显的方法将文本转换为数字。其中两种在大规模上不起作用。
词级分词按空格和标点分割。”The cat sat” 变成 [“The”, “cat”, “sat”]。简单。但 “tokenization” 呢?或 “GPT-4o”?或德语复合词 “Geschwindigkeitsbegrenzung”?词级需要巨大的词汇表来覆盖每种语言中的每个词。漏掉一个词就会得到可怕的 [UNK] token——模型表示”我不知道这是什么”的方式。仅英语就有超过一百万种词形。加上代码、URL、科学记数法和 100 种其他语言,你需要无限的词汇表。
字符级分词走另一个方向。”hello” 变成 [“h”, “e”, “l”, “l”, “o”]。词汇表很小(几百个字符)。永远没有未知 token。但序列变得极长。一个 10 个词级 token 的句子变成 50 个字符级 token。模型必须学会 “t”、”h”、”e” 在一起意味着 “the”——在人类三岁时就学会的事情上燃烧注意力容量。
子词分词找到了平衡点。常见词保持完整:”the” 是一个 token。罕见词分解为有意义的部分:”unhappiness” 变成 [“un”, “happi”, “ness”]。词汇表保持可控(30K 到 128K token)。序列保持短。未知 token 基本消失,因为任何词都可以由子词片段构建。
每个现代 LLM 都使用子词分词。GPT-2、GPT-4、BERT、Llama 3、Claude——全部。问题是哪种算法。
BPE:字节对编码
BPE 是一个贪心压缩算法,被重新用于分词。这个想法简单到可以放在一张索引卡上。
从单个字符开始。计算训练语料库中每个相邻对。将最频繁的对合并为新 token。重复直到达到目标词汇表大小。
这里是 BPE 在包含 “lower”、”lowest” 和 “newest” 的小语料库上运行:
1 | 语料库(带词频): |
合并表就是分词器。要编码新文本,按学习顺序应用合并。训练语料库决定了哪些合并存在,这个选择永久地塑造了模型看到的东西。
字节级 BPE(GPT-2、GPT-3、GPT-4)
标准 BPE 在 Unicode 字符上操作。字节级 BPE 在原始字节(0-255)上操作。这给你恰好 256 的基础词汇表,处理任何语言或编码,且永远不产生未知 token。
GPT-2 引入了这种方法。基础词汇表覆盖每个可能的字节。BPE 合并建立在此之上。OpenAI 的 tiktoken 库实现了这些词汇表大小的字节级 BPE:
- GPT-2:50,257 token
- GPT-3.5/GPT-4:约 100,256 token(cl100k_base 编码)
- GPT-4o:200,019 token(o200k_base 编码)
WordPiece(BERT)
WordPiece 看起来类似 BPE 但以不同方式选择合并。它不是基于原始频率,而是最大化训练数据的似然:
1 | BPE 合并标准: count(A, B) |
BPE 问:”哪个对出现最频繁?”WordPiece 问:”哪个对一起出现的频率比你随机预期的更高?”这个细微差别产生不同的词汇表。WordPiece 偏好共现令人惊讶的合并,而不仅仅是频繁的。
WordPiece 还使用 “##” 前缀表示续接子词:
1 | "unhappiness" -> ["un", "##happi", "##ness"] |
“##” 前缀告诉你这个片段续接前面的 token。BERT 使用 30,522 token 词汇表的 WordPiece。
SentencePiece(Llama、T5)
SentencePiece 将输入视为原始 Unicode 字符流,包括空格。没有预分词步骤。没有关于词边界的特定语言规则。这使它真正与语言无关——它适用于中文、日文、泰语和其他空格不分隔词的语言。
SentencePiece 支持两种算法:
- BPE 模式:与标准 BPE 相同的合并逻辑,应用于原始字符序列
- Unigram 模式:从大词汇表开始,迭代删除对整体似然影响最小的 token。BPE 的反向——修剪而非合并。
Llama 2 使用 32,000 token 词汇表的 SentencePiece BPE。T5 使用 32,000 token 的 SentencePiece Unigram。注意:Llama 3 切换到基于 tiktoken 的字节级 BPE 分词器,词汇表 128,256 token。
词汇表大小权衡
这是一个有可衡量后果的真实工程决策。
具体数字。对于 4,096 维嵌入的 128K 词汇表,仅embedding 矩阵就是 128,000 x 4,096 = 5.24 亿参数。对于 32K 词汇表,是 1.31 亿参数。仅分词器选择就带来 4 亿参数差异。
但更大的词汇表更积极地压缩文本。用 32K 词汇表需要 100 token 的同一英文段落,用 128K 词汇表可能只需 70 token。这意味着生成期间减少 30% 的前向传播。对于服务数百万请求的模型,这是计算成本的直接降低。
趋势很明确:词汇表大小在增长。GPT-2 使用 50,257。GPT-4 使用约 100K。Llama 3 使用 128K。GPT-4o 使用 200K。
| 模型 | 词汇表大小 | 分词器类型 | 每个英文词平均 token 数 |
|---|---|---|---|
| BERT | 30,522 | WordPiece | 约 1.4 |
| GPT-2 | 50,257 | 字节级 BPE | 约 1.3 |
| Llama 2 | 32,000 | SentencePiece BPE | 约 1.4 |
| GPT-4 | 约 100,256 | 字节级 BPE | 约 1.2 |
| Llama 3 | 128,256 | 字节级 BPE (tiktoken) | 约 1.1 |
| GPT-4o | 200,019 | 字节级 BPE | 约 1.0 |
多语言税
主要在英语上训练的分词器对其他语言很残酷。GPT-2 分词器中的韩语文本平均每词 2-3 个 token。中文可能更糟。这意味着韩语用户实际上拥有英语用户一半大小的上下文窗口——为更低的信息密度支付相同的价格。
这就是为什么 Llama 3 将词汇表从 32K 增加到 128K。更多 token 专用于非英语文字意味着跨语言更公平的压缩。
构建
步骤 1:字符级分词器
从基础开始。字符级分词器将每个字符映射到其 Unicode 码位。无需训练。没有未知 token。只是直接映射。
步骤 2:从零构建 BPE 分词器
真正的实现。我们在原始字节上训练(如 GPT-2),计算对,合并最频繁的,并按顺序记录每次合并。合并表就是分词器。
训练循环是 BPE 的核心:计算对,合并赢家,重复。每次合并减少总 token 数。经过 num_merges 轮后,词汇表从 256(基础字节)增长到 256 + num_merges。
编码按学习的精确顺序应用合并。这很重要。如果合并 1 创建了 “th”,合并 5 创建了 “the”,编码必须先应用合并 1,这样 “the” 才能在合并 5 中从 “th” + “e” 形成。
解码是反向的:在词汇表中查找每个 token ID,连接字节,解码为 UTF-8。
步骤 3:编码和解码往返
压缩比告诉你分词器有多有效。比率 0.50 意味着分词器将文本压缩到原始字节一半的 token 数。越低越好。
步骤 4:与 tiktoken 比较
tiktoken 使用完全相同的算法,但在数百 GB 文本上用 100,000 次合并训练。算法相同。区别在于训练数据和合并次数。
步骤 5:词汇表分析
这揭示了词汇表中的 Zipf 分布。少数 token 占主导(空格、”the”、”e”)。大多数 token 很少使用。生产分词器针对此分布优化——常见模式获得短 token ID,罕见模式获得更长的表示。
使用
你从零构建的 BPE 有效了。现在看看生产工具是什么样的。
tiktoken(OpenAI)
tiktoken 用 Rust 编写,带 Python 绑定。它每秒编码数百万 token。相同的 BPE 算法,工业级实现。
Hugging Face tokenizers
Hugging Face tokenizers 库底层也是 Rust。它在秒级在 GB 规模语料库上训练 BPE。这是训练自己模型时使用的。
加载 Llama 的分词器
Llama 3 的 128K 词汇表比 GPT-2 的 50K 词汇表显著更好地压缩非英语文本。你可以自己验证——用多种语言编码同一句子并计算 token。
交付
本课产出 outputs/prompt-tokenizer-analyzer.md——一个可复用的 prompt,分析任何文本和模型组合的分词效率。给它一个文本样本,它会告诉你哪个模型的分词器处理得最好。
练习
修改 BPE 分词器以在每个合并步骤打印词汇表。观察 “t” + “h” 如何变成 “th”,然后 “th” + “e” 如何变成 “the”。跟踪常见英语词如何逐片段组装。
向 BPE 分词器添加特殊 token(
<pad>、<eos>、<unk>)。将它们分配为 ID 0、1、2 并相应移动所有其他 token。实现运行 BPE 之前按空格分割的预分词步骤。实现 WordPiece 合并标准(似然比而非频率)。在相同语料库上用相同合并次数训练 BPE 和 WordPiece。比较生成的词汇表——哪个产生更多语言学上有意义的子词?
构建多语言分词器效率基准。取 10 个英语、西班牙语、中文、韩语和阿拉伯语句子。用 tiktoken (cl100k_base) 分词每个并测量每字符平均 token 数。量化每种语言的”多语言税”。
在更大的语料库上训练你的 BPE 分词器(下载一篇维基百科文章)。调整合并次数以在该文本上实现与 tiktoken 10% 以内的压缩比。
关键术语
| 术语 | 人们怎么说 | 实际含义 |
|---|---|---|
| Token | 一个词 | 模型词汇表中的一个单位——可以是字符、子词、词或多词块 |
| BPE | 某种压缩东西 | 字节对编码——迭代合并最频繁的相邻 token 对直到达到目标词汇表大小 |
| WordPiece | BERT 的分词器 | 类似 BPE 但合并最大化似然比 count(AB)/(count(A)*count(B)) 而非原始频率 |
| SentencePiece | 一个分词器库 | 与语言无关的分词器,在原始 Unicode 上操作无需预分词,支持 BPE 和 Unigram 算法 |
| 词汇表大小 | 它知道多少词 | 唯一 token 总数:GPT-2 有 50,257,BERT 有 30,522,Llama 3 有 128,256 |
| Fertility | 不是分词器术语 | 每个词的平均 token 数——跨语言衡量分词器效率(1.0 是完美的,3.0 意味着模型工作量是三倍) |
| 字节级 BPE | GPT 的分词器 | 在原始字节(0-255)而非 Unicode 字符上操作的 BPE,保证任何输入都没有未知 token |
| 合并表 | 分词器文件 | 训练期间学习的配对合并的有序列表——这就是分词器,顺序很重要 |
| 预分词 | 按空格分割 | 子词分词之前应用的规则:空格分割、数字分割、标点处理 |
| 压缩比 | 分词器有多高效 | 产生的 token 数除以输入字节——越低意味着越好的压缩和越快的推理 |
延伸阅读
- Sennrich et al., 2016 – “Neural Machine Translation of Rare Words with Subword Units” – 将 BPE 引入 NLP 的论文,将 1994 年的压缩算法变成现代分词的基础
- Kudo & Richardson, 2018 – “SentencePiece: A simple and language independent subword tokenizer” – 与语言无关的分词,使多语言模型变得实用
- OpenAI tiktoken repository – Rust 中的生产 BPE 实现,带 Python 绑定,被 GPT-3.5/4/4o 使用
- Hugging Face Tokenizers documentation – 具有 Rust 性能的生产级分词器训练
📚 本文改编自 AI Engineering from Scratch(MIT License · 作者 Rohit Ghumare),中文内容来自官方中文镜像。原课程共 503 课 · 20 阶段 · 免费开源,教程网站见 aiengineeringfromscratch.com。
📝 自我检查(课程配套测验)
Q1(学前) 分词器在 LLM 流水线中的主要作用是什么?
A. 从文本中移除停用词
B. 将文本转换为模型可处理的整数序列
C. 在语言之间翻译文本
D. 压缩文本以便存储
答案: B 解析: LLM 处理的是数字而非文本。分词器将每个字符、词和符号转换为固定词表中的整数 ID。这一转换并非中性——它决定了模型如何「看见」语言。
Q2(学前) BPE(字节对编码)如何构建词表?
A. 仅将文本拆成单个字符
B. 迭代合并最频繁的相邻 token 对,直到达到目标词表大小
C. 用词典查找整词
D. 随机为子串分配 ID
答案: B 解析: BPE 从单个字节/字符开始,反复合并最常见的相邻对。’th’ + ‘e’ 变成 ‘the’。数千次合并后,常见词成为单个 token,罕见词被拆成子词片段。
Q3(学后) 为什么词表大小在 LLM 设计中存在权衡?
A. 更大的词表总是表现更好
B. 太小会导致序列变长(计算更多);太大会在罕见 token 上浪费嵌入参数
C. 词表大小不影响模型性能
D. 更小的词表总是更高效
答案: B 解析: 小词表(如字符级)意味着每个词对应很多 token,增加序列长度和计算。大词表在训练数据中很少出现的 token 上浪费参数。大多数 LLM 使用 32K–100K token。
Q4(学后) 字节级回退在分词中解决什么问题?
A. 加快分词速度
B. 确保任何输入(emoji、罕见文字、二进制数据)都能编码,无需「未知」token
C. 减小词表大小
D. 提高模型准确率
答案: B 解析: 有了字节级回退,分词器可对词表外的任何字符回退到原始字节值(256 种可能)。这保证完全覆盖——没有任何输入会是「未知」。
Q5(学后) 分词器如何影响 LLM 的非英语语言性能?
A. 分词器对所有语言同样有效
B. 训练数据中代表性不足的语言获得更差的 token 合并,每词需要更多 token,浪费上下文窗口
C. 非英语文本总是按字符分词
D. 分词不影响语言性能
答案: B 解析: BPE 合并从训练数据学习。若日语文本占语料 5%,日语字符获得更少合并,每词比英语需要 2–5 倍 token,实际上缩小了非英语文本的上下文窗口。
- 标题: 【大模型】- 分词器:BPE、WordPiece、SentencePiece
- 作者: 宋
- 创建于 : 2026-08-19 09:01:00
- 更新于 : 2026-08-21 16:20:12
- 链接: https://sxl-space.tk/2026/08/19/010_LLM/010_LLM-01-Tokenizers/
- 版权声明: 版权所有 © 宋,禁止转载。