你有没有发现一个怪现象:同样一个意思,用英文写,提示词能装下好几页;换成中文,三四千字就把上下文撑满了。我最早以为是中文表达太“密”,后来把 tokenizer 的输出打出来一看,差点以为是 bug——一句 10 个字的中文,被编码成了 20 多个 token。这跟模型聪不聪明没关系,纯粹是 tokenizer 在切分环节多做了一大笔“生意”。

Tokenizer 看起来只是“预处理”:把文字映射成数字而已。但它直接决定你的提示词能装多少内容、训练语料有多少 token、以及账单上的数字。今天我把这笔账算清楚。
Tokenizer 干的活:把文本切成模型数得清的“词块”
语言模型不读文字,它读整数。Tokenizer 负责把文字变成 id。“我爱自然语言处理”可能被切成这样:
[我][爱][自然][语言][处理] // 5 个 token
[我爱][自然语言][处理] // 3 个 token
到底是按字切、按词切、还是按半个词切,取决于词表和合并规则。现在大模型最常用的 BPE(Byte Pair Encoding)思路很直白:先拆成最小的字符,统计相邻字符对的出现次数,把高频对合并成一个 token,反复合并,直到词表达到目标大小。
“自然语言”频繁出现时,过程是这样的:
[自][然][语][言] → [自然][语][言] → [自然][语言]
这套算法最早是搞数据压缩的,后来被 Sennrich 等人引入 NLP,处理神经机器翻译中的稀有词。大模型后来普遍使用字节级变体,中文这种没有空格的字符也能用字节兜底,不会出现 OOV。
BPE、WordPiece、Unigram、SentencePiece 是什么关系
| 算法/框架 | 核心标准 | 代表模型 | 一句话解释 |
|---|---|---|---|
| BPE | 相邻字符对出现频率 | GPT、Llama、Qwen | 谁出现得多,就把谁焊在一起 |
| WordPiece | 合并后语言模型似然增益 | BERT | 不仅看频率,还要看合并后是否更“像”词 |
| Unigram | 删掉哪些 token 让整句损失最小 | ALBERT、部分 T5 | 从大词表出发,剪到最合适的大小 |
| SentencePiece | 预处理框架,内部跑 BPE/Unigram | Llama、Gemma | 把空格也当一个普通符号,适合中文、日文 |
注意 SentencePiece 不是算法,而是一套训练/预处理框架。它的特殊之处是把空格当作普通符号,不依赖语言确实有空格分隔,所以很适合中文、日文这种没有空格的语言。
差多少:同样语义,token 数能差两三倍
下面是一个具体例子。英文句 The quick brown fox jumps over the lazy dog. 和中文语义等价句 敏捷的棕色狐狸跳过懒狗。,用三类典型 tokenizer 编码,token 数大致如下。数字不是严格 benchmark,不同版本会有出入,重点看比例:
| Tokenizer 类型 | 英文句 | 中文句 |
|---|---|---|
| 英文单语字节 BPE(类似 GPT-2) | 10 | 约 22 |
| 多语言 SentencePiece(类似 Llama 2) | 10 | 约 12 |
| 中文增强 BPE(类似 Qwen 系列) | 10 | 约 9 |
同一句英文,三者差不多,因为空格分隔天然适合 BPE,高频英文单词几乎都变成了单个 token。同一句中文,差距就从约 22 降到约 9,达到两倍多。换成一篇 5000 字的中文文档,浪费的 token 是成千上万的。
为什么中文会被切成这么多 token
- 预分词规则:英文 tokenizer 依赖空格把单词分开,再按词合并;中文没有空格,如果训练语料里中文占比低,每个汉字只能以单个字符或字节形式留下。
- 词表语言覆盖:GPT-2 这类 tokenizer 的词汇表大半由英文子词构成,中文字符很少出现在合并路径上。
- 训练语料分布:Qwen 这类 tokenizer 在中文语料上训练过,不仅常用汉字是 token,连“狐狸”“跳过”这种双字组合也常被合并成一个 token。
训练成本:token 少 20%,不是省 20%
如果用来训练模型,差距会被放大成真金白银。假设语料是 1 亿字符的中文文本,低效 tokenizer 可能切出 2 亿 token,高效 tokenizer 只要 9 千万。业内常用的估算公式是 总计算量 ≈ 6 × 模型参数量 × token 数,具体推导可参考 Chinchilla 论文。多出来的 1.1 亿 token,就是多出来一截训练账单。
更麻烦的是长文档。Transformer 的 attention 理论复杂度是 O(n²):一段本来 2000 token 的文档,被切碎后变成 4000 token,attention 层的计算量不是 2 倍,而是 4 倍。所以 tokenizer 效率对训练成本的影响经常被低估。
别把所有常用词都塞进词表
我以前为压缩 token 做过一件傻事:把词表调得很大,把中文常用词尽量变成单个 token。结果模型在新词上泛化很差。后来才想明白,子词粒度恰恰是模型能组合出新词的原因。“自然语言处理”是 1 个 token,不等于模型就理解它;保留“自然”“语言”这些子模块,反而能让没见过的新组合也被拼出来。
因此 token 效率不是唯一指标。词表越大,embedding 表和 softmax 输出层也越大,训练和推理参数都会变多;过度合并还会伤害泛化。选 tokenizer 是在“省 token”和“保持子词可组合性”之间做平衡。
FAQ:三个容易搞混的问题
API 能自己选 tokenizer 吗?
不能。模型权重和 tokenizer 是绑定的,计费也按它自己的 encode 结果来。OpenAI 的 官方说明也写得很清楚:按 token 计价。你唯一能做的是改写 prompt,减少冗余空格、标点和重复表达。
词表越大 token 一定越少吗?
不一定。词表增长到一定程度后会饱和,多出来的词频几乎为零,却增加参数和训练开销。更常见的做法是让 tokenizer 在目标语料上多训练几轮,更合理地分配合并。
换 tokenizer 能提升效率吗?
能,但已发布模型换 tokenizer 要重新训练 embedding 层,很不划算。对从零预训练的模型,则值得在训练前多测几个候选 tokenizer。
我的建议:把 tokenizer 当成一个训练超参来调
我在训练前一定会拿真实语料给至少三个 tokenizer 跑一遍,统计 token-per-byte、平均序列长度、P99 序列长度以及稀有 token 覆盖率。不要只看平均,还要看长尾样本会不会把 max sequence length 推高。
短期看,tokenizer 不会消失。像 Byte Latent Transformer、MambaByte 这类想跳过 tokenizer 的方向还在探索阶段,还没有成为主流。但至少在这个时代,tokenizer 效率就是训练成本的一部分。早花 10 分钟评估,后面能省一大笔钱。
想自己验证?用两行代码跑 tokenizer
用 Hugging Face 的 transformers 跑一下:
from transformers import AutoTokenizer
for name in ["gpt2", "Qwen/Qwen2.5-1.5B-Instruct"]:
tok = AutoTokenizer.from_pretrained(name)
for text in ["The quick brown fox jumps over the lazy dog.", "敏捷的棕色狐狸跳过懒狗。"]:
print(name, len(tok.encode(text)))
如果只关心 OpenAI 系,可以装 tiktoken。想了解更底层的切分机制,看 Hugging Face Tokenizers 文档。
原创文章,作者:guanweilu,如若转载,请注明出处:https://guanweilu.cn/article/750.html