transformers - ✅(Solved) Fix `AutoTokenizer` produces wrong token IDs for all Granite models (silent v4→v5 regression) [1 pull requests, 1 comments, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
huggingface/transformers#45812Fetched 2026-05-07 03:31:15
View on GitHub
Comments
1
Participants
1
Timeline
9
Reactions
0
Author
Participants
Timeline (top)
cross-referenced ×2mentioned ×2subscribed ×2commented ×1

Fix Action

Workaround

Use PreTrainedTokenizerFast directly (works in both v4 and v5):

from transformers import PreTrainedTokenizerFast
tok = PreTrainedTokenizerFast.from_pretrained("ibm-granite/granite-4.1-8b")

PR fix notes

PR #45813: fix: route Granite models to TokenizersBackend to preserve tokenizer.json pre-tokenizer

Description (problem / solution / changelog)

What does this PR do?

Change TOKENIZER_MAPPING_NAMES for Granite model types from "GPT2Tokenizer" to "TokenizersBackend" so that AutoTokenizer loads tokenizer.json faithfully instead of routing through GPT2Tokenizer.__init__ which hardcodes a wrong pre-tokenizer.

Fixes #45812

Code Agent Policy

The Transformers repo is currently being overwhelmed by a large number of PRs and issue comments written by code agents. We are currently bottlenecked by our ability to review and respond to them. As a result, we ask that new users do not submit pure code agent PRs at this time. You may use code agents in drafting or to help you diagnose issues. We'd also ask autonomous "OpenClaw"-like agents not to open any PRs or issues for the moment.

PRs that appear to be fully agent-written will probably be closed without review, and we may block users who do this repeatedly or maliciously.

This is a rapidly-evolving situation that's causing significant shockwaves in the open-source community. As a result, this policy is likely to be updated regularly in the near future. For more information, please read CONTRIBUTING.md.

  • I confirm that this is not a pure code agent PR.

Before submitting

  • This PR fixes a typo or improves the docs (you can dismiss the other checks if that's the case).
  • Did you read the contributor guideline, Pull Request section?
  • Was this discussed/approved via a Github issue or the forum? Please add a link to it if that's the case.
  • Did you make sure to update the documentation with your changes? Here are the documentation guidelines, and here are tips on formatting docstrings.
  • Did you write any new necessary tests?

Who can review?

Anyone in the community is free to review the PR once the tests have passed. Feel free to tag members/contributors who may be interested in your PR.

  • tokenizers: @ArthurZucker and @itazap

Changed files

  • src/transformers/models/auto/tokenization_auto.py (modified, +4/-4)

Code Example

from transformers import AutoTokenizer, PreTrainedTokenizerFast

model_id = "ibm-granite/granite-4.1-8b"  # any Granite 4+ model

tok_auto = AutoTokenizer.from_pretrained(model_id)
tok_correct = PreTrainedTokenizerFast.from_pretrained(model_id)

# Numeric strings are most visibly affected (digit splitting differs)
print(tok_auto.encode("2023", add_special_tokens=False))       # [508, 1419]WRONG
print(tok_correct.encode("2023", add_special_tokens=False))    # [2366, 18]  ← correct

print(tok_auto.encode("650841823", add_special_tokens=False))      # [13655, 5833, 972, 1419]WRONG
print(tok_correct.encode("650841823", add_special_tokens=False))   # [13655, 25496, 23848]    ← correct

print(tok_auto.encode("ISO 9001:2015", add_special_tokens=False))      # [25141, 220, 24, 4119, 25, 679, 20]WRONG
print(tok_correct.encode("ISO 9001:2015", add_special_tokens=False))   # [25141, 220, 7467, 16, 25, 679, 20] ← correct

---

print(tok_auto.backend_tokenizer.pre_tokenizer)
# ByteLevel(add_prefix_space=False, trim_offsets=True, use_regex=True)WRONG

print(tok_correct.backend_tokenizer.pre_tokenizer)
# Sequence([Split(regex, ...), ByteLevel(..., use_regex=False)])correct (matches tokenizer.json)

---

from transformers import PreTrainedTokenizerFast
tok = PreTrainedTokenizerFast.from_pretrained("ibm-granite/granite-4.1-8b")

---

-        ("granite", "GPT2Tokenizer"),
-        ("granitemoe", "GPT2Tokenizer"),
-        ("granitemoehybrid", "GPT2Tokenizer"),
-        ("granitemoeshared", "GPT2Tokenizer"),
+        ("granite", "TokenizersBackend" if is_tokenizers_available() else None),
+        ("granitemoe", "TokenizersBackend" if is_tokenizers_available() else None),
+        ("granitemoehybrid", "TokenizersBackend" if is_tokenizers_available() else None),
+        ("granitemoeshared", "TokenizersBackend" if is_tokenizers_available() else None),
RAW_BUFFERClick to expand / collapse

System Info

  • transformers version: 5.8.0 (also reproduced on 5.0.0 through 5.7.0)
  • Platform: Linux-5.14.0-503.11.1.el9_5.x86_64-x86_64-with-glibc2.34
  • Python version: 3.12.13
  • Huggingface_hub version: 1.14.0
  • Safetensors version: 0.7.0
  • Tokenizers version: 0.22.2
  • PyTorch version: not installed (tokenizer-only reproduction)
  • Using distributed or parallel set-up in script?: No

Who can help?

@ArthurZucker and @itazap

Information

  • The official example scripts
  • My own modified scripts

Tasks

  • An officially supported task in the examples folder (such as GLUE/SQuAD, ...)
  • My own task or dataset (give details below)

Reproduction

from transformers import AutoTokenizer, PreTrainedTokenizerFast

model_id = "ibm-granite/granite-4.1-8b"  # any Granite 4+ model

tok_auto = AutoTokenizer.from_pretrained(model_id)
tok_correct = PreTrainedTokenizerFast.from_pretrained(model_id)

# Numeric strings are most visibly affected (digit splitting differs)
print(tok_auto.encode("2023", add_special_tokens=False))       # [508, 1419] ← WRONG
print(tok_correct.encode("2023", add_special_tokens=False))    # [2366, 18]  ← correct

print(tok_auto.encode("650841823", add_special_tokens=False))      # [13655, 5833, 972, 1419] ← WRONG
print(tok_correct.encode("650841823", add_special_tokens=False))   # [13655, 25496, 23848]    ← correct

print(tok_auto.encode("ISO 9001:2015", add_special_tokens=False))      # [25141, 220, 24, 4119, 25, 679, 20] ← WRONG
print(tok_correct.encode("ISO 9001:2015", add_special_tokens=False))   # [25141, 220, 7467, 16, 25, 679, 20] ← correct

Pre-tokenizer mismatch:

print(tok_auto.backend_tokenizer.pre_tokenizer)
# ByteLevel(add_prefix_space=False, trim_offsets=True, use_regex=True)  ← WRONG

print(tok_correct.backend_tokenizer.pre_tokenizer)
# Sequence([Split(regex, ...), ByteLevel(..., use_regex=False)])  ← correct (matches tokenizer.json)

Expected behavior

Granite models ship a tokenizer.json with a Sequence(Split(\p{N}{1,3}) + ByteLevel) pre-tokenizer (tiktoken/cl100k style — splits digit runs into ≤3-character chunks before BPE). But AutoTokenizer routes Granite to GPT2Tokenizer, whose __init__ hardcodes ByteLevel(use_regex=True) — which uses the GPT-2 regex (\p{N}+, keeps all digits together).

Since BPE was trained with \p{N}{1,3} splitting, using a different pre-tokenizer at inference produces wrong merges and wrong token IDs.

Workaround

Use PreTrainedTokenizerFast directly (works in both v4 and v5):

from transformers import PreTrainedTokenizerFast
tok = PreTrainedTokenizerFast.from_pretrained("ibm-granite/granite-4.1-8b")

Proposed Fix

Change TOKENIZER_MAPPING_NAMES for Granite model types from "GPT2Tokenizer" to "TokenizersBackend":

-        ("granite", "GPT2Tokenizer"),
-        ("granitemoe", "GPT2Tokenizer"),
-        ("granitemoehybrid", "GPT2Tokenizer"),
-        ("granitemoeshared", "GPT2Tokenizer"),
+        ("granite", "TokenizersBackend" if is_tokenizers_available() else None),
+        ("granitemoe", "TokenizersBackend" if is_tokenizers_available() else None),
+        ("granitemoehybrid", "TokenizersBackend" if is_tokenizers_available() else None),
+        ("granitemoeshared", "TokenizersBackend" if is_tokenizers_available() else None),

This triggers the existing mismatch detection (mapping says "TokenizersBackend" ≠ hub's "GPT2Tokenizer"), which falls through to TokenizersBackend.from_pretrained() — loading tokenizer.json faithfully.

Why not MODELS_WITH_INCORRECT_HUB_TOKENIZER_CLASS? That override set is only consulted inside the mismatch block (line 733) — which requires TOKENIZER_MAPPING_NAMES[model_type] to disagree with the hub's tokenizer_class. For Granite, both say "GPT2Tokenizer", so no mismatch is detected and the override set is never checked.

Related Issues

  • #45488 — Same bug class: LlamaTokenizer hardcodes Metaspace, breaks DeepSeek V3/R1 (GSM8K drops 63.7% → 26.1%)
  • #44462 — Same bug class: AutoTokenizer ignores tokenizer.json pre-tokenizer for deepseek-coder
  • #43122 — Same bug class: different tokenization between v4.57.3 and v5.0 (MiniMax)
  • #45701 — Same bug class: character-level tokenization in v5.7.0 instead of subword (camembert v2)

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

Expected behavior

Granite models ship a tokenizer.json with a Sequence(Split(\p{N}{1,3}) + ByteLevel) pre-tokenizer (tiktoken/cl100k style — splits digit runs into ≤3-character chunks before BPE). But AutoTokenizer routes Granite to GPT2Tokenizer, whose __init__ hardcodes ByteLevel(use_regex=True) — which uses the GPT-2 regex (\p{N}+, keeps all digits together).

Since BPE was trained with \p{N}{1,3} splitting, using a different pre-tokenizer at inference produces wrong merges and wrong token IDs.

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

transformers - ✅(Solved) Fix `AutoTokenizer` produces wrong token IDs for all Granite models (silent v4→v5 regression) [1 pull requests, 1 comments, 1 participants]