分词与词汇表(Tokenization)¶
分词(Tokenization)是 LLM 处理文本的第一步——将人类写的文字切分成模型能理解的最小单元(Token),并映射为数字 ID。分词策略的好坏直接影响模型的理解能力和计算效率。
为什么需要分词?¶
神经网络只能处理数字,不能直接看懂"你好世界"这样的文字。我们需要一套规则,把文字变成数字序列:
这个"规则"就是分词器(Tokenizer),而"表"就是词汇表(Vocabulary)。
三种分词粒度¶
1. 词级别(Word-Level)¶
最直觉的方式——按空格或标点切分。
问题
- 词汇表爆炸:英语有几十万个单词,还有各种变形(loves, loved, loving...),词汇表极其庞大
- OOV 问题:遇到训练时没见过的词(Out-of-Vocabulary),模型完全不认识,只能标记为
<UNK> - 中文灾难:中文没有天然的空格分隔,需要额外的分词工具,且效果不稳定
2. 字符级别(Character-Level)¶
把每个字符(字母/汉字)当作一个 Token。
优点
词汇表极小(英语只有 26 个字母 + 标点),永远不会遇到 OOV 问题。
问题
- 序列太长:一个单词就要拆成好几个 Token,计算成本暴增
- 语义丢失:单个字符几乎没有语义,模型需要耗费大量算力去"拼"出单词的意思
3. 子词级别(Subword-Level) ⭐ 主流方案¶
核心思想: 既不按完整的词切分,也不拆到字符级别,而是找到一种介于两者之间的最优切分方式。
- 高频词保留完整(如 "the", "is")
- 低频词拆分为有意义的子词片段(如 "un" + "happy" + "ness")
为什么子词法最好?
- 词汇表可控:通常 3 万~10 万个 Token 就够了
- 无 OOV:任何生僻词都能拆成已知的子词组合
- 保留语义:子词本身有意义("un" 表示否定,"ing" 表示进行时)
主流子词分词算法¶
BPE(Byte Pair Encoding)¶
BPE 是目前最主流的分词算法,被 GPT 系列、LLaMA、Qwen 等模型广泛使用。
核心思想¶
从最小的字符出发,反复合并出现频率最高的相邻字符对,逐步构建词汇表。
训练过程(构建词汇表)¶
假设我们的训练语料只有三个词及其出现频率:
第 0 步:初始化
将每个词拆到字符级别,初始词汇表为所有出现过的字符:
第 1 步:统计所有相邻字符对的频率
合并 频率最高的 (u, g) → 新 Token ug:
第 2 步:继续统计
合并 (b, ug) → 新 Token bug:
如此反复,直到词汇表达到预设大小(如 32000 个 Token)。
分词过程(使用词汇表)¶
给定一个新词,按照训练时的合并顺序依次尝试合并:
WordPiece¶
WordPiece 被 BERT 使用,思路和 BPE 类似,但合并策略不同。
| 对比 | BPE | WordPiece |
|---|---|---|
| 合并依据 | 选频率最高的字符对 | 选使语言模型似然提升最大的字符对 |
| 标记方式 | 直接合并 | 非首子词用 ## 前缀标记 |
WordPiece 的分词示例:
## 前缀表示"这个子词不是一个新词的开头,而是前一个词的延续"。
SentencePiece¶
SentencePiece 不是一种新的分词算法,而是一个与语言无关的分词工具包。
关键创新
SentencePiece 将空格也视为普通字符(用 ▁ 表示),而不是把空格当作天然的分词边界。这使得它对任何语言(包括中文、日文等没有空格的语言)都能统一处理。
SentencePiece 内部支持 BPE 和 Unigram 两种算法,被 LLaMA、T5 等模型广泛使用。
Byte-Level BPE:现代 LLM 的标配¶
GPT-2 起,OpenAI 引入了 Byte-Level BPE,它将 BPE 的基础单位从字符进一步下沉到字节(Byte)。
- 任何文本(无论什么语言、什么符号)本质上都是字节序列(0~255)
- 基础词汇表只有 256 个字节,完全无需预处理
- 通过 BPE 合并,从字节逐步学到子词、词、甚至短语
优势
- 真正的零 OOV:任何 Unicode 字符都能分解为字节
- 语言无关:中英文、Emoji、代码、数学公式……全部统一处理
- 无需预分词:不依赖空格或任何语言特定规则
GPT-3/4、LLaMA 2/3 等现代主流模型均采用这种方案。
特殊 Token¶
分词器除了普通的文本 Token 外,还会定义一些特殊 Token,用于标记结构性信息:
| 特殊 Token | 含义 | 用途 |
|---|---|---|
<BOS> / <s> |
序列开始(Beginning of Sequence) | 告诉模型"从这里开始" |
<EOS> / </s> |
序列结束(End of Sequence) | 告诉模型"到这里结束,停止生成" |
<PAD> |
填充(Padding) | 将不等长的序列补齐到相同长度 |
<UNK> |
未知词(Unknown) | 极少使用(子词法几乎不会 OOV) |
<|im_start|> |
对话角色开始 | 用于区分 system / user / assistant |
词汇表大小的权衡¶
词汇表大小是一个关键的设计选择:
| 词汇表小(如 8K) | 词汇表大(如 128K) | |
|---|---|---|
| 序列长度 | 更长(同一句话拆的 Token 更多) | 更短(更紧凑) |
| Embedding 参数 | 更少 | 更多 |
| 语义粒度 | 更细 | 更粗(可能一整个词就是一个 Token) |
| 罕见词处理 | 拆得更碎,但都认识 | 可能整个保留 |
现代 LLM 的词汇表大小通常在 32K ~ 150K 之间:
| 模型 | 词汇表大小 | 分词算法 |
|---|---|---|
| GPT-2 | 50,257 | Byte-Level BPE |
| GPT-4 | ~100,000 | Byte-Level BPE (cl100k) |
| LLaMA 2 | 32,000 | SentencePiece (BPE) |
| LLaMA 3 | 128,256 | Byte-Level BPE (tiktoken) |
| Qwen 2 | 151,643 | Byte-Level BPE |
为什么中文模型偏好大词汇表?
中文字符多、词组丰富。如果词汇表太小,一个中文词可能被拆成 3-4 个 Token,既浪费上下文窗口长度,也增加了推理成本。扩大词汇表让常见中文词能作为独立 Token 存在,显著提升中文处理效率。