1.2 分词 Tokenization
字 / 词 / 子词三条路线;BPE、WordPiece、SentencePiece 的差异;中文分词特殊性;tokenizer 选择如何直接影响 LLM 行为
为什么 Tokenizer 比你想象的更重要
几乎每个 LLM 用户都遇到过这些"玄学":为什么 GPT-4 算不对 9.11 > 9.8?为什么同样的中文长文本喂给 Qwen 和 Llama,上下文长度差一倍?为什么 ChatGPT 的 API 按 token 计费,一段中文要比同等信息量的英文贵两三倍?
全部都是 tokenizer 的锅。Tokenizer 是"原始文本"与"模型看到的 ID 序列"之间的翻译层,它决定了模型能在多少空间里表达一个句子、能否把一个数字作为整体处理、能否正确切分一个罕见术语的边界。理解 tokenizer 不是"实现细节",它直接关系到 LLM 行为的每一个可观测指标。
三条分词路线
字级(Character-level)
把每个字符作为一个 token。
- 优点:词表小(中文约 6000-7000 常用字,英文 26 个字母),无 OOV 问题
- 缺点:序列极长,模型需要学习更多层次才能从字符组合出语义
- 典型应用:字符级 RNN、部分早期中文模型
词级(Word-level)
按空格或分词工具切成完整单词。
- 优点:单位符合人的直觉,序列短
- 缺点:词表极大(英文 100k+,中文分词后 500k+),长尾词无法覆盖,OOV 一律变成
<UNK> - 典型应用:早期 Word2Vec、经典 NLP 管道
子词级(Subword-level)
在字和词之间折中:常用词作为整体,罕见词拆成更小的有意义片段。这是所有现代 LLM 的默认选择。
三种子词算法
BPE(Byte-Pair Encoding)
BPE 由 Sennrich 等(2016)引入 NLP,核心思想:从字级开始,贪心合并出现频率最高的相邻字符对,直到达到预设词表大小。
初始化
把所有单词拆成字符序列,每个字符是一个基础 token。
统计相邻对
扫描语料,统计所有相邻字符对的出现次数。
合并最频繁对
把出现最多的相邻对合并为一个新 token,加入词表。
重复到目标词表大小
典型设置:30k-100k 个子词 token。
BPE 的变种 byte-level BPE(GPT-2 起广泛使用)以字节而非 Unicode 字符为基础单位,词表里永远不需要未知字符——任何字符串都能被拆成字节序列后再分词。
WordPiece(BERT 使用)
WordPiece 与 BPE 非常相似,区别在合并准则:BPE 合并"出现次数最多的对",WordPiece 合并"使语料似然提升最大的对"。
WordPiece 用 ## 前缀标记"这是一个单词的中间或末尾片段":
playing → play ##ingSentencePiece(T5、Llama、Qwen 使用)
SentencePiece 与前两者最大的区别:它把空格也当作普通字符。这意味着它不需要前置分词(pre-tokenization),能直接对任何语言的原始字节流训练——对中文、日语、泰语这些没有空格分隔的语言特别友好。
SentencePiece 内部可以用 BPE 或 Unigram LM 作为算法。Unigram LM 的做法反过来:先构建超大词表,再逐步删除对整体似然贡献最小的 token。
| 算法 | 合并准则 | 代表模型 | 空格处理 |
|---|---|---|---|
| BPE | 最频繁的相邻对 | GPT-2/3/4、RoBERTa | 需要前置分词(英文按空格) |
| WordPiece | 最大化语料似然的对 | BERT、DistilBERT | 需要前置分词,用 ## 标记续接 |
| SentencePiece (BPE) | 把空格作普通字符 | T5、Llama、Qwen、Mistral | 原始字节流直接训练,无需前置分词 |
| SentencePiece (Unigram) | 删除对似然贡献最小的 token | XLNet、ALBERT、mBART | 同上 |
中文分词的特殊性
中文没有天然的词边界空格,这让分词成为一个独立的 NLP 任务。传统中文分词工具:
- jieba:最流行的 Python 中文分词库,基于前缀词典 + HMM
- pkuseg:北大团队开发,领域自适应性强(新闻、医学、旅游等多域模型)
- THULAC / HanLP / LAC:清华、百度等出品的替代方案
import jieba
text = "自然语言处理是人工智能的核心领域"
# 经典分词输出
print(list(jieba.cut(text)))
# ['自然语言', '处理', '是', '人工智能', '的', '核心', '领域']但现代中文 LLM 并不使用 jieba。Qwen、Yi、DeepSeek、ChatGLM 等全部采用 SentencePiece BPE 直接在字节流上训练,原因有三:
- 端到端训练:tokenizer 和模型联合优化,跳过中间错误传递
- 多语言统一:一个 tokenizer 能同时处理中英日韩,无需切换分词器
- 罕见词/术语更稳健:jieba 对新词、专业术语依赖词典,BPE 能自动拆成可组合的子词
Tokenizer 对 LLM 行为的影响
压缩率与上下文长度
同一段中文在不同 tokenizer 下的 token 数差异巨大:
| Tokenizer | "自然语言处理是人工智能的核心领域" | token 数 |
|---|---|---|
| GPT-2 原版(英文优化) | 按字节拆,几乎 1 字 ≈ 2-3 token | ~30-40 |
| GPT-4 (cl100k_base) | 中文有较多整词 | ~15-20 |
| Qwen3 tokenizer | 对中文高度优化 | ~10-12 |
这直接意味着:同样的 32k 上下文窗口,Qwen3 能塞进的中文内容量可以是早期 tokenizer 的 3 倍。也直接意味着 API 账单上的 token 费用差异。
数字处理的坑
考虑 GPT-3.5 对数字 9.11 vs 9.8 的比较。许多 tokenizer 把 9.11 拆成 9, ., 11 三个 token,把 9.8 拆成 9, ., 8——在模型看来后者的第三个 token 是 8,前者是 11,从数值大小到 token 排序的对应关系彻底断了。Llama 3、Qwen3 等新一代模型为数学专门调整 tokenizer,将每个数字单独作为 token,显著改善算术能力。
语言不公平
大多数开源 tokenizer 以英文语料为主训练,造成低资源语言被严重"罚款":
- 一条英文句子可能是 20 tokens
- 同等信息量的印地语可能要 60 tokens,斯瓦希里语可能要 80 tokens
这不仅是成本问题,也让低资源语言在有限上下文中能承载的信息量更少,是 LLM 公平性的重要议题。
实践建议:在为特定领域(法律、医学、中文古籍)做 LLM 应用前,先用 tokenizer.tokenize(your_text) 看看你的领域文本被拆成了什么形状。如果关键术语被拆得稀碎,模型大概率在这类术语上表现不佳——此时考虑继续预训练(continued pre-training)时扩词表或改用领域专用 tokenizer。
实操:查看 tokenizer 的切分行为
from transformers import AutoTokenizer
qwen_tok = AutoTokenizer.from_pretrained("Qwen/Qwen3-1.7B")
llama_tok = AutoTokenizer.from_pretrained("meta-llama/Llama-3.1-8B")
text = "北京大学软件与微电子学院位于大兴区"
for name, tok in [("Qwen3", qwen_tok), ("Llama-3", llama_tok)]:
ids = tok.encode(text)
pieces = tok.convert_ids_to_tokens(ids)
print(f"[{name}] token 数 = {len(ids)}")
print(f" pieces: {pieces}")你会清楚看到不同模型家族对同样中文的切分差异——这往往比阅读 10 篇 tokenizer 论文更直观。
本节小结
| 概念 | 要点 |
|---|---|
| 字级 / 词级 / 子词 | 现代 LLM 几乎全部使用子词:词表可控、无 OOV |
| BPE | 贪心合并最频繁的相邻对;GPT 系列使用 |
| WordPiece | 以语料似然最大化为准则合并;BERT 系列使用 |
| SentencePiece | 把空格当普通字符,直接处理字节流;Llama / Qwen / T5 使用 |
| 中文场景 | jieba 等工具仍用于传统 pipeline;现代 LLM 直接 SentencePiece |
| Tokenizer 影响 | 上下文压缩率、数字处理、语言公平性——不是实现细节 |
| 查看切分 | tokenizer.tokenize(text) 是诊断 LLM 异常的第一把刀 |