Qwen2Tokenizer overwrites pre_tokenizer from tokenizer.json. Wrong token IDs for all Qwen3.6 / Qwen3.8 models (v4→v5 regression)

在 transformers v5 中使用 AutoTokenizer 加载 Qwen3.6 / Qwen3.8 系列(以及声明了 "tokenizer_class": "Qwen2Tokenizer" 的派生 checkpoint)时, Qwen2Tokenizer.__init__ 会用模块内置

快速结论:在 transformers v5 中使用 AutoTokenizer 加载 Qwen3.6 / Qwen3.8 系列(以及声明了 "tokenizer_class": "Qwen2Tokenizer" 的派生 checkpoint)时,Qwen2Tokenizer.__init__ 会用模块内置的 PRETOKENIZE_REGEX 覆盖 tokenizer.json 自带的 pre_tokenizer,导致含组合附加符号(combining marks)的文本(泰语、天城文、阿拉伯语等)token ID 与官方 tokenizer 不一致。优先排查加载类是否走了 Qwen2Tokenizer 而被硬编码正则覆盖。

适用环境:Issue 已确认:Linux aarch64、Python 3.12、transformers 5.17.0(4.57.6 表现正常)、tokenizers 0.23.2、huggingface_hub 1.8.0;未安装 PyTorch(纯 tokenizer 复现)。另有 5.16.1 / tokenizers 0.23.2 上的复现报告,以及 5.20.1 上对显式声明 tokenizer 类的派生 checkpoint 仍可复现。

最快修复方案:暂无确认的一步修复方案。Issue 中已合并的 #49116 通过 tokenization_auto.py 的自动路由规避该类,但仅对依赖模型类型解析类的路径有效;对 tokenizer_config.json 中显式声明 Qwen2Tokenizer 的 checkpoint 无效。可优先尝试:对派生 checkpoint 直接使用 tokenizers.Tokenizer.from_file(hf_hub_download(repo, "tokenizer.json")) 绕过 AutoTokenizer,或在加载后自行确认 backend 的 pre_tokenizer 是否与发布文件一致。

注意事项:#49116 只能覆盖自动路由路径,无法覆盖显式声明 tokenizer 类的路径;Issue 中提到的 #49114(透传发布的 regex)与 #49341(不匹配时告警)尚未确认已覆盖该路径。另需注意:用 2.5 词表做回归测试时,天城文样例可能 ID 一致(vocab-dependent),仅比较 token 数量也可能误判通过,应以完整 ID 列表断言、并用阿拉伯语等样例验证。逐字段对比 tokenizer.json 会因 use_regex、trim_offsets 等 flag 默认值差异误报,应比较行为而非 JSON 相等。

问题场景

用户在 transformers v5(至少 5.16.1、5.17.0,5.20.1 仍有报告)下用 AutoTokenizer.from_pretrained 加载 Qwen3.6 / Qwen3.8 系列 checkpoint(如 Qwen/Qwen3.8-Flash-Next、Qwen/Qwen3.8-27B、Qwen/Qwen3.6-27B),或加载声明了 "tokenizer_class": "Qwen2Tokenizer" 的派生 checkpoint(如 Intel/Qwen3.8-Flash-Next-W4A16-AutoRound、nvidia/Qwen3.8-Flash-Next-FP4-Local)。文本编码结果与 checkpoint 自带 tokenizer.json 直接编码的结果不一致;save_pretrained 还会把错误正则写回新仓库,污染下游派生模型。

报错原文

Qwen2Tokenizer overwrites pre_tokenizer from tokenizer.json. Wrong token IDs for all Qwen3.6 / Qwen3.8 models (v4→v5 regression)

# 复现脚本断言失败
assert loaded(TEXT)["input_ids"] == published.encode(TEXT).ids
AssertionError

# 实际表现
print(len(published.encode(TEXT).ids))   # 6
print(len(loaded(TEXT)["input_ids"]))    # 10

原因分析

确定原因:models/qwen2/tokenization_qwen2.py 中 Qwen2Tokenizer.__init__ 会新建 backend tokenizer,并把模块级常量 PRETOKENIZE_REGEX 赋给 pre-tokenizer,丢弃刚从 tokenizer.json 加载的 pre_tokenizer。维护者说明这是 v5 的有意设计:以官方 checkpoint 的 tokenizer 作为 tokenization_<model>.py 中的“ground truth”,并允许用户从零构建 Qwen2Tokenizer。

Qwen3 及更早版本中,该常量与发布文件一致,因此无感知;Qwen3.6 / Qwen3.8 的发布 pattern 在两个字符类中加入了 \p{M},被静默丢弃,组合附加符号不再并入 \p{L}+ 连续段,而落入标点/符号分支,导致单词在每个 mark 处被切开。发布的 pattern:

(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\r\n\p{L}\p{N}]?[\p{L}\p{M}]+|\p{N}| ?[^\s\p{L}\p{M}\p{N}]+[\r\n]*|\s*[\r\n]+|\s+(?!\S)|\s+

实际应用的 PRETOKENIZE_REGEX:

(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\r\n\p{L}\p{N}]?\p{L}+|\p{N}| ?[^\s\p{L}\p{N}]+[\r\n]*|\s*[\r\n]+|\s+(?!\S)|\s+

影响范围:拉丁文本不受影响(NFC 归一化先执行,café 无独立 mark);希伯来文和 emoji 在该复现中 ID 相同(BPE 合并恰好重新拼上)。影响集中在无预组合形式的文字系统。是否在其他版本/类声明路径下也生效:5.16.1 已存在同样的无条件赋值,#49116 仅修复自动路由路径,显式声明 tokenizer 类的路径仍会触发。

环境排查

  • transformers 版本:Issue 确认 5.17.0 有问题、4.57.6 正常;5.16.1 同样有问题;5.20.1 对显式声明 tokenizer 类的 checkpoint 仍复现。
  • tokenizers 版本:0.23.2(5.20.1 报告为 0.23.x)。
  • huggingface_hub:1.8.0。
  • Python:3.12;平台:Linux aarch64;PyTorch 非必需(纯 tokenizer 即可复现)。
  • checkpoint 的 tokenizer.json 中 pre-tokenizer pattern 是否包含 \p{M}(Qwen3.6 / Qwen3.8 为 yes,Qwen3-8B、Qwen2.5、Qwen2 为 no)。
  • checkpoint 的 tokenizer_config.json 是否显式设置 "tokenizer_class": "Qwen2Tokenizer"(显式声明会绕过 #49116 的路由表)。
  • 若为量化/微调派生 checkpoint,确认其 tokenizer.json 与 tokenizer_config.json 中的 regex 是否自相矛盾。

解决步骤

  1. 确认问题是否命中:对目标 repo 分别用 tokenizers.Tokenizer.from_file(hf_hub_download(REPO, "tokenizer.json")) 与 AutoTokenizer.from_pretrained(REPO) 编码同一段含组合附加符号的文本(如泰语、天城文、阿拉伯语),比较完整 ID 列表而非仅 token 数量。
  2. 检查加载得到的 backend pre-tokenizer 的 pattern 是否等于 PRETOKENIZE_REGEX;若被替换,说明命中该 bug。
  3. 升级 transformers 并重测(#49116 已合入,可优先尝试):若 checkpoint 依赖模型类型自动解析 tokenizer 类,升级后可能恢复正常。
  4. 对显式声明 "tokenizer_class": "Qwen2Tokenizer" 的 checkpoint(尤其量化包与旧配置微调模型),升级无法修复,可优先尝试直接用 tokenizers.Tokenizer.from_file(...) 加载发布的 tokenizer.json 做编码,绕开 Qwen2Tokenizer.__init__ 的覆盖。
  5. 避免用受影响的 transformers 版本执行 save_pretrained 再上传派生模型,否则错误 pattern 会被固化进新仓库;如需重新发布,先确认写出的 tokenizer.json 中 pre-tokenizer 保留 \p{M}。
  6. 关注上游后续修复:#49114(透传发布的 regex)与 #49341(不匹配时告警)在 Issue 中被建议用于覆盖显式声明路径,但目前尚未确认已合入并解决该场景。

验证方法

用同一段含组合附加符号的文本,比较 AutoTokenizer(或修复后路径)输出的 input_ids 与直接使用发布 tokenizer.json 得到的 ID 列表是否逐项相等,而非只比长度。Issue 中给出参考值:Qwen/Qwen3.8-Flash-Next 下 नमस्ते दुनिया 应为 6 个 token(错误时为 10),สวัสดีชาวโลก 应为 4(错误时为 7),مُحَمَّد 应为 4(错误时为 6)。另外确认 save_pretrained 写出的 tokenizer.json 中 pre-tokenizer pattern 含 \p{M}。

参考来源

huggingface/transformers #49066

GamsGo AI

AI 工具推荐

想把多个 AI 模型放在一个入口?

GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。

了解 GamsGo AI

推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 29425

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注