renderers 0.1.10.dev1__tar.gz → 0.1.10.dev10__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.
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/PKG-INFO +2 -2
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/README.md +1 -1
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/docs/renderer-config.md +4 -2
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/renderers/__init__.py +8 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/renderers/_version.py +2 -2
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/renderers/base.py +14 -3
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/renderers/configs.py +68 -0
- renderers-0.1.10.dev10/renderers/laguna_s21.py +33 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/renderers/laguna_xs2.py +14 -2
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/renderers/nemotron3.py +22 -3
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/tests/conftest.py +7 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/tests/test_bridge.py +1 -0
- renderers-0.1.10.dev10/tests/test_laguna_s21.py +128 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/tests/test_nemotron3_parity.py +16 -9
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/tests/test_nemotron3_ultra.py +32 -7
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/tests/test_renderer_config_parity.py +4 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/tests/test_roundtrip.py +2 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/.github/workflows/publish-dev.yml +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/.github/workflows/publish.yml +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/.github/workflows/style.yml +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/.github/workflows/test.yml +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/.gitignore +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/.pre-commit-config.yaml +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/LICENSE +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/examples/README.md +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/examples/sglang/multiturn_generate_sglang.py +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/examples/sglang/online_multiturn_sglang.py +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/examples/tinker/multiturn_generate_tinker.py +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/examples/transformers/multiturn_generate_transformers.py +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/examples/vllm/multiturn_generate_vllm.py +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/pyproject.toml +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/renderers/client.py +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/renderers/deepseek_r1.py +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/renderers/deepseek_v3.py +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/renderers/default.py +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/renderers/glm45.py +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/renderers/glm5.py +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/renderers/gpt_oss.py +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/renderers/hy3.py +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/renderers/kimi_k2.py +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/renderers/kimi_k25.py +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/renderers/llama_3.py +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/renderers/minimax_m2.py +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/renderers/parsers.py +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/renderers/parsing.py +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/renderers/prime_qwen3.py +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/renderers/qwen3.py +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/renderers/qwen35.py +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/renderers/qwen36.py +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/renderers/qwen3_vl.py +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/tests/test_build_helpers.py +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/tests/test_client.py +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/tests/test_deepseek_r1.py +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/tests/test_disabled_thinking_stability.py +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/tests/test_glm_tool_name_validation.py +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/tests/test_gpt_oss_harmony_parity.py +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/tests/test_hy3.py +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/tests/test_incremental.py +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/tests/test_is_content.py +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/tests/test_kimi_k25_tool_schema.py +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/tests/test_laguna_m1.py +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/tests/test_laguna_xs21.py +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/tests/test_llama_3.py +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/tests/test_load_tokenizer.py +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/tests/test_message_indices.py +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/tests/test_message_tool_names.py +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/tests/test_multimodal.py +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/tests/test_parse_response.py +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/tests/test_parse_response_robustness.py +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/tests/test_parsers.py +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/tests/test_preserve_thinking.py +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/tests/test_prime_qwen3_parity.py +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/tests/test_qwen35_size_coverage.py +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/tests/test_render_ids.py +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/tests/test_renderer_config.py +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/tests/test_sampled_mask.py +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/tests/test_tokens_per_message.py +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/tests/test_tool_arg_type_preservation.py +0 -0
- {renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: renderers
|
|
3
|
-
Version: 0.1.10.
|
|
3
|
+
Version: 0.1.10.dev10
|
|
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
|
|
@@ -56,7 +56,7 @@ next_prompt_ids = r.bridge_to_next_turn(
|
|
|
56
56
|
)
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
Hand-coded renderers ship for `qwen3`, `qwen3-vl`, `qwen3.5`, `qwen3.6`, `glm-5`, `glm-5.1`, `glm-4.5`, `minimax-m2`, `deepseek-v3`, `deepseek-r1`, `kimi-k2`, `kimi-k2.5` / `kimi-k2.6`, `laguna-xs.2`, `laguna-xs-2.1`, `laguna-m.1`, `nemotron-3`, `nemotron-3-ultra`, `llama-3`, `gpt-oss`, `hy3`, and `prime-qwen3`. Anything else falls back to `DefaultRenderer`, a generic `apply_chat_template` wrapper.
|
|
59
|
+
Hand-coded renderers ship for `qwen3`, `qwen3-vl`, `qwen3.5`, `qwen3.6`, `glm-5`, `glm-5.1`, `glm-4.5`, `minimax-m2`, `deepseek-v3`, `deepseek-r1`, `kimi-k2`, `kimi-k2.5` / `kimi-k2.6`, `laguna-xs.2`, `laguna-xs-2.1`, `laguna-s-2.1`, `laguna-m.1`, `nemotron-3`, `nemotron-3-ultra`, `nemotron-3.5`, `llama-3`, `gpt-oss`, `hy3`, and `prime-qwen3`. Anything else falls back to `DefaultRenderer`, a generic `apply_chat_template` wrapper.
|
|
60
60
|
|
|
61
61
|
## API
|
|
62
62
|
|
|
@@ -40,7 +40,7 @@ next_prompt_ids = r.bridge_to_next_turn(
|
|
|
40
40
|
)
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
Hand-coded renderers ship for `qwen3`, `qwen3-vl`, `qwen3.5`, `qwen3.6`, `glm-5`, `glm-5.1`, `glm-4.5`, `minimax-m2`, `deepseek-v3`, `deepseek-r1`, `kimi-k2`, `kimi-k2.5` / `kimi-k2.6`, `laguna-xs.2`, `laguna-xs-2.1`, `laguna-m.1`, `nemotron-3`, `nemotron-3-ultra`, `llama-3`, `gpt-oss`, `hy3`, and `prime-qwen3`. Anything else falls back to `DefaultRenderer`, a generic `apply_chat_template` wrapper.
|
|
43
|
+
Hand-coded renderers ship for `qwen3`, `qwen3-vl`, `qwen3.5`, `qwen3.6`, `glm-5`, `glm-5.1`, `glm-4.5`, `minimax-m2`, `deepseek-v3`, `deepseek-r1`, `kimi-k2`, `kimi-k2.5` / `kimi-k2.6`, `laguna-xs.2`, `laguna-xs-2.1`, `laguna-s-2.1`, `laguna-m.1`, `nemotron-3`, `nemotron-3-ultra`, `nemotron-3.5`, `llama-3`, `gpt-oss`, `hy3`, and `prime-qwen3`. Anything else falls back to `DefaultRenderer`, a generic `apply_chat_template` wrapper.
|
|
44
44
|
|
|
45
45
|
## API
|
|
46
46
|
|
|
@@ -39,10 +39,12 @@ chat-template kwargs. Those fields are covered by parity tests against
|
|
|
39
39
|
| Laguna XS.2 | `LagunaXS2RendererConfig` | `enable_thinking`, `render_assistant_messages_raw` | - |
|
|
40
40
|
| Laguna M.1 | `LagunaM1RendererConfig` | `enable_thinking`, `render_assistant_messages_raw` | - |
|
|
41
41
|
| Laguna XS-2.1 | `LagunaXS21RendererConfig` | `enable_thinking` | - |
|
|
42
|
+
| Laguna S-2.1 | `LagunaS21RendererConfig` | `enable_thinking`, `preserve_thinking` | - |
|
|
42
43
|
| Llama 3 | `Llama3RendererConfig` | `date_string`, `tools_in_user_message` | - |
|
|
43
44
|
| MiniMax M2 | `MiniMaxM2RendererConfig` | `model_identity` | - |
|
|
44
45
|
| Nemotron-3 Nano / Super | `Nemotron3RendererConfig` | `enable_thinking`, `truncate_history_thinking`, `low_effort` | - |
|
|
45
46
|
| Nemotron-3 Ultra | `Nemotron3UltraRendererConfig` | `enable_thinking`, `truncate_history_thinking`, `medium_effort` | - |
|
|
47
|
+
| Nemotron-3.5 Lightning | `Nemotron35RendererConfig` | `enable_thinking`, `truncate_history_thinking` | - |
|
|
46
48
|
| DeepSeek V3 | `DeepSeekV3RendererConfig` | - | - |
|
|
47
49
|
| DeepSeek R1 | `DeepSeekR1RendererConfig` | - | - |
|
|
48
50
|
|
|
@@ -134,10 +136,10 @@ the knobs its template actually exposes:
|
|
|
134
136
|
| gpt-oss | `auto_drop_analysis=False -> all`, else `tool_cycle` |
|
|
135
137
|
| Hy3 | `preserved_thinking=True -> all`, else `tool_cycle` |
|
|
136
138
|
| Kimi K2.5 / 2.6 | `thinking=False -> all`, else `tool_cycle` |
|
|
137
|
-
| Nemotron-3 | `truncate_history_thinking=False -> all`; else `enable_thinking=False -> all`; else `tool_cycle` |
|
|
139
|
+
| Nemotron-3 / 3.5 | `truncate_history_thinking=False -> all`; else `enable_thinking=False -> all`; else `tool_cycle` |
|
|
138
140
|
| DeepSeek R1 | `template` |
|
|
139
141
|
| MiniMax M2 | `tool_cycle` |
|
|
140
|
-
| DeepSeek V3, Qwen3-VL, Kimi K2, Laguna XS.2 / M.1 / XS-2.1, Llama 3 | `all` |
|
|
142
|
+
| DeepSeek V3, Qwen3-VL, Kimi K2, Laguna XS.2 / M.1 / XS-2.1 / S-2.1, Llama 3 | `all` |
|
|
141
143
|
| PrimeIntellect Qwen3 | `all` |
|
|
142
144
|
|
|
143
145
|
Config construction raises when an explicit template knob directly contradicts
|
|
@@ -55,10 +55,12 @@ from renderers.configs import (
|
|
|
55
55
|
KimiK25RendererConfig,
|
|
56
56
|
KimiK2RendererConfig,
|
|
57
57
|
LagunaM1RendererConfig,
|
|
58
|
+
LagunaS21RendererConfig,
|
|
58
59
|
LagunaXS2RendererConfig,
|
|
59
60
|
LagunaXS21RendererConfig,
|
|
60
61
|
Llama3RendererConfig,
|
|
61
62
|
MiniMaxM2RendererConfig,
|
|
63
|
+
Nemotron35RendererConfig,
|
|
62
64
|
Nemotron3RendererConfig,
|
|
63
65
|
Nemotron3UltraRendererConfig,
|
|
64
66
|
PrimeQwen3RendererConfig,
|
|
@@ -92,10 +94,12 @@ _LAZY_RENDERERS: dict[str, str] = {
|
|
|
92
94
|
"KimiK25Renderer": "renderers.kimi_k25",
|
|
93
95
|
"KimiK2Renderer": "renderers.kimi_k2",
|
|
94
96
|
"LagunaM1Renderer": "renderers.laguna_xs2",
|
|
97
|
+
"LagunaS21Renderer": "renderers.laguna_s21",
|
|
95
98
|
"LagunaXS21Renderer": "renderers.laguna_xs2",
|
|
96
99
|
"LagunaXS2Renderer": "renderers.laguna_xs2",
|
|
97
100
|
"Llama3Renderer": "renderers.llama_3",
|
|
98
101
|
"MiniMaxM2Renderer": "renderers.minimax_m2",
|
|
102
|
+
"Nemotron35Renderer": "renderers.nemotron3",
|
|
99
103
|
"Nemotron3Renderer": "renderers.nemotron3",
|
|
100
104
|
"Nemotron3UltraRenderer": "renderers.nemotron3",
|
|
101
105
|
"PrimeQwen3Renderer": "renderers.prime_qwen3",
|
|
@@ -149,6 +153,8 @@ __all__ = [
|
|
|
149
153
|
"KimiK2RendererConfig",
|
|
150
154
|
"LagunaM1Renderer",
|
|
151
155
|
"LagunaM1RendererConfig",
|
|
156
|
+
"LagunaS21Renderer",
|
|
157
|
+
"LagunaS21RendererConfig",
|
|
152
158
|
"LagunaXS2Renderer",
|
|
153
159
|
"LagunaXS2RendererConfig",
|
|
154
160
|
"LagunaXS21Renderer",
|
|
@@ -162,6 +168,8 @@ __all__ = [
|
|
|
162
168
|
"MiniMaxM2RendererConfig",
|
|
163
169
|
"MultiModalData",
|
|
164
170
|
"MultimodalRenderer",
|
|
171
|
+
"Nemotron35Renderer",
|
|
172
|
+
"Nemotron35RendererConfig",
|
|
165
173
|
"Nemotron3Renderer",
|
|
166
174
|
"Nemotron3RendererConfig",
|
|
167
175
|
"Nemotron3UltraRenderer",
|
|
@@ -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.10.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 1, 10, '
|
|
21
|
+
__version__ = version = '0.1.10.dev10'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 10, 'dev10')
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -1065,6 +1065,9 @@ MODEL_RENDERER_MAP: dict[str, str] = {
|
|
|
1065
1065
|
"nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-BF16": "nemotron-3",
|
|
1066
1066
|
"nvidia/NVIDIA-Nemotron-3-Ultra-550B-A55B-BF16": "nemotron-3-ultra",
|
|
1067
1067
|
"nvidia/NVIDIA-Nemotron-3-Ultra-550B-A55B-FP8": "nemotron-3-ultra",
|
|
1068
|
+
# Nemotron 3.5 (Lightning). Its template is the Ultra variant's minus the
|
|
1069
|
+
# effort kwarg (``nemotron-3.5``).
|
|
1070
|
+
"nvidia/NVIDIA-Nemotron-3.5-Lightning-30B-A3B-BF16": "nemotron-3.5",
|
|
1068
1071
|
# Llama 3.2 (Instruct). Tested against the gated meta-llama repos and
|
|
1069
1072
|
# the unrestricted unsloth/... mirror, which ships a byte-identical
|
|
1070
1073
|
# chat template. ``Llama3Renderer`` defaults ``date_string`` to
|
|
@@ -1073,11 +1076,12 @@ MODEL_RENDERER_MAP: dict[str, str] = {
|
|
|
1073
1076
|
# construction to pin a different date.
|
|
1074
1077
|
"meta-llama/Llama-3.2-1B-Instruct": "llama-3",
|
|
1075
1078
|
"meta-llama/Llama-3.2-3B-Instruct": "llama-3",
|
|
1076
|
-
# Poolside Laguna. These checkpoints ship distinct chat templates,
|
|
1077
|
-
#
|
|
1079
|
+
# Poolside Laguna. These checkpoints ship distinct chat templates, each
|
|
1080
|
+
# mirrored by its own renderer class/config discriminator.
|
|
1078
1081
|
"poolside/Laguna-XS.2": "laguna-xs.2",
|
|
1079
1082
|
"poolside/Laguna-M.1": "laguna-m.1",
|
|
1080
1083
|
"poolside/Laguna-XS-2.1": "laguna-xs-2.1",
|
|
1084
|
+
"poolside/Laguna-S-2.1": "laguna-s-2.1",
|
|
1081
1085
|
# GPT-OSS.
|
|
1082
1086
|
"openai/gpt-oss-20b": "gpt-oss",
|
|
1083
1087
|
"openai/gpt-oss-120b": "gpt-oss",
|
|
@@ -1325,6 +1329,7 @@ def _populate_registry():
|
|
|
1325
1329
|
from renderers.hy3 import Hy3Renderer
|
|
1326
1330
|
from renderers.kimi_k2 import KimiK2Renderer
|
|
1327
1331
|
from renderers.kimi_k25 import KimiK25Renderer
|
|
1332
|
+
from renderers.laguna_s21 import LagunaS21Renderer
|
|
1328
1333
|
from renderers.laguna_xs2 import (
|
|
1329
1334
|
LagunaM1Renderer,
|
|
1330
1335
|
LagunaXS2Renderer,
|
|
@@ -1332,7 +1337,11 @@ def _populate_registry():
|
|
|
1332
1337
|
)
|
|
1333
1338
|
from renderers.llama_3 import Llama3Renderer
|
|
1334
1339
|
from renderers.minimax_m2 import MiniMaxM2Renderer
|
|
1335
|
-
from renderers.nemotron3 import
|
|
1340
|
+
from renderers.nemotron3 import (
|
|
1341
|
+
Nemotron3Renderer,
|
|
1342
|
+
Nemotron3UltraRenderer,
|
|
1343
|
+
Nemotron35Renderer,
|
|
1344
|
+
)
|
|
1336
1345
|
from renderers.prime_qwen3 import PrimeQwen3Renderer
|
|
1337
1346
|
from renderers.qwen3 import Qwen3Renderer
|
|
1338
1347
|
from renderers.qwen3_vl import Qwen3VLRenderer
|
|
@@ -1359,9 +1368,11 @@ def _populate_registry():
|
|
|
1359
1368
|
"laguna-xs.2": LagunaXS2Renderer,
|
|
1360
1369
|
"laguna-m.1": LagunaM1Renderer,
|
|
1361
1370
|
"laguna-xs-2.1": LagunaXS21Renderer,
|
|
1371
|
+
"laguna-s-2.1": LagunaS21Renderer,
|
|
1362
1372
|
"llama-3": Llama3Renderer,
|
|
1363
1373
|
"nemotron-3": Nemotron3Renderer,
|
|
1364
1374
|
"nemotron-3-ultra": Nemotron3UltraRenderer,
|
|
1375
|
+
"nemotron-3.5": Nemotron35Renderer,
|
|
1365
1376
|
"gpt-oss": GptOssRenderer,
|
|
1366
1377
|
}
|
|
1367
1378
|
)
|
|
@@ -546,6 +546,37 @@ class LagunaXS21RendererConfig(BaseRendererConfig):
|
|
|
546
546
|
upstream default."""
|
|
547
547
|
|
|
548
548
|
|
|
549
|
+
class LagunaS21RendererConfig(BaseRendererConfig):
|
|
550
|
+
"""Laguna S-2.1 renderer config.
|
|
551
|
+
|
|
552
|
+
S-2.1 is a larger sibling of XS-2.1 sharing its tokenizer (same vocab,
|
|
553
|
+
special tokens, and merges), but its chat template is *not* byte-identical:
|
|
554
|
+
``enable_thinking`` defaults to ``True`` (XS-2.1 defaults ``False``), and a
|
|
555
|
+
new ``preserve_thinking`` kwarg widens the reasoning-display gate to
|
|
556
|
+
``enable_thinking or preserve_thinking``. The token format is otherwise
|
|
557
|
+
identical, so this is served by
|
|
558
|
+
:class:`renderers.laguna_s21.LagunaS21Renderer`, a thin subclass of
|
|
559
|
+
``LagunaXS21Renderer`` that only overrides that gate.
|
|
560
|
+
"""
|
|
561
|
+
|
|
562
|
+
name: Literal["laguna-s-2.1"] = "laguna-s-2.1"
|
|
563
|
+
|
|
564
|
+
enable_thinking: bool = True
|
|
565
|
+
"""When ``True``, the generation prompt ends with ``<think>`` and every
|
|
566
|
+
assistant turn renders ``<think>{reasoning}</think>``; when ``False``,
|
|
567
|
+
turns open with a bare ``</think>``. Mirrors the template's
|
|
568
|
+
``enable_thinking`` kwarg — note S-2.1's upstream default is ``True``,
|
|
569
|
+
unlike XS-2.1's ``False``."""
|
|
570
|
+
|
|
571
|
+
preserve_thinking: bool = False
|
|
572
|
+
"""When ``True``, assistant turns keep their ``<think>{reasoning}</think>``
|
|
573
|
+
block even while ``enable_thinking`` is ``False`` — the template gates
|
|
574
|
+
reasoning display on ``enable_thinking or preserve_thinking``. With the
|
|
575
|
+
default ``False`` the gate collapses to ``enable_thinking`` and the
|
|
576
|
+
renderer matches XS-2.1 turn-for-turn. Mirrors the template's
|
|
577
|
+
``preserve_thinking`` kwarg and its upstream default."""
|
|
578
|
+
|
|
579
|
+
|
|
549
580
|
class Llama3RendererConfig(BaseRendererConfig):
|
|
550
581
|
"""Llama-3.x Instruct renderer config.
|
|
551
582
|
|
|
@@ -657,6 +688,37 @@ class Nemotron3UltraRendererConfig(BaseRendererConfig):
|
|
|
657
688
|
user message. Mirrors the Ultra chat template's ``medium_effort`` kwarg."""
|
|
658
689
|
|
|
659
690
|
|
|
691
|
+
class Nemotron35RendererConfig(BaseRendererConfig):
|
|
692
|
+
"""Nemotron-3.5 (Lightning) renderer config.
|
|
693
|
+
|
|
694
|
+
Nemotron 3.5's chat template is the Ultra variant's minus the effort
|
|
695
|
+
kwarg: same reasoning-block glue (no ``\\n`` around ``</think>``, no
|
|
696
|
+
trim on truncated tool-call history), but its Jinja defines no
|
|
697
|
+
reasoning-effort variable at all. It shares the
|
|
698
|
+
:class:`renderers.nemotron3.Nemotron3Renderer` implementation via the
|
|
699
|
+
:class:`renderers.nemotron3.Nemotron35Renderer` subclass and is reached
|
|
700
|
+
via the ``nemotron-3.5`` discriminator.
|
|
701
|
+
"""
|
|
702
|
+
|
|
703
|
+
name: Literal["nemotron-3.5"] = "nemotron-3.5"
|
|
704
|
+
|
|
705
|
+
enable_thinking: bool = True
|
|
706
|
+
"""See :class:`Nemotron3RendererConfig.enable_thinking`."""
|
|
707
|
+
|
|
708
|
+
truncate_history_thinking: bool = True
|
|
709
|
+
"""See :class:`Nemotron3RendererConfig.truncate_history_thinking`."""
|
|
710
|
+
|
|
711
|
+
@model_validator(mode="after")
|
|
712
|
+
def _check_thinking_retention(self):
|
|
713
|
+
_reject_thinking_retention_conflict(
|
|
714
|
+
self,
|
|
715
|
+
"truncate_history_thinking",
|
|
716
|
+
true_implies="tool_cycle",
|
|
717
|
+
false_implies="all",
|
|
718
|
+
)
|
|
719
|
+
return self
|
|
720
|
+
|
|
721
|
+
|
|
660
722
|
class DeepSeekV3RendererConfig(BaseRendererConfig):
|
|
661
723
|
"""DeepSeek-V3 renderer config (non-reasoning).
|
|
662
724
|
|
|
@@ -704,10 +766,12 @@ RendererConfig = Annotated[
|
|
|
704
766
|
LagunaXS2RendererConfig,
|
|
705
767
|
LagunaM1RendererConfig,
|
|
706
768
|
LagunaXS21RendererConfig,
|
|
769
|
+
LagunaS21RendererConfig,
|
|
707
770
|
Llama3RendererConfig,
|
|
708
771
|
MiniMaxM2RendererConfig,
|
|
709
772
|
Nemotron3RendererConfig,
|
|
710
773
|
Nemotron3UltraRendererConfig,
|
|
774
|
+
Nemotron35RendererConfig,
|
|
711
775
|
DeepSeekV3RendererConfig,
|
|
712
776
|
DeepSeekR1RendererConfig,
|
|
713
777
|
],
|
|
@@ -745,10 +809,12 @@ _CONFIG_BY_NAME: dict[str, type[BaseRendererConfig]] = {
|
|
|
745
809
|
"laguna-xs.2": LagunaXS2RendererConfig,
|
|
746
810
|
"laguna-m.1": LagunaM1RendererConfig,
|
|
747
811
|
"laguna-xs-2.1": LagunaXS21RendererConfig,
|
|
812
|
+
"laguna-s-2.1": LagunaS21RendererConfig,
|
|
748
813
|
"llama-3": Llama3RendererConfig,
|
|
749
814
|
"minimax-m2": MiniMaxM2RendererConfig,
|
|
750
815
|
"nemotron-3": Nemotron3RendererConfig,
|
|
751
816
|
"nemotron-3-ultra": Nemotron3UltraRendererConfig,
|
|
817
|
+
"nemotron-3.5": Nemotron35RendererConfig,
|
|
752
818
|
"deepseek-v3": DeepSeekV3RendererConfig,
|
|
753
819
|
"deepseek-r1": DeepSeekR1RendererConfig,
|
|
754
820
|
}
|
|
@@ -792,10 +858,12 @@ __all__ = [
|
|
|
792
858
|
"KimiK25RendererConfig",
|
|
793
859
|
"KimiK2RendererConfig",
|
|
794
860
|
"LagunaM1RendererConfig",
|
|
861
|
+
"LagunaS21RendererConfig",
|
|
795
862
|
"LagunaXS2RendererConfig",
|
|
796
863
|
"LagunaXS21RendererConfig",
|
|
797
864
|
"Llama3RendererConfig",
|
|
798
865
|
"MiniMaxM2RendererConfig",
|
|
866
|
+
"Nemotron35RendererConfig",
|
|
799
867
|
"Nemotron3RendererConfig",
|
|
800
868
|
"Nemotron3UltraRendererConfig",
|
|
801
869
|
"PrimeQwen3RendererConfig",
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Laguna S-2.1 Renderer — a larger sibling of Laguna XS-2.1.
|
|
2
|
+
|
|
3
|
+
S-2.1 shares XS-2.1's tokenizer and token format, so this is a thin subclass of
|
|
4
|
+
:class:`renderers.laguna_xs2.LagunaXS21Renderer`; see that module for the shared
|
|
5
|
+
format. The template delta is two thinking kwargs: ``enable_thinking`` defaults
|
|
6
|
+
to ``True`` (XS-2.1 defaults ``False``), and ``preserve_thinking`` widens the
|
|
7
|
+
reasoning-display gate to ``enable_thinking or preserve_thinking``.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from transformers.tokenization_utils import PreTrainedTokenizer
|
|
13
|
+
|
|
14
|
+
from renderers.configs import LagunaS21RendererConfig
|
|
15
|
+
from renderers.laguna_xs2 import LagunaXS21Renderer
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class LagunaS21Renderer(LagunaXS21Renderer):
|
|
19
|
+
"""Mirrors the ``poolside/Laguna-S-2.1`` chat template."""
|
|
20
|
+
|
|
21
|
+
# Narrows the inherited attribute so the S-2.1-only ``preserve_thinking``
|
|
22
|
+
# resolves; ``__init__`` always stores an S-2.1 config.
|
|
23
|
+
config: LagunaS21RendererConfig
|
|
24
|
+
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
tokenizer: PreTrainedTokenizer,
|
|
28
|
+
config: LagunaS21RendererConfig | None = None,
|
|
29
|
+
):
|
|
30
|
+
super().__init__(tokenizer, config or LagunaS21RendererConfig())
|
|
31
|
+
|
|
32
|
+
def _render_history_reasoning(self) -> bool:
|
|
33
|
+
return self.config.enable_thinking or self.config.preserve_thinking
|
|
@@ -41,6 +41,9 @@ Laguna M.1's official template (revision ``2bf8a4ab``) is served by
|
|
|
41
41
|
:class:`LagunaM1Renderer`. It shares XS.2's byte layout, tool syntax, and
|
|
42
42
|
generation prompt, but has no fallback system prompt and reads assistant
|
|
43
43
|
reasoning from ``reasoning`` before falling back to ``reasoning_content``.
|
|
44
|
+
|
|
45
|
+
S-2.1 subclasses :class:`LagunaXS21Renderer` from :mod:`renderers.laguna_s21`,
|
|
46
|
+
overriding only the ``_render_history_reasoning`` seam.
|
|
44
47
|
"""
|
|
45
48
|
|
|
46
49
|
from __future__ import annotations
|
|
@@ -63,6 +66,7 @@ from renderers.base import (
|
|
|
63
66
|
)
|
|
64
67
|
from renderers.configs import (
|
|
65
68
|
LagunaM1RendererConfig,
|
|
69
|
+
LagunaS21RendererConfig,
|
|
66
70
|
LagunaXS2RendererConfig,
|
|
67
71
|
LagunaXS21RendererConfig,
|
|
68
72
|
)
|
|
@@ -120,6 +124,7 @@ class LagunaXS2Renderer:
|
|
|
120
124
|
LagunaXS2RendererConfig
|
|
121
125
|
| LagunaM1RendererConfig
|
|
122
126
|
| LagunaXS21RendererConfig
|
|
127
|
+
| LagunaS21RendererConfig
|
|
123
128
|
| None
|
|
124
129
|
) = None,
|
|
125
130
|
):
|
|
@@ -688,10 +693,17 @@ class LagunaXS21Renderer(LagunaXS2Renderer):
|
|
|
688
693
|
def __init__(
|
|
689
694
|
self,
|
|
690
695
|
tokenizer: PreTrainedTokenizer,
|
|
691
|
-
config: LagunaXS21RendererConfig | None = None,
|
|
696
|
+
config: LagunaXS21RendererConfig | LagunaS21RendererConfig | None = None,
|
|
692
697
|
):
|
|
693
698
|
super().__init__(tokenizer, config or LagunaXS21RendererConfig())
|
|
694
699
|
|
|
700
|
+
def _render_history_reasoning(self) -> bool:
|
|
701
|
+
"""Whether an assistant turn renders its ``<think>{reasoning}</think>``
|
|
702
|
+
block (vs opening with a bare ``</think>``). Mirrors the template's
|
|
703
|
+
reasoning-display gate; XS-2.1 gates this on ``enable_thinking`` alone.
|
|
704
|
+
"""
|
|
705
|
+
return self.config.enable_thinking
|
|
706
|
+
|
|
695
707
|
def render(
|
|
696
708
|
self,
|
|
697
709
|
messages: list[Message],
|
|
@@ -975,7 +987,7 @@ class LagunaXS21Renderer(LagunaXS2Renderer):
|
|
|
975
987
|
# scaffolding the model never samples.
|
|
976
988
|
emit_special(self._assistant, msg_idx, is_sampled=False, is_content=False)
|
|
977
989
|
|
|
978
|
-
if self.
|
|
990
|
+
if self._render_history_reasoning():
|
|
979
991
|
# ``<think>{reasoning}</think>`` renders verbatim, even when
|
|
980
992
|
# the reasoning is empty; the opener is the gen-prompt
|
|
981
993
|
# prefill, the rest the model sampled.
|
|
@@ -31,7 +31,11 @@ from renderers.base import (
|
|
|
31
31
|
should_rerender_for_thinking_retention,
|
|
32
32
|
trim_to_turn_close,
|
|
33
33
|
)
|
|
34
|
-
from renderers.configs import
|
|
34
|
+
from renderers.configs import (
|
|
35
|
+
Nemotron3RendererConfig,
|
|
36
|
+
Nemotron3UltraRendererConfig,
|
|
37
|
+
Nemotron35RendererConfig,
|
|
38
|
+
)
|
|
35
39
|
from renderers.parsing import parse_qwen35
|
|
36
40
|
|
|
37
41
|
# ---------------------------------------------------------------------------
|
|
@@ -76,10 +80,12 @@ def _render_extra_keys(obj: dict[str, Any], handled_keys: set[str]) -> list[str]
|
|
|
76
80
|
return lines
|
|
77
81
|
|
|
78
82
|
|
|
79
|
-
# The Nemotron
|
|
83
|
+
# The Nemotron family ships three chat-template variants. Nano / Super share
|
|
80
84
|
# one (renderer ``Nemotron3Renderer`` / config ``name="nemotron-3"``); Ultra
|
|
81
85
|
# differs in the reasoning-block glue — no ``\n`` around ``</think>`` — and is
|
|
82
|
-
# the ``Nemotron3UltraRenderer`` subclass (``name="nemotron-3-ultra"``)
|
|
86
|
+
# the ``Nemotron3UltraRenderer`` subclass (``name="nemotron-3-ultra"``);
|
|
87
|
+
# Nemotron 3.5 (Lightning) uses the Ultra glue but defines no effort kwarg and
|
|
88
|
+
# is the ``Nemotron35Renderer`` subclass (``name="nemotron-3.5"``). Which
|
|
83
89
|
# variant a checkpoint uses is carried by ``MODEL_RENDERER_MAP``, so the right
|
|
84
90
|
# renderer class is constructed and the variant is encoded by the class itself.
|
|
85
91
|
|
|
@@ -852,3 +858,16 @@ class Nemotron3UltraRenderer(Nemotron3Renderer):
|
|
|
852
858
|
|
|
853
859
|
_config_cls = Nemotron3UltraRendererConfig
|
|
854
860
|
_ultra = True
|
|
861
|
+
|
|
862
|
+
|
|
863
|
+
class Nemotron35Renderer(Nemotron3Renderer):
|
|
864
|
+
"""Renderer for Nemotron **3.5** (Lightning).
|
|
865
|
+
|
|
866
|
+
The 3.5 chat template is the Ultra variant's minus the effort kwarg: same
|
|
867
|
+
reasoning-block glue (carried by the ``_ultra`` class hook), but its Jinja
|
|
868
|
+
defines no reasoning-effort variable, so the config exposes none and the
|
|
869
|
+
effort hint is always empty.
|
|
870
|
+
"""
|
|
871
|
+
|
|
872
|
+
_config_cls = Nemotron35RendererConfig
|
|
873
|
+
_ultra = True
|
|
@@ -36,11 +36,18 @@ RENDERER_MODELS = [
|
|
|
36
36
|
# Ultra resolves to the `nemotron-3-ultra` config variant via the model
|
|
37
37
|
# name (auto → MODEL_RENDERER_MAP → nemotron-3-ultra).
|
|
38
38
|
("nvidia/NVIDIA-Nemotron-3-Ultra-550B-A55B-BF16", "auto"),
|
|
39
|
+
# Nemotron 3.5 (Lightning): Ultra's template variant minus the effort
|
|
40
|
+
# kwarg (auto → MODEL_RENDERER_MAP → nemotron-3.5).
|
|
41
|
+
("nvidia/NVIDIA-Nemotron-3.5-Lightning-30B-A3B-BF16", "auto"),
|
|
39
42
|
("poolside/Laguna-XS.2", "auto"),
|
|
40
43
|
("poolside/Laguna-M.1", "auto"),
|
|
41
44
|
# XS-2.1 resolves to `LagunaXS21Renderer` via the model name — its
|
|
42
45
|
# chat template differs from XS.2's (see renderers/laguna_xs2.py).
|
|
43
46
|
("poolside/Laguna-XS-2.1", "auto"),
|
|
47
|
+
# S-2.1 is a larger sibling of XS-2.1 sharing its tokenizer but NOT its
|
|
48
|
+
# chat template (S-2.1 defaults enable_thinking=True and adds a
|
|
49
|
+
# preserve_thinking gate), so it resolves to LagunaS21Renderer.
|
|
50
|
+
("poolside/Laguna-S-2.1", "auto"),
|
|
44
51
|
# DeepSeek-V3/R1 are intentionally NOT in this shared barrage: their
|
|
45
52
|
# chat templates can't render the barrage's tool-call fixtures (the
|
|
46
53
|
# templates require ``tool['type']`` and a string-serialized
|
|
@@ -33,6 +33,7 @@ _BRIDGE_MODELS = [
|
|
|
33
33
|
("moonshotai/Kimi-K2-Instruct", "auto"),
|
|
34
34
|
("moonshotai/Kimi-K2.5", "auto"),
|
|
35
35
|
("nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16", "auto"),
|
|
36
|
+
("nvidia/NVIDIA-Nemotron-3.5-Lightning-30B-A3B-BF16", "auto"),
|
|
36
37
|
("tencent/Hy3", "auto"),
|
|
37
38
|
("unsloth/Llama-3.2-1B-Instruct", "llama-3"),
|
|
38
39
|
("openai/gpt-oss-20b", "gpt-oss"),
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""Laguna-S-2.1 focused tests.
|
|
2
|
+
|
|
3
|
+
S-2.1 shares XS-2.1's tokenizer and token format, so the shared matrices
|
|
4
|
+
(conftest / config-parity) already assert byte parity on the common shapes
|
|
5
|
+
via :class:`LagunaS21Renderer`. This file pins the two behaviours that make
|
|
6
|
+
S-2.1 *differ* from XS-2.1 — and that the shared barrage can't reach because
|
|
7
|
+
it never sets the relevant kwargs:
|
|
8
|
+
|
|
9
|
+
- ``enable_thinking`` defaults to ``True`` (XS-2.1 defaults ``False``), so the
|
|
10
|
+
auto-resolved renderer renders reasoning by default, and
|
|
11
|
+
- the new ``preserve_thinking`` kwarg keeps historical ``<think>`` blocks even
|
|
12
|
+
while ``enable_thinking`` is ``False`` — the template gates reasoning display
|
|
13
|
+
on ``enable_thinking or preserve_thinking``.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from functools import lru_cache
|
|
19
|
+
from itertools import product
|
|
20
|
+
|
|
21
|
+
from renderers import create_renderer
|
|
22
|
+
from renderers.base import load_tokenizer
|
|
23
|
+
from renderers.configs import LagunaS21RendererConfig
|
|
24
|
+
from renderers.laguna_s21 import LagunaS21Renderer
|
|
25
|
+
|
|
26
|
+
_MODEL = "poolside/Laguna-S-2.1"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@lru_cache(maxsize=None)
|
|
30
|
+
def _tok():
|
|
31
|
+
return load_tokenizer(_MODEL)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _renderer(**config_kwargs) -> LagunaS21Renderer:
|
|
35
|
+
renderer = create_renderer(_tok(), LagunaS21RendererConfig(**config_kwargs))
|
|
36
|
+
assert isinstance(renderer, LagunaS21Renderer)
|
|
37
|
+
return renderer
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _expected(msgs, *, add_generation_prompt=False, **template_kwargs):
|
|
41
|
+
return list(
|
|
42
|
+
_tok().apply_chat_template(
|
|
43
|
+
msgs,
|
|
44
|
+
add_generation_prompt=add_generation_prompt,
|
|
45
|
+
tokenize=True,
|
|
46
|
+
return_dict=False,
|
|
47
|
+
**template_kwargs,
|
|
48
|
+
)
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_auto_resolves_to_s21_renderer_with_thinking_on_by_default():
|
|
53
|
+
"""``poolside/Laguna-S-2.1`` resolves to :class:`LagunaS21Renderer`, and
|
|
54
|
+
its config defaults ``enable_thinking=True`` / ``preserve_thinking=False``
|
|
55
|
+
— matching S-2.1's template defaults (unlike XS-2.1's thinking-off)."""
|
|
56
|
+
r = create_renderer(_tok()) # auto-resolve via MODEL_RENDERER_MAP
|
|
57
|
+
assert isinstance(r, LagunaS21Renderer)
|
|
58
|
+
assert r.config.name == "laguna-s-2.1"
|
|
59
|
+
assert r.config.enable_thinking is True
|
|
60
|
+
assert r.config.preserve_thinking is False
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_default_renders_empty_think_wrapper():
|
|
64
|
+
"""With thinking on by default, a reasoning-free assistant turn still
|
|
65
|
+
opens with the empty ``<think></think>`` wrapper (this is exactly the
|
|
66
|
+
render_ids divergence from the XS-2.1 renderer)."""
|
|
67
|
+
msgs = [
|
|
68
|
+
{"role": "user", "content": "What is 2+2?"},
|
|
69
|
+
{"role": "assistant", "content": "4"},
|
|
70
|
+
]
|
|
71
|
+
r = _renderer() # defaults: enable_thinking=True
|
|
72
|
+
ours = r.render_ids(msgs)
|
|
73
|
+
assert ours == _expected(msgs)
|
|
74
|
+
assert "<assistant><think></think>4</assistant>" in _tok().decode(ours)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def test_preserve_thinking_keeps_history_when_thinking_off():
|
|
78
|
+
"""``enable_thinking=False`` alone drops reasoning, but with
|
|
79
|
+
``preserve_thinking=True`` the historical ``<think>{reasoning}</think>``
|
|
80
|
+
survives — matching the template's ``enable_thinking or preserve_thinking``
|
|
81
|
+
gate."""
|
|
82
|
+
msgs = [
|
|
83
|
+
{"role": "user", "content": "What is 2+2?"},
|
|
84
|
+
{"role": "assistant", "reasoning_content": "Simple arithmetic", "content": "4"},
|
|
85
|
+
]
|
|
86
|
+
r = _renderer(enable_thinking=False, preserve_thinking=True)
|
|
87
|
+
ours = r.render_ids(msgs)
|
|
88
|
+
assert ours == _expected(msgs, enable_thinking=False, preserve_thinking=True)
|
|
89
|
+
assert "<think>Simple arithmetic</think>" in _tok().decode(ours)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def test_no_preserve_thinking_drops_history_when_thinking_off():
|
|
93
|
+
"""With both flags off the gate collapses to ``enable_thinking`` and the
|
|
94
|
+
turn opens with a bare ``</think>``, dropping the reasoning — identical to
|
|
95
|
+
the XS-2.1 renderer's default."""
|
|
96
|
+
msgs = [
|
|
97
|
+
{"role": "user", "content": "What is 2+2?"},
|
|
98
|
+
{"role": "assistant", "reasoning_content": "Simple arithmetic", "content": "4"},
|
|
99
|
+
]
|
|
100
|
+
r = _renderer(enable_thinking=False, preserve_thinking=False)
|
|
101
|
+
ours = r.render_ids(msgs)
|
|
102
|
+
assert ours == _expected(msgs, enable_thinking=False, preserve_thinking=False)
|
|
103
|
+
assert "Simple arithmetic" not in _tok().decode(ours)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def test_all_thinking_flag_combos_match_template():
|
|
107
|
+
"""Byte parity against ``apply_chat_template`` for every
|
|
108
|
+
``enable_thinking`` × ``preserve_thinking`` combination, over a multi-turn
|
|
109
|
+
conversation carrying historical reasoning."""
|
|
110
|
+
msgs = [
|
|
111
|
+
{"role": "user", "content": "Q1"},
|
|
112
|
+
{"role": "assistant", "reasoning_content": "deduce", "content": "A1"},
|
|
113
|
+
{"role": "user", "content": "Q2"},
|
|
114
|
+
{"role": "assistant", "reasoning_content": "more", "content": "A2"},
|
|
115
|
+
]
|
|
116
|
+
for enable_thinking, preserve_thinking in product((False, True), repeat=2):
|
|
117
|
+
r = _renderer(
|
|
118
|
+
enable_thinking=enable_thinking, preserve_thinking=preserve_thinking
|
|
119
|
+
)
|
|
120
|
+
for add_generation_prompt in (False, True):
|
|
121
|
+
assert r.render_ids(
|
|
122
|
+
msgs, add_generation_prompt=add_generation_prompt
|
|
123
|
+
) == _expected(
|
|
124
|
+
msgs,
|
|
125
|
+
add_generation_prompt=add_generation_prompt,
|
|
126
|
+
enable_thinking=enable_thinking,
|
|
127
|
+
preserve_thinking=preserve_thinking,
|
|
128
|
+
), (enable_thinking, preserve_thinking, add_generation_prompt)
|
|
@@ -25,9 +25,10 @@ Every assertion compares ``renderer.render_ids(...)`` to
|
|
|
25
25
|
renderer is byte-for-byte faithful for that case. Tokenizers are loaded from
|
|
26
26
|
the local HF cache (offline); no network.
|
|
27
27
|
|
|
28
|
-
The variants split across
|
|
29
|
-
``low_effort``)
|
|
30
|
-
|
|
28
|
+
The variants split across three configs: ``nemotron-3`` (Nano / Super, with
|
|
29
|
+
``low_effort``), ``nemotron-3-ultra`` (Ultra, with ``medium_effort``), and
|
|
30
|
+
``nemotron-3.5`` (Lightning — Ultra's glue, no effort kwarg). The helper
|
|
31
|
+
resolves the right config class per model from ``MODEL_RENDERER_MAP``.
|
|
31
32
|
"""
|
|
32
33
|
|
|
33
34
|
from __future__ import annotations
|
|
@@ -44,7 +45,8 @@ from renderers.configs import _config_class_for
|
|
|
44
45
|
NANO = "nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16"
|
|
45
46
|
SUPER = "nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-BF16"
|
|
46
47
|
ULTRA = "nvidia/NVIDIA-Nemotron-3-Ultra-550B-A55B-BF16"
|
|
47
|
-
|
|
48
|
+
LIGHTNING = "nvidia/NVIDIA-Nemotron-3.5-Lightning-30B-A3B-BF16"
|
|
49
|
+
MODELS = [NANO, SUPER, ULTRA, LIGHTNING]
|
|
48
50
|
|
|
49
51
|
|
|
50
52
|
@lru_cache
|
|
@@ -107,7 +109,9 @@ def _assert_parity(
|
|
|
107
109
|
)
|
|
108
110
|
|
|
109
111
|
|
|
110
|
-
pytestmark = pytest.mark.parametrize(
|
|
112
|
+
pytestmark = pytest.mark.parametrize(
|
|
113
|
+
"model", MODELS, ids=["nano", "super", "ultra", "lightning"]
|
|
114
|
+
)
|
|
111
115
|
|
|
112
116
|
|
|
113
117
|
TOOLS = [
|
|
@@ -648,8 +652,8 @@ _EFFORT_SHAPES = [
|
|
|
648
652
|
def test_low_effort_kwarg(model, flag, shape, extra):
|
|
649
653
|
"""``low_effort`` appends ``\\n\\n{reasoning effort: low}`` to the last user
|
|
650
654
|
message on **Super**; it's a no-op on **Nano** (its template never defines
|
|
651
|
-
it). Ultra
|
|
652
|
-
if model
|
|
655
|
+
it). The Ultra / Lightning configs have no such field, so they're skipped."""
|
|
656
|
+
if model in (ULTRA, LIGHTNING):
|
|
653
657
|
pytest.skip("low_effort is a nemotron-3 (Nano/Super) kwarg")
|
|
654
658
|
_assert_parity(model, shape, low_effort=flag, **extra)
|
|
655
659
|
|
|
@@ -660,7 +664,7 @@ def test_low_effort_kwarg(model, flag, shape, extra):
|
|
|
660
664
|
)
|
|
661
665
|
def test_medium_effort_kwarg(model, flag, shape, extra):
|
|
662
666
|
"""``medium_effort`` appends ``\\n\\n{reasoning effort: efficient}`` on
|
|
663
|
-
**Ultra**.
|
|
667
|
+
**Ultra**. The other configs have no such field, so they're skipped."""
|
|
664
668
|
if model != ULTRA:
|
|
665
669
|
pytest.skip("medium_effort is a nemotron-3-ultra kwarg")
|
|
666
670
|
_assert_parity(model, shape, medium_effort=flag, **extra)
|
|
@@ -668,9 +672,12 @@ def test_medium_effort_kwarg(model, flag, shape, extra):
|
|
|
668
672
|
|
|
669
673
|
def test_effort_kwarg_lives_on_the_right_variant(model):
|
|
670
674
|
"""Each effort kwarg is declared only on the variant whose template defines
|
|
671
|
-
it — the discriminated union rejects the wrong combination at config load.
|
|
675
|
+
it — the discriminated union rejects the wrong combination at config load.
|
|
676
|
+
Lightning's template defines no effort variable, so its config has neither."""
|
|
672
677
|
fields = _config_cls(model).template_field_names()
|
|
673
678
|
if model == ULTRA:
|
|
674
679
|
assert "medium_effort" in fields and "low_effort" not in fields
|
|
680
|
+
elif model == LIGHTNING:
|
|
681
|
+
assert "low_effort" not in fields and "medium_effort" not in fields
|
|
675
682
|
else:
|
|
676
683
|
assert "low_effort" in fields and "medium_effort" not in fields
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Offline wiring tests for the Nemotron
|
|
1
|
+
"""Offline wiring tests for the Nemotron variant split.
|
|
2
2
|
|
|
3
3
|
Assert the model→renderer mapping, the per-variant typed-config surface, and
|
|
4
4
|
the name-based ``low_effort`` gating WITHOUT loading any tokenizer (no
|
|
@@ -6,15 +6,16 @@ network). This pins the wiring the parity matrix can't reach — in particular
|
|
|
6
6
|
the FP8 Ultra entry, which no test loads a tokenizer for — so it can't
|
|
7
7
|
silently rot.
|
|
8
8
|
|
|
9
|
-
The
|
|
9
|
+
The three variants:
|
|
10
10
|
|
|
11
11
|
* ``nemotron-3`` — Nano / Super, shared template. Config exposes ``low_effort``
|
|
12
12
|
(honoured on Super, a no-op on Nano).
|
|
13
13
|
* ``nemotron-3-ultra`` — Ultra, distinct ``</think>`` glue. Config exposes
|
|
14
14
|
``medium_effort``.
|
|
15
|
+
* ``nemotron-3.5`` — Lightning, Ultra's glue but no effort kwarg at all.
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
All route to the one ``Nemotron3Renderer`` implementation via per-variant
|
|
18
|
+
subclasses.
|
|
18
19
|
"""
|
|
19
20
|
|
|
20
21
|
from types import SimpleNamespace
|
|
@@ -23,9 +24,15 @@ from renderers.base import MODEL_RENDERER_MAP, RENDERER_REGISTRY, _populate_regi
|
|
|
23
24
|
from renderers.configs import (
|
|
24
25
|
Nemotron3RendererConfig,
|
|
25
26
|
Nemotron3UltraRendererConfig,
|
|
27
|
+
Nemotron35RendererConfig,
|
|
26
28
|
_config_class_for,
|
|
27
29
|
)
|
|
28
|
-
from renderers.nemotron3 import
|
|
30
|
+
from renderers.nemotron3 import (
|
|
31
|
+
Nemotron3Renderer,
|
|
32
|
+
Nemotron3UltraRenderer,
|
|
33
|
+
Nemotron35Renderer,
|
|
34
|
+
_is_super,
|
|
35
|
+
)
|
|
29
36
|
|
|
30
37
|
_ULTRA_REPOS = [
|
|
31
38
|
"nvidia/NVIDIA-Nemotron-3-Ultra-550B-A55B-BF16",
|
|
@@ -35,6 +42,9 @@ _NANO_SUPER_REPOS = [
|
|
|
35
42
|
"nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16",
|
|
36
43
|
"nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-BF16",
|
|
37
44
|
]
|
|
45
|
+
_LIGHTNING_REPOS = [
|
|
46
|
+
"nvidia/NVIDIA-Nemotron-3.5-Lightning-30B-A3B-BF16",
|
|
47
|
+
]
|
|
38
48
|
|
|
39
49
|
|
|
40
50
|
def _fake_tok(name):
|
|
@@ -46,18 +56,23 @@ def test_models_map_to_their_variant():
|
|
|
46
56
|
assert MODEL_RENDERER_MAP.get(repo) == "nemotron-3-ultra", repo
|
|
47
57
|
for repo in _NANO_SUPER_REPOS:
|
|
48
58
|
assert MODEL_RENDERER_MAP.get(repo) == "nemotron-3", repo
|
|
59
|
+
for repo in _LIGHTNING_REPOS:
|
|
60
|
+
assert MODEL_RENDERER_MAP.get(repo) == "nemotron-3.5", repo
|
|
49
61
|
|
|
50
62
|
|
|
51
63
|
def test_each_discriminator_maps_to_its_config_and_renderer_class():
|
|
52
64
|
# Config discriminator → config class.
|
|
53
65
|
assert _config_class_for("nemotron-3") is Nemotron3RendererConfig
|
|
54
66
|
assert _config_class_for("nemotron-3-ultra") is Nemotron3UltraRendererConfig
|
|
55
|
-
|
|
56
|
-
#
|
|
67
|
+
assert _config_class_for("nemotron-3.5") is Nemotron35RendererConfig
|
|
68
|
+
# Registry → renderer class (Ultra / 3.5 are sibling subclasses, matching
|
|
69
|
+
# the GLM-5/5.1 and Qwen3.5/3.6 house style — not one class under N names).
|
|
57
70
|
_populate_registry()
|
|
58
71
|
assert RENDERER_REGISTRY["nemotron-3"] is Nemotron3Renderer
|
|
59
72
|
assert RENDERER_REGISTRY["nemotron-3-ultra"] is Nemotron3UltraRenderer
|
|
73
|
+
assert RENDERER_REGISTRY["nemotron-3.5"] is Nemotron35Renderer
|
|
60
74
|
assert issubclass(Nemotron3UltraRenderer, Nemotron3Renderer)
|
|
75
|
+
assert issubclass(Nemotron35Renderer, Nemotron3Renderer)
|
|
61
76
|
|
|
62
77
|
|
|
63
78
|
def test_variant_is_encoded_by_the_class():
|
|
@@ -66,8 +81,10 @@ def test_variant_is_encoded_by_the_class():
|
|
|
66
81
|
# config.name → class). Default config also follows the class.
|
|
67
82
|
assert Nemotron3Renderer._ultra is False
|
|
68
83
|
assert Nemotron3UltraRenderer._ultra is True
|
|
84
|
+
assert Nemotron35Renderer._ultra is True
|
|
69
85
|
assert Nemotron3Renderer._config_cls is Nemotron3RendererConfig
|
|
70
86
|
assert Nemotron3UltraRenderer._config_cls is Nemotron3UltraRendererConfig
|
|
87
|
+
assert Nemotron35Renderer._config_cls is Nemotron35RendererConfig
|
|
71
88
|
|
|
72
89
|
|
|
73
90
|
def test_template_fields_per_variant():
|
|
@@ -80,6 +97,10 @@ def test_template_fields_per_variant():
|
|
|
80
97
|
assert Nemotron3UltraRendererConfig.template_field_names() == frozenset(
|
|
81
98
|
{"enable_thinking", "truncate_history_thinking", "medium_effort"}
|
|
82
99
|
)
|
|
100
|
+
# Lightning's template defines no effort variable at all.
|
|
101
|
+
assert Nemotron35RendererConfig.template_field_names() == frozenset(
|
|
102
|
+
{"enable_thinking", "truncate_history_thinking"}
|
|
103
|
+
)
|
|
83
104
|
|
|
84
105
|
|
|
85
106
|
def test_configs_reject_the_other_variants_effort_kwarg():
|
|
@@ -91,6 +112,10 @@ def test_configs_reject_the_other_variants_effort_kwarg():
|
|
|
91
112
|
Nemotron3RendererConfig(medium_effort=True) # type: ignore[call-arg]
|
|
92
113
|
with pytest.raises(ValidationError):
|
|
93
114
|
Nemotron3UltraRendererConfig(low_effort=True) # type: ignore[call-arg]
|
|
115
|
+
with pytest.raises(ValidationError):
|
|
116
|
+
Nemotron35RendererConfig(low_effort=True) # type: ignore[call-arg]
|
|
117
|
+
with pytest.raises(ValidationError):
|
|
118
|
+
Nemotron35RendererConfig(medium_effort=True) # type: ignore[call-arg]
|
|
94
119
|
# And the removed ``ultra`` selector is gone entirely.
|
|
95
120
|
with pytest.raises(ValidationError):
|
|
96
121
|
Nemotron3RendererConfig(ultra=True) # type: ignore[call-arg]
|
|
@@ -63,9 +63,13 @@ _RENDERER_MODELS = [
|
|
|
63
63
|
# Ultra: auto-resolves to the ``nemotron-3-ultra`` config via the model
|
|
64
64
|
# name; parity asserted against the Ultra apply_chat_template (``medium_effort``).
|
|
65
65
|
("nvidia/NVIDIA-Nemotron-3-Ultra-550B-A55B-BF16", "auto"),
|
|
66
|
+
# Nemotron 3.5 (Lightning): Ultra's template minus the effort kwarg — its
|
|
67
|
+
# config declares only ``enable_thinking`` / ``truncate_history_thinking``.
|
|
68
|
+
("nvidia/NVIDIA-Nemotron-3.5-Lightning-30B-A3B-BF16", "auto"),
|
|
66
69
|
("poolside/Laguna-XS.2", "auto"),
|
|
67
70
|
("poolside/Laguna-M.1", "auto"),
|
|
68
71
|
("poolside/Laguna-XS-2.1", "auto"),
|
|
72
|
+
("poolside/Laguna-S-2.1", "auto"),
|
|
69
73
|
("tencent/Hy3", "auto"),
|
|
70
74
|
("openai/gpt-oss-20b", "gpt-oss"),
|
|
71
75
|
]
|
|
@@ -46,6 +46,8 @@ _ROUNDTRIP_MODELS = [
|
|
|
46
46
|
# Ultra: parse must recover content after a </think> glued directly to it
|
|
47
47
|
# (no separating newline) — the Ultra-specific glue stresses the round-trip.
|
|
48
48
|
("nvidia/NVIDIA-Nemotron-3-Ultra-550B-A55B-BF16", "auto"),
|
|
49
|
+
# Nemotron 3.5 (Lightning): same glue as Ultra, distinct renderer class.
|
|
50
|
+
("nvidia/NVIDIA-Nemotron-3.5-Lightning-30B-A3B-BF16", "auto"),
|
|
49
51
|
("poolside/Laguna-XS.2", "auto"),
|
|
50
52
|
("poolside/Laguna-M.1", "auto"),
|
|
51
53
|
# Laguna-XS-2.1 is deliberately absent: under the default
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/examples/sglang/multiturn_generate_sglang.py
RENAMED
|
File without changes
|
|
File without changes
|
{renderers-0.1.10.dev1 → renderers-0.1.10.dev10}/examples/tinker/multiturn_generate_tinker.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|