【大模型】- 从零构建分词器

算法

从零构建分词器

第 01 课给你的是玩具。这课给你的是武器。

类型: 学习 | 语言: Python | 🏷 前置:《分词器:BPE、WordPiece、SentencePiece》(本系列第 1 篇)

学习目标

  • 构建处理 Unicode、空格规范化和特殊 token 的生产级 BPE 分词器
  • 实现字节级回退,使分词器可以编码任何输入(包括 emoji、CJK 和代码)而没有未知 token
  • 添加预分词正则表达式模式,在应用 BPE 合并之前按词边界分割文本
  • 在语料库上训练自定义分词器并评估其在多语言文本上与 tiktoken 的压缩比

问题

你从第 01 课得到的 BPE 分词器适用于英语文本。现在给它扔日语。或 emoji。或带有混合制表符和空格的 Python 代码。

它坏了。

不是因为 BPE 错了——是因为实现不完整。生产分词器处理任何编码的原始字节、在分割前规范化 Unicode、管理永不合并的特殊 token、将预分词与子词分割链接,并且所有这些都足够快,不会成为处理 15 万亿 token 的训练管道的瓶颈。

GPT-2 的分词器有 50,257 token。Llama 3 有 128,256。GPT-4 大约 100,000。这些不是玩具数字。这些词汇表背后的合并表在数百 GB 文本上训练,而周围的机制——规范化、预分词、特殊 token 注入、聊天模板格式化——是将处理 “hello world” 的分词器与处理整个互联网的分词器区分开来的东西。

你将构建那个机制。

概念

完整管道

生产分词器不是一个算法。它是五个阶段的管道,每个阶段解决不同的问题。

每个阶段有特定的工作:

阶段 做什么 为什么重要
规范化 NFKC Unicode、可选小写、可选去除重音 “fi” 连字变成 “fi”(两个字符)。没有这个,同一个词会获得不同的 token。
预分词 在 BPE 之前将文本分成块 防止 BPE 跨词边界合并。”the cat” 永远不应产生 token “e c”。
BPE 合并 将学习的合并规则应用于字节序列 核心压缩。将原始字节变成子词 token。
特殊 token 注入 BOS、EOS、PAD、聊天模板 token 这些 token 有固定 ID。它们永不参与 BPE 合并。
ID 映射 将 token 字符串转换为整数 ID 模型看到整数,不是字符串。

字节级 BPE

字节级 BPE 通过将每个可能的字节值(0-255)视为有效 token 来解决问题。你的基础词汇表恰好有 256 个条目。任何文件都可以分词而不会产生未知 token。

GPT-2 添加了一个技巧:将每个字节映射到可打印的 Unicode 字符,使词汇表保持人类可读。

真正的威力:字节级 BPE 处理地球上的每种语言。中文字符每个 3 个 UTF-8 字节。日文可以是 3-4 个字节。阿拉伯文、天城文、emoji——全部只是字节序列。

预分词

在 BPE 接触你的文本之前,你需要将其分成块。这防止合并算法创建跨词边界的 token。

GPT-2 使用正则表达式模式分割文本,在缩写、带可选前导空格的词、数字、标点和空格处分割。

选择很重要。GPT-2 的正则表达式防止分词器学习跨词边界的合并。SentencePiece 允许这样做,有时产生更高效的压缩但更难解释的 token。

特殊 token

每个生产分词器为结构 token保留 token ID。特殊 token 永远不会被 BPE 分割。它们在合并算法运行之前被精确匹配,替换为固定 ID。

聊天模板

这是大多数人困惑和大多数实现崩溃的地方。模型看不到 JSON。它看到一个扁平的 token 序列。聊天模板使用特殊 token 将消息转换为该扁平序列。每个模型都这样做不同。搞错模板,模型就会产生垃圾输出。

速度

Python 对生产分词来说太慢了。tiktoken 用 Rust 编写,HuggingFace tokenizers 也是 Rust。SentencePiece 是 C++。这些比纯 Python 快 10-100 倍。

构建

步骤 1:字节级编码

基础。将任何字符串转换为字节序列,将每个字节映射到可打印字符用于显示,并反转该过程。

“hello” 是 5 个字节。”你好” 是 6 个字节(每个字符 3 个)。fire emoji 是 4 个字节。字节级分词器不在乎它是什么语言。字节就是字节。

步骤 2:带正则表达式的预分词器

使用 GPT-2 正则表达式模式将文本分成块。每个块由 BPE 独立分词。

regex 模块支持 Unicode 属性转义。标准库 re 模块不支持,所以我们回退到 ASCII 字符类。对于生产多语言分词器,安装 regex。

前导空格保持附着在词上。缩写在撇号处分割。标点成为自己的块。BPE 永远不会跨这些边界合并 token。

步骤 3:字节序列上的 BPE

第 01 课的核心算法,但现在独立地在预分词块上操作。

步骤 4:特殊 token 处理

特殊 token 需要精确匹配和固定 ID。它们完全绕过 BPE。

步骤 5:完整分词器类

链接所有内容:规范化、按特殊 token 分割、预分词、BPE 合并、映射到 ID。

步骤 6:多语言测试

真正的测试。扔英语、中文、emoji 和代码给它。

中文字符每个产生 3 个字节。emoji 产生 4 个字节。这些都不会使分词器崩溃。都不会产生未知 token。这就是字节级 BPE 的威力。

使用

比较真实分词器

加载 Llama 3、GPT-4 和 Mistral 的实际分词器。看看每个如何处理相同的多语言段落。

你会看到相同文本的不同 token 数。128K 词汇表的 Llama 3 在合并常见模式上更积极。100K 的 GPT-4 居中。32K 的 Mistral 产生更多 token 但有更小的embedding 层。

权衡始终相同:更大的词汇表意味着更短的序列但更多参数。

交付

本课产出一个构建和调试生产分词器的 prompt。见 outputs/prompt-tokenizer-builder.md

练习

  1. 添加一个 get_token_bytes(id) 方法,显示任何 token ID 的原始字节。用它检查你最常见的合并 token 实际代表什么。
  2. 实现 Llama 风格的预分词器,在空格和数字处分割但保留前导空格。在相同语料库上将其词汇表与 GPT-2 正则表达式方法比较。
  3. 添加一个聊天模板方法,接受消息列表并为 Llama 3 聊天格式生成正确的 token 序列。对照 HuggingFace 实现测试。

关键术语

术语 人们怎么说 实际含义
字节级 BPE 在字节上工作的分词器 基础词汇表为 256 个字节值的 BPE——处理任何输入而没有未知 token
预分词 BPE 之前的分割 正则表达式或基于规则的分割,防止 BPE 跨词边界合并
NFKC 规范化 Unicode 清理 规范分解后跟兼容性组合
聊天模板 消息如何变成 token 将角色/内容消息列表转换为扁平 token 序列的精确格式
特殊 token 控制 token 绕过 BPE 的保留 token ID
Fertility 每个词的 token 数 输出 token 与输入词的比率
tiktoken OpenAI 分词器 带 Python 绑定的 Rust BPE 实现
合并表 词汇表 训练期间学习的字节对合并的有序列表

延伸阅读


📚 本文改编自 AI Engineering from Scratch(MIT License · 作者 Rohit Ghumare),中文内容来自官方中文镜像。原课程共 503 课 · 20 阶段 · 免费开源,教程网站见 aiengineeringfromscratch.com

📝 自我检查(课程配套测验)

Q1(学前) 为什么基础 BPE 分词器在多语言或代码输入上会失效?

A. BPE 本质上是单语的
B. 没有正确的 Unicode 处理、字节回退和预分词正则,会产生不正确或低效的 token 序列
C. 多语言文本无法分词
D. BPE 只适用于 ASCII

答案: B 解析: 朴素的 BPE 实现可能无法正确处理多字节 Unicode 字符,可能在词边界处错误合并,且可能对训练词表外的字符缺少字节级回退。

Q2(学前) 生产级分词器中预分词正则的作用是什么?

A. 移除标点
B. 在 BPE 合并前于词边界切分文本,防止跨空格和词边界合并
C. 压缩空白
D. 将文本转为小写

答案: B 解析: 预分词正则将文本切成块(通常在词边界、数字和标点处),使 BPE 合并只在块内发生。否则 BPE 可能把 ‘end’ 与下一词前的空格合并。

Q3(学后) 什么是特殊 token,为什么分词器要专门处理它们?

A. 出现频率高的 token
B. 如 <|endoftext|> 或 等保留 token,用于控制模型行为,必须编码为单一、特定的 ID
C. 嵌入值最高的 token
D. 仅评估时使用的 token

答案: B 解析: 特殊 token 有结构用途:标识文档边界、填充序列、指示生成起止。必须识别并编码为精确 ID,不能拆成子词。

Q4(学后) 如何评估自定义分词器是否良好?

A. 检查能否分词你的名字
B. 在多样化文本上测量压缩比(每字符 token 数),并与 tiktoken 等成熟分词器比较
C. 统计词表大小
D. 仅测量编码速度

答案: B 解析: 压缩比(每 token 字节数或每词 token 数)衡量效率。好的分词器对相同文本产生更少 token,意味着更多内容能放入上下文窗口。跨语言和领域比较。

Q5(学后) 为什么现代 LLM 偏好字节级 BPE 而非词级分词?

A. 更快
B. 能表示任何输入而无未知 token,同时为常见模式学习高效子词合并
C. 产生更小词表
D. 词级分词更准确

答案: B 解析: 词级分词器无法处理未见词(产生 token)。字节级 BPE 从原始字节开始(保证任何输入可覆盖),并为常见序列学习合并,在覆盖与效率间取得平衡。

  • 标题: 【大模型】- 从零构建分词器
  • 作者:
  • 创建于 : 2026-08-19 09:02:00
  • 更新于 : 2026-08-21 16:20:11
  • 链接: https://sxl-space.tk/2026/08/19/010_LLM/010_LLM-02-BuildingATokenizer/
  • 版权声明: 版权所有 © 宋,禁止转载。