renderers 0.1.10.dev10__tar.gz → 0.1.10.dev11__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.dev10 → renderers-0.1.10.dev11}/PKG-INFO +3 -3
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/README.md +2 -2
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/docs/renderer-config.md +2 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/renderers/__init__.py +4 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/renderers/_version.py +2 -2
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/renderers/base.py +14 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/renderers/client.py +66 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/renderers/configs.py +30 -0
- renderers-0.1.10.dev11/renderers/gemma4.py +1369 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/tests/conftest.py +1 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/tests/test_bridge.py +1 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/tests/test_build_helpers.py +14 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/tests/test_client.py +59 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/tests/test_disabled_thinking_stability.py +10 -2
- renderers-0.1.10.dev11/tests/test_gemma4.py +473 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/tests/test_multimodal.py +69 -14
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/tests/test_render_ids.py +20 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/tests/test_renderer_config_parity.py +16 -8
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/tests/test_roundtrip.py +1 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/.github/workflows/publish-dev.yml +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/.github/workflows/publish.yml +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/.github/workflows/style.yml +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/.github/workflows/test.yml +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/.gitignore +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/.pre-commit-config.yaml +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/LICENSE +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/examples/README.md +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/examples/sglang/multiturn_generate_sglang.py +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/examples/sglang/online_multiturn_sglang.py +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/examples/tinker/multiturn_generate_tinker.py +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/examples/transformers/multiturn_generate_transformers.py +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/examples/vllm/multiturn_generate_vllm.py +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/pyproject.toml +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/renderers/deepseek_r1.py +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/renderers/deepseek_v3.py +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/renderers/default.py +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/renderers/glm45.py +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/renderers/glm5.py +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/renderers/gpt_oss.py +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/renderers/hy3.py +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/renderers/kimi_k2.py +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/renderers/kimi_k25.py +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/renderers/laguna_s21.py +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/renderers/laguna_xs2.py +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/renderers/llama_3.py +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/renderers/minimax_m2.py +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/renderers/nemotron3.py +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/renderers/parsers.py +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/renderers/parsing.py +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/renderers/prime_qwen3.py +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/renderers/qwen3.py +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/renderers/qwen35.py +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/renderers/qwen36.py +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/renderers/qwen3_vl.py +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/tests/test_deepseek_r1.py +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/tests/test_glm_tool_name_validation.py +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/tests/test_gpt_oss_harmony_parity.py +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/tests/test_hy3.py +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/tests/test_incremental.py +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/tests/test_is_content.py +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/tests/test_kimi_k25_tool_schema.py +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/tests/test_laguna_m1.py +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/tests/test_laguna_s21.py +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/tests/test_laguna_xs21.py +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/tests/test_llama_3.py +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/tests/test_load_tokenizer.py +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/tests/test_message_indices.py +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/tests/test_message_tool_names.py +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/tests/test_nemotron3_parity.py +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/tests/test_nemotron3_ultra.py +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/tests/test_parse_response.py +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/tests/test_parse_response_robustness.py +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/tests/test_parsers.py +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/tests/test_preserve_thinking.py +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/tests/test_prime_qwen3_parity.py +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/tests/test_qwen35_size_coverage.py +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/tests/test_renderer_config.py +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/tests/test_sampled_mask.py +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/tests/test_tokens_per_message.py +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/tests/test_tool_arg_type_preservation.py +0 -0
- {renderers-0.1.10.dev10 → renderers-0.1.10.dev11}/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.dev11
|
|
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-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.
|
|
59
|
+
Hand-coded renderers ship for `qwen3`, `qwen3-vl`, `qwen3.5`, `qwen3.6`, `gemma4`, `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
|
|
|
@@ -175,7 +175,7 @@ Fallback for unsupported text-only models. Wraps `apply_chat_template` and accep
|
|
|
175
175
|
|
|
176
176
|
## Roadmap
|
|
177
177
|
|
|
178
|
-
- **VLM expansion.** `ImagePart` support exists for Qwen3-VL
|
|
178
|
+
- **VLM expansion.** `ImagePart` support exists for Qwen3-VL, Qwen3.5-family, Gemma 4, and Kimi K2.5 / K2.6 multimodal templates. Remaining work: audio/video support, broader VLM coverage, and more RL validation. Gemma 4 image preprocessing requires a Transformers release that provides `Gemma4Processor`.
|
|
179
179
|
- **Patched chat templates.** Some shipped templates re-tokenize history or normalize JSON in ways that break token identity. Plan: a `use_patched` opt-in per renderer that renders the same surface form while avoiding known-bad patterns. (Auto-stripping thinking from past turns is *not* one of these — that's intended template behaviour the renderer reproduces; use `thinking_retention` to override it.)
|
|
180
180
|
|
|
181
181
|
## Testing
|
|
@@ -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-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.
|
|
43
|
+
Hand-coded renderers ship for `qwen3`, `qwen3-vl`, `qwen3.5`, `qwen3.6`, `gemma4`, `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
|
|
|
@@ -159,7 +159,7 @@ Fallback for unsupported text-only models. Wraps `apply_chat_template` and accep
|
|
|
159
159
|
|
|
160
160
|
## Roadmap
|
|
161
161
|
|
|
162
|
-
- **VLM expansion.** `ImagePart` support exists for Qwen3-VL
|
|
162
|
+
- **VLM expansion.** `ImagePart` support exists for Qwen3-VL, Qwen3.5-family, Gemma 4, and Kimi K2.5 / K2.6 multimodal templates. Remaining work: audio/video support, broader VLM coverage, and more RL validation. Gemma 4 image preprocessing requires a Transformers release that provides `Gemma4Processor`.
|
|
163
163
|
- **Patched chat templates.** Some shipped templates re-tokenize history or normalize JSON in ways that break token identity. Plan: a `use_patched` opt-in per renderer that renders the same surface form while avoiding known-bad patterns. (Auto-stripping thinking from past turns is *not* one of these — that's intended template behaviour the renderer reproduces; use `thinking_retention` to override it.)
|
|
164
164
|
|
|
165
165
|
## Testing
|
|
@@ -30,6 +30,7 @@ chat-template kwargs. Those fields are covered by parity tests against
|
|
|
30
30
|
| Qwen3.5 | `Qwen35RendererConfig` | `enable_thinking`, `add_vision_id` | `image_cache_max` |
|
|
31
31
|
| Qwen3.6 | `Qwen36RendererConfig` | `enable_thinking`, `add_vision_id`, `preserve_thinking` | `image_cache_max` |
|
|
32
32
|
| Qwen3-VL | `Qwen3VLRendererConfig` | `add_vision_id` | `image_cache_max` |
|
|
33
|
+
| Gemma 4 | `Gemma4RendererConfig` | `enable_thinking`, `preserve_thinking` | `image_cache_max` |
|
|
33
34
|
| GLM-5 / 5.1 | `GLM5RendererConfig` / `GLM51RendererConfig` | `enable_thinking`, `clear_thinking` | - |
|
|
34
35
|
| GLM-4.5 | `GLM45RendererConfig` | `enable_thinking` | - |
|
|
35
36
|
| gpt-oss | `GptOssRendererConfig` | `reasoning_effort`, `conversation_start_date` | `use_system_prompt`, `knowledge_cutoff`, `model_identity`, `auto_drop_analysis` |
|
|
@@ -131,6 +132,7 @@ the knobs its template actually exposes:
|
|
|
131
132
|
| Qwen3 | `enable_thinking=False -> all`, else `tool_cycle` |
|
|
132
133
|
| Qwen3.5 | `enable_thinking=False -> all`, else `tool_cycle` |
|
|
133
134
|
| Qwen3.6 | `preserve_thinking=True -> all`; else `enable_thinking=False -> all`; else `tool_cycle` |
|
|
135
|
+
| Gemma 4 | `preserve_thinking=True -> all`; else `enable_thinking=False -> all`; else `tool_cycle` |
|
|
134
136
|
| GLM-5 / 5.1 | `clear_thinking=False -> all`; else `enable_thinking=False -> all`; else `tool_cycle` |
|
|
135
137
|
| GLM-4.5 | `enable_thinking=False -> all`, else `tool_cycle` |
|
|
136
138
|
| gpt-oss | `auto_drop_analysis=False -> all`, else `tool_cycle` |
|
|
@@ -50,6 +50,7 @@ from renderers.configs import (
|
|
|
50
50
|
GLM45RendererConfig,
|
|
51
51
|
GLM51RendererConfig,
|
|
52
52
|
GLM5RendererConfig,
|
|
53
|
+
Gemma4RendererConfig,
|
|
53
54
|
GptOssRendererConfig,
|
|
54
55
|
Hy3RendererConfig,
|
|
55
56
|
KimiK25RendererConfig,
|
|
@@ -90,6 +91,7 @@ _LAZY_RENDERERS: dict[str, str] = {
|
|
|
90
91
|
"GLM51Renderer": "renderers.glm5",
|
|
91
92
|
"GLM5Renderer": "renderers.glm5",
|
|
92
93
|
"GptOssRenderer": "renderers.gpt_oss",
|
|
94
|
+
"Gemma4Renderer": "renderers.gemma4",
|
|
93
95
|
"Hy3Renderer": "renderers.hy3",
|
|
94
96
|
"KimiK25Renderer": "renderers.kimi_k25",
|
|
95
97
|
"KimiK2Renderer": "renderers.kimi_k2",
|
|
@@ -142,6 +144,8 @@ __all__ = [
|
|
|
142
144
|
"GLM51RendererConfig",
|
|
143
145
|
"GLM5Renderer",
|
|
144
146
|
"GLM5RendererConfig",
|
|
147
|
+
"Gemma4Renderer",
|
|
148
|
+
"Gemma4RendererConfig",
|
|
145
149
|
"GptOssRenderer",
|
|
146
150
|
"GptOssRendererConfig",
|
|
147
151
|
"Hy3Renderer",
|
|
@@ -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.dev11'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 10, 'dev11')
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -1035,6 +1035,14 @@ MODEL_RENDERER_MAP: dict[str, str] = {
|
|
|
1035
1035
|
"Qwen/Qwen3-VL-4B-Instruct": "qwen3-vl",
|
|
1036
1036
|
"Qwen/Qwen3-VL-8B-Instruct": "qwen3-vl",
|
|
1037
1037
|
"Qwen/Qwen3-VL-30B-A3B-Instruct": "qwen3-vl",
|
|
1038
|
+
# Gemma 4 instruction checkpoints share Google's canonical turn/tool
|
|
1039
|
+
# grammar and dynamic Gemma4Processor image expansion. E2B/E4B omit the
|
|
1040
|
+
# disabled-thinking empty-channel prefill used by the 26B/31B revision;
|
|
1041
|
+
# Gemma4Renderer detects that small template variant per tokenizer.
|
|
1042
|
+
"google/gemma-4-E2B-it": "gemma4",
|
|
1043
|
+
"google/gemma-4-E4B-it": "gemma4",
|
|
1044
|
+
"google/gemma-4-26B-A4B-it": "gemma4",
|
|
1045
|
+
"google/gemma-4-31B-it": "gemma4",
|
|
1038
1046
|
# GLM-5 family (GLM-4.7 reuses the GLM-5 template).
|
|
1039
1047
|
"zai-org/GLM-5": "glm-5",
|
|
1040
1048
|
"zai-org/GLM-5-FP8": "glm-5",
|
|
@@ -1107,6 +1115,10 @@ MULTIMODAL_MODELS: dict[str, set[str]] = {
|
|
|
1107
1115
|
"Qwen/Qwen3-VL-4B-Instruct": {"image"},
|
|
1108
1116
|
"Qwen/Qwen3-VL-8B-Instruct": {"image"},
|
|
1109
1117
|
"Qwen/Qwen3-VL-30B-A3B-Instruct": {"image"},
|
|
1118
|
+
"google/gemma-4-E2B-it": {"image"},
|
|
1119
|
+
"google/gemma-4-E4B-it": {"image"},
|
|
1120
|
+
"google/gemma-4-26B-A4B-it": {"image"},
|
|
1121
|
+
"google/gemma-4-31B-it": {"image"},
|
|
1110
1122
|
# Qwen3.5 is itself a VLM family (HF tag ``image-text-to-text``,
|
|
1111
1123
|
# processor class ``Qwen3VLProcessor``) — same vision tokens and
|
|
1112
1124
|
# image-processor as Qwen3-VL, with a different tool-call format.
|
|
@@ -1326,6 +1338,7 @@ def _populate_registry():
|
|
|
1326
1338
|
from renderers.glm5 import GLM5Renderer, GLM51Renderer
|
|
1327
1339
|
from renderers.glm45 import GLM45Renderer
|
|
1328
1340
|
from renderers.gpt_oss import GptOssRenderer
|
|
1341
|
+
from renderers.gemma4 import Gemma4Renderer
|
|
1329
1342
|
from renderers.hy3 import Hy3Renderer
|
|
1330
1343
|
from renderers.kimi_k2 import KimiK2Renderer
|
|
1331
1344
|
from renderers.kimi_k25 import KimiK25Renderer
|
|
@@ -1354,6 +1367,7 @@ def _populate_registry():
|
|
|
1354
1367
|
"qwen3": Qwen3Renderer,
|
|
1355
1368
|
"prime-qwen3": PrimeQwen3Renderer,
|
|
1356
1369
|
"qwen3-vl": Qwen3VLRenderer,
|
|
1370
|
+
"gemma4": Gemma4Renderer,
|
|
1357
1371
|
"qwen3.5": Qwen35Renderer,
|
|
1358
1372
|
"qwen3.6": Qwen36Renderer,
|
|
1359
1373
|
"glm-5": GLM5Renderer,
|
|
@@ -428,6 +428,7 @@ def _build_mm_features(
|
|
|
428
428
|
(``MultiModalData``) is already framework-agnostic and does not need
|
|
429
429
|
to change. Don't pre-build the abstraction with one engine in tree.
|
|
430
430
|
"""
|
|
431
|
+
from renderers.gemma4 import Gemma4Renderer
|
|
431
432
|
from renderers.qwen3_vl import Qwen3VLRenderer
|
|
432
433
|
from renderers.qwen35 import Qwen35Renderer
|
|
433
434
|
|
|
@@ -443,6 +444,8 @@ def _build_mm_features(
|
|
|
443
444
|
# the family default and matches every Qwen-VL processor in tree.
|
|
444
445
|
if issubclass(renderer_cls, (Qwen3VLRenderer, Qwen35Renderer)):
|
|
445
446
|
return _build_qwen_vl_features(mm_data, spatial_merge_size=2)
|
|
447
|
+
if issubclass(renderer_cls, Gemma4Renderer):
|
|
448
|
+
return _build_gemma4_features(mm_data)
|
|
446
449
|
|
|
447
450
|
raise NotImplementedError(
|
|
448
451
|
f"Multimodal serialization not implemented for {renderer_cls.__name__}. "
|
|
@@ -450,6 +453,69 @@ def _build_mm_features(
|
|
|
450
453
|
)
|
|
451
454
|
|
|
452
455
|
|
|
456
|
+
def _build_gemma4_features(mm_data: MultiModalData) -> dict[str, Any]:
|
|
457
|
+
"""vLLM features payload for Gemma 4 image inputs.
|
|
458
|
+
|
|
459
|
+
Hugging Face names the position field ``image_position_ids`` while
|
|
460
|
+
vLLM's Gemma 4 processor schema calls it ``pixel_position_ids``. Keep
|
|
461
|
+
renderer output faithful to the HF processor and translate at this
|
|
462
|
+
engine-specific boundary.
|
|
463
|
+
"""
|
|
464
|
+
try:
|
|
465
|
+
import torch
|
|
466
|
+
from transformers.feature_extraction_utils import BatchFeature
|
|
467
|
+
from vllm.entrypoints.scale_out.token_in_token_out.mm_serde import (
|
|
468
|
+
encode_mm_kwargs_item,
|
|
469
|
+
)
|
|
470
|
+
from vllm.multimodal.inputs import (
|
|
471
|
+
MultiModalFieldConfig,
|
|
472
|
+
MultiModalKwargsItems,
|
|
473
|
+
)
|
|
474
|
+
except ImportError as exc:
|
|
475
|
+
raise RuntimeError(
|
|
476
|
+
"Gemma 4 multimodal generate via /inference/v1/generate requires "
|
|
477
|
+
"a vLLM release with Gemma 4 support and `torch`."
|
|
478
|
+
) from exc
|
|
479
|
+
|
|
480
|
+
out: dict[str, Any] = {
|
|
481
|
+
"mm_hashes": {},
|
|
482
|
+
"mm_placeholders": {},
|
|
483
|
+
"kwargs_data": {},
|
|
484
|
+
}
|
|
485
|
+
image_items = mm_data.mm_items.get("image") or []
|
|
486
|
+
if image_items:
|
|
487
|
+
pixel_values = torch.cat(
|
|
488
|
+
[torch.as_tensor(item["pixel_values"]) for item in image_items], dim=0
|
|
489
|
+
)
|
|
490
|
+
pixel_position_ids = torch.cat(
|
|
491
|
+
[torch.as_tensor(item["image_position_ids"]) for item in image_items],
|
|
492
|
+
dim=0,
|
|
493
|
+
)
|
|
494
|
+
hf_inputs = BatchFeature(
|
|
495
|
+
data={
|
|
496
|
+
"pixel_values": pixel_values,
|
|
497
|
+
"pixel_position_ids": pixel_position_ids,
|
|
498
|
+
}
|
|
499
|
+
)
|
|
500
|
+
field_config = {
|
|
501
|
+
"pixel_values": MultiModalFieldConfig.batched("image"),
|
|
502
|
+
"pixel_position_ids": MultiModalFieldConfig.batched("image"),
|
|
503
|
+
}
|
|
504
|
+
kwargs_items = MultiModalKwargsItems.from_hf_inputs(hf_inputs, field_config)
|
|
505
|
+
out["kwargs_data"]["image"] = [
|
|
506
|
+
encode_mm_kwargs_item(item) for item in kwargs_items["image"]
|
|
507
|
+
]
|
|
508
|
+
out["mm_hashes"]["image"] = list(mm_data.mm_hashes.get("image") or [])
|
|
509
|
+
out["mm_placeholders"]["image"] = [
|
|
510
|
+
{"offset": placeholder.offset, "length": placeholder.length}
|
|
511
|
+
for placeholder in mm_data.mm_placeholders.get("image") or []
|
|
512
|
+
]
|
|
513
|
+
|
|
514
|
+
if not any(out["kwargs_data"].values()):
|
|
515
|
+
out["kwargs_data"] = None
|
|
516
|
+
return out
|
|
517
|
+
|
|
518
|
+
|
|
453
519
|
def _build_qwen_vl_features(
|
|
454
520
|
mm_data: MultiModalData, *, spatial_merge_size: int
|
|
455
521
|
) -> dict[str, Any]:
|
|
@@ -270,6 +270,33 @@ class Qwen3VLRendererConfig(BaseRendererConfig):
|
|
|
270
270
|
_internal_fields = frozenset({"image_cache_max"})
|
|
271
271
|
|
|
272
272
|
|
|
273
|
+
class Gemma4RendererConfig(BaseRendererConfig):
|
|
274
|
+
"""Gemma 4 renderer config."""
|
|
275
|
+
|
|
276
|
+
name: Literal["gemma4"] = "gemma4"
|
|
277
|
+
|
|
278
|
+
enable_thinking: bool = False
|
|
279
|
+
"""Enable Gemma 4's thinking mode. Mirrors the canonical template kwarg."""
|
|
280
|
+
|
|
281
|
+
preserve_thinking: bool = False
|
|
282
|
+
"""Keep thinking on historical tool-call turns when the template permits it."""
|
|
283
|
+
|
|
284
|
+
image_cache_max: int = 256
|
|
285
|
+
"""FIFO bound on processed image entries. Renderer-internal."""
|
|
286
|
+
|
|
287
|
+
_internal_fields = frozenset({"image_cache_max"})
|
|
288
|
+
|
|
289
|
+
@model_validator(mode="after")
|
|
290
|
+
def _check_thinking_retention(self):
|
|
291
|
+
_reject_thinking_retention_conflict(
|
|
292
|
+
self,
|
|
293
|
+
"preserve_thinking",
|
|
294
|
+
true_implies="all",
|
|
295
|
+
false_implies="tool_cycle",
|
|
296
|
+
)
|
|
297
|
+
return self
|
|
298
|
+
|
|
299
|
+
|
|
273
300
|
class GLM5RendererConfig(BaseRendererConfig):
|
|
274
301
|
"""GLM-5 renderer config."""
|
|
275
302
|
|
|
@@ -756,6 +783,7 @@ RendererConfig = Annotated[
|
|
|
756
783
|
Qwen35RendererConfig,
|
|
757
784
|
Qwen36RendererConfig,
|
|
758
785
|
Qwen3VLRendererConfig,
|
|
786
|
+
Gemma4RendererConfig,
|
|
759
787
|
GLM5RendererConfig,
|
|
760
788
|
GLM51RendererConfig,
|
|
761
789
|
GLM45RendererConfig,
|
|
@@ -799,6 +827,7 @@ _CONFIG_BY_NAME: dict[str, type[BaseRendererConfig]] = {
|
|
|
799
827
|
"qwen3.5": Qwen35RendererConfig,
|
|
800
828
|
"qwen3.6": Qwen36RendererConfig,
|
|
801
829
|
"qwen3-vl": Qwen3VLRendererConfig,
|
|
830
|
+
"gemma4": Gemma4RendererConfig,
|
|
802
831
|
"glm-5": GLM5RendererConfig,
|
|
803
832
|
"glm-5.1": GLM51RendererConfig,
|
|
804
833
|
"glm-4.5": GLM45RendererConfig,
|
|
@@ -853,6 +882,7 @@ __all__ = [
|
|
|
853
882
|
"GLM45RendererConfig",
|
|
854
883
|
"GLM51RendererConfig",
|
|
855
884
|
"GLM5RendererConfig",
|
|
885
|
+
"Gemma4RendererConfig",
|
|
856
886
|
"GptOssRendererConfig",
|
|
857
887
|
"Hy3RendererConfig",
|
|
858
888
|
"KimiK25RendererConfig",
|