
LlamaTokenizer in v5 overrides tokenizer.json’s ByteLevel pre-tokenizer with Metaspace, silently breaks DeepSeek V3/R1 family
快速结论:该问题出现在使用 Hugging Face Transformers v5 版本的 AutoTokenizer 加载 DeepSeek V3/R1 系列模型时,LlamaTokenizerFast 的 __init__ 会无条件地用 Metaspace pre-tokenizer 覆盖 tokenizer.json 中声明的 ByteLevel pre-tokenizer,导致空格丢失、文本黏连。优先排查 Transformers 版本是否 ≥5.7.0,或使用 PreTrainedTokenizerFast 作为临时绕过方案。
问题场景
使用 transformers v5 的 AutoTokenizer.from_pretrained() 加载任何 tokenizer_config.json 中设置了 "tokenizer_class": "LlamaTokenizerFast" 或 "LlamaTokenizer" 的模型。已确认受影响的模型包括:DeepSeek-V3、DeepSeek-V3.1、DeepSeek-V3.2-Exp、DeepSeek-R1、DeepSeek-R1-0528,以及 DeepSeek-R1-Distill-Llama-70B(包括其 FP8 量化变体)。在 vLLM 0.25.0 等推理框架中调用推理接口时,可能以原始 byte-level 标记(如 Ġ)的形式出现在输出中。
报错原文
# 复现代码
from transformers import AutoTokenizer
tok = AutoTokenizer.from_pretrained("deepseek-ai/DeepSeek-V3")
print(repr(tok.decode(tok.encode("hello world", add_special_tokens=False))))
# 'helloworld' — expected 'hello world'
# vLLM 下游症状(接口返回原始 byte-level 标记)
"content": "\n\nOkay,ĠtheĠuserĠgreetedĠme..."
原因分析
DeepSeek V3/R1 的 tokenizer.json 使用的是 ByteLevel BPE(GPT-2 风格,非 SentencePiece),其 pre-tokenizer 为 Sequence([..., ByteLevel])。但在 Transformers v5 的 LlamaTokenizer.__init__ 中,当加载现有 tokenizer.json 时,会无条件安装一个 Metaspace pre-tokenizer(replacement="▁", split=False),覆盖了原始配置。由于词汇表中没有 ▁ 前缀的 token,Metaspace 标记空间消失,导致 round-trip 后空格丢失。
相同文件通过 tokenizers.Tokenizer.from_file(...) 或 PreTrainedTokenizerFast.from_pretrained(...) 加载时工作正常——问题特定于 LlamaTokenizer 的 __init__。
环境排查
- transformers:确认版本(v5.0.0 – v5.6.2 受影响,v5.7.0 已修复,但后续 v5.13.0 等版本仍有复现报告)。
- tokenizers:确认版本(0.22.0、0.22.1、0.22.2 均复现)。
- Python:3.12 / Linux(报告环境中)。
- 加载方式:使用
AutoTokenizer.from_pretrained()而不是PreTrainedTokenizerFast.from_pretrained()。 - 检查预训练仓库的 tokenizer_config.json:确认
tokenizer_class是否包含Llama关键字(如DeepSeek-R1-Distill-Llama-8B可能错误映射到 Llama pre-tokenizer)。
解决步骤
- 首要修复:更新 Transformers 到 v5.7.0 或更新版本(虽然 v5.8.0/v5.8.1 在一定条件下仍有复现,但 main 分支已修复)。若仍不行,切换到 main 分支:
pip install git+https://github.com/huggingface/transformers.git@main - 临时绕过:使用 PreTrainedTokenizerFast 加载(建议 v5.6.x 及以下版本优先尝试):
from transformers import PreTrainedTokenizerFast tok = PreTrainedTokenizerFast.from_pretrained("deepseek-ai/DeepSeek-V3") - 如果模型是 DeepSeek-R1-Distill-Llama-8B 等可能错误映射到 Llama 的仓库:需要等待 Transformers 的 workaround PR(#45936)合并,或在 Hub 上修正模型配置。目前可手动加载时指定
trust_remote_code=False并检查 tokenizer_config.json 中的 class name。
验证方法
运行以下 round-trip 测试,确认输出是否保留空格:
from transformers import AutoTokenizer
tok = AutoTokenizer.from_pretrained("deepseek-ai/DeepSeek-V3")
print(repr(tok.decode(tok.encode("hello world", add_special_tokens=False))))
# 期望输出:'hello world'
如果输出为 'helloworld',说明问题未解决;如果输出为 'hello world',则修复成功。
同时验证 convert_ids_to_tokens 是否包含正确的 byte-level 标记(如 Ġ)——如果 tokenize 步骤正确但 decode 步骤丢失空格,则属于此 bug 的典型表现。
参考来源
huggingface/transformers #45488
修复 PR:#44801(初始修复)、#45936(针对 Distill 模型的 workaround)、#46091(后续修复)



