· liyu · ai · 9 min read

大模型分词器(Tokenizer)核心原理与 BPE 算法图解

一文讲清大模型分词器的本质:从“为什么需要子词分词”,到结合具体数据的 BPE 训练推导,再到现代大模型标配 BBPE 的核心机制。

一文讲清大模型分词器的本质:从“为什么需要子词分词”,到结合具体数据的 BPE 训练推导,再到现代大模型标配 BBPE 的核心机制。

大模型虽然表现出惊人的理解力,但其底层本质只是数学张量计算器——它既不认识汉字,也不认识英文单词,只能对一系列数字(Token IDs)进行矩阵运算。

**分词器(Tokenizer)**就是人类语言与大模型数字世界之间的“同传翻译官”。

输入: "你好世界" ──[Tokenizer 编码]──► Token IDs: [57648, 54221] ──► 神经网络计算
输出: Token IDs: [1024, 3056] ──[Tokenizer 解码]──► "很高兴为你解答"

本文用最直白的方式,带你快速弄懂分词器的核心作用、训练机制与经典 BPE(Byte Pair Encoding) 算法。


一、 分词器是做什么的?

1. 为什么不用传统切词方式?

在处理文本时,我们通常有三种切分粒度:

┌──────────────┬───────────────────────────────┬──────────────────────────────────────────┐
│ 切分粒度     │ 怎么切                        │ 致命缺点                                 │
├──────────────┼───────────────────────────────┼──────────────────────────────────────────┤
│ 词级 (Word)  │ ["unhappiness"]               │ 1. 词表动辄上百万,参数爆炸              │
│              │                               │ 2. 遇到新词直接变成 <unk>(未登录词)    │
├──────────────┼───────────────────────────────┼──────────────────────────────────────────┤
│ 字符级 (Char)│ ['u', 'n', 'h', 'a', 'p' ...] │ 1. 序列长度翻几倍,Attention O(N²) 算不动 │
│              │                               │ 2. 单个字符几乎没有独立语义              │
├──────────────┼───────────────────────────────┼──────────────────────────────────────────┤
│ 子词 (Subword)│ ["un", "happi", "ness"]       │ ✅ 词表可控 (32k~150k)、序列长度合理、    │
│ (现代主流)   │                               │    0 OOV(任何新词都能拆解表示)         │
└──────────────┴───────────────────────────────┴──────────────────────────────────────────┘

子词切分(Subword) 是现代大模型的标准方案:

  • 高频词直接保留:如 theapple深度学习,只占 1 个 Token,压缩效率极高。
  • 低频/生僻词拆解组合:如 unhappiness 拆分为 un + happi + ness,模型能借助词根理解语义,且永不报错。

二、 分词器是怎么训练出来的?(数据推导演练)

💡 核心常识:分词器的训练是纯统计过程,与神经网络权重的训练完全无关。它不需要 GPU,也不需要反向传播,纯靠 CPU 统计海量文本里的字符共现频率。

分词器训练的目标很简单:在限定的词表容量内,通过不断把“高频相邻对”合并为新词,让文本整体压缩率最高


1. 初始语料与词频统计

假设我们的训练语料库统计后包含以下 4 个词(词尾加上 </w> 区分词边界):

  • l o w </w> : 出现 5 次
  • l o w e r </w> : 出现 2 次
  • n e w e s t </w> : 出现 6 次
  • w i d e s t </w> : 出现 3 次

初始基础词表(11 个字符){'l', 'o', 'w', 'e', 'r', 'n', 's', 't', 'i', 'd', '</w>'}


2. 逐步合并过程

算法每轮统计语料库中所有相邻两两组合(Pair)出现的总频次,贪心地挑出最高频的一个合并为新 Token。

轮次统计最高频相邻对频次统计来源执行合并词表新增语料当前状态
第 1 轮('e', 's')6 (newest) + 3 (widest) = 9'e' + 's''es'esl o w </w> (5)
l o w e r </w> (2)
n e w es t </w> (6)
w i d es t </w> (3)
第 2 轮('es', 't')6 (newest) + 3 (widest) = 9'es' + 't''est'estl o w </w> (5)
l o w e r </w> (2)
n e w est </w> (6)
w i d est </w> (3)
第 3 轮('est', '</w>')6 (newest) + 3 (widest) = 9'est' + '</w>''est</w>'est</w>l o w </w> (5)
l o w e r </w> (2)
n e w est</w> (6)
w i d est</w> (3)
第 4 轮('l', 'o')5 (low) + 2 (lower) = 7'l' + 'o''lo'lolo w </w> (5)
lo w e r </w> (2)
n e w est</w> (6)
w i d est</w> (3)
第 5 轮('lo', 'w')5 (low) + 2 (lower) = 7'lo' + 'w''low'lowlow </w> (5)
low e r </w> (2)
n e w est</w> (6)
w i d est</w> (3)
第 6 轮('n', 'e')6 (newest) = 6'n' + 'e''ne'nelow </w> (5)
low e r </w> (2)
ne w est</w> (6)
w i d est</w> (3)
第 7 轮('ne', 'w')6 (newest) = 6'ne' + 'w''new'newlow </w> (5)
low e r </w> (2)
new est</w> (6)
w i d est</w> (3)
第 8 轮('new', 'est</w>')6 (newest) = 6'new' + 'est</w>''newest</w>'newest</w>low </w> (5)
low e r </w> (2)
newest</w> (6)
w i d est</w> (3)
第 9 轮('low', '</w>')5 (low) = 5'low' + '</w>''low</w>'low</w>low</w> (5)
low e r </w> (2)
newest</w> (6)
w i d est</w> (3)

📌 :在遇到频次相同的情况时(如第 1 轮 ('e', 's')('s', 't') 均为 9 次,第 6 轮 ('n', 'e')('e', 'w') 均为 6 次),算法依据在语料中首次出现的顺序或字典序打破并列(Tie-breaking)。


3. 训练产物

当达到指定的词表大小或合并轮数后训练结束,输出两大成果:

  1. 词表(Vocabulary):基础字符 + 新合并出的子词(每个子词赋予一个唯一数字 ID)。
  2. 有序合并规则表(Merge Rules):按合并先后排序的规则表(决定后续分词时的优先级)。

三、 BPE 算法如何对新文本分词?(Inference 过程)

训练完成后,当给分词器输入一个从未在训练集中作为独立完整词出现过的词 "lowest" 时,分词器是如何切分的?

输入单词: "lowest"

1. 初始切分为基础字符序列(加上词尾边界):
   ['l', 'o', 'w', 'e', 's', 't', '</w>']

2. 按训练好的合并规则顺序依次匹配执行:
   - 匹配规则 1: ('e', 's')         ➔ ['l', 'o', 'w', 'es', 't', '</w>']
   - 匹配规则 2: ('es', 't')        ➔ ['l', 'o', 'w', 'est', '</w>']
   - 匹配规则 3: ('est', '</w>')    ➔ ['l', 'o', 'w', 'est</w>']
   - 匹配规则 4: ('l', 'o')         ➔ ['lo', 'w', 'est</w>']
   - 匹配规则 5: ('lo', 'w')        ➔ ['low', 'est</w>']
   - 后续规则 6~9 (ne, new, newest, low</w>) 均无法在当前序列匹配,合并结束。

3. 最终切分结果:
   ["low", "est</w>"]  ──► 查词表映射为 Token IDs: [15, 13]

可以看到,即便 "lowest" 从未在训练集里完整出现过,它也能被优雅地拆分为 low + est,不会出现任何信息丢失。


四、 现代大模型的进阶:Byte-level BPE (BBPE)

传统的 BPE 依然以字符为单位,但在处理多语言(中文、Emoji、罕见符号)时,Unicode 字符过多,生僻符号依然可能造成 OOV。

因此,GPT-4、LLaMA 3、DeepSeek、Qwen 均升级为了 Byte-level BPE(BBPE,字节级 BPE)

中文 "大模型"
   └── UTF-8 字节编码 ──► [0xE5, 0xA4, 0xA7, 0xE6, 0xA8, 0xA1, 0xE5, 0x9E, 0x8B] (9 个字节)
          └── BBPE 合并 ─────► [Token ("大"), Token ("模型")] (2 个 Token)
  1. 基础词表极小:以 256 个 UTF-8 字节0x00 ~ 0xFF)作为最小原子,初始词表只需 256 个单元。
  2. 真正的 100% 零 OOV:任何文字、代码、Emoji 都是合法的字节序列,彻底消灭 <unk>
  3. 预分词正则(Pre-tokenization)
    • 为避免把标点、空格、数字和英文随意黏连(例如把 hello! 合并为一个 Token),现代分词器在合并前会先用正则表达式将单词、数字、标点隔开,禁止跨界合并

五、 3 句话总结

  1. 为什么用分词器:大模型只能算矩阵,分词器用子词机制在“词表大小”与“计算效率”之间实现了最佳平衡。
  2. BPE 训练的本质:从单个字符/字节出发,按频率不断“两两合并”,直到攒够词表容量。
  3. 现代 BBPE 的威力:基于 256 个字节起步,搭配预分词正则约束,实现真正无死角的多语言与代码切分。
Share:
Back to Blog

Related Posts

View All Posts »
从预训练到指令微调:中文 LLaMA2 (204M) 原生 LoRA SFT 实战与 Kaggle GPU 避坑指南

从预训练到指令微调:中文 LLaMA2 (204M) 原生 LoRA SFT 实战与 Kaggle GPU 避坑指南

完整记录中文 LLaMA2(204M 参数)从预训练向 SFT 指令微调演进的全流程。深入剖析 Loss Masking 数据管道、原生手写 LoRA/QLoRA 模块实现,并深度复盘 Kaggle 云端 GPU 迁移中的只读文件系统、Git LFS 假死、GPU 架构兼容与跨设备张量对齐四大经典工程踩坑。文末联动 AstrBot Agent 微信机器人实现 GPU 训练定时看护。