从0训练LLM · 现代大模型体系 实验 实验 ≈ 20 分钟 页内实验 ×1
C1 文字怎么变成数字 实验课

同一段话交给三个真分词器,token 数差多少?

实验课:真 tokenizer 与我们的 tokenizer 比一比
把任意文字交给 Qwen3.8、GPT-2 和我们自己在三国演义上训练的分词器,实时看 token 数、压缩率、fertility,以及汉字被拆成字节的地方。

本课一问:同一段话交给三个真分词器,token 数差多少?

这是本章的实验课,不引入新概念。这一课先不碰:分词器的训练代码(交给 AI 写,见本章项目推进)。只动手验证前面几课的结论。

② 从已有出发

你手里已经有的工具

从已有出发 · 累计数据流 点已学环节看形状与参数
解码层

前面几课的结论:切法由词表决定(第 1 课),子词取中间(第 2 课),词表从语料里统计出来(第 3 课),字节级 BPE 永不缺字但会把没学过的语言切碎(岔路 B2),词表大小是一笔交易(第 4 课)。下面把它们一次性放到真分词器上验证。组件打开时默认就是贯穿例句「小明把苹果给了小王」:Qwen3.8 5 个 token、GPT-2 21 个;我们在繁体古白话上训练的分词器,切简体的「苹」「给」只能退回字节——三行放在一起比。

③ 先自己设计

朴素的想法:分词器都差不多,随便挑一个

  1. ① 朴素方案
  2. ② 它在哪失败
  3. ③ 改进方案
朴素方案随便挑一个分词器,反正都能把文字变成 token

两个分词器都是字节级 BPE,都能把任何文字编码、再无损解码回来。看上去随便挑一个都行。

中文段落(209 字)同义英文段落(817 字符)
GPT-2461 个 token149 个 token
Qwen3.8106 个 token149 个 token
④ 实验

三个真分词器,同一段话

实验 · Tokenizer Playground(真 Qwen3.8 · 真 GPT-2 · 我们的分词器)

这节实验覆盖演进图里的两代:

文字 → 数字的演进:每一步都被上一步的某个真数字逼出来 · 高亮 = 本课
  1. 起点 每个字符直接用 Unicode 码点当编号 最容易想到的做法
    解决了不用训练,任何文字都能编号。
    卡在码点从 0 排到 1,114,111,embedding 表得开 1,114,112 行,是 Qwen3.8 实际 248,320 行的 4.5 倍;而且一个字一个 ID,序列一点不缩短。
  2. 朴素 ① 按字切(字符级词表) 只收语料里出现过的字
    解决了字符集封闭,词表小。
    卡在序列最长:209 字的段落按字切要做 43,681 次注意力打分,是 Qwen3.8 子词切分(106 个 token)的 3.9 倍。
  3. 朴素 ② 按词切 + 固定词表 早期神经机器翻译的做法(Sennrich 2015 的对照组)
    解决了一个词一块,序列短。
    卡在词是开放集合:我们的语料按空白和标点切出 70,310 种块,94.8% 只出现过一次;验证集里连训练集没见过的字都有 83 种——含它们的词只能记成 UNK。
  4. 第一代 子词 BPE(以字符为初始单元) Gage 1994 压缩算法 → Sennrich 2015 用于切词
    解决了按频次反复合并相邻对:常见的整块、罕见的拆碎,词表大小由合并次数控制。
    卡在初始单元是字符:要覆盖全部 Unicode,基础词表就超过 13 万(GPT-2 论文 §2.2);只收见过的字,我们的验证集照样缺 83 种字。
  5. 第二代 字节级 BPE(256 个字节兜底) GPT-2 2019 本课
    解决了初始词表就是 256 个字节,任何文字都能编码,永远不出 UNK。
    卡在词表没学过的语言会被切碎:同义的中文段落,GPT-2 切 461 个 token,Qwen3.8 切 106 个,差 4.3 倍;同义英文段落两者一样多。
  6. 第三代 为多语言训练的大词表 2024–2026 本课
    解决了中文常用字串整块进词表:「我喜欢机器学习」只要 2 个 token(GPT-2 要 17 个)。各家词表:DeepSeek-V3 129,280 · Qwen3 151,936 · GLM-5.3 154,880 · Kimi K3 163,840 · Qwen3.8 248,320。
    卡在两头各一张 V × d 的表:2 × 248,320 × 5,120 = 2,542,796,800 个参数,占 Qwen3.8 的 9.2%;同样的词表搬到我们 d = 256 的小模型上,embedding 占 95.3%。
  7. 表示 ① ID → Embedding 查表 神经语言模型的标准做法
    解决了ID 只是行号,取出一行 5,120 维向量;one-hot 乘矩阵与查表等价,但查表只拷现成的数。
    卡在表里的 1,271,398,400 个数(输入端一张表)从哪来?人填不完,也不知道每一维该是什么意思。
  8. 表示 ② 随机初始化、和整个模型一起训练的 Embedding Bengio 等 2003 → 今天的 LLM
    解决了用「预测下一个 token」的 loss 端到端训练,用法相近的 token 被推到相近的位置。
    卡在只在出现时才被推:我们的项目里一个 batch 8,192 个位置,平均只出现 1,453 个不同 token;罕见 token 学得少——MiniMax 的「嘉祺」在后训练里被推偏,输出层那一行与原来的余弦只剩 0.329。
  9. 表示 ③ 向量该多宽(hidden size d) 各家自选:GPT-2 768 · Qwen3-8B 4,096 · Qwen3.8-2.4T 8,192 · Qwen3.8-27B 5,120
    解决了维度越高,每个 token 能装的信息越多。
    卡在参数按 d² 涨:我们的模型 d 从 128 到 256,总参数 1,066,112 → 3,688,704,val loss 4.776 → 4.641(d = 256 这一档是参照 run ref_baseline,与项目基线同配置同种子;基线记录的 4.626 与它之差是重跑波动,见 C14);没有公式能给出「最优的 d」。
数字口径:码点总数按 Unicode 标准(U+0000–U+10FFFF);词表与宽度取各模型官方 config.json(anchor-model.json、frontier-models.json);token 数取真 Qwen3.8 / GPT-2 分词器的离线切分(c1-tokenize.json);语料统计、batch 覆盖、宽度扫描取项目实测(c1-tokens.json);「13 万以上」出自 GPT-2 论文 §2.2;「嘉祺」的余弦 0.329 出自 MiniMax 官方博客 2026-05-25(官方自报)。
👀

看什么: ① 同一句中文,三行的 token 数与压缩率差多少——这就是「词表对这种语言的友好度」;② 橙色虚线框:一个字被拆成了几个字节 token,框里的编号合起来才是一个完整的字(单独解码会显示 �,不是 bug);③ 点「strawberry 问句」:句中的「 strawberry」在两个大词表里都是 1 个 token;④ 点「价格与数字」:Qwen3.8 把 100 切成 1、0、0 三块,GPT-2 整块切;⑤ 点「三国演义原句」,再点「今天天气真好」,比较我们的分词器在「见过的文体」与「没见过的简体字」上的差别。

实验记录建议(给本章项目用): 选一段你的项目将来要处理的文字,把三行的 token 数、压缩率、fertility 抄下来。第 4 课的问题「词表该多大」,最终要用你自己语料上的这类数字回答。

压缩率与 fertility 的定义见第 2 课与第 4 课:压缩率 = 字符数 ÷ token 数;fertility 在文本以汉字为主时是「token 数 ÷ 汉字数」,以英文为主时是「token 数 ÷ 英文单词数」,混合文本不给 fertility。组件里所有数字都是对当前输入实时算的。

⚠

常见误解:「压缩率越高越好,没有代价」。 压缩率靠更大的词表换来:输入输出两张 V × d 的表跟着变大,罕见 token 得到的训练也更少(第 4 课)。Qwen3.8 能用 24.8 万的词表,是因为它有 5,120 维、约 278 亿参数来承担;我们的小模型用同样的词表,embedding 会吃掉 95% 以上的参数。

⑤ 极小真数字

实时算,离线核对

组件里的每个数字都对当前输入现算:token 数来自分词器本身,字符数与字节数由浏览器数,压缩率与 fertility 由它们相除。Qwen3.8 与 GPT-2 的分词器在页面里实时加载(第一次要从 Hugging Face 下载约 13 MB 的 Qwen3.8 词表文件);加载成功后,预置例句会与离线预计算的结果逐个 ID 核对,一致时显示 ✓。网络不通时,两者自动改用离线预计算结果,只覆盖预置例句,并在状态栏写明。我们的分词器直接在浏览器里用 JS 跑 BPE,启动时先与 Python 端的编码结果逐 ID 核对。

⑦ 证据三问

这些数字能信到什么程度

⑧ ⑨ ⑩ 带走与下一步

一句话、下一个缺口、你的提问

📌

一句话: 同一段话交给不同的分词器,token 数可以差好几倍;分词器要和你的语料、你的语言匹配——先测,再选或自己训。

下一个缺口

分词、查表都做完了:一句话已经是一个 [T × 5120] 的矩阵。但查表只看 ID,「小明打了小王」和「小王打了小明」得到的是同一组向量。C2:位置——模型怎么知道顺序

你的提问

C1 还没有真实提问记录。实验里遇到看不懂的切法,直接在对话里问,会归档到 05-问答记录/。回到本章导读看项目推进:用这些数字给我们自己的分词器定词表。