字节级 BPE:为什么 DeepSeek 的分词表里没有中文?¶
学习词元(token)和分词器的时候,我下载了 DeepSeek 官方提供的
deepseek_v4_tokenizer.zip。打开tokenizer.json翻词表,第一眼完全没看到汉字,全是!、#、Ġthe和一堆像乱码一样的字符。当时的第一个疑问是:这样的词表,到底是怎么训练出中文的?
省流¶
这份词表不是没有中文,而是用了字节级 BPE(byte-level BPE)。词条不是"字符/词语",是 UTF-8 字节片段;为了能放进 JSON 又保持可读,每个字节被映射成一个可见字符(GPT-2 风格的 bytes_to_unicode)。所以中文字符在词表里,就以"乱码"的形式存在。
比如词表里 id 30594 的词条长这样:
| Text Only | |
|---|---|
按同样的映射还原成 UTF-8,就是"你好"。
拆开 tokenizer.json¶
这份分词器的词表规模是 128,000:
| Text Only | |
|---|---|
256 个单字节 token 全都在,unk_token 为 null,所以任何 UTF-8 文本都不会出现 OOV。
预切分器(pre-tokenizer)是一个规则序列,其中有一条是专门给中文的:
| Text Only | |
|---|---|
把 128,000 个词条按同样映射还原一遍,有 35,334 个含汉字。常用单字也都有独立 token,例如:
| 汉字 | token id |
|---|---|
| 的 | 301 |
| 一 | 378 |
| 是 | 389 |
| 不 | 422 |
| 了 | 429 |
| 中 | 525 |
| 我 | 531 |
实际切几个中文例子(token 字符串是 tokenizer.json 里的原始样子):
| 文本 | token 字符串 | token id |
|---|---|---|
| 你好 | ä½łå¥½ |
[30594] |
| 你好世界 | ä½łå¥½ + ä¸ĸçķĮ |
[30594, 3427] |
| 深度求索 | 深度 + æ±Ĥ + ç´¢ |
[17180, 1645, 4568] |
下面看这些字节 token 是怎么从中文语料里长出来的。
中文语料是怎么"训练"进去的¶
字节级 BPE 的训练流程可以概括成四步:
- 预切分:先把语料按规则切成"词",连续的中文片段会被单独切出来,避免和英文、数字混在一起。
- 字节化:每个词转成 UTF-8 字节序列,每个字节对应一个基础 token。一个汉字是 3 个字节。
- 统计合并:统计相邻 token 对的出现频率,把最高频的一对合并成新 token 加入词表,重复到词表达到目标大小。
- 训练完成:所有合并结果就是
merges,最终词条就是vocab。
不需要预先准备一张中文词表。只要语料里"你 + 好"经常相邻出现,BPE 就会把它们合并成 id 30594;"中文""世界""分词器"也一样。反过来,如果语料里完全没有中文,"你好"就永远是 6 个字节 token,不会产生任何合并。
踩坑:AutoTokenizer 返回空列表¶
按官方示例,把输入从 "Hello!" 改成中文:
| Python | |
|---|---|
输出是空列表 []。这不是文件编码问题(确认过 .py 文件保存为 UTF-8),而是 transformers 5.x 的一个兼容问题。
原因:tokenizer_config.json 里写的 tokenizer_class 是 LlamaTokenizerFast。transformers 5.x 重构之后,加载这个类时会强制把预切分器换成 Metaspace("▁"),丢掉 tokenizer.json 里真正的 CJK/ByteLevel 规则。"你好"先变成 ▁你好,但词表里没有 你、好 这两个直接字符(它们以字节形态存在),unk_token 又是 null,匹配不到的内容就被静默丢弃,最终得到空列表。英文还能正常输出,只是碰巧 ASCII 字母在两种规则下的样子完全一样。
| 加载方式 | encode("你好") |
|---|---|
transformers 5.16.1 + AutoTokenizer |
[] |
transformers 4.49.0 + AutoTokenizer |
[30594] |
tokenizers.Tokenizer.from_file("tokenizer.json") |
[30594] |
修复办法任选其一:
| Python | |
|---|---|
| Python | |
|---|---|
如果还想继续用 AutoTokenizer.from_pretrained,把 tokenizer_config.json 里的 "tokenizer_class": "LlamaTokenizerFast" 改成 "PreTrainedTokenizerFast" 就行;或者直接 pip install "transformers<5"。
小结¶
词表含有中文,中文 token 是语料统计出来的,藏在 merges 里。以后再遇到 encode() 返回空列表,先检查预切分器是不是被换掉了。
参考资料¶
- DeepSeek V4 Tokenizer
- Hugging Face Tokenizers 文档
- Hugging Face NLP Course:BPE 与字节级 BPE
- OpenAI GPT-2 的 bytes_to_unicode 实现