minbpe
AI大模型词元化算法的教学级实现,用800行代码揭开GPT-4/BPE tokenizer的工作原理
加载项目详情…
本应用为开源项目,仅供学习研究,请遵守其开源协议。
AI大模型词元化算法的教学级实现,用800行代码揭开GPT-4/BPE tokenizer的工作原理
加载项目详情…
本应用为开源项目,仅供学习研究,请遵守其开源协议。
想象一下:你在读一段英文小说,每一个英语单词都被拆成了一串看不见的数字 ID——这就是 LLM "看见"的世界。但这些 ID 究竟是怎么来的?凭什么 "hello" 变成 [15339],而 " world" 变成 [1917]?
这个看似简单的问题,困扰了无数 AI 学习者。而 Andrej Karpathy——前特斯拉 AI 总监、OpenAI 创始成员——用一个仅有 800 行 Python 代码的仓库给出了答案。
图1:minbpe 仓库中 GPT-4 tokenizer 词汇表可视化样例,展示常见词汇的 token 划分方式。
Byte Pair Encoding(字节对编码,简称 BPE)并不是 Karpathy 的发明。这套算法最早诞生于 1994 年,是一种用于压缩数据的无监督文本分词方法。2015 年,Sennrich 等学者将其引入 NLP 领域,用于处理机器翻译中的未登录词问题。
真正让 BPE 成为 LLM 标配的,是 2019 年 OpenAI 发布的 GPT-2。GPT-2 论文中详细描述了如何将 BPE 应用于字节级别文本,从而实现对任意输入的全覆盖——无论你输入的是英文、中文还是 Emoji,BPE 都能将其映射到固定大小的词汇表中。这套方法随后被 GPT-3、GPT-4、LLaMA、Mistral 等几乎所有现代大语言模型沿用。
然而长期以来,这些模型的 tokenizer 实现一直是一个"黑箱"——OpenAI 的 tiktoken、Cohere 的 sentencepiece 等虽然开源了代码,但往往伴随着大量工程优化和历史遗留代码,理解起来门槛不低。Karpathy 的 minbpe 正是为了填补这个教育性空白而诞生的。
minbpe 的设计哲学极度克制:整个仓库只有 4 个核心 Python 文件,加上 train.py(训练脚本)和 tests/(测试用例),所有代码加起来不超过 800 行。Karpathy 本人在 README 中写道:"你应该不会害怕阅读这些代码并理解它的工作原理。"
这种"透明即正义"的设计理念,使得 minbpe 成为目前最易理解的 BPE 实现之一。下面我们逐文件拆解其架构:
minbpe/base.py(约 180 行)—— 基类与核心工具函数
这个文件定义了 Tokenizer 基类,以及两个核心辅助函数:get_stats() 和 merge()。
get_stats() 接受一个整数列表(代表 UTF-8 字节序列),统计所有连续字节对的出现频率。merge() 则将列表中所有匹配指定字节对的连续片段,替换为新的 token ID。这两个函数构成了整个 BPE 算法的数学核心——它们用最朴素的 Python 代码,实现了从文本到 token 的往返映射。
Tokenizer 基类还实现了序列化/反序列化功能:调用 save() 会输出 .model(供程序加载)和 .vocab(供人工阅读)两个文件。这两个文件格式设计得非常简洁:.model 文件仅存储正则模式、特殊 token 和合并规则;.vocab 则以可读格式展示每个 token ID 对应的字节序列和合并来源。
minbpe/basic.py(约 90 行)—— 最简 BPE 实现
BasicTokenizer 是整个仓库最简洁的 tokenizer。它直接继承 Tokenizer,实现了三个核心方法:
train():从原始文本训练 vocabulary。核心循环极其直观——每次迭代找出出现频率最高的连续字节对,将其合并为一个新 token,重复直到 vocabulary 达到目标大小。encode():将文本转为 token ID 序列。逆过程:遍历字节序列,每次找到可合并的、merge index 最小的那对字节进行合并,重复直到无法继续。decode():将 token ID 序列还原为文本。直接通过预计算的 vocab 字典查询每个 ID 对应的字节序列,拼接后用 UTF-8 解码。整个过程不超过 20 行核心循环代码,任何学过 Python 基础的人都能理解。
minbpe/regex.py(约 230 行)—— GPT-2/4 的正则预处理
真实世界的 tokenizer 远比 BasicTokenizer 复杂。GPT-4 的 tokenizer 在 BPE 之前增加了一层正则表达式预处理:使用精心设计的 regex 模式,将文本按类别(字母、数字、标点、空格等)预先拆分,再对每个 chunk 分别执行 BPE。
这步正则拆分至关重要——它确保了 BPE 永远不会跨越类别边界进行合并。例如,"hello" 和 "world" 之间不会因为都含字母而被意外合并成一个 token。这个设计最初出现在 GPT-2 论文中,至今仍是所有主流 LLM tokenizer 的标准做法。
RegexTokenizer 另一个重要特性是特殊 token 支持。通过 register_special_tokens() 注册特殊 token(如 <|endoftext|>),encode() 方法提供了 allowed_special 参数来控制是否处理特殊 token——默认行为是遇到未声明的特殊 token 时抛出异常,这个"严格模式"设计是为了防止 prompt 注入攻击。
minbpe/gpt4.py(约 190 行)—— 精确复现 GPT-4 tokenizer
这是整个仓库最精妙的文件。GPT4Tokenizer 不是训练出来的,而是通过"逆向工程"从 OpenAI tiktoken 库中提取 GPT-4 的 vocabulary 和 merge 规则。
实现中有一个有趣的坑:GPT-4 tokenizer 对 256 个原始字节进行了"乱序"(byte_shuffle)——这显然是一个历史遗留的设计决策,Karpathy 在注释中直接写道:"这毫无意义,但历史原因导致必须处理它。" 代码通过 recover_merges() 函数从 tiktoken 的 rank 信息反向推导出 merge 规则,再通过 byte_shuffle 补偿字节乱序,最终与 OpenAI 官方 GPT-4 tokenizer 输出完全一致(精度验证:encode/decode 结果逐 token 对比)。
| 维度 | 详情 |
|---|---|
| 编程语言 | Python 3 |
| 依赖项 | regex、tiktoken(仅 GPT4Tokenizer 需要) |
| 代码总量 | ~800 行(含注释) |
| 核心类 | BasicTokenizer / RegexTokenizer / GPT4Tokenizer |
| License | MIT |
| 训练能力 | BasicTokenizer、RegexTokenizer 支持自定义训练 |
| 预训练模型 | GPT4Tokenizer 直接加载 cl100k_base(GPT-4 vocabulary) |
minbpe 的上手门槛极低。如果只是使用预训练的 GPT-4 tokenizer,只需 4 行代码:
from minbpe import GPT4Tokenizer
tokenizer = GPT4Tokenizer()
print(tokenizer.encode("hello world"))
# [15339, 1917]
如果想训练自己的 tokenizer,示例代码同样简洁:
from minbpe import RegexTokenizer
tokenizer = RegexTokenizer()
text = open("data.txt", "r").read()
tokenizer.train(text, vocab_size=32768)
tokenizer.save("my_tokenizer") # 生成 .model 和 .vocab
Karpathy 还在仓库中提供了配套的 YouTube 视频教程(BV1Q94直译版见 lecture.md),手把手讲解 BPE 的工作原理和代码实现。配合 exercise.md 中的阶段性练习,任何人都可以从零开始构建自己的 tokenizer。
minbpe 并非银弹。GPT4Tokenizer 是一个预训练 tokenizer,它无法被重新训练——train() 方法直接抛出 NotImplementedError。同样,save()/load() 也未实现(Karpathy 坦承这需要修改 base 类的序列化逻辑)。
性能也是关注点之一。minbpe 追求代码可读性而非极致速度。Karpathy 在 TODO 中明确提到:"未来计划写一个能处理大文件和大 vocabulary 的优化版本,或用 C/Rust 重写。" 对于生产环境中的超大规模语料,建议仍使用 tiktoken 或 sentencepiece。
minbpe 的价值不在于取代任何现有 tokenizer,而在于它是目前最透明的 LLM tokenizer 教学实现。
词元化(Tokenization)是 LLM 的第一道工序,但长期被"黑箱化"——大多数工程师只关心模型架构和训练数据,却忽略了"模型看到了什么"这个根本问题。minbpe 让这个问题变得可理解:任何有基本编程能力的人,都可以在 30 分钟内读完全部源码,理解 BPE 为什么这样设计。
在 LLM 越来越强调"可解释性"和"可审计性"的今天,minbpe 代表了一种难得的趋势——用最小化的代码,揭示最大化的原理。截至目前,该仓库已获得 10500+ GitHub Stars,社区也出现了 Rust 移植版本(gnp/minbpe-rs),进一步证明了其教育价值和技术影响力。

图2:minbpe 仓库中的 tokenizer 可视化资源,帮助理解 BPE 词汇表结构。
本分析基于 GitHub 最新代码(master 分支)。项目由 Andrej Karpathy 维护,MIT 协议开源。