文本怎样变成 token:分词、词表与训练成本

成长演进

具备清晰骨架、初步数学推导或工程验证,持续补充论据与实测。

ℹ数字花园

🌿 什么是数字花园与思想演进?

遵循 Maggie Appleton 与 Andy Matuschak 的常青笔记理念:这里不是按时间沉底的流水账,而是持续修剪打磨的知识生态。文章按成熟度经历:

  • 🌱 萌芽 (Seedling): 发散灵感、读书摘录、开放猜想
  • 🌿 演进 (Budding): 结构渐明、初步代码推导、积极修正
  • 🌳 常青 (Evergreen): 逻辑闭环、持久经得起检验的基石
  • 🔬 实验待证 (Speculative): 结合沙盒与仿真探针的理论探索
浏览全站数字花园与笔记全景 →

第一卷结束时,我们已经知道怎样准备一份可追溯的语料。现在拿出其中一句话:

梯度累积不会减少模型参数占用的内存。

人看到的是一句中文,模型接收的却是一串整数。在它们之间,需要一个分词器:把文本拆成有限词表里已有的片段,再把片段换成编号。

token 是模型处理文本的单位,不一定是一个词,也不一定是一个字。 同样一句话,换一个分词器,长度和编号都可能变化。这件事会一路影响上下文长度、数据比例、训练成本和模型兼容性。

本篇先用自己设计的小例子说明分词为什么这样做,再讨论真实项目应该检查什么。例子里的切分不代表某个实际模型的输出。

为什么不直接把每个汉字编号#

这样当然能做。假设只处理少量常见中文,每个字一个编号,是容易检查的起点。

问题出在“只处理”这三个字上。技术语料包含英文标识符、数字、空格、代码、表情和生僻字。只收常见汉字会漏内容;为所有常见整词分配编号,又会遇到没见过的新词和拼写变体。

还有长度问题。把 optimizer 拆成九个字母,模型需要九个位置;如果词表里有这个完整片段,就可能只需一个位置。反过来,为每个偶尔出现一次的长字符串都建一个词条,词表会很大,许多条目还缺少足够训练机会。

子词方法采取一个折中:常见片段可以较长,不常见内容拆得更细。最初把 BPE 用于神经机器翻译的研究,正是为了改善罕见词和开放词表的处理。那是特定翻译实验的研究动机与结果,不能直接当作“任意大模型用 BPE 都最好”的证据。Sennrich 等,2016

用三条文本手算一次 BPE#

BPE 的直觉是:相邻片段经常一起出现,就尝试给它们一个共同编号。

为了能手算,我们先人为规定:每条文本独立处理,初始单位是汉字,不跨条目合并。语料只有下面三种字符串,次数也是假设的:

文本出现次数初始切分
数据库5数 / 据 / 库
数据集3数 / 据 / 集
库存2库 / 存

先数相邻对。数 + 据 出现八次,据 + 库 五次,据 + 集 三次,库 + 存 两次。所以第一轮合并 数 + 据 → 数据。

合并后,前两条变成 数据 / 库 和 数据 / 集。重新计数,数据 + 库 出现五次,成为下一轮合并对象。若此时停止,词表里有初始的五个字,也有新增的 数据 和 数据库,共七个条目。

原始语料的总长度是:

5×3+3×3+2×2=285\times3+3\times3+2\times2=28

第一次合并后减少八个位置,剩二十个;第二次再减少五个,剩十五个。这里计算的是这份教学语料的 token 数,没有加入任何特殊标记。

# 教学验证:三条文本的 BPE 相邻片段统计与首轮合并
from collections import Counter

corpus = [ (“数 据 库”, 5), (“数 据 集”, 3), (“库 存”, 2), ]

pairs = Counter() for text, freq in corpus: words = text.split() for i in range(len(words) - 1): pairs[(words[i], words[i + 1])] += freq

print(“初始相邻对出现频次:”) for (w1, w2), count in pairs.most_common(): print(f” {w1} + {w2} -> {count} 次”)

best_pair, best_count = pairs.most_common(1)[0] print(f”\n最佳首轮合并对: {best_pair[0]} + {best_pair[1]}(出现 {best_count} 次)”)

更短的序列不是凭空得到的:我们为此增加了两个词表条目。实际训练分词器时还要规定基础符号、边界、词表容量,以及频次相同怎样处理。

对于新文本 数据库存,按照这两条已学会的规则,在允许合并的片段范围内会得到 数据库 / 存。这未必符合人想表达的“数据 / 库存”。分词器学习的是统计规则,不负责保证每个片段都是语义上的完整词。

训练 BPE 是学习合并规则;使用它则是遵循既有规则及其优先次序。不能给每段新文本重新统计并另选一套合并方式,否则同一个编号体系就失去稳定意义。Hugging Face BPE 教程

算法前后还有几步会改变结果#

真实分词流程通常还涉及文本规范化、预切分,以及添加特殊标记。只说“用了 BPE”,不足以复现一次切分。Tokenizers 流程说明

可以把这些环节理解为四个不同问题:

环节要决定什么应检查的例子
规范化输入是否先改写大小写、全角字符、组合字符
预切分哪些边界不能继续合并空格、标点、数字和换行
子词切分在允许的范围内怎样拆分长英文标识符、中英混排
特殊标记处理是否额外添加模型约定的标记开始、结束、角色边界

假设我们把所有英文都改成小写。自然语言里某些区别可能不重要,但代码里的 UserID 和 userid 未必等价。后面的分词即使可以完全还原规范化后的文本,也不能恢复被前一步抹掉的大小写。

因此,“可以解码”要问清楚还原到哪里:是原始输入,还是规范化后的输入?是否保留空格和换行?解码时是否跳过了特殊 token?

SentencePiece 是一种分词工具,不能与某一种切分算法画等号。它的论文强调直接处理原始句子和语言无关的处理方式,并支持不同子词模型。讨论实验时要同时写清工具、算法和训练配置。Kudo 与 Richardson,2018

这些区别对于第一卷的清洗流程也有影响。如果上游已经合并空白,下游再宣称“分词器保留空白”,并不能补回原始代码缩进。

字节覆盖解决了什么,又没有解决什么#

字符词表可能遇到没收录的字符。另一种做法是从字节出发,再学习较长片段。UTF-8 文本可以表示成字节序列,因此包含完整字节基础单位的设计,能够避免仅仅因为没收录某个字符就无法表示它。Hugging Face BPE 教程

但“能表示”不等于“表示得紧凑”,更不等于模型理解它。

一个汉字可能由多个字节组成。若没有适合的合并片段,它可能占多个 token。反过来,常见中文片段也可能合并得很好。不能背诵一个固定的“中文一字等于几个 token”,然后用它估算所有模型。

另外,单个 token 可能只含一个字符的部分字节。逐个解码时看到不完整字符,不一定代表整个序列已经损坏;应同时检查完整序列的解码结果。若实际流程使用了有损规范化或清理,字节基础单位本身也不能保证从头到尾无损。

这给我们一个明确的检查方式:拿真实任务的文本看长度与还原结果,别只根据分词器名字下判断。

词表大小怎样进入模型成本#

词表不仅是一张文本到整数的对照表。模型通常要为每个 token 保存一个输入向量,还要为词表中的候选 token 计算输出分数。

设词表有 VV 个条目,隐藏维度为 dd,一个普通嵌入矩阵有 VdVd 个参数。以教学配置 d=512d=512 为例:

词表大小一个嵌入矩阵的参数量
16,0008,192,000
32,00016,384,000

这是矩阵形状的计算,不是某个模型的实际占用。如果输入嵌入和输出权重共享,这部分权重可以复用;如果不共享,输出矩阵还要另算。梯度、优化器状态、激活和实现细节会继续影响训练内存。

扩大词表可能缩短序列,也会增加相关矩阵的大小。对小模型,词表参数占比尤其值得检查。不能只看到“同样文本少了两成 token”,就宣布训练成本必然下降两成。

一个更合理的决策至少同时看三项:同一批原始文本被切成多长、整个模型因此增加多少参数、实际硬件上的吞吐与内存怎样变化。前两项可以先离线统计,第三项需要真实测量。

中英文比例为什么会跟着分词器变化#

第一卷讨论过数据混合比例。这里能看出,分词器也是比例定义的一部分。

假设一份固定文本集合,在分词器 A 下,中文部分得到八十万个 token,英文部分得到四十万个;中文 token 占比是三分之二。

再假设分词器 B 把同一批中文切成四十万个 token,英文仍然是四十万个。原始文件一字没变,中文 token 占比却变成一半。

这些是假设数字,只用于解释计量单位变化。如果训练按 token 预算取样,换分词器之后需要重新算每种来源实际进入训练多少文本。不能直接沿用旧表里的百分比。

同样,比较两个分词器时,“各训练一亿 token”与“各读取同一批完整文档”回答的是不同问题。前者固定预测位置的数量,后者固定原始内容;两者一般不能同时固定。报告里应明确选了哪个预算,再补充另一个口径的实际消耗。

后训练最容易出错的是编号兼容性#

如果从已有预训练模型开始 SFT,分词器通常应该与该模型配套。

原因不神秘。假设模型训练时编号 100 对应某个片段,它的嵌入向量和输出参数已经围绕这个含义学习。如果我们换一份词表,让编号 100 指向别的内容,即使词表大小相同,整数也不会自己携带原来的意思。

特殊 token 同样如此。结束标记可以是模型学习预测的目标,但它不是一种自动切断注意力的屏障。角色标记是否有效,也取决于模板、编号和模型曾经学到的用法。

因此,迁移时至少要核对:

  • 分词器文件和模型版本是否配套,编号映射有没有变化。
  • 模板是否已经添加开始或结束标记,后续编码是否又加了一遍。
  • 新增 token 后,模型的相关矩阵是否扩展,新条目是否得到训练。
  • 训练与推理是否采用同一套角色和结束约定。

这些是接口一致性问题。只检查“程序没有报词表越界”远远不够。

第一轮实验先检查表示,不急着训练模型#

本篇建议先完成一个小型分词审计。这是实验设计,不是已完成的分词器排名。

从已经划分好的验证文本中抽取中文说明、英文说明、代码、中英混排和罕见字符各一组。若要自行训练分词器,只使用训练部分学习词表;验证文本用于检查,不能反过来反复调整后仍当作未见测试集。

对每个候选保存原文、token ID、可读片段、总长度和完整解码文本。再看以下问题:

问题记录方式不能直接推出什么
是否损坏内容逐项比较原文与解码,解释允许的规范化能还原不代表模型理解
哪类文本切得长分类别报告总 token、字符数和长度分布平均更短不代表所有类别更短
是否容易截断固定上下文窗口,统计超长文档与被截断内容不截断不代表学得更好
边界是否一致检查空白、结束标记、角色模板格式一致不代表训练目标正确

字符数应明确按什么定义,例如 Python 字符串长度与用户看见的字形数量并不总一致。跨语言汇总时也别让数量最多的一类掩盖其他类别。

如果审计已经发现代码被改坏,或中英文比例发生明显变化,就先修复这些问题。只有表示层面过关,后续模型比较才有清楚的起点。

两个值得自己检查的问题#

第一,BPE 教学例子里,第一轮减少八个 token,为什么不能说“模型参数也减少了八个”?试着分别说出序列长度、词表大小和嵌入矩阵大小的关系。

第二,两个分词器在同一批中文文本上分别产生一万和七千个 token。你会怎样设计后续比较,区分“序列更短带来的预算变化”与“模型实际预测能力改善”?

接下来讨论语言模型究竟在预测什么。分词得到整数以后,还要明确每个位置用什么信息预测哪个目标;否则损失能下降,也可能是在学错任务。

文章概念网络

从章节进入正文概念。实线表示所属层级,虚线连接跨章节重复出现的概念,不代表因果关系。

67 / 67 个节点 · 正文标记术语与关键词概念

文本怎样变成 token:分词、词表与训练成本同一概念 / Shared concept同一概念 / Shared concept同一概念 / Shared concept同一概念 / Shared concept同一概念 / Shared concept同一概念 / Shared concept同一概念 / Shared concept同一概念 / Shared concept同一概念 / Shared concept同一概念 / Shared concept同一概念 / Shared concept同一概念 / Shared concept同一概念 / Shared concept同一概念 / Shared concept同一概念 / Shared concept同一概念 / Shared concept同一概念 / Shared concept同一概念 / Shared concept同一概念 / Shared concept同一概念 / Shared concept同一概念 / Shared concept同一概念 / Shared concept同一概念 / Shared concept文本怎样变成 token:分词、词表与训练成本文本怎样变成 token:分词、词表与训练成本准备一份可追溯的语料准备一份可追溯的语料token 是模型处理文本的单位,不一定是一个词,也不一定…token 是模型处理文本的单位,不一定是一个词,也不一定是一个字为什么不直接把每个汉字编号为什么不直接把每个汉字编号optimizeroptimizerSennrich 等,2016Sennrich 等,2016用三条文本手算一次 BPE用三条文本手算一次 BPE相邻片段经常一起出现,就尝试给它们一个共同编号相邻片段经常一起出现,就尝试给它们一个共同编号数 + 据数 + 据据 + 库据 + 库据 + 集据 + 集库 + 存库 + 存数 + 据 → 数据数 + 据 → 数据数据 / 库数据 / 库数据 / 集数据 / 集算法前后还有几步会改变结果算法前后还有几步会改变结果Tokenizers 流程说明Tokenizers 流程说明UserIDUserIDKudo 与 Richardson,2018Kudo 与 Richardson,2018字节覆盖解决了什么,又没有解决什么字节覆盖解决了什么,又没有解决什么Hugging Face BPE 教程Hugging Face BPE 教程词表大小怎样进入模型成本词表大小怎样进入模型成本中英文比例为什么会跟着分词器变化中英文比例为什么会跟着分词器变化数据混合比例数据混合比例后训练最容易出错的是编号兼容性后训练最容易出错的是编号兼容性第一轮实验先检查表示,不急着训练模型第一轮实验先检查表示,不急着训练模型两个值得自己检查的问题两个值得自己检查的问题语言模型究竟在预测什么语言模型究竟在预测什么分词分词tokentokenBPEBPE实验实验技术技术BPEBPE分词分词tokentoken语义语义规范化规范化分词分词tokentokenBPEBPE实验实验tokentoken规范化规范化BPEBPE分词分词tokentoken状态状态优化优化tokentoken分词分词数据混合数据混合tokentoken分词分词预训练预训练注意力注意力SFTSFT推理推理分词分词tokentokenPythonPython测试集测试集规范化规范化实验实验tokentoken分词分词BPEBPE
文本怎样变成 token:分词、词表与训练成本

文本怎样变成 token:分词、词表与训练成本

回到原文 ↗
BIBTEX

引用本文

@article{2026-10-20-llm-training-pretrain-01-tokenization,
  author  = {枫语},
  title   = {文本怎样变成 token:分词、词表与训练成本},
  journal = {枫语},
  year    = {2026},
  month   = {oct},
  url     = {https://meiluosi.github.io/posts/2026-10-20-llm-training-pretrain-01-tokenization/}
}
🌐

本篇知识星丛与滑动侧栈

文本怎样变成 token:分词、词表与训练成本 当前主轴

搜索

正在载入索引…