renderers 0.1.8.dev45__tar.gz → 0.1.8.dev49__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/PKG-INFO +1 -1
  2. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/renderers/_version.py +2 -2
  3. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/renderers/base.py +85 -20
  4. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/tests/conftest.py +5 -6
  5. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/tests/test_llama_3.py +13 -5
  6. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/tests/test_load_tokenizer.py +67 -1
  7. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/tests/test_preserve_thinking.py +4 -0
  8. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/.github/workflows/publish-dev.yml +0 -0
  9. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/.github/workflows/publish.yml +0 -0
  10. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/.github/workflows/style.yml +0 -0
  11. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/.github/workflows/test.yml +0 -0
  12. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/.gitignore +0 -0
  13. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/.pre-commit-config.yaml +0 -0
  14. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/LICENSE +0 -0
  15. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/README.md +0 -0
  16. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/docs/renderer-config.md +0 -0
  17. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/examples/README.md +0 -0
  18. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/examples/sglang/multiturn_generate_sglang.py +0 -0
  19. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/examples/sglang/online_multiturn_sglang.py +0 -0
  20. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/examples/tinker/multiturn_generate_tinker.py +0 -0
  21. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/examples/transformers/multiturn_generate_transformers.py +0 -0
  22. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/examples/vllm/multiturn_generate_vllm.py +0 -0
  23. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/pyproject.toml +0 -0
  24. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/renderers/__init__.py +0 -0
  25. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/renderers/client.py +0 -0
  26. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/renderers/configs.py +0 -0
  27. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/renderers/deepseek_r1.py +0 -0
  28. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/renderers/deepseek_v3.py +0 -0
  29. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/renderers/default.py +0 -0
  30. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/renderers/glm45.py +0 -0
  31. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/renderers/glm5.py +0 -0
  32. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/renderers/gpt_oss.py +0 -0
  33. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/renderers/kimi_k2.py +0 -0
  34. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/renderers/kimi_k25.py +0 -0
  35. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/renderers/laguna_xs2.py +0 -0
  36. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/renderers/llama_3.py +0 -0
  37. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/renderers/minimax_m2.py +0 -0
  38. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/renderers/nemotron3.py +0 -0
  39. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/renderers/parsers.py +0 -0
  40. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/renderers/parsing.py +0 -0
  41. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/renderers/qwen3.py +0 -0
  42. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/renderers/qwen35.py +0 -0
  43. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/renderers/qwen36.py +0 -0
  44. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/renderers/qwen3_vl.py +0 -0
  45. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/tests/test_bridge.py +0 -0
  46. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/tests/test_build_helpers.py +0 -0
  47. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/tests/test_client.py +0 -0
  48. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/tests/test_deepseek_r1.py +0 -0
  49. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/tests/test_gpt_oss_harmony_parity.py +0 -0
  50. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/tests/test_incremental.py +0 -0
  51. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/tests/test_is_content.py +0 -0
  52. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/tests/test_kimi_k25_tool_schema.py +0 -0
  53. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/tests/test_load_tokenizer_fastokens.py +0 -0
  54. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/tests/test_message_indices.py +0 -0
  55. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/tests/test_message_tool_names.py +0 -0
  56. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/tests/test_multimodal.py +0 -0
  57. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/tests/test_nemotron3_parity.py +0 -0
  58. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/tests/test_nemotron3_ultra.py +0 -0
  59. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/tests/test_parse_response.py +0 -0
  60. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/tests/test_parse_response_robustness.py +0 -0
  61. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/tests/test_parsers.py +0 -0
  62. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/tests/test_qwen35_size_coverage.py +0 -0
  63. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/tests/test_render_ids.py +0 -0
  64. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/tests/test_renderer_config.py +0 -0
  65. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/tests/test_renderer_config_parity.py +0 -0
  66. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/tests/test_roundtrip.py +0 -0
  67. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/tests/test_sampled_mask.py +0 -0
  68. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/tests/test_tokens_per_message.py +0 -0
  69. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/tests/test_tool_arg_type_preservation.py +0 -0
  70. {renderers-0.1.8.dev45 → renderers-0.1.8.dev49}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: renderers
3
- Version: 0.1.8.dev45
3
+ Version: 0.1.8.dev49
4
4
  Summary: Chat template renderers — deterministic message-to-token conversion for LLM training
5
5
  License-Expression: Apache-2.0
6
6
  License-File: LICENSE
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.1.8.dev45'
22
- __version_tuple__ = version_tuple = (0, 1, 8, 'dev45')
21
+ __version__ = version = '0.1.8.dev49'
22
+ __version_tuple__ = version_tuple = (0, 1, 8, 'dev49')
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -1152,6 +1152,17 @@ TRUSTED_REVISIONS: dict[str, str] = {
1152
1152
  }
1153
1153
 
1154
1154
 
1155
+ # Tokenizer repos to use when a canonical model repo is gated but an
1156
+ # audited unrestricted mirror ships byte-identical tokenizer files and
1157
+ # chat_template. The returned tokenizer keeps the caller's original
1158
+ # ``name_or_path`` so exact-match renderer resolution still uses
1159
+ # ``MODEL_RENDERER_MAP``.
1160
+ TOKENIZER_SOURCE_OVERRIDES: dict[str, str] = {
1161
+ "meta-llama/Llama-3.2-1B-Instruct": "unsloth/Llama-3.2-1B-Instruct",
1162
+ "meta-llama/Llama-3.2-3B-Instruct": "unsloth/Llama-3.2-3B-Instruct",
1163
+ }
1164
+
1165
+
1155
1166
  # Models for which ``fastokens`` is known to diverge from vanilla
1156
1167
  # ``transformers.AutoTokenizer`` and therefore must NOT be patched.
1157
1168
  # Empirical audit ran each entry of ``MODEL_RENDERER_MAP`` through both
@@ -1175,6 +1186,42 @@ _FASTOKENS_PATCH_LOCK = threading.Lock()
1175
1186
  _FASTOKENS_ANNOUNCED = False
1176
1187
 
1177
1188
 
1189
+ def _tokenizer_source_for(model_name_or_path: str) -> str:
1190
+ return TOKENIZER_SOURCE_OVERRIDES.get(model_name_or_path, model_name_or_path)
1191
+
1192
+
1193
+ def _tokenizer_load_kwargs(model_name_or_path: str) -> dict[str, Any]:
1194
+ revision = TRUSTED_REVISIONS.get(model_name_or_path)
1195
+ if revision is not None:
1196
+ return {"trust_remote_code": True, "revision": revision}
1197
+ return {"trust_remote_code": False}
1198
+
1199
+
1200
+ def _preserve_requested_tokenizer_name(
1201
+ tokenizer,
1202
+ *,
1203
+ requested_name_or_path: str,
1204
+ loaded_name_or_path: str,
1205
+ ):
1206
+ if requested_name_or_path == loaded_name_or_path:
1207
+ return tokenizer
1208
+
1209
+ try:
1210
+ tokenizer.name_or_path = requested_name_or_path
1211
+ except Exception:
1212
+ init_kwargs = getattr(tokenizer, "init_kwargs", None)
1213
+ if isinstance(init_kwargs, dict):
1214
+ init_kwargs["name_or_path"] = requested_name_or_path
1215
+
1216
+ if getattr(tokenizer, "name_or_path", "") != requested_name_or_path:
1217
+ raise RuntimeError(
1218
+ f"Loaded tokenizer for {requested_name_or_path!r} from "
1219
+ f"{loaded_name_or_path!r}, but could not preserve the requested "
1220
+ "name_or_path for renderer auto-resolution."
1221
+ )
1222
+ return tokenizer
1223
+
1224
+
1178
1225
  def _patched_load(model_name_or_path: str, **kwargs):
1179
1226
  """Run ``AutoTokenizer.from_pretrained`` with fastokens patched in
1180
1227
  process-locally — patch around the load, unpatch right after.
@@ -1312,29 +1359,41 @@ def load_tokenizer(
1312
1359
  validation for configs with nested ``rope_parameters``), we fall
1313
1360
  back to loading the repo's self-contained ``tokenizer.json``
1314
1361
  directly — see ``_load_tokenizer_via_auto``.
1315
- """
1316
- kwargs: dict[str, Any] = {}
1317
- revision = TRUSTED_REVISIONS.get(model_name_or_path)
1318
- if revision is not None:
1319
- kwargs = {"trust_remote_code": True, "revision": revision}
1320
- else:
1321
- kwargs = {"trust_remote_code": False}
1322
1362
 
1323
- if not use_fastokens or model_name_or_path in FASTOKENS_INCOMPATIBLE:
1324
- return _load_tokenizer_via_auto(model_name_or_path, **kwargs)
1363
+ Canonical Meta Llama-3.2 Instruct repos are gated on HuggingFace. For
1364
+ those exact IDs we load tokenizer files from the audited unrestricted
1365
+ ``unsloth`` mirrors instead, then restore ``tokenizer.name_or_path`` to
1366
+ the requested Meta ID so auto-resolution still selects ``Llama3Renderer``.
1367
+ """
1368
+ load_name_or_path = _tokenizer_source_for(model_name_or_path)
1369
+ kwargs = _tokenizer_load_kwargs(load_name_or_path)
1370
+
1371
+ if not use_fastokens or load_name_or_path in FASTOKENS_INCOMPATIBLE:
1372
+ tok = _load_tokenizer_via_auto(load_name_or_path, **kwargs)
1373
+ return _preserve_requested_tokenizer_name(
1374
+ tok,
1375
+ requested_name_or_path=model_name_or_path,
1376
+ loaded_name_or_path=load_name_or_path,
1377
+ )
1325
1378
 
1326
1379
  try:
1327
- return _patched_load(model_name_or_path, **kwargs)
1380
+ tok = _patched_load(load_name_or_path, **kwargs)
1328
1381
  except Exception as exc:
1329
1382
  logger.info(
1330
1383
  "fastokens could not load %r (%s: %s); falling back to vanilla "
1331
1384
  "AutoTokenizer. Add this model to FASTOKENS_INCOMPATIBLE in "
1332
1385
  "renderers.base to suppress the retry.",
1333
- model_name_or_path,
1386
+ load_name_or_path,
1334
1387
  type(exc).__name__,
1335
1388
  str(exc)[:160],
1336
1389
  )
1337
- return _load_tokenizer_via_auto(model_name_or_path, **kwargs)
1390
+ tok = _load_tokenizer_via_auto(load_name_or_path, **kwargs)
1391
+
1392
+ return _preserve_requested_tokenizer_name(
1393
+ tok,
1394
+ requested_name_or_path=model_name_or_path,
1395
+ loaded_name_or_path=load_name_or_path,
1396
+ )
1338
1397
 
1339
1398
 
1340
1399
  def _populate_registry():
@@ -1702,12 +1761,8 @@ def _get_offset_tokenizer(tokenizer):
1702
1761
  if cached is not None:
1703
1762
  return cached
1704
1763
 
1705
- kwargs: dict[str, Any] = {}
1706
- revision = TRUSTED_REVISIONS.get(name_or_path)
1707
- if revision is not None:
1708
- kwargs = {"trust_remote_code": True, "revision": revision}
1709
- else:
1710
- kwargs = {"trust_remote_code": False}
1764
+ load_name_or_path = _tokenizer_source_for(name_or_path)
1765
+ kwargs = _tokenizer_load_kwargs(load_name_or_path)
1711
1766
 
1712
1767
  def _has_offsets(tok) -> bool:
1713
1768
  if not getattr(tok, "is_fast", False):
@@ -1727,7 +1782,12 @@ def _get_offset_tokenizer(tokenizer):
1727
1782
  # off — serialized against pool patch/unpatch via ``_FASTOKENS_PATCH_LOCK``
1728
1783
  # so no concurrent window can swap the shim back in mid-load — then
1729
1784
  # restore the prior patch state. Never cache a non-offset tokenizer.
1730
- offset_tok = _load_tokenizer_via_auto(name_or_path, **kwargs)
1785
+ offset_tok = _load_tokenizer_via_auto(load_name_or_path, **kwargs)
1786
+ offset_tok = _preserve_requested_tokenizer_name(
1787
+ offset_tok,
1788
+ requested_name_or_path=name_or_path,
1789
+ loaded_name_or_path=load_name_or_path,
1790
+ )
1731
1791
  if not _has_offsets(offset_tok):
1732
1792
  import fastokens
1733
1793
 
@@ -1737,7 +1797,12 @@ def _get_offset_tokenizer(tokenizer):
1737
1797
  with contextlib.redirect_stdout(io.StringIO()):
1738
1798
  fastokens.unpatch_transformers()
1739
1799
  try:
1740
- offset_tok = _load_tokenizer_via_auto(name_or_path, **kwargs)
1800
+ offset_tok = _load_tokenizer_via_auto(load_name_or_path, **kwargs)
1801
+ offset_tok = _preserve_requested_tokenizer_name(
1802
+ offset_tok,
1803
+ requested_name_or_path=name_or_path,
1804
+ loaded_name_or_path=load_name_or_path,
1805
+ )
1741
1806
  finally:
1742
1807
  if was_patched:
1743
1808
  with contextlib.redirect_stdout(io.StringIO()):
@@ -47,11 +47,10 @@ RENDERER_MODELS = [
47
47
  # there's just no byte-output to parity-check against. Split-specific
48
48
  # parity (V3 bare prompt vs R1 <think>+history-strip) is covered in
49
49
  # 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"),
55
54
  ("openai/gpt-oss-20b", "gpt-oss"),
56
55
  ("Qwen/Qwen2.5-0.5B-Instruct", "default"),
57
56
  ]
@@ -139,7 +138,7 @@ _LLAMA_HF_PARITY_TEST_FILES = {
139
138
  def _skip_llama_for_hf_parity_tests(request):
140
139
  callspec = getattr(request.node, "callspec", None)
141
140
  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":
143
142
  return
144
143
  test_file = os.path.basename(str(request.node.fspath))
145
144
  if test_file in _LLAMA_HF_PARITY_TEST_FILES:
@@ -1,10 +1,10 @@
1
1
  """Llama-3 renderer coverage.
2
2
 
3
3
  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.
8
8
  """
9
9
 
10
10
  from __future__ import annotations
@@ -34,7 +34,7 @@ _MODEL_PAIRS = [
34
34
  @pytest.fixture(scope="module", params=_MODEL_PAIRS, ids=[m for m, _ in _MODEL_PAIRS])
35
35
  def llama_pair(request):
36
36
  canonical, mirror = request.param
37
- tok = load_tokenizer(mirror)
37
+ tok = load_tokenizer(canonical)
38
38
  renderer = Llama3Renderer(tok, Llama3RendererConfig(date_string=_PINNED_DATE))
39
39
  return canonical, mirror, tok, renderer
40
40
 
@@ -58,6 +58,14 @@ def test_create_renderer_via_explicit_config(llama_pair):
58
58
  assert isinstance(r, Llama3Renderer)
59
59
 
60
60
 
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
+
61
69
  # ---------------------------------------------------------------------------
62
70
  # Constructor contract
63
71
  # ---------------------------------------------------------------------------
@@ -9,9 +9,11 @@ repo doesn't auto-propagate.
9
9
  from __future__ import annotations
10
10
 
11
11
  import re
12
+ from types import SimpleNamespace
12
13
  from unittest.mock import patch
13
14
 
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
15
17
 
16
18
 
17
19
  # ---------------------------------------------------------------------------
@@ -70,6 +72,23 @@ def test_kimi_loads_with_pinned_revision(mock_from_pretrained):
70
72
  }
71
73
 
72
74
 
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
+
73
92
  @patch("transformers.AutoTokenizer.from_pretrained")
74
93
  def test_unknown_path_falls_through_to_no_remote_code(mock_from_pretrained):
75
94
  """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):
92
111
  )
93
112
 
94
113
 
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
+
95
161
  # ---------------------------------------------------------------------------
96
162
  # Smoke: real tokenizer loads behave as expected
97
163
  # ---------------------------------------------------------------------------
@@ -53,6 +53,8 @@ NO_OP_MODELS = {
53
53
  "poolside/Laguna-XS.2",
54
54
  # Llama-3 has no reasoning channel at all — preserve flags can't add
55
55
  # or drop anything, so they're pure no-ops.
56
+ "meta-llama/Llama-3.2-1B-Instruct",
57
+ "meta-llama/Llama-3.2-3B-Instruct",
56
58
  "unsloth/Llama-3.2-1B-Instruct",
57
59
  }
58
60
 
@@ -324,6 +326,8 @@ NEVER_PRESERVES_MODELS = {
324
326
  "Qwen/Qwen3-VL-30B-A3B-Instruct",
325
327
  # Llama-3 ships no <think> rendering path, so reasoning_content never
326
328
  # surfaces in the output regardless of the preserve flags.
329
+ "meta-llama/Llama-3.2-1B-Instruct",
330
+ "meta-llama/Llama-3.2-3B-Instruct",
327
331
  "unsloth/Llama-3.2-1B-Instruct",
328
332
  }
329
333
 
File without changes
File without changes