从0训练LLM(旧版 · 存档中) · M1 阅读+实验 ≈ 45 分钟 页内实验 ×3

分词:把文字变成数字

神经网络只认识数字。你写的每一句话,在进入模型之前,都会被切成一串「token」并编号。这一步的每个决定,都会跟随模型一生。
① 主线定位

这一步在整条主线的哪里

我们的目标:亲手从 0 训练一个属于你自己的小 LLM。

整个项目的第一件事,不是搭神经网络,而是解决一个更基础的问题:模型的输入到底是什么?

你将在这个模块里为我们的项目做出第一批真正的工程决策——训练一个属于你自己的分词器(tokenizer)。它决定了:

🧭

上游: M0 我们盘点了算力,定下了要训练的模型规模。
本节: 把文字世界翻译成数字世界的「翻译官」。
下游: M2 的 Transformer 收到的第一个输入,就是这里输出的 token id 序列。

② 核心问题

神经网络只吃数字,怎么把文字喂给它?

以及:切多细?这是一个两难。

神经网络内部只有矩阵乘法,输入必须是数字。所以必须有一个环节:把文本切成单元(token),再给每个单元一个编号(id)。问题是切多细:

切法例:「今天天气真好」/ “unhappiness”好处致命伤
按字符切今|天|天|气|真|好 · u|n|h|a|p|p|i|n|e|s|s词表极小,永不缺字序列太长。注意力计算量随长度平方增长(M2 会亲眼看到),成本爆炸
按词切今天|天气|真好 · unhappiness序列短词是开放集合:新词、错拼、生僻词永远收不完 → 词表爆炸 + 一堆「未知词」
子词(subword)今天|天气|真|好 · un|happi|ness折中:常见词整块保留,罕见词拆成碎片。词表大小可控,且理论上永不缺字。现代所有主流 LLM 都用这条路。
❓

那「子词」怎么来?谁决定 un、happi、ness 是合法单元?——**不是人工规定的,是从数据里统计出来的。**这就是下一节的 BPE 算法。

③ 原理 · 直说

BPE:把「最常一起出现的相邻碎片」不断合并

Byte Pair Encoding。GPT 系、LLaMA、Qwen、DeepSeek 的分词器都是它(或它的变体)。

BPE 的训练过程只有一条规则,重复执行:

🔍 深挖 BPE 的来历

BPE 不是为 NLP 发明的——它 1994 年就存在,原本是一个数据压缩算法。把它引入 NLP 做子词切分的是 Sennrich 等人 2015 年的论文 《Neural Machine Translation of Rare Words with Subword Units》(arXiv:1508.07909 · ACL 2016)。当时要解决的问题是机器翻译里的「罕见词」:词表装不下所有词,遇到没见过的词就只能输出 UNK。子词切分让罕见词由已知碎片拼出来——这个动机和我们今天用它的理由完全一致。PDF 已在本地论文库。

1. 把语料全部拆成最小单元(字符/字节),作为初始词表;
2. 统计所有相邻单元对的出现次数;
3. 把出现最多的那一对合并成一个新单元,加进词表;
4. 回到第 2 步,直到词表达到你设定的大小(这是一个你来定的超参数)。

训练完成后得到两样东西:词表(每个单元一个 id)和合并规则表(按学习顺序排列)。之后任何新文本,按同样顺序应用合并规则,就能切出 token——哪怕这个词从没见过。

动画 · 一句话的旅程
今天天气真好
第 0 步 · 原始文本。对模型来说这只是一串字节,还什么都不是。
今天天气真好
第 1 步 · 切分。分词器按合并规则把文本切成 token。「今天」「天气」常见 → 整块保留;示意切法,真实切法见下面的实验。
104588今天108704天气99461真52801好
第 2 步 · 查表编号。每个 token 换成词表里的 id(示意值)。文字到此消失,只剩整数序列。
[104588, 108704, 99461, 52801] ⟶ Transformer
第 3 步 · 进入模型。M2 里我们亲手搭的 Transformer,收到的就是这串数字。它对「文字」一无所知——它的全部世界就是 token id。
1 / 4
模拟器 · 亲手训练一个 BPE(真算法)

语料只有 4 个词(右上角是出现次数)。每点一次「执行合并」,就执行一步真正的 BPE 训练:先看下面的相邻对统计表,最高频的一对(紫色行)将被合并。

相邻对出现次数
已学到的合并规则(按顺序):
(还没有)
🎯 合并 8~10 步后试试:用学到的规则切一个语料里没有的新词 lowest →

从字符到字节:中文怎么办

上面演示用的是英文字符。真实的现代分词器(GPT-2 起)用的是 字节级 BPE:初始单元不是字符,而是 256 个字节。一个汉字在 UTF-8 里是 3 个字节,所以「没学过中文合并规则」的分词器,会把一个汉字拆成 2~3 个 token——同样一句话,token 数是中文友好词表的两三倍,训练和调用成本也翻倍。这就是为什么 Qwen、GLM、DeepSeek 都训练了自己的大词表。字节级的好处:任何语言、emoji、乱码都能表示,永远不会出现「未知字符」。

🔍 字节级 BPE 的出处

字节级 BPE 首次被系统提出和使用,是在 GPT-2 论文的 §2.2 “Input Representation”(本地 PDF)。它做了两个关键决定:① 用 256 个字节值而非 Unicode 码点作基础词表(Unicode 有 14 万+ 字符,直接当基础词表太大);② 规定禁止跨字符类别合并(空格除外)——否则贪心合并会产生 dog. dog! dog? 这种把标点粘在词上的劣质 token,白白浪费词表空间。这个「禁止跨类别合并」的细节,就是 GPT-2/GPT-4 的分词器要先用正则把文本预分块、再在块内跑 BPE 的原因。

透视 · 你的文字在 UTF-8 里是几个字节(真实编码)

关键指标(本模块的验收数字)

词表大小你定的超参。GPT-2: 50,257 · LLaMA-2: 32,000 · Qwen2.5: ~152K · DeepSeek-V3: ~129K。越大→序列越短,但 embedding 参数越多
压缩率字符数 ÷ token 数。同一份语料,压缩率越高,训练/推理越省钱。下面实验里会实时算给你看
Fertility平均每个词被拆成几个 token。对中文:平均每个汉字几个 token。>1.5 说明词表对这种语言不友好
④ 亲手实验 · 真模型

用真正的分词器切你自己的话

下面加载的是真实模型的真实词表(GPT-2 英文向 vs Qwen 中文友好),不是演示数据。第一次加载需要从 HuggingFace 下载几 MB 词表文件,请稍等。

实验 · Tokenizer Playground
⏳ 正在加载真实分词器……(若加载失败会自动切换到内置演示模式)
👀

看什么: ① 同一句中文,GPT-2 要用多少 token、Qwen 要多少(这就是「词表对语言的友好度」);② 橙色虚线框=一个字符被拆成了好几个字节 token(下面详解);③ strawberry 被切成 st|raw|berry——模型看到的是这 3 块,而不是 10 个字母,所以它数不出有几个 r。

橙色虚线框:字节级 BPE 的真面目

用 GPT-2 切中文时,你会看到大量橙色虚线框,每个框里挤着 2~3 个 token id。这不是显示错误,而是字节级 BPE 的本来面目:

  • 一个汉字在 UTF-8 里是 3 个字节(如「今」= E4 BB 8A);
  • GPT-2 的词表几乎没学过中文合并规则,所以这 3 个字节各自成为一个 token;
  • 单独拿出任何一个字节都不是合法字符,所以直接显示会变成 �——必须 3 个字节凑齐才能还原出「今」。虚线框就是把「凑齐一个字」所需的 token 圈在一起。
💰

这就是成本: 同样写「今天天气真好」,Qwen 花 4 个 token,GPT-2 要花 10 个(实测)。token 数 × 单价 = 钱,同时你的上下文窗口也被消耗了 2.5 倍。这是「词表要不要为你的语言而训练」的最直接后果。

🔍

反过来看 Qwen: 它的 15 万词表里学过大量中文合并规则,「今天」直接就是一个 token——所以你几乎看不到虚线框。这正是我们在项目里要为自己的语料训练分词器的原因。

⑤ 项目推进 · 从 0 搭

给我们的模型,训练它自己的分词器

记住分工:代码 AI 来写,决策你来做。这里列出的是你要拍板的事,和验收标准。

本模块的项目任务单

步骤你的决策 / 动作说明
1. 选语料中英比例(建议中文为主 + 少量英文/代码)分词器只认识「它见过统计的」,语料配比直接决定压缩率
2. 定词表大小8,192(我们的推荐起点)我们的模型很小(~50M 参数),词表太大 embedding 会占掉大半参数预算
3. 让 AI 写训练脚本向 Claude 描述需求,指定用 HF tokenizers 库(或 Karpathy 的 minbpe 学习版)脚本 50 行左右;跑几分钟
4. 验收算三个指标:中文压缩率、英文压缩率、每汉字 token 数及格线:中文压缩率 ≥ 1.5 字符/token(即平均每 token 至少 1.5 个汉字)
5. 冻结把词表和合并规则存入 04-实践项目/tokenizer/,git 提交从此不可再改——它将陪伴模型一生(原因见下)
⚠️

为什么「冻结后不可再改」: M2 里模型的 embedding 矩阵按「id → 向量」一一对应训练。换了分词器,id 全变,之前训练的所有权重立即作废。分词器和模型是终身绑定的。

预告下一个坑(M3 会遇到): 训练语料里如果混有大量重复网页模板,BPE 会把整段模板学成一个 token——词表被垃圾占据。到时候我们要做数据去重,那是 M1 数据部分的延续。

⑥ 坑与带走

常见的坑

坑症状 / 后果怎么办
用英文向词表处理中文token 数 ×2~3,训练成本、API 费用翻倍,上下文窗口被浪费中文项目必须用中文友好词表;上面实验亲测对比
特殊 token 漏配模型不知道对话在哪结束,无限续写BOS/EOS/对话模板 token 在 M4(SFT)会正式登场,届时回看
训练后换分词器全部权重作废训练前定死,git 冻结
数不出 strawberry 的 r被误认为「模型太笨」本质是 token 粒度问题:模型看不见 token 内部的字符
词表大小拍脑袋小模型配 15 万词表 → embedding 占掉 70% 参数词表大小要和模型规模匹配(我们 50M 模型配 8K 词表)

这一模块你应该带走的

💬

有不明白的? 随时在对话里问我(比如「为什么合并次数越多词表越大」「Qwen 的词表怎么定到 15 万的」)。我会把每个问题和解答记录到 05-问答记录/M1-分词-问答.md,它们会成为你自己的 FAQ 库。

🔭

概念索引(本模块登场): token · 词表 vocabulary · BPE · 字节级 BPE · 压缩率 · fertility · embedding(预告,M2 正式登场)· 特殊 token(预告,M4 正式登场)

深入这一模块

想往下钻,按你愿意花的时间选

下面每条我都实际验证过可访问、且讲清了「为什么」。速通型内容、同质化转载已被排除。

🎮 想更直观 10–30 分钟

用真工具玩一下,比读十页字管用
5–10 min中文可输入需联网 OpenAI · 免登录
切到 GPT-3 (legacy) 输入「你好,世界」→ 11 个 token,还会弹警告说你的输入包含映射到多个 token 的字符;切到 GPT-5.x → 同一句只要 3 个 token。
为什么值得玩:这是「词表迭代如何降低中文成本」最直接的实证——同一家公司、同一句话,几年之间差 3.7 倍。对应本模块「字节级 BPE」「词表大小的时代演进」。
10 min界面英文需联网 开源 · 20+ 分词器
可切换 gpt2 / cl100k / o200k / GPT-4o / Llama-3 / DeepSeek-R1 / Qwen2.5 等 20 多种词表,对比同一句中文在不同模型下的 token 数。
为什么值得玩:比上面覆盖的模型广得多,是做「跨模型对比」的趁手工具(Karpathy 视频里也推荐它)。对应「fertility」「中文 vs 英文词表成本」。
20 min英文需联网 官方教程 · 含思考题
用 5 个词(hug/pug/pun/bun/hugs)手把手走完整轮合并迭代,中间穿插「你猜下一步会合并哪对?」
为什么值得看:和你刚才玩的 BPE 模拟器互为印证——你先动手跑过,再看它的推导会更快。

🔬 想吃透原理 1–3 小时

这一档只有一个必看,其余是它的替代品或补充
2h13m中文字幕(人工精校)需联网 36 万播放 · 2024-02
从零手写一个能训练/编码/解码的 BPE 分词器(对标 GPT-4 的 tokenizer)。覆盖:字符级→字节级的动机、正则预分块、特殊 token、SentencePiece 对比、为什么模型数不清 strawberry 的字母、为什么 GPT 做算术容易错、为什么中日文 token 更贵、vocab.json / merges.txt 与模型的绑定关系。
为什么是必看:它几乎覆盖了本模块的每一个概念,且是唯一「既讲清为什么、又有完整手写代码」的资源。中文字幕是人工精校非机翻——英文不熟也能啃。配套代码:minbpe(含 lecture.md 讲稿 + exercise.md 四步练习)。
长文英文需联网 2025-10 · 含时间戳跳转
把 2h13m 视频改编成可阅读文章,保留全部代码片段,每段可跳回视频对应时刻。
什么时候用:不想看视频、或想快速回查某个点时用。
4–5 千字中文需联网 极客公园 · 2026-05
实测 22 段平行文本对比 5 个分词器:Claude 中文比英文多耗 11%–64% token,而 DeepSeek/Qwen 反而中文更省。并追到根因——GPT-2 时代训练语料以英文为主,中文字符频率不足以被合并成 token。
为什么选它:这是我找到的唯一一篇把「中文税」讲出完整因果链(数据 → 算法 → 历史)而非罗列结论的文章。直接对应你刚才在实验里看到的 GPT-2 vs Qwen 差距。
4500 字 + 代码中文需联网 MartinLwx 个人博客
从字节/比特概念讲起,用 "the cat in the hat" 演示多轮合并,附完整可运行的 BPE 类。
为什么选它:中文原创技术博客里最扎实的一篇,且诚实地讨论了实现细节的坑(不是抄来的模板文)。
长文 + 完整代码英文需联网 Sebastian Raschka · 2025-01
从零实现 GPT-2→GPT-4 系列用的 BPE,重点讲 256 字节初始词表的设计动机。
为什么选它:作者是《Build a LLM From Scratch》的作者,本课程 M1–M4 的深读书目也是他的书。代码可直接跑。
2 千字英文需联网 2025-03
模型看到的从来不是 S-T-R-A-W-B-E-R-R-Y,而是 st|raw|berry 三块;字母 R 被「囚禁」在 token 内部,模型只能基于 token 关系猜测而非逐字符计数。
为什么选它:网上讲 strawberry 的文章大多是「AI 好笨」的猎奇稿,这篇是少数真正落到「token 粒度与字符计数错位」这个根因上的。

📄 想读原文 按需

PDF 都已下载到本地论文库,点击直接打开
BPE 引入 NLP 的原点 Sennrich, Haddow, Birch · 2015-08 · ACL 2016
把 1994 年的 BPE 压缩算法引入 NLP 做子词切分。注意:BPE 算法本身不是这篇发明的,这篇是把它用来解决机器翻译的罕见词问题。
字节级 BPE 出处 Radford 等 · OpenAI 2019 · 看 §2.2 Input Representation
字节级 BPE 首次被系统提出:用 256 字节作基础词表 + 禁止跨字符类别合并(避免产生 dog. dog! 这类劣质 token)。
🚫

我替你排除了什么(也是筛选标准): 「5 分钟搞懂 BPE」类速通视频(只给结论无推导)· CSDN/掘金上互相转载的同质化模板文 · strawberry 问题的猎奇新闻稿(停在「AI 好笨」层面)· 2023 年前只讲 jieba 中文分词的旧文(已被 subword 范式取代)。
另注:李沐、李宏毅的频道整体质量很高,但没有专门讲分词/BPE 的深度单集,所以本模块没列——他们的内容会在 M2/M3 出现。

已是第一个模块 M2 手搭 Transformer:让 token 互相看见 →