CS336 · Lecture 1 · 图解 02

BPE Tokenizer:为什么四种方案只有它没有明显短板

字符级词表太大、字节级压缩比最差、词级有 UNK 问题——三种极端各有死穴。BPE 从字节出发,迭代合并最高频 pair,正好落在没有短板的那个点。

讲者:Percy Liang & Tatsunori Hashimoto Stanford CS336 · 2026 原始讲义 ↗

Tokenizer 是文本和模型之间的桥梁:把字符串 encode 成 token 序列,再能 decode 回来(必须可逆)。它做的好不好,看两个维度——词汇表多大压缩比多高

两个核心指标

词汇表大小:模型要为每个 token 学一个 embedding。太大 → embedding 矩阵稀疏、罕见 token 学不好;太小 → 每种方案有自己的麻烦。
压缩比 = UTF-8 字节数 ÷ token 数。越高 → 序列越短 → 越好,因为 attention 的计算量是序列长度的平方,序列减半就是 4 倍省。

全景:四种方案各在哪

把四种方案画到「词汇表大小 × 压缩比」的二维平面上,谁有死穴一目了然。

四种 tokenizer 在「词汇表大小 × 压缩比」平面上的位置 → 无上限 词汇表大小 → 压缩比(越高越好)→ 可行区域 字节级 256 词表极小,但压缩比 = 1.0(最差) 字符级 ~150K 词表巨大, 压缩比也低 词级 词表无上限 → UNK 问题 BPE 可配置 ← 无明显短板 (数据驱动)
Figure 1. 三种极端各自踩中一个死穴(词表太大 / 压缩比最差 / UNK),只有 BPE 落在「词表适中 + 压缩比高」的可行区域里。

同一句话,四种切法

"café matcha 🐱" 这句话(含多字节字符)看四种方案的切分差异——token 数和痛点都不同。

"café matcha 🐱" 的四种切分 字符级 13 tokens 粒度太细 c a f é m a t c h a 🐱 字节级 17 tokens 信息打碎 c a f C3 A9 m a t c h a F0 9F 90 B1 é=2字节, 🐱=4字节 词级 3 tokens UNK 问题 café [UNK] [UNK] matcha、🐱 不在词表 → 丢失信息 BPE ~4 tokens 平衡 ✓ café match a 🐱 常见词完整,罕见词拆成 有意义的子词,永不 UNK token 数:字节级 17 > 字符级 13 > BPE ~4 > 词级 3 —— 但词级付出 UNK 的代价
Figure 2. 词级最短却最危险(UNK 直接丢信息);字节级最长(多字节字符被拆成无意义的字节);BPE 让常见词保持完整、罕见词拆成有意义子词,短而不丢信息。
方案词汇表大小压缩比UNK 问题死穴
字符级~150K(太大)词表大 + 压缩比低,两头差
字节级256(太小)1.0(最差)序列太长,attention 爆炸
词级无上限罕见词、形态变化、新词全 UNK
BPE可配置无明显缺陷

BPE 训练:迭代合并最高频 pair

BPE 的核心:从字节出发,每轮统计相邻 token 对的频率,合并出现最多的那一对,重复 num_merges 次。常见序列逐渐被压成单个 token,罕见序列保持多 token。

以 "the the" 为例的两次合并 初始(字节) t h e t h e ↑ (t,h) 出现 2 次 = 最高频 合并 (t,h) → [th] 第 1 次合并后 th e th e ↑ (th,e) 出现 2 次 = 最高频 合并 (th,e) → [the] 第 2 次合并后 the the 7 个字节 token → 3 个子词 token
Figure 3. 每轮只做一件事:找最高频相邻对、合并它。重复下去,高频序列(如 "the")被压成单 token,低频序列保持多 token——这就是「常见序列少 token、罕见序列多 token」的来源。
# train_bpe 伪代码(CS336 Assignment 1)
indices = utf8_bytes(文本)          # 初始化:每个字节一个 token
vocab   = {0..255 → 对应字节}
merges  = {}
重复 num_merges 次:
    pairs = 统计 indices 中所有相邻对的频率
    (A, B) = 频率最高的 pair
    merges[(A,B)] = 256 + i          # 记录合并规则
    把 indices 里所有 (A,B) 替换为新 index

为什么行得通:两端的死穴

BPE 之所以没有明显短板,是因为它站在两个失败模式中间——词汇表大小由 num_merges 这一个超参控制,数据驱动地找到平衡点。

词汇表大小是一根数轴,两端都是死穴 词汇表大小 → 词表太小(如 256) 序列太长 → attention O(n²) 爆炸 字节级的死穴 BPE 平衡点 num_merges 调节 词表大小(~50K) 数据驱动 · 可配置 词表太大 embedding 矩阵稀疏 → 罕见 token 学不好 字符级/词级的死穴 压缩比 = UTF-8 字节数 ÷ token 数:越高序列越短,但词表越大 embedding 越稀疏 BPE 不在两端极限,而是让高频序列少 token(高压缩)、低频序列多 token(不稀疏)
Figure 4. BPE 的优雅在于:它不是在「字符 vs 词」里二选一,而是用一套合并规则同时拿到词级的高压缩和字节级的通用性。

GPT-2 的改进:先切块,再 BPE

真实的 BPE(GPT-2 起)多了一步 pre-tokenization:先用正则把文本切成块(词、数字、空格绑在词上),再在每个块独立做 BPE。这避免跨词边界的奇怪合并,并加入 <|endoftext|> 等特殊 token 标记文档边界。

GPT-2 tokenizer 的处理流程 原始文本 "Hello world" Pre-tokenization 正则切块 ["Hello", " world"] 各块独立 BPE 块内合并 pair 不跨词边界 + 特殊 token <|endoftext|>
Figure 5. 先正则切块、再块内 BPE,加上特殊 token——GPT-2 以来 tokenizer 的标准做法。GPT-5 用 tiktoken 的 o200k_base 编码,词表约 200K。
一句话收束

BPE = 词级的高效率 × 字节级的通用性。它从字节出发,把高频序列压成单 token、低频序列留给多 token,于是永远不会 UNK,同时把序列压到很短。这正是它成为 GPT-2 以来语言模型标准选择的原因。

未来:去掉 tokenizer?

当前 tokenization 是个独立步骤,未来也许能端到端从字节学习(ByT5、MEGABYTE、BLT、H-Net、T-FREE 等)。但任何替代方案都必须满足两个条件:模型应操作在序列的抽象块上,且块应是可变长的——把更多容量分配给更有信息量的块。目前这些方案尚未被扩展到前沿规模

来源:CS336 Lecture 1: Language Modeling From Scratch — Percy Liang & Tatsunori Hashimoto, Stanford, 2026(Tokenization 单元,受 Karpathy 的 tokenization 视频启发)

参考文献:Sennrich et al. 2015《Neural Machine Translation of Rare Words with Subword Units》(BPE 原始来源)