Tokenizer 设计与训练:BPE、多语言、多模态 Token¶
更新日期:2026-04-14
本文目标:能设计和训练一个适合目标模型的 tokenizer,理解 vocab size / 多语言 / 多模态 token 的权衡。
一、为什么 Tokenizer 如此重要¶
| 影响维度 | 说明 | 为什么 |
|---|---|---|
| 训练效率 | Token 数 = 实际训练量。同样的文本,好的 tokenizer 用更少 token 表示 → 等效训练更多"内容" | 训练 cost 正比于 token 数 × 步数。压缩率高的 tokenizer 让模型在固定 FLOPs 预算内看到更多语义内容,等价于用更少算力获得更好的 loss |
| 推理成本 | 推理按 token 计费。token 效率高意味着同等语义输出所需的 autoregressive decoding 步骤更少 → 延迟和 API 费用同步下降 | KV-cache 占用与序列长度线性相关,更少的 token 直接减少 GPU 显存占用和每次 attention 的计算量 |
| 多语言公平性 | 中文/日文等 1 个字可能需要 2-4 个 token,英文 1 个词通常 1-2 个 → 中文用户付更多钱 | BPE 是频率驱动的:英文占训练语料主体时,高频英文 n-gram 优先合并成完整 token,而中日韩字符因频率低被碎片化,导致 fertility 不对等 |
| 模型能力 | Tokenizer 的粒度影响模型对字符/字节的理解 | 粒度过粗(整词 token)会让模型无法做字符级推理(如拼写纠错、回文判断);粒度过细(byte-level)会拉长序列使 self-attention 成本暴增。需在语义粒度和序列长度间取平衡 |
| 模型大小 | vocab_size × d_model = embedding 参数量。vocab 越大,embedding layer 和 output projection 矩阵越大 | 以 LLaMA-3 为例:128K × 4096 ≈ 5.2 亿参数仅用于 embedding,占 8B 模型总参数的 ~6.5%。vocab 翻倍则该开销翻倍,且 softmax 归一化的计算量也线性增长 |
二、Tokenizer 算法对比¶
2.1 BPE 训练过程¶
def train_bpe(corpus, vocab_size):
# 初始 vocab: 所有单字符 (或 bytes)
vocab = set(all_chars_in_corpus)
# 将文本表示为字符序列
# "hello" → ['h', 'e', 'l', 'l', 'o']
sequences = [list(word) for word in corpus]
while len(vocab) < vocab_size:
# 统计所有相邻 pair 的频率
pair_counts = Counter()
for seq in sequences:
for i in range(len(seq) - 1):
pair_counts[(seq[i], seq[i+1])] += 1
# 找最频繁的 pair
best_pair = max(pair_counts, key=pair_counts.get)
# 例如: ('l', 'l') 出现最多
# 合并: 创建新 token
new_token = best_pair[0] + best_pair[1] # 'll'
vocab.add(new_token)
# 更新所有序列: 将 pair 替换为新 token
for seq in sequences:
merge_pair(seq, best_pair, new_token)
# ['h', 'e', 'l', 'l', 'o'] → ['h', 'e', 'll', 'o']
return vocab, merge_rules # merge_rules 记录合并顺序
2.2 Byte-level BPE¶
标准 BPE 的问题在于遇到未知字符会产生 OOV (Out-of-Vocabulary)。Byte-level BPE 以 256 个 byte 为基础 vocab,任何 UTF-8 文本都能用 byte 序列表示,因此永远不会 OOV。
示例:"你好" → UTF-8 bytes: [0xe4, 0xbd, 0xa0, 0xe5, 0xa5, 0xbd]。经 BPE 合并后,如果训练语料有足够中文,可能变成 ['你', '好'] 两个 token。
SentencePiece 提供 byte_fallback=True 选项:优先使用学到的 subword,遇到未知字符时回退到 byte 表示。
三、Vocab Size 选择¶
3.1 Vocab Size 的权衡¶
3.2 经验法则¶
Vocab Size 建议: - 纯英文: 32K-64K 够用 - 中英双语: 64K-128K - 全球多语言: 128K-200K - 代码能力需求高: 额外加 token 给代码关键词
判断方法: Token Fertility (每个词需要多少 token) - 英文: 好的 tokenizer fertility ≈ 1.2-1.5 - 中文: 好的 tokenizer fertility ≈ 1.5-2.0 (每个字 1-2 token) - 差的 tokenizer: 中文 fertility > 3 (每个字 3+ token) → 不公平
四、多语言 Token 分配¶
4.1 核心问题¶
BPE 训练是频率驱动的:高频对先合并。如果训练语料英文占 80%,那么大部分 vocab 会被分配给英文 subword → 中文等语言 token 效率极差。
4.2 解决方案¶
# 训练多语言 tokenizer 的最佳实践
# 1. 从各语言采样相同量的文本 (不是按原始比例!)
import sentencepiece as spm
spm.SentencePieceTrainer.train(
input='balanced_multilingual_corpus.txt', # 各语言等量采样
model_prefix='multilingual_tokenizer',
vocab_size=128000,
model_type='bpe',
byte_fallback=True,
character_coverage=0.9999, # 高覆盖率
split_by_unicode_script=True,
# 关键: input_sentence_size 控制训练用的句子数
input_sentence_size=10_000_000,
shuffle_input_sentence=True,
)
# 2. 验证 token fertility
for lang in ['en', 'zh', 'ja', 'ko', 'de', 'fr']:
text = load_test_text(lang, 1000_sentences)
tokens = tokenizer.encode(text)
words = count_words(text, lang)
fertility = len(tokens) / words
print(f'{lang}: fertility = {fertility:.2f}')
# 好的 tokenizer: 各语言 fertility 差距不超过 2x
五、多模态 Token¶
5.1 图像 Token¶
5.2 统一 Tokenizer(前沿方向)¶
传统方案:文本和图像使用不同的 tokenizer——文本走 BPE 得到 token ID,图像走 ViT 得到连续特征向量。
统一方向:所有模态共用一个离散 tokenizer。代表工作是 Meta 的 Chameleon: 挑战:图像的 codebook 需要足够大才能保留细节。典型配置:8192 个 visual codes + 128K text vocab。
六、Tokenizer 对训练的影响¶
| 影响 | 详细 | 为什么 |
|---|---|---|
| 训练步数 | 相同文本,token 数不同 → 实际看到的"内容量"不同 | 以 "Chinchilla 定律" 为例,最优训练需要 token 数与参数量匹配。token 效率高的 tokenizer 让模型在固定步数内看到更多语义内容,等价于增加训练数据量 |
| 数学能力 | 数字是否逐字符 tokenize 影响数学推理 | 若 "137" 被分为 ["1", "37"] 或 ["13", "7"],模型难以学习位值对齐和进位规则。逐字符 tokenize("1","3","7")让模型可以学到每一位的独立含义,但序列变长;一些模型特意为数字添加逐字符规则 |
| 代码能力 | 缩进是否保留、关键词是否完整 tokenize 影响代码生成 | Python 对缩进敏感:若 4 空格被 tokenize 为不同组合(如 " "+" " vs " "),模型难以学到一致的缩进模式。完整保留缩进 token 和将 def/class 等关键词作为单独 token 可提升代码生成的语法正确率 |
| 中文能力 | fertility 高 → 中文在训练中的有效权重低 | 若中文 fertility=3(每字 3 token),则同等长度的中文文本占 3 倍 token 预算。在固定上下文窗口下,模型能看到的中文语义内容仅为英文的 ⅓,导致中文理解和生成能力系统性偏弱 |
| 训练速度 | vocab 大 → embedding lookup 和 LM head softmax 的计算量随 vocab size 线性增长 | LM head 的矩阵乘法为 [batch×seq, d_model] × [d_model, V],V 从 32K→128K 使该层 FLOPs 增加 4 倍。实践中通过 tied embedding(共享 input/output 权重)和 softmax 近似(如 adaptive softmax)缓解 |
七、追问延伸¶
| 问题 | 方向 | 为什么 |
|---|---|---|
| LLaMA-3 为什么从 32K 扩到 128K vocab? | 提升多语言效率,尤其代码和非拉丁语言 | Meta 的实验数据显示 32K vocab 下中文 fertility ~3.5,扩展到 128K 后降至 ~1.5。同时代码 token(如 function、return、import)成为完整 token,代码 benchmark(HumanEval)提升约 5%。额外 embedding 参数开销(约 4 亿)对 70B+ 模型影响可忽略 |
| Tokenizer 训练需要多少数据? | 通常 100M-1B 个句子的采样子集就够 | BPE 合并只依赖 n-gram 频率统计,100M 句子已足够让高频模式的频率估计收敛(更多数据边际收益递减)。关键不是数据总量而是语言分布的均衡性——10M 句均匀采样的多语言数据好于 1B 句英文数据 |
| 训练完后能修改 tokenizer 吗? | 可以扩展(加新 token),但不建议修改已有 token 的 mapping | 已训练好的模型的 embedding 和 LM head 权重与 token ID 绑定。修改已有 token 的 ID mapping 会使对应权重失效。扩展(append)新 token 是安全的:新 token 的 embedding 可用已有 subword 的均值初始化,再通过少量 continued pretraining 学习 |
| BPE vs Unigram 哪个好? | BPE 更主流,Unigram 在多语言上有优势但使用较少 | BPE 的确定性分词在工程上更简单(便于缓存、调试、复现),且多数大模型实证表明 BPE 和 Unigram 的下游性能差异小于 0.5%。Unigram 的优势在于 subword regularization 带来的鲁棒性,但实践中 BPE + dropout 也能实现类似效果 |
参考链接¶
↑ 上级 · B. 数据工程