Skip to content

Commit b140e28

Browse files
committed
Merge origin/main into refactor/thinking-retention
Brings in PR #90 (ungated Llama-3.2 tokenizer mirrors + canonical Llama classified preserve-thinking no-op). Conflict in tests/test_preserve_thinking.py resolved: keep the renamed thinking_retention wording, add the canonical meta-llama no-op entries from main.
2 parents 4765d6d + 1933293 commit b140e28

5 files changed

Lines changed: 174 additions & 32 deletions

File tree

renderers/base.py

Lines changed: 85 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1161,6 +1161,17 @@ def _model_has_vision_config(model_name: str) -> bool:
11611161
}
11621162

11631163

1164+
# Tokenizer repos to use when a canonical model repo is gated but an
1165+
# audited unrestricted mirror ships byte-identical tokenizer files and
1166+
# chat_template. The returned tokenizer keeps the caller's original
1167+
# ``name_or_path`` so exact-match renderer resolution still uses
1168+
# ``MODEL_RENDERER_MAP``.
1169+
TOKENIZER_SOURCE_OVERRIDES: dict[str, str] = {
1170+
"meta-llama/Llama-3.2-1B-Instruct": "unsloth/Llama-3.2-1B-Instruct",
1171+
"meta-llama/Llama-3.2-3B-Instruct": "unsloth/Llama-3.2-3B-Instruct",
1172+
}
1173+
1174+
11641175
# Models for which ``fastokens`` is known to diverge from vanilla
11651176
# ``transformers.AutoTokenizer`` and therefore must NOT be patched.
11661177
# Empirical audit ran each entry of ``MODEL_RENDERER_MAP`` through both
@@ -1184,6 +1195,42 @@ def _model_has_vision_config(model_name: str) -> bool:
11841195
_FASTOKENS_ANNOUNCED = False
11851196

11861197

1198+
def _tokenizer_source_for(model_name_or_path: str) -> str:
1199+
return TOKENIZER_SOURCE_OVERRIDES.get(model_name_or_path, model_name_or_path)
1200+
1201+
1202+
def _tokenizer_load_kwargs(model_name_or_path: str) -> dict[str, Any]:
1203+
revision = TRUSTED_REVISIONS.get(model_name_or_path)
1204+
if revision is not None:
1205+
return {"trust_remote_code": True, "revision": revision}
1206+
return {"trust_remote_code": False}
1207+
1208+
1209+
def _preserve_requested_tokenizer_name(
1210+
tokenizer,
1211+
*,
1212+
requested_name_or_path: str,
1213+
loaded_name_or_path: str,
1214+
):
1215+
if requested_name_or_path == loaded_name_or_path:
1216+
return tokenizer
1217+
1218+
try:
1219+
tokenizer.name_or_path = requested_name_or_path
1220+
except Exception:
1221+
init_kwargs = getattr(tokenizer, "init_kwargs", None)
1222+
if isinstance(init_kwargs, dict):
1223+
init_kwargs["name_or_path"] = requested_name_or_path
1224+
1225+
if getattr(tokenizer, "name_or_path", "") != requested_name_or_path:
1226+
raise RuntimeError(
1227+
f"Loaded tokenizer for {requested_name_or_path!r} from "
1228+
f"{loaded_name_or_path!r}, but could not preserve the requested "
1229+
"name_or_path for renderer auto-resolution."
1230+
)
1231+
return tokenizer
1232+
1233+
11871234
def _patched_load(model_name_or_path: str, **kwargs):
11881235
"""Run ``AutoTokenizer.from_pretrained`` with fastokens patched in
11891236
process-locally — patch around the load, unpatch right after.
@@ -1321,29 +1368,41 @@ def load_tokenizer(
13211368
validation for configs with nested ``rope_parameters``), we fall
13221369
back to loading the repo's self-contained ``tokenizer.json``
13231370
directly — see ``_load_tokenizer_via_auto``.
1324-
"""
1325-
kwargs: dict[str, Any] = {}
1326-
revision = TRUSTED_REVISIONS.get(model_name_or_path)
1327-
if revision is not None:
1328-
kwargs = {"trust_remote_code": True, "revision": revision}
1329-
else:
1330-
kwargs = {"trust_remote_code": False}
13311371
1332-
if not use_fastokens or model_name_or_path in FASTOKENS_INCOMPATIBLE:
1333-
return _load_tokenizer_via_auto(model_name_or_path, **kwargs)
1372+
Canonical Meta Llama-3.2 Instruct repos are gated on HuggingFace. For
1373+
those exact IDs we load tokenizer files from the audited unrestricted
1374+
``unsloth`` mirrors instead, then restore ``tokenizer.name_or_path`` to
1375+
the requested Meta ID so auto-resolution still selects ``Llama3Renderer``.
1376+
"""
1377+
load_name_or_path = _tokenizer_source_for(model_name_or_path)
1378+
kwargs = _tokenizer_load_kwargs(load_name_or_path)
1379+
1380+
if not use_fastokens or load_name_or_path in FASTOKENS_INCOMPATIBLE:
1381+
tok = _load_tokenizer_via_auto(load_name_or_path, **kwargs)
1382+
return _preserve_requested_tokenizer_name(
1383+
tok,
1384+
requested_name_or_path=model_name_or_path,
1385+
loaded_name_or_path=load_name_or_path,
1386+
)
13341387

13351388
try:
1336-
return _patched_load(model_name_or_path, **kwargs)
1389+
tok = _patched_load(load_name_or_path, **kwargs)
13371390
except Exception as exc:
13381391
logger.info(
13391392
"fastokens could not load %r (%s: %s); falling back to vanilla "
13401393
"AutoTokenizer. Add this model to FASTOKENS_INCOMPATIBLE in "
13411394
"renderers.base to suppress the retry.",
1342-
model_name_or_path,
1395+
load_name_or_path,
13431396
type(exc).__name__,
13441397
str(exc)[:160],
13451398
)
1346-
return _load_tokenizer_via_auto(model_name_or_path, **kwargs)
1399+
tok = _load_tokenizer_via_auto(load_name_or_path, **kwargs)
1400+
1401+
return _preserve_requested_tokenizer_name(
1402+
tok,
1403+
requested_name_or_path=model_name_or_path,
1404+
loaded_name_or_path=load_name_or_path,
1405+
)
13471406

13481407

13491408
def _populate_registry():
@@ -1709,12 +1768,8 @@ def _get_offset_tokenizer(tokenizer):
17091768
if cached is not None:
17101769
return cached
17111770

1712-
kwargs: dict[str, Any] = {}
1713-
revision = TRUSTED_REVISIONS.get(name_or_path)
1714-
if revision is not None:
1715-
kwargs = {"trust_remote_code": True, "revision": revision}
1716-
else:
1717-
kwargs = {"trust_remote_code": False}
1771+
load_name_or_path = _tokenizer_source_for(name_or_path)
1772+
kwargs = _tokenizer_load_kwargs(load_name_or_path)
17181773

17191774
def _has_offsets(tok) -> bool:
17201775
if not getattr(tok, "is_fast", False):
@@ -1734,7 +1789,12 @@ def _has_offsets(tok) -> bool:
17341789
# off — serialized against pool patch/unpatch via ``_FASTOKENS_PATCH_LOCK``
17351790
# so no concurrent window can swap the shim back in mid-load — then
17361791
# restore the prior patch state. Never cache a non-offset tokenizer.
1737-
offset_tok = _load_tokenizer_via_auto(name_or_path, **kwargs)
1792+
offset_tok = _load_tokenizer_via_auto(load_name_or_path, **kwargs)
1793+
offset_tok = _preserve_requested_tokenizer_name(
1794+
offset_tok,
1795+
requested_name_or_path=name_or_path,
1796+
loaded_name_or_path=load_name_or_path,
1797+
)
17381798
if not _has_offsets(offset_tok):
17391799
import fastokens
17401800

@@ -1744,7 +1804,12 @@ def _has_offsets(tok) -> bool:
17441804
with contextlib.redirect_stdout(io.StringIO()):
17451805
fastokens.unpatch_transformers()
17461806
try:
1747-
offset_tok = _load_tokenizer_via_auto(name_or_path, **kwargs)
1807+
offset_tok = _load_tokenizer_via_auto(load_name_or_path, **kwargs)
1808+
offset_tok = _preserve_requested_tokenizer_name(
1809+
offset_tok,
1810+
requested_name_or_path=name_or_path,
1811+
loaded_name_or_path=load_name_or_path,
1812+
)
17481813
finally:
17491814
if was_patched:
17501815
with contextlib.redirect_stdout(io.StringIO()):

tests/conftest.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,10 @@
4747
# there's just no byte-output to parity-check against. Split-specific
4848
# parity (V3 bare prompt vs R1 <think>+history-strip) is covered in
4949
# tests/test_deepseek_r1.py.
50-
# Llama-3 loads via the unrestricted unsloth mirror (byte-identical
51-
# chat template) so CI needs no Meta-gated HF token. Pinned to the
52-
# explicit "llama-3" config because the mirror name isn't in
53-
# MODEL_RENDERER_MAP (so "auto" would fall back to DefaultRenderer).
54-
("unsloth/Llama-3.2-1B-Instruct", "llama-3"),
50+
# Llama-3 uses the canonical Meta ID for renderer auto-resolution, while
51+
# load_tokenizer fetches the tokenizer/chat_template from the unrestricted
52+
# unsloth mirror so CI needs no Meta-gated HF token.
53+
("meta-llama/Llama-3.2-1B-Instruct", "auto"),
5554
("openai/gpt-oss-20b", "gpt-oss"),
5655
("Qwen/Qwen2.5-0.5B-Instruct", "default"),
5756
]
@@ -139,7 +138,7 @@ def _skip_gpt_oss_for_hf_parity_tests(request):
139138
def _skip_llama_for_hf_parity_tests(request):
140139
callspec = getattr(request.node, "callspec", None)
141140
model_name = callspec.params.get("model_name") if callspec else None
142-
if model_name != "unsloth/Llama-3.2-1B-Instruct":
141+
if model_name != "meta-llama/Llama-3.2-1B-Instruct":
143142
return
144143
test_file = os.path.basename(str(request.node.fspath))
145144
if test_file in _LLAMA_HF_PARITY_TEST_FILES:

tests/test_llama_3.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
"""Llama-3 renderer coverage.
22
33
Covers ``Llama3Renderer`` and the ``meta-llama/Llama-3.2-{1B,3B}-Instruct``
4-
entries in ``MODEL_RENDERER_MAP``. Tokenizers are loaded via the
5-
unrestricted ``unsloth/Llama-3.2-{1B,3B}-Instruct`` mirrors (verified
6-
byte-identical chat templates) so CI doesn't need an HF token with Meta
7-
license access.
4+
entries in ``MODEL_RENDERER_MAP``. ``load_tokenizer`` uses the
5+
unrestricted ``unsloth/Llama-3.2-{1B,3B}-Instruct`` mirrors underneath
6+
(verified byte-identical chat templates) so CI doesn't need an HF token
7+
with Meta license access.
88
"""
99

1010
from __future__ import annotations
@@ -34,7 +34,7 @@
3434
@pytest.fixture(scope="module", params=_MODEL_PAIRS, ids=[m for m, _ in _MODEL_PAIRS])
3535
def llama_pair(request):
3636
canonical, mirror = request.param
37-
tok = load_tokenizer(mirror)
37+
tok = load_tokenizer(canonical)
3838
renderer = Llama3Renderer(tok, Llama3RendererConfig(date_string=_PINNED_DATE))
3939
return canonical, mirror, tok, renderer
4040

@@ -58,6 +58,14 @@ def test_create_renderer_via_explicit_config(llama_pair):
5858
assert isinstance(r, Llama3Renderer)
5959

6060

61+
def test_create_renderer_auto_resolves_after_mirror_load(llama_pair):
62+
"""``load_tokenizer(canonical_meta_id)`` loads from the unrestricted
63+
mirror but preserves the canonical name needed for auto-resolution."""
64+
canonical, _, tok, _ = llama_pair
65+
assert tok.name_or_path == canonical
66+
assert isinstance(create_renderer(tok), Llama3Renderer)
67+
68+
6169
# ---------------------------------------------------------------------------
6270
# Constructor contract
6371
# ---------------------------------------------------------------------------

tests/test_load_tokenizer.py

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@
99
from __future__ import annotations
1010

1111
import re
12+
from types import SimpleNamespace
1213
from unittest.mock import patch
1314

14-
from renderers.base import TRUSTED_REVISIONS, load_tokenizer
15+
from renderers import base
16+
from renderers.base import TOKENIZER_SOURCE_OVERRIDES, TRUSTED_REVISIONS, load_tokenizer
1517

1618

1719
# ---------------------------------------------------------------------------
@@ -70,6 +72,23 @@ def test_kimi_loads_with_pinned_revision(mock_from_pretrained):
7072
}
7173

7274

75+
@patch("transformers.AutoTokenizer.from_pretrained")
76+
def test_meta_llama_loads_tokenizer_from_unsloth_mirror(mock_from_pretrained):
77+
"""Canonical Meta Llama repos are gated; load their tokenizer/chat
78+
template from the audited unrestricted mirror while preserving the
79+
canonical name for renderer auto-resolution."""
80+
canonical = "meta-llama/Llama-3.2-1B-Instruct"
81+
mirror = "unsloth/Llama-3.2-1B-Instruct"
82+
mock_from_pretrained.return_value = SimpleNamespace(name_or_path=mirror)
83+
84+
tok = load_tokenizer(canonical, use_fastokens=False)
85+
86+
args, kwargs = mock_from_pretrained.call_args
87+
assert args == (mirror,)
88+
assert kwargs == {"trust_remote_code": False}
89+
assert tok.name_or_path == canonical
90+
91+
7392
@patch("transformers.AutoTokenizer.from_pretrained")
7493
def test_unknown_path_falls_through_to_no_remote_code(mock_from_pretrained):
7594
"""Unknown / fine-tuned model paths — including ``moonshotai/Kimi-K2*``
@@ -92,6 +111,53 @@ def test_unknown_path_falls_through_to_no_remote_code(mock_from_pretrained):
92111
)
93112

94113

114+
def test_tokenizer_source_overrides_are_exact_llama_mirrors():
115+
"""Mirror overrides are intentionally narrow: only verified
116+
byte-identical Llama tokenizer/template mirrors should live here."""
117+
assert TOKENIZER_SOURCE_OVERRIDES == {
118+
"meta-llama/Llama-3.2-1B-Instruct": "unsloth/Llama-3.2-1B-Instruct",
119+
"meta-llama/Llama-3.2-3B-Instruct": "unsloth/Llama-3.2-3B-Instruct",
120+
}
121+
122+
123+
def test_offset_tokenizer_uses_unsloth_mirror_for_meta_llama(monkeypatch):
124+
"""Offset-tokenizer reloads must use the same unrestricted source
125+
override, otherwise Llama rendering can hit the gated Meta repo after
126+
the initial tokenizer load succeeds."""
127+
128+
class _NoOffsets:
129+
name_or_path = "meta-llama/Llama-3.2-1B-Instruct"
130+
131+
def __call__(self, *args, **kwargs):
132+
raise NotImplementedError("fastokens shim has no offsets")
133+
134+
class _OffsetTokenizer:
135+
is_fast = True
136+
137+
def __init__(self, name_or_path: str):
138+
self.name_or_path = name_or_path
139+
140+
def __call__(self, *args, **kwargs):
141+
return {"offset_mapping": [(0, 1)]}
142+
143+
calls = []
144+
145+
def _fake_load(name_or_path, **kwargs):
146+
calls.append((name_or_path, kwargs))
147+
return _OffsetTokenizer(name_or_path)
148+
149+
base._offset_tokenizers.clear()
150+
monkeypatch.setattr(base, "_load_tokenizer_via_auto", _fake_load)
151+
152+
try:
153+
tok = base._get_offset_tokenizer(_NoOffsets())
154+
finally:
155+
base._offset_tokenizers.clear()
156+
157+
assert calls == [("unsloth/Llama-3.2-1B-Instruct", {"trust_remote_code": False})]
158+
assert tok.name_or_path == "meta-llama/Llama-3.2-1B-Instruct"
159+
160+
95161
# ---------------------------------------------------------------------------
96162
# Smoke: real tokenizer loads behave as expected
97163
# ---------------------------------------------------------------------------

tests/test_preserve_thinking.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ def _make(tokenizer, renderer_name, **flags):
5454
"poolside/Laguna-XS.2",
5555
# Llama-3 has no reasoning channel at all — thinking_retention can't
5656
# add or drop anything, so it's a pure no-op.
57+
"meta-llama/Llama-3.2-1B-Instruct",
58+
"meta-llama/Llama-3.2-3B-Instruct",
5759
"unsloth/Llama-3.2-1B-Instruct",
5860
}
5961

@@ -317,6 +319,8 @@ def test_thinking_retention_tool_cycle_matches_all_on_live_cycle(
317319
"Qwen/Qwen3-VL-30B-A3B-Instruct",
318320
# Llama-3 ships no <think> rendering path, so reasoning_content never
319321
# surfaces in the output regardless of thinking_retention.
322+
"meta-llama/Llama-3.2-1B-Instruct",
323+
"meta-llama/Llama-3.2-3B-Instruct",
320324
"unsloth/Llama-3.2-1B-Instruct",
321325
}
322326

0 commit comments

Comments
 (0)