Skip to content

🔬 Task 1 · 手搓一个分词器

封面

作者:Winnie | 2026-04-16 | 阅读时间约 10 分钟


📌 先说结论(给没空读完的人)

  • 🧠 分词器是模型的"眼睛" —— 它决定模型眼里的一段话是字、是词、还是子词碎片
  • 🏆 BPE = Byte Pair Encoding,GPT-4 / Llama 3 / DeepSeek 都在用的主流方案
  • 💡 核心思想一句话:统计哪两个挨在一起的片段最常见,合并它——重复 N 次
  • 我跑通了:25 次合并,三个测试句子全部 encode → decode 可逆

🎬 为什么想搞这个

报名了 Datawhale 的 diy-llm 课程——斯坦福 CS336 的中文共建版。

一共 15 章 + 6 个大作业,主线是从 0 手搓一个 LLM

  • 🔧 基础件(分词器 / Transformer / Optimizer)
  • 🚀 规模化(MoE / 分布式 / Scaling Laws)
  • 🎯 对齐(SFT / GRPO)

这是我打算长期啃的一个系列,和之前《推理王国》共读同级——都是学习类,不急着出结论,慢慢啃。

Task 1 是分词器。占坑先,慢慢补。


🗺️ 这篇讲什么(思维导图)

在真开始讲之前,先铺张地图让你知道我要带你走到哪:

🔬 分词器(Task 1)

├── 🧐 它在干嘛
│   └── 文字 → 数字序列(模型看到的世界)

├── 🍡 四种切法
│   ├── 字符级    粒度细 · 序列长
│   ├── 字节级    GPT-2 风
│   ├── 词级      OOV 严重
│   └── BPE 🏆    自适应 · 当前主流

├── 🔨 BPE 算法
│   ├── 拆字符 + </w> 词尾标记
│   ├── 统计相邻 pair 频次
│   ├── 合并最高频的 pair
│   └── 重复 N 次

├── 🧪 实战跑通
│   ├── 25 次合并
│   ├── 编解码可逆 ✅
│   └── 罕见字自动退回字符级

└── 🪞 连回 Harness Engineering
    └── 热路径合并,冷路径退化(同构)

(后面会迭代成真正可交互的 Markmap,先占坑)


🧠 分词器到底在干嘛

打个不严谨的比方:

模型看到的不是"你好世界",而是一串数字 [1234, 5678, 9012]

从文字到数字的那一步翻译,就是分词器干的。而"怎么切"这件事,直接决定:

  • 🪟 同一段话要用几个 token(上下文窗口够不够用
  • 🧩 罕见词、emoji、专业术语能不能表示(OOV 问题
  • 🎯 模型学到的语义单元合不合理(理解效率

🍡 四种切法,各有代价

切法粒度词表序列长代表模型
字符级100–5k非常长Char-RNN
字节级更细~256很长GPT-2
词级>100kWord2Vec
🏆 BPE自适应30k–100k适中GPT-4 / Llama 3

BPE 赢在哪 ——

常见词合成一大块(省 token),罕见词自动退回字符(零 OOV)。

一个会看菜下饭的分词器 🍽️


🔨 BPE 算法本体(30 行能写完)

初始化:每个词拆成字符 + 一个词尾标记 </w>

循环 N 次:
  1️⃣ 数一数:所有相邻字符对里,哪对最常见?
  2️⃣ 合并它:把所有这对出现的地方,粘成一个
  3️⃣ 记下来:这条合并规则存进 merges 表

这就是全部。简单到让人怀疑"真就这?"

为什么要那个 </w> 🤔 ——

标记词尾,防止 thee 和下一个词的 v 错误合并成 ev。小细节,但没它编解码就不可逆了。


🧪 实战:用 DeepSeek 风格正则训了一版

语料(故意塞了重复):

python
["这只猫🐈很可爱",
 "the quick brown fox jumps over the lazy 🐕‍🦺",
 "the the the quick quick brown fox",
 "猫猫猫很可爱很可爱"]

训练 25 次合并后,看它学到了啥 👀:

 1. (' ', '</w>')      ← 空格 + 词尾先合
 2. ('t', 'h')   → 'th'
 3. ('th', 'e')  → 'the'
 5. ('很', '可')  → '很可'
 6. ('很可', '爱') → '很可爱'
22. ('这', '只')  → '这只'
23. ('这只', '猫') → '这只猫'

看懂了吗:重复出现的 the / quick / brown / 这只猫 / 很可爱 都被合成了整块——这就是 BPE 的"自适应"。

编解码测试 3 个句子:

输入编码结果还原
"the quick fox"['the</w>', 'quick</w>', 'fox</w>']
"这只猫很可爱"['这只猫', '很可爱</w>']
"敏捷的棕色狐狸🦊"退回字符级(语料未覆盖)

第三个最有意思 💎 ——

它没学过"敏捷"这个组合,但它没崩,而是退回字符级照样给你切好。这就是"无 OOV"的工程美感。


🪞 连回我在搞的 Harness Engineering

⚠️ 这一段是 N=1 的直觉,不是方法论,只是一个让我停下来想了一会儿的联想。

BPE 的核心逻辑:热路径合并,冷路径退化

  • 🔥 常见搭配 → 合成一个大 token(节省预算)
  • ❄️ 罕见搭配 → 退回字符(保证覆盖)

我在搞的 Harness Engineering 的编排逻辑:热路径合并,冷路径独立

  • 🔥 常见协作 → 固化成 skill / 合并成一个 Agent 调用
  • ❄️ 罕见任务 → 临时拉一个新 Agent

同构 🎯。

都是"在有限预算下做自适应压缩"——只是一个压字数,一个压调用。


📋 打卡清单

  • [x] 课程仓库 clone 到本地
  • [x] 独立虚拟环境(.venv)搭好
  • [x] 第 1 章工具 wandb 装好(占坑用)
  • [x] 第 2 章理论过一遍(1321 行,捡重点看)
  • [x] BPE 代码跑通(25 merges + 3 case 可逆)
  • [x] 打卡笔记发博客 ← 就是这篇
  • [ ] 完整作业 Notebook(慢慢补)
  • [ ] DeepSeek tokenizer 拆解(慢慢补)
  • [ ] 四种分词器对比实测(慢慢补)

🌱 慢慢补

这个坑我会慢慢填——每个 Task 一篇,和《推理王国》并列成两条学习线。

目标:学完这 6 个作业,能手搓一个极简 LLM 从 0 到 1。

不为证明什么,就是想亲手摸一遍底层 🔧


Winnie · 2026-04-16

慢慢迭代,不追求完美。