分词与 Tokenization
问题
LLM 是如何处理文本的?BPE 算法的原理是什么?词表大小对模型有什么影响?
答案
LLM 不直接处理原始文本,而是将文本分割成 Token(最小处理单元),再映射为数字 ID。这个过程叫 Tokenization(分词)。
一、为什么需要 Tokenization
文本不能直接输入模型——需要数值化。但如何切分是关键:
| 方式 | 示例:"ChatGPT is amazing" | 问题 |
|---|---|---|
| 按字符切 | C h a t G P T i s ... | 序列太长,失去语义 |
| 按单词切 | ChatGPT is amazing | 词表爆炸,无法处理新词 |
| 子词切分 | Chat GPT ▁is ▁amazing | ✅ 平衡词表大小和语义 |
子词 Tokenization(Subword Tokenization)是现代 LLM 的标准方案。
二、BPE 算法(Byte-Pair Encoding)
BPE 是最主流的子词分词算法,GPT 系列使用的基础方法。
核心思路
从单个字符开始,不断合并最频繁出现的相邻 Token 对,直到词表达到目标大小:
训练语料: "low lower lowest"
初始词表: ['l', 'o', 'w', 'e', 'r', 's', 't', ' ']
第 1 轮: 'l' + 'o' 出现最多 → 合并为 'lo'
第 2 轮: 'lo' + 'w' 出现最多 → 合并为 'low'
第 3 轮: 'e' + 'r' → 合并为 'er'
第 4 轮: 'e' + 's' → 合并为 'es'
...
最终词表: ['low', 'er', 'es', 't', ...]
直觉理解
BPE 自动学习语言的"零件"——高频词保留完整(如 the),低频词被拆成常见子词(如 un + believ + able),完全未知的词也能用字节表示。
三、主要 Tokenizer 方案
| 方案 | 算法 | 特点 | 使用模型 |
|---|---|---|---|
| GPT-2 BPE | Byte-level BPE | 从字节(256)开始合并 | GPT-2/3 |
| tiktoken | 改进的 BPE | 高效 Rust 实现 | GPT-4、Claude |
| SentencePiece | BPE / Unigram | 语言无关、支持中文 | LLaMA、Qwen |
| WordPiece | 类似 BPE(最大似然合并) | Google 原生 | BERT、T5 |
Byte-level BPE vs 传统 BPE
- 传统 BPE:从字符级别开始合并,遇到未知字符(如 emoji)会出
[UNK] - Byte-level BPE:从 256 个 字节 开始合并,任何字符都能用字节组合表示——永远不会出现
[UNK]
四、词表大小的影响
| 词表大小 | 优点 | 缺点 |
|---|---|---|
| 小(如 32K) | Embedding 参数少、LM Head 小 | 同样的文本需要更多 Token |
| 大(如 128K+) | 每个 Token 承载更多信息、序列更短 | Embedding 矩阵大、存在稀疏 Token |
| 模型 | 词表大小 |
|---|---|
| GPT-2 | 50,257 |
| LLaMA 1 | 32,000 |
| LLaMA 3 | 128,256 |
| GPT-4 (cl100k) | 100,256 |
| Qwen 2 | 151,643 |
中文 Token 效率
GPT-2 的词表以英文为主,一个中文字可能被编码为 2-3 个 Token,中文成本是英文的 2-3 倍。LLaMA 3、Qwen 等专门扩大了中文 Token 比例,大幅提升了中文效率。
五、特殊 Token
| Token | 用途 | 示例 |
|---|---|---|
<bos> | 序列开始 | Beginning of Sequence |
<eos> | 序列结束 | End of Sequence |
<pad> | 填充到相同长度 | Padding |
<|im_start|> | 角色/轮次开始 | ChatML 格式 |
<|tool_call|> | 工具调用标记 | Function Calling |
六、Tokenization 对 LLM 的影响
1. 数学能力
"12345" 可能被分为 "123" + "45" 或 "1" + "2345"——LLM 无法"看到"单个数字,这是算术能力弱的原因之一。
2. 代码生成
代码中的缩进、括号等被分成不同 Token,好的 Tokenizer 应该将常见代码模式(如 def 、return )作为完整 Token。
3. 多语言支持
词表中某语言的 Token 越多,该语言的处理效率越高、成本越低。
七、实际使用
# 使用 tiktoken(GPT-4 的 tokenizer)
import tiktoken
enc = tiktoken.encoding_for_model("gpt-4")
text = "大语言模型的分词很重要"
tokens = enc.encode(text)
print(f"Token 数量: {len(tokens)}") # 约 7-8 个 Token
print(f"Token IDs: {tokens}")
print(f"解码: {[enc.decode([t]) for t in tokens]}")
# 使用 transformers(LLaMA 的 tokenizer)
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-3-8B")
tokens = tokenizer.encode("Hello, world!")
print(tokenizer.convert_ids_to_tokens(tokens))
常见面试问题
Q1: BPE 和 WordPiece 的区别是什么?
答案:
- BPE:合并最高频的相邻 Token 对(按计数)
- WordPiece:合并使语言模型似然度提升最大的 Token 对(按概率)
- 实践中差异不大,BPE 更简单直观,是当前主流
Q2: 为什么 LLaMA 3 将词表从 32K 扩大到 128K?
答案:
- 多语言效率:32K 词表以英文为主,中文等语言 Token 效率低。128K 词表大幅增加了多语言 Token,同样的中文需要的 Token 数减少 ~50%
- 更短的序列:同样内容用更少 Token 表示,等效于增加了上下文窗口
- 更低的推理成本:Token 越少,KV Cache 越小,推理越快
- 代价:Embedding 矩阵从 131M 增长到 525M 参数
Q3: Tokenization 如何影响模型性能?
答案:
- 数学:数字被不一致地切分,导致算术能力弱
- 拼写:单词被拆成子词后,模型难以"看到"完整拼写
- 提示注入:特殊 Token(如
<|im_start|>)如果被注入,可能绕过安全机制 - 多语言公平性:词表偏向某些语言,导致其他语言效率低、成本高
Q4: 什么是 Token 数与 API 成本的关系?
答案: LLM API 按 Token 数量计费(如 GPT-4o:输入 10/M tokens)。因此:
- 同样的中文文本,用 GPT-2 tokenizer 比 Qwen tokenizer 贵 2-3 倍
- 系统提示(System Prompt)越长,每次请求的固定成本越高
- Prompt 工程 的一个目标就是用更少 Token 达到更好效果