分词与多语言处理

面向 NLP 研究生的深度教材 · 改编自 Stanford CS224N Winter 2026 Lecture 14 (Julie Kallini)
覆盖:分词理论 · BPE 算法详解 · SentencePiece / SuperBPE · 故障 Token · 多语言迁移 · 公平性 · 字节级前沿架构
版本 1.0 · 2026 春 · 配套阅读 Lab Wiki foundations/Tokenizer/frontiers/Multilingual/

引论:为什么"分词"是 LLM 一切奇怪行为的根源

"We will see that a lot of weird behaviors and problems of LLMs actually trace back to tokenization. … Why someone out there ideally finds a way to delete this stage entirely." — Andrej Karpathy

打开任意一篇关于大语言模型(LLM)的论文,你会读到 transformer、attention、scaling laws、RLHF… 唯独很少有人花一整章去讲分词(tokenization)——这个把人类文字喂进模型之前的"预处理步骤"。 然而正是这个看似平凡的步骤,决定了:

分词不是一个工程细节,而是语言模型的本体论——它定义了模型眼中"语言的原子"是什么。 原子选错了,下游一切都会跑偏。这门课要带你看到,tokenization 是 LLM 的隐形基石,也是隐形天花板

原始文本(人类可读 / 比特流) 编码 Tokenizer (词表 V + 合并规则) token IDs 嵌入层(|V| × d_model 参数) Transformer 主体 ⚠ 词表越大,参数越多 ⚠ 切法决定序列长度 ⚠ 不同语言 token 数差 15×
分词处在原始文本与模型之间的位置。词表大小决定嵌入层参数量;分词粒度决定序列长度;分词方案对语言公平性有决定性影响。
本教材读法建议
本文长度约 30000 字,建议分两次精读:第一次理解 Part 1–3 的核心算法(BPE);第二次在做 multilingual / cross-lingual 项目时再读 Part 6–8。 若你已熟悉 BPE,可直接跳到 Part 5(故障 Token)或 Part 8(字节级前沿)。

第一部分:分词的语言学与计算基础

1.1 什么是"词"?语言学的尴尬定义

看似简单的问题:"下面这句话有几个词?"

We'dsatbackonthegrass,andtimeflewbyaswelookedattheMilkyWay.
17 个空格分隔的词 + 2 个标点。但语言学上真的是这样吗?

我们可以"凭空格"数出 17 个词,但这种朴素切分会立刻撞墙:

为了把上述模糊性收紧,语言学家用词素(morpheme)这个更精细的概念:

定义:词素(morpheme)
词素是携带意义或语法功能的最小语言单位looked 包含两个词素:实义词素 look + 屈折词素 -ed(过去时)。

词素粒度也并非完美:sat(坐的过去式)显然包含 sit + 过去时的语义,但在拼写上无法机械拆出 -ed; 而 Milky Way 是一个整体专有名词,按词素切反而丢失语义。 这就是语言学家给我们的第一个教训:不存在一个"客观正确"的分词粒度

1.2 词、词素、字符、短语:分词粒度的连续谱

不同粒度的分词如同把同一句话用不同分辨率显微镜观察:

字符 W e l o o k e d a t |V| ≈ 100;序列极长;无 OOV 词素 We look -ed at 语言学上有意义,但要语言学家标注 We looked at |V| 巨大;OOV 严重;语义直观 短语 We looked at the Milky Way 语义完整;难以泛化新短语
分词粒度的连续谱。语言模型的核心问题是在覆盖率(vocabulary 是否能表示任意输入)与语义性(每个 token 是否对应有意义的单位)之间取得平衡。

当代主流 LLM(GPT、Llama、Gemini、Claude、DeepSeek)都没有选择上面任何一种原始方案,而是选择了一个数据驱动的折衷—— 子词(subword)分词,本教材的核心算法 BPE 即属于此。我们稍后会看到,子词的"token"通常并不对应任何语言学单位, 但凭借数据统计的力量,它意外地工作得非常好。

1.3 语言模型如何"看见"文本

要理解为什么 tokenization 如此关键,我们需要先把 LLM 的"视觉"过程完整还原一遍:

flowchart LR A["原始字符串
'We looked at the Milky Way.'"] --> B[Tokenizer
encode] B --> C["Token 序列
(We)(looked)(at)(the)(Milky)(Way)(.)"] C --> D["Token IDs
[2167, 10802, 540, 290, 10282, 5781, 0]"] D --> E[Embedding Lookup
E[id] ∈ ℝ^d] E --> F[嵌入向量序列
x_1, x_2, ..., x_n ∈ ℝ^d] F --> G[Transformer
主体] G --> H[输出隐状态
h_1, ..., h_n] H --> I[Un-embed
h · E^T 得 logits] I --> J[Argmax / Sample
下一个 token ID] J --> K[Tokenizer
decode] K --> L["新生成的字符串"]

注意两个隐藏但关键的事实:

  1. Tokenizer 是一段独立代码是 transformer 的一部分。它通常在预训练之前就被独立训练并冻结。
  2. Decode 是 encode 的逆过程,但因为分词的多对一映射,decode 时必须使用相同的 tokenizer。 若你用 Llama tokenizer encode、用 GPT tokenizer decode,结果将是乱码。
公式 1.1:语言模型的整体目标函数
$$ \mathcal{L}(\theta) = -\frac{1}{N}\sum_{n=1}^{N}\sum_{t=1}^{|x^{(n)}|}\log P_\theta\!\left(x_t^{(n)} \mid x_{<t}^{(n)}\right) $$ 其中 $x^{(n)} = \text{Tokenizer}(s^{(n)})$ 是第 $n$ 条文本 $s^{(n)}$ 经过分词后得到的 token 序列。损失被定义在 token 序列上,而非字符序列;这意味着分词方案本身就影响损失景观(loss landscape)

1.4 词表大小如何影响参数量与序列长度

词表大小 $|V|$ 是 tokenizer 最重要的超参数。它通过两个独立通道影响模型:

(a) 参数量通道

嵌入矩阵 $\mathbf{E} \in \mathbb{R}^{|V| \times d_\text{model}}$ 与输出投影 $\mathbf{W}_\text{out} \in \mathbb{R}^{d_\text{model} \times |V|}$(在很多模型中权重共享)直接随 $|V|$ 线性增长:

$$ \#\text{params}_\text{embed} = |V| \cdot d_\text{model} $$ 例如 Llama 4 的 $|V| = 201{,}000$、$d_\text{model} = 8192$,仅嵌入层就有 16 亿参数,对总参数量的占比已经接近 5%。

(b) 序列长度通道

更细的分词 = 同样文本变成更长的 token 序列。Self-attention 的复杂度是序列长度的平方:

$$ \mathcal{O}_\text{attn} = \mathcal{O}\!\left(n^2 \cdot d_\text{model}\right) $$ 若分词把序列长度增加 2×,attention 计算量增加 4×,KV cache 内存增加 2×。 对推理成本和上下文窗口利用率都是直接打击。

所以选择 $|V|$ 总是在权衡:更大的词表 → 更短的序列 + 更稀疏的语义 + 更多嵌入参数; 更小的词表 → 更密集的语义 + 更长的序列 + 更难训练的低频长尾

字符级 (|V|≈256) BPE (|V|≈50k) 词级 (|V|≈200k) t h e M i l k y W a y . Transformer n=14, slow the Milky Way . Transformer n=4, fast ✓ the Milky_Way . Transformer embed 巨大 三种分词把同一句话送入模型时的成本对比

第二部分:三大经典分词方案

2.1 词级分词:Zipf 定律与 OOV 的诅咒

最朴素的方案:按空格切,再加上标点处理。这有两个根本困难。

困难一:词表无上限增长

语料不同词数 (types)
Shakespeare 全集31,000
Brown corpus (1M words)38,000
Switchboard 电话会话20,000
COCA (Corpus of Contemporary American English)2,000,000
Google n-grams13,000,000

更糟糕的是,词频遵循 Zipf 定律——一个词排名为 $r$,频率与 $1/r$ 成正比:

公式 2.1:Zipf 定律
$$ f(r) \approx \frac{C}{r^{s}}, \quad s \approx 1 $$ 即第二常见词的频率约为第一常见的 1/2,第十的约为 1/10。这意味着词表的长尾极度肥厚: 大量词只出现 1–2 次,但它们的总和占语料的相当大比例。
词频排名 r (log) 频率 f(r) (log) "the" → 7% "and" → 3% "unicorn" → 0.001% "COVID-19" → 1 次 Zipf 定律:长尾决定了 OOV 永远无法消除

困难二:Out-Of-Vocabulary (OOV) 必然发生

即使你把训练语料里所有词都收入词表,也无法防止:

传统应对方案:

规则归一化
正则化拼写,如 importaNt → importantESPRESSSOOO → ESPRESSO;但维护成本高且漏洞百出。
UNK 符号
把所有未知词映射为单一的 <UNK>;问题是太多语义信息被压成同一个槽。
UNK 的灾难性示例
原文:Zipf's law is importaNt in NLP.
朴素词级分词器:UNK law isUNK in NLP.
模型完全失去了语义内容。这就是为什么 Sutskever et al. (2014) 在早期 seq2seq 论文里专门讨论"如何处理稀有词"——OOV 一度是机器翻译的最大死穴。

2.2 字符/字节级分词:"tokenizer-free"的诱惑

另一个极端:把每个字符(或每个字节)当作 token。词表瞬间收缩到几十到几百个槽。

字符 vs 字节的微妙区别
对 ASCII 文本,字符与字节一一对应。但对 Unicode:
  • "😀" = 1 个字符,4 个字节 (UTF-8 编码 F0 9F 98 80)
  • "地" = 1 个字符,3 个字节 (UTF-8 编码 E5 9C B0)
字节级分词更彻底地"无歧义",所有 Unicode 都能表示;字符级与 Unicode codepoint 一一对应,但要处理 100k+ 个 codepoint。
代表工作:CharBERTCANINECharformerByT5
维度字符/字节级
✅ 优势极小词表(~256)、零 OOV、可直接看到拼写
❌ 缺陷序列极长(中文 1 字 = 3 字节,attention 计算 9× 增加);高层语义需要更深网络去组合

争议至今未息:字符级模型究竟更"难"还是更"清晰"?Karpathy 在 2024 年的播客里强烈支持"长期来说我们应该删掉 tokenization 这一步"。Part 8 我们会重新审视这个方向的最新研究。

2.3 子词分词:词与字符之间的最优中庸

工业界 2016 年以来的事实标准:子词(subword)分词。核心思想:

让数据告诉我们词表应该是什么。频繁组合的字符序列升级为单个 token; 罕见词自动被切成更小的字符片段。最频繁的"完整词"(如 the)保持为单个 token; 而 antidisestablishmentarianism 这种罕见词可能被切成多个有意义的子词单元。

子词的设计哲学:

三大主流子词算法:

算法核心思想来源
BPE (Byte Pair Encoding)从字节开始,迭代合并最高频的相邻 token 对Sennrich et al., 2016
WordPiece合并最大化数据似然的相邻 token 对Wu et al., 2016 (BERT 用此)
Unigram LM从超大词表开始,逐步删除边际贡献最小的 tokenKudo, 2018 (SentencePiece 默认)

本教材聚焦 BPE,因为它已成为 GPT、Llama、Gemini、Claude、DeepSeek、Qwen、Mistral 等所有前沿模型的事实标准。

第三部分:字节对编码 BPE 算法深度解析

3.1 为什么所有前沿模型都用 BPE?

看看 2025–2026 年度主流 LLM 的分词器:

模型家族类型词表大小
Gemini 2.x / Gemma 3BPE262k
Gemini 1.x / Gemma 2BPE256k
Llama 4BPE201k
GPT-4o / GPT-5 / o1 / o3BPE200k
Kimi K2BPE163k
Qwen2.5 / Qwen3 / GLM-4BPE151k
Grok 2BPE131k
Llama 3 / Hunyuan T1 / TurboSBPE128k
DeepSeek v3 / R1BPE128k

为什么所有人都选 BPE?三大原因:

  1. 历史遗产:GPT-2 (2019) 使用 BPE 并开源后,整个开源生态都跟进了;
  2. 简单且确定:训练算法仅 ~30 行代码;测试时分词是贪心的,无歧义;
  3. 压缩效率好:BPE 实际上是一个数据压缩算法(Gage, 1994),它会自动找出高频 n-gram。

3.2 BPE 训练算法的形式化描述

算法 3.1:BPE Training
输入:
  D    — 训练语料(字符串集合)
  N    — 目标词表大小

输出:
  V    — 词表(token 集合)
  M    — 有序合并规则列表 [(v_a, v_b) → v_new, ...]

步骤:
  1. Pre-tokenize: 按空格切 D,得到"词序列"
  2. 初始化 V ← {所有字节} ∪ {空格符 _}
  3. 将 D 转换为字节序列(每个字符 → 1+ 字节)
  4. M ← []
  5. while |V| < N:
       a. 统计 D 中所有相邻 token 对 (v_i, v_j) 的频率
       b. 取频率最高的对 (v_a, v_b)
       c. 创建新 token v_new = v_a + v_b
       d. V ← V ∪ {v_new}
       e. M.append((v_a, v_b) → v_new)
       f. 将 D 中所有 (v_a, v_b) 替换为 v_new
  6. return V, M

关键设计选择:

3.3 完整训练 Walkthrough:Peter Piper 示例

让我们用 PDF 课件里的小语料完整走一遍:

语料 D = "Peter Piper picked a peck of pickled peppers"

步骤 1:Pre-tokenize

按空格切,并在每个词前加上空格标记 _(表示"词首",这是 GPT-2 BPE 的惯例):

Peter_Piper_picked_a_peck_of_pickled_peppers

步骤 2:初始化词表

$V_0 = \{$_, a, c, d, e, f, i, k, l, o, p, P, r, s, t$\}$ — 共 15 个字节。

步骤 3:把语料转成 token 序列

P e t e r _ P i p e r _ p i c k e d _ a _ p e c k _ o f _ p i c k l e d _ p e p p e r s

步骤 4–N:迭代合并

统计所有相邻对的频率,找出最高的 7 个:

Token PairFrequency
_ + p4
p + e4
c + k3
e + r3
e + d2
i + c2
p + i2
合并 1:_ + p_p
Peter _piper _picked _a _peck _of _pickled _peppers
合并规则 M
1. _ + p → _p
词表更新
V = V_0 ∪ {_p}
直觉

把"空格 + p"合并成一个 token,等价于学习到"很多 p 开头的词"。 这一步把 4 个 token 对压缩成 4 个新 token。

合并 2:c + kck(注意,_p 合并后 p+e 的计数从 4 降到 2,所以下一个最高是 c+k
Peter _piper _picked _a _peck _of _pickled _peppers
合并规则 M
1. _ + p → _p
2. c + k → ck
英语中 'ck' 是常见组合

注意 BPE 自动发现了正字法规律(pick, peck, pickle 都包含 ck),无需任何语言学知识。

合并 3:e + rer
Peter _piper _picked _a _peck _of _pickled _peppers
合并规则 M
1. _ + p → _p
2. c + k → ck
3. e + r → er
名词后缀 'er'

英语高频名词后缀(pepper, Peter, Piper)。BPE 又自动发现了一个形态学单元。

合并 4:_p + e_pe
Peter _piper _picked _a _peck _of _pickled _peppers
合并规则 M
1. _ + p → _p
2. c + k → ck
3. e + r → er
4. _p + e → _pe
注意:合并的是 token 对,不是字符对

现在 _p 已经是一个 token,BPE 接着把 _p + e 合并成 _pe,这是一个三字节序列!合并可以无限嵌套。

合并 5:_p + i_pi
Peter _piper _picked _a _peck _of _pickled _peppers
合并规则 M
5. _p + i → _pi
"_pi" 现在覆盖 Piper, picked, pickled
合并 6:_pi + ck_pick
Peter _piper _picked _a _peck _of _pickled _peppers
合并规则 M
6. _pi + ck → _pick
学到了完整词根 "pick"

注意此时 _pick5 个字节合并成的单个 token。 这让 picked, pickled 共享一个共同的 _pick 前缀,是 BPE 泛化能力的核心。

…依此类推,直到 $|V| = N$。

观察总结
经过 6 步 BPE 合并,我们的微型语料从 41 个字节缩短到约 18 个 token。原本没有任何语言学输入, 算法却"自动"发现了:(1) 英语正字法组合 ck;(2) 名词后缀 -er;(3) 完整词根 pick; (4) 词首标记 _pe_pi。这正是子词分词的魅力——形态学是统计的副产品。

3.4 测试时分词:贪心应用合并规则

训练完成后,得到了词表 $V$ 和有序合并规则 $M$。对新文本 _pier

输入: _pier 字节: _ p i e r 合并1: _p i e r ← 应用规则 1: _ + p → _p 合并3: _p i er ← 应用规则 3: e + r → er(规则 2 不适用) 合并5: _pi er ← 应用规则 5: _p + i → _pi 最终 token: _pi er 最终序列:[_pi, er](2 个 token)
测试时 BPE 应用:把输入按字节展开,然后按训练时记录的顺序贪心应用合并规则;不能合并的规则跳过。
注意 _pier 没有作为完整词出现在训练语料里,但通过合并 _pi + er 实现了优雅切分。
为什么测试时也"贪心"?
理论上,对一个测试串,最优切分可能要做 dynamic programming。但 BPE 选择的有序贪心应用有两个优势: (1) 时间复杂度线性于串长;(2) 与训练时的合并顺序保持一致,让"训练合并出来的高频片段"在测试时也会被合出来。 SentencePiece 在某些场景下确实实现了 DP-based 最优切分(叫 Viterbi-BPE),但开销大且无明显收益。

3.5 Python 实现:从零写一个 BPE

为了让你彻底吃透算法,下面是一份 可运行 的极简 BPE 实现(仅 ~50 行):

from collections import Counter, defaultdict

def get_stats(corpus):
    """统计相邻 token 对的频率。corpus: List[List[str]]"""
    pairs = Counter()
    for word in corpus:
        for i in range(len(word) - 1):
            pairs[(word[i], word[i+1])] += 1
    return pairs

def merge_pair(corpus, pair):
    """把语料中所有 pair (a,b) 替换为 'a+b'"""
    a, b = pair
    new_corpus = []
    for word in corpus:
        i = 0
        new_word = []
        while i < len(word):
            if i < len(word) - 1 and word[i] == a and word[i+1] == b:
                new_word.append(a + b)
                i += 2
            else:
                new_word.append(word[i])
                i += 1
        new_corpus.append(new_word)
    return new_corpus

def train_bpe(text, vocab_size):
    """BPE 训练主函数"""
    # Step 1: pre-tokenize (按空格切,每个词前加 _ 标记词首)
    words = ["_" + w for w in text.split()]
    # 转成 List[List[str]],每个内层 list 是字符序列
    corpus = [list(w) for w in words]
    # 初始化词表
    vocab = set(ch for word in corpus for ch in word)
    merges = []  # 有序合并规则

    while len(vocab) < vocab_size:
        pairs = get_stats(corpus)
        if not pairs:
            break
        best_pair = max(pairs, key=pairs.get)
        if pairs[best_pair] < 2:  # 不再有可合并对
            break
        corpus = merge_pair(corpus, best_pair)
        new_token = best_pair[0] + best_pair[1]
        vocab.add(new_token)
        merges.append(best_pair)

    return vocab, merges

def tokenize(text, merges):
    """测试时分词:按 merges 顺序贪心应用"""
    words = ["_" + w for w in text.split()]
    result = []
    for word in words:
        tokens = list(word)
        for pair in merges:
            tokens = merge_pair([tokens], pair)[0]
        result.extend(tokens)
    return result

# Demo
text = "Peter Piper picked a peck of pickled peppers"
vocab, merges = train_bpe(text, vocab_size=25)
print("Merges:", merges[:6])
# [('_', 'p'), ('c', 'k'), ('e', 'r'), ('_p', 'e'), ('_p', 'i'), ('_pi', 'ck')]

print(tokenize("pier picked", merges))
# ['p', 'i', 'er', '_pick', 'e', 'd']
生产级实现的差别
真实工业 BPE(如 tiktokentokenizers 库)有大量优化:
  • priority queue + linked list,把每步合并的统计更新降到 $O(\log n)$;
  • regex pre-tokenization(GPT-2 famous regex),保证标点、数字、空格的特殊处理;
  • 采用 byte-level 而非 character-level,词表能严格保证 $\geq 256$ 且无 OOV;
  • tiktoken 这种 Rust 实现,1 GB 文本秒级训练。

3.6 BPE 的姐妹算法:WordPiece 与 Unigram LM

WordPiece

BERT、DistilBERT、ELECTRA 使用。改动很简单:合并准则不是频率,而是合并后对训练数据似然的提升

$$ \text{score}(v_a, v_b) = \frac{\#(v_a v_b)}{\#(v_a) \cdot \#(v_b)} $$ 分子是合并后 token 的频率,分母是两个原 token 独立频率的乘积。这等价于挑选互信息(pointwise mutual information)最大的对。 直觉:如果两个 token 总是一起出现(比 chance 高得多),就应该合并。

Unigram Language Model

Kudo (2018) 提出,被 SentencePiece 默认采用,T5、XLM-R、Llama 2 都基于它。哲学完全相反:

  1. 初始化:从一个超大词表开始(数百万个候选子词,包括所有可能的字符 n-gram);
  2. EM 迭代
    • E 步:对每个词,计算用当前词表分词的所有可能切分及其概率;
    • M 步:根据当前切分的统计量更新每个子词的 unigram 概率;
  3. 修剪:删除对总似然贡献最小的 X% 子词;
  4. 重复 2–3 直到词表大小达到目标 $N$。

Unigram LM 的一个独特优势:测试时可以产生多个可能的切分,每个切分有概率。这支持 BPE 不支持的子词正则化(subword regularization)——训练时随机采样不同切分,等价于数据增强。

算法合并方向合并准则测试时典型用户
BPE自底向上频率最高确定性贪心GPT, Llama, Claude
WordPiece自底向上似然增益最大 (PMI)确定性贪心BERT, ELECTRA
Unigram自顶向下对似然边际贡献概率性,可采样T5, XLM-R, Llama 2

第四部分:超越 BPE — SentencePiece 与 SuperBPE

4.1 Pre-tokenization 的代价:为什么按空格切并不普适

回顾 BPE 算法的第 1 步:按空格切。这个"无害"假设隐含着两个大问题:

问题 1:很多语言不用空格

按空格切,对这些语言相当于不切,整个句子是一个"词",BPE 完全失去其设计前提。

问题 2:多词表达(multi-word expressions)

英文里 on the other handdepend onkick the bucket 是固定搭配, 但按空格切后变成多个独立 token,模型必须靠 attention 重新把它们"粘"起来。

4.2 SentencePiece:把空格当字符的库

Google 2018 年发布的 SentencePiece 库提出了一个简洁方案:

SentencePiece 的核心想法
不预分词。把空格视为词表里的一个普通字符(用特殊符号 表示)。 BPE 或 Unigram 直接在未切分的字节流上跑。

这样:

SentencePiece 不是一个算法——它是一个实现库,里面同时支持 BPE 和 Unigram。 T5、Llama 2、XLM-R、Gemma 都用 SentencePiece + Unigram;Llama 3、4 切换到 SentencePiece + BPE。

4.3 SuperBPE:跨越空格的"超级词"

2025 年 Liu et al. 提出 SuperBPE,对预分词的"教条"做了进一步突破:

SuperBPE 的两阶段课程
阶段 1(subword learning):按 BPE 标准做法,强制不跨空格地学习子词;
阶段 2(superword learning):移除空格约束,让 BPE 继续合并,可以跨越多个英文单词。

直观看效果:

原始 BPE
Bytheway,IamafanoftheMilkyWay.
SuperBPE
By the way,I ama fan oftheMilky Way.

效果:

词表大小 |V| Bytes / Token (↑ better) BPE w/ pretok BPE w/o pretok SuperBPE BPE upper bound (整词均为 1 token)
SuperBPE 在所有词表大小下都实现更高压缩率,因为它能合并多词表达(数据来源:Liu et al. 2025 论文 Figure 1 示意)。

第五部分:分词的隐秘代价

即使 BPE 已是 LLM 的事实标准,它远未"解决所有问题"。本部分聚焦三个广为人知的失败模式。

5.1 草莓里有几个 r?Spelling 的根本失败

2024 年最有名的 LLM "笑话":

用户:How many 'r's are in 'strawberry'?
ChatGPT (GPT-4o):There are two "r"s in the word "strawberry."
用户:Verify with code.
ChatGPT:There are three "r"s in the word "strawberry."

为什么连"数字母"这种小学一年级问题,最强 LLM 都数不对?根源就在分词:

模型strawberry 被切成
GPT-2["str", "aw", "berry"]
GPT-4o["strawberry"](单个 token)

两者都没法让模型"看到"单个字符 r。模型必须从"strawberry"这个不透明 token 的嵌入向量里"反推"出它的字符组成——而这从未在训练目标里被监督过

更广义地说,子词模型在以下任务上系统性失败:

拼写
把 token 分解成字符;模型从未学过此映射,只能靠"字符在多少个语境里出现"做模糊推理。
反转字符串
Huang et al. 2023:要求模型反转 "Stanford",模型必须先"猜"出字符序列再反转,错误率极高。
对错别字的鲁棒性
单个字符的改动会让 BPE 合并路径完全不同:importaNtimportant 的 token 序列毫无重叠。
细粒度算术
"723 + 856" 中数字常被切成奇怪的子片段,导致进位算错。
字符级基准 CUTE
Edman et al. 2024 提出的 CUTE benchmark 专门测试 LLM 的字符级能力:拼写、反转、字符替换、字符计数。 GPT-4o 在多项任务上仍然显著低于人类。这印证了一个深刻结论:模型的"语言原子"是子词,任何需要原子内部信息的任务都被迫做逆向工程

5.2 故障 Token:SolidGoldMagikarp 之谜

2023 年 2 月,一组研究者发现 GPT-3 对某些字符串表现出极端诡异行为:

用户:Tell me about SolidGoldMagikarp.
GPT-3:(输出关于宝可梦 Magikarp 的内容,但坚持把"SolidGold"误读为"distribute")

用户:Is there a difference between SolidGoldMagikarp and SolidGoldMagikarp?
GPT-3:(声称两者完全不同,回答语无伦次)

还有比如 petertoddattRotNOTHING IS FAIR IN THIS WORLD OF MADNESS 等等。这些被称为 glitch tokens(故障 token)。

根源:训练分布不匹配
BPE tokenizer 是在某个语料 $\mathcal{D}_\text{tok}$ 上训练的;LLM 是在另一个语料 $\mathcal{D}_\text{LM}$ 上训练的。 若 $\mathcal{D}_\text{tok}$ 包含大量 Reddit 抓取数据,SolidGoldMagikarp(一个高活跃用户名)会因高频被合并成单个 token。 但 OpenAI 后来在训练 GPT-3 时清洗了 Reddit 数据,导致这个 token 的嵌入几乎从未在 LM 训练中被更新,保留随机初始化状态。
Tokenizer 训练语料 D_tok (含 Reddit 等) SolidGoldMagikarp 出现 10000+ 次 → 被收入词表,分配 token ID 12345 词表 V "hello" → 5 "the" → 290 "SolidGoldMagikarp" → 12345 LM 训练语料 D_LM (Reddit 被过滤掉) SolidGoldMagikarp 几乎从不出现 → 嵌入向量从未被更新 结果:故障 Token (Glitch Token) 嵌入向量随机 → 模型见到时行为不可预测、产生幻觉

有名的研究 "Fishing for Magikarp" (Land & Bartolo, 2024) 系统性地在 GPT-2、Llama 2/3、OLMo、Qwen 等模型上自动检测了这类 token,发现每个模型都有数百到数千个

故障 token 的危害不仅是"好奇心":

缓解策略
最简单也最有效的对策:tokenizer 训练数据应与 LM 训练数据严格一致。 这个看似显然的原则在工业实践里经常被忽略——因为 tokenizer 训练比 LM 训练早数月,数据策略可能已经变化。

5.3 Tokenization 与所有"奇怪行为"的对照表

Karpathy 在 2024 年播客里给出的"症状—病因"表,至今仍准确:

LLM 的奇怪行为根源
不会拼写单词子词 token 隐藏字符信息
不能反转字符串同上
在非英语任务上更差非英语 token 的训练样本少
简单算术做错数字常被切成奇怪的子片段
Python 代码经常出错缩进、空格被切得不一致
看到 <|endoftext|> 时突然停止这是真实的特殊 token,触发 EOS
对 trailing whitespace 警告训练时 trailing space 几乎从未出现
遇到 SolidGoldMagikarp 时崩溃故障 token
偏好 YAML 而非 JSONJSON 的特殊符号在 BPE 下 token 更多
不是真正的 end-to-end 模型tokenizer 是独立预处理步骤

第六部分:多语言 NLP — 当世界不只是英语

6.1 7000 种语言与 40% 英语的 Common Crawl

全世界有超过 7000 种活语言。但 NLP 的全部训练数据严重偏向英语:

如果 NLP 只服务英语
我们就在建立一种系统性排斥世界绝大多数人的技术。这不是浪漫主义口号——商业现实是,亚洲、非洲、拉丁美洲市场恰恰是数字化最快的地区。
语言第一语言使用者(百万)
普通话 (Mandarin)939
西班牙语485
英语380
印地语345
孟加拉语234
葡萄牙语236
俄语147
日语123
越南语85
韩语81.7
高资源 vs 低资源语言
高资源语言(high-resource):拥有大量数字文本与标注数据,如英语、汉语、西班牙语。
低资源语言(low-resource):可用数据稀少,如 Yoruba、Tibetan、Quechua。
划分不仅是绝对数据量,还包括对 NLP 工具/标准的支持程度

推荐资源:

6.2 多语言语言模型的架构演化

多语言语言模型(multilingual LM)架构上和单语言完全相同。唯一差别是训练数据和分词器都覆盖多语言。

flowchart LR A["1. 收集
各语种文本"] --> B["2. 训练
多语言 BPE/Unigram"] B --> C["3. 训练 LM
(MLM 或 CLM)"] C --> D["多语言 LM"]

2018 年至今主要里程碑:

模型年份语言数架构
mBERT2018104Encoder
XLM-R2020100Encoder
mT52021101Encoder-Decoder
BLOOM202246Decoder
GPT-4, Llama, Gemini, Claude2023+隐式Decoder
早期方案:Translation Language Modeling (TLM)
XLM (Conneau & Lample, 2019) 用了一个有趣的技巧:把两个语言的平行句对拼接,然后在拼接后的序列上做 MLM。 模型可以在预测一个掩码词时"窥探"另一种语言的对应内容,强制学习跨语言对齐。
但 XLM-R 证明:只用大规模多语言单语数据 + MLM 也能学到强跨语言能力,TLM 渐被弃用。

6.3 跨语言迁移:XLM-R 的奇迹

多语言 LM 最神奇的能力是 cross-lingual transfer(跨语言迁移):

在英语标注数据上 fine-tune 一个分类头,然后直接在从未见过任何标注数据的法语、中文、Swahili 上做预测——竟然也能 work。
Stage 1: 预训练 XLM-R MLM on 2.5TB CC, 100 lang Stage 2: Fine-tune + Classifier Head 仅用英语标注数据训练 Stage 3: 零样本推理 部署到任意语言 无需目标语标注 Stage 2 训练样本(英语 only): "Great movie, loved it!" → Positive "Terrible service, awful." → Negative "Best purchase ever!" → Positive Stage 3 预测(零样本): FR: "Film magnifique !" → Positive DE: "Schrecklicher Service" → Negative ZH: "非常好的体验" → Positive 共享的多语言表示空间是迁移的关键
XLM-R 的跨语言迁移 pipeline。预训练时模型学到一个跨语言共享的语义空间;fine-tune 仅用英语数据;推理时直接处理任意预训练见过的语言。

为什么这能 work?两个核心机制:

  1. 共享子词词表:相近语言(如英语、法语)会有大量重叠 token,比如 nationsolution
  2. 对齐的语义空间:MLM 训练迫使模型把"上下文相似"的词放到向量空间的相近位置,这一性质跨语言传递。

6.4 词表重叠与跨语言对齐

研究表明(Limisiewicz et al., 2023Kallini et al., 2025a):

词表重叠促进跨语言迁移
重叠越多的词表,跨语言迁移性能越好。即使是"假朋友"(false friends)——两种语言里同形但意义不同的词——也能帮助学到有用的跨语言嵌入。 但需要避免极端:每种语言仍需足够多的专属 token
happy, apple, Batman, pie, of (a) 完全重叠 happy apple Batman pie feliz manzana (b) 高相似度 happy apple Batman pie/pie feliz manzana (c) 低相似度 happy_EN apple_EN feliz_ES manzana_ES (d) 无重叠 实证结果:跨语言迁移效果 (a) ≈ (b) > (c) ≫ (d) 完全重叠和高相似度重叠都能产生有效的跨语言嵌入 无重叠(如 XLM-V 给每个语言独立子集)会严重损害迁移能力 参考:Kallini et al. 2025a, "Cross-lingual transfer through vocabulary overlap"

反例:XLM-V 把词表扩大到 100 万 token(XLM-R 的 4 倍),给每种语言更多专属 token。 覆盖率确实更好,但推理效率代价巨大,且某些跨语言任务上表现反不如 XLM-R。

6.5 多语言的诅咒:容量与覆盖的取舍

"既然跨语言迁移这么神奇,那加上几千种语言不就完美了吗?"——并非如此。

多语言的诅咒(Curse of Multilinguality)
固定模型容量下,加入更多语言会逐渐损害每种语言的表现。 XLM-R 在 XNLI 跨语言 NLI 任务上:
  • 训练 7 种语言:准确率 71.8%
  • 训练 100 种语言:准确率 67.7%
4 个百分点的下降意味着每种语言分到的模型容量都在缩水
语言数量 准确率 (%) 7 15 30 60 100 80 70 60 50 40 低资源语言 高资源语言 All
XLM-R 在不同语言数下的 XNLI 准确率。高资源语言(红)显著下降;低资源(蓝)初期受益于迁移,但语言数继续增加后也会下降。模型容量是瓶颈。

应对方案:

第七部分:分词的公平性问题

7.1 Token 通胀:同样意思在缅甸语要多花 15 倍

回到 PDF 课件里那张表——同一句话在不同语言里被切成多少 token:

语言文本Token 数字符数Token/Char
英语We'd sat back on the grass, and time flew by as we looked at the Milky Way.21750.28
法语Nous étions assis dans l'herbe, et le temps a filé tandis que nous contemplions la Voie lactée.27950.28
索马里语Waxaan dib u fadhiisanay cawskii, wakhtiguna wuu duulay markii aanu eegin Jidka caanaha.30880.34
泰语พวกเรานั่งพักผ่อนบนพื้นหญ้า และเวลาก็ผ่านไปอย่างรวดเร็ว...36790.46

更刺眼的对比来自 Petrov et al. 2023

Token 长度差距高达 15 倍
同一段意思,OpenAI tokenizer 在英语里切成 N 个 token,在缅甸语、僧伽罗语、藏语里能切到 15N 个 token。
每词平均 token 数(subword fertility) — 越低越好 拉丁字母 1.0 日语 1.2 韩语 1.5 西里尔字母 2.0 阿拉伯 2.5 泰语 3.0 希伯来 3.3 天城文 (印地) 3.8 孟加拉 5.0 泰米尔 6.0 格鲁吉亚 7.5 藏语 11.3 数据来源:Ahia et al. 2023, "Do all languages cost the same?"

7.2 子词肥沃度与下游性能的负相关

定义:子词肥沃度 (subword fertility)
$$ \text{fertility}(\text{lang}) = \frac{\text{tokens in target language}}{\text{words in target language}} $$ 即每个"词"(按标准分词得到)平均被切成多少个 token。英语典型 fertility ≈ 1.2–1.5; 缅甸语、藏语可达 8–11。

Rust et al. (2021) 给出了关键论断:

分词器的选择对下游性能的影响,可以和预训练数据集规模相当。 也就是说,给一个语言适配的 tokenizer 可能比单纯堆更多数据更重要。

7.3 API 定价不公的伦理含义

商业 LLM API 几乎都按 token 数计费。这意味着:

非英语用户被"系统性多收费"
Ahia et al. 2023 系统分析 OpenAI API 在 22 种语言上的收费:
  • 用户用低资源语言(如肖纳语、约鲁巴语)提交同等信息量的请求,要支付15 倍的费用;
  • 这些用户往往恰恰来自消费能力较弱的国家;
  • 更糟糕:他们得到的回答质量也更差(因为 fertility 高导致序列截断更频繁)。

这是双重不公:(1) 价格更高;(2) 质量更低。 分词不再只是工程细节,而成为数字鸿沟的算法化

思考题 7.1
若你是 LLM 公司的产品经理,发现非英语用户的每条消息成本是英语用户的 5 倍。请提出三个可以缓解(但不必彻底解决)这种不公平的方案,并讨论各自的工程/商业代价。

第八部分:字符/字节级模型的复兴

本课最有 inspirational 价值的部分。多语言公平性、Spelling 失败、Glitch token 这一切问题, 都可以被一个根本方案绕过:不要 tokenizer 了。直接在字节上训练。

8.1 CANINE:mBERT 的字符级对手

Clark et al. (2022) 提出的 CANINE 是字符级 multilingual 模型:

模型输入参数MasakhaNER 平均 F1
mBERT子词179M72.4
CANINE-C字符127M65.5 (-6.9)
CANINE-C + n-grams字符167M76.8 (+4.4)

在 MasakhaNER(11 种非洲语言 NER 基准)上的尤其抢眼结果——CANINE-C 在 Amharic 上 F1 = 50.0,mBERT = 0.0。 原因:mBERT 完全没在 Amharic 上训练;CANINE-C 字符级覆盖让它能"看懂"全 Unicode。

8.2 ByT5 与 MrT5:动态删除的字节序列

ByT5 把 T5 的 SentencePiece 替换成纯字节输入,证明字节级模型可以匹敌子词模型,但代价是更长的序列和更慢的训练。

2025 年 MrT5 (Kallini et al.) 在 ByT5 基础上加入一个可学习的"删除门"(delete gate),让模型动态决定哪些字节可以丢弃:

T h e ' ' Delete Gate(可学习) -0.1 ✓ -28.6 ✗ -1.4 ✓ T e ' ' ⟵ 'h' 被删除(gate score 极低) 后续 Transformer 层(处理更短的序列) 学到语言特定压缩: 中文压缩率 ↑ 英文压缩率 ↓

MrT5 的精彩之处:学到的删除模式因语言而异——中文的 codepoint 被大量保留(信息密度高),英文的字节被大量删除(冗余多)。这本质上是把分词学进了模型架构,而非作为预处理步骤。

8.3 Byte Latent Transformer (BLT):熵驱动的动态 patch

Meta 2024 年的 BLT (Pagnoni et al., 2025) 提出更激进的设计:

BLT 在 8B 参数级别上首次匹敌 Llama 3 8B(subword 模型),同时使用 50% 更少的推理 FLOP

8.4 H-Nets 与层次化自回归 Transformer

两条平行的新方向:

8.5 潜在分词:把"切词"也学进去

研究愿景:Latent Tokenization
所有这些前沿工作有一个共同主题:让"什么是 token"由模型自己学。 传统 BPE 是预处理;字节级模型让模型在内部学到适合的语义聚合粒度。

字符/字节级模型解决的不只是公平性,还包括:

问题BPE 的处理字节级的处理
故障 Token普遍存在不存在(每个字节都在训练中)
拼写任务无法直接做原生支持
对错别字鲁棒性极差(合并路径剧变)
低资源语言fertility 高、性能差无 fertility 概念,性能均衡
训练效率较低(序列长)
推理效率较低(除非动态降采样)

2025–2026 年最重要的开放问题:动态字节级 / latent tokenization 能否在保持质量的同时把推理效率推到与 BPE 相当? 若答案是肯定的,整个 tokenizer 这一层有可能在未来 5 年内消失。

第九部分:练习题与研究方向

课后练习

练习 9.1(BPE 训练)
对语料 "low lower lowest newer newest wider widest",从字节开始手动执行 BPE, 列出前 5 步合并。每步给出:(a) 当前频率最高的 pair;(b) 合并后的语料;(c) 当前的合并规则列表。
练习 9.2(测试时分词)
基于练习 9.1 训练出的 BPE,给 "newest""slowest" 分词。 注意 slowest 在训练语料里没出现过——它会被切成什么?说明这反映了 BPE 的哪个性质。
练习 9.3(嵌入参数计算)
对 Llama 4 ($d=8192$, $|V|=201{,}000$) 和 Gemma 3 ($d=2304$, $|V|=262{,}000$):
  1. 计算各自嵌入层的参数量;
  2. 若两个模型都把 $|V|$ 改为 50{,}000,嵌入层参数分别节省多少?
  3. 讨论:为什么大模型倾向用更大词表?
练习 9.4(公平性诊断)
选三种你熟悉但 fertility 较高的语言(如阿拉伯语、印地语、泰语),用 tiktoken 或 HuggingFace tokenizer 测量:
  1. 把 UDHR(世界人权宣言)的前 1000 字符在该语言版本与英文版本下分别 token 化,比较 token/字符比;
  2. 计算"消息成本比"——若英文用户花 1 美元,该语言用户实际花多少?
  3. 若 OpenAI 想做"按信息量定价",你会怎么设计这个机制?
练习 9.5(故障 Token 检测)
设计一个算法,自动从给定 LLM 检测 glitch token:
  1. 输入:tokenizer 与 LM;
  2. 输出:嵌入向量"诡异"的 token 列表;
  3. 提示:嵌入范数、与平均嵌入的余弦距离、邻居一致性都可作信号。
参考 Land & Bartolo (2024) 后比较你的设计。

研究方向

  1. 更好的多语言分词器:能否设计一个分词算法,给所有语言一个"公平"的 fertility 上限?
  2. 潜在分词的可解释性:BLT/H-Nets 学到的 patch 划分对应什么语言学单元?
  3. 分词鲁棒性:能否在 BPE 训练时加入对抗扰动,让 token 序列对错别字更稳定?
  4. 分词器与对齐:tokenizer 训练数据的偏差如何传播到下游 RLHF / DPO 中?
  5. 字符级智能体:字节级 LLM 在工具调用(需要精确字符串处理)上表现是否优于 BPE?

本教材由 Claude (Opus 4.7) 基于 CS224N Winter 2026 Lecture 14 (Julie Kallini) 改编,结合 deep-research 方法论原则,于 2026 春为研究生学习者深度展开。
鸣谢:Julie Kallini 的原始讲座设计;Dan Jurafsky、Alisa Liu、Yejin Choi、Christopher Potts、Catherine Arnett、Tolúlọpẹ́ Ògúnrẹ̀mí 的素材共享。
建议配合手写 BPE 实现、Tiktokenizer 在线工具、HuggingFace tokenizers 库一同学习。