renderers 0.1.11.dev1__tar.gz → 0.1.11.dev2__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.11.dev1 → renderers-0.1.11.dev2}/PKG-INFO +29 -17
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/README.md +23 -15
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/docs/renderer-config.md +2 -7
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/pyproject.toml +21 -5
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/renderers/__init__.py +11 -15
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/renderers/_version.py +2 -2
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/renderers/base.py +83 -181
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/renderers/client.py +10 -36
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/renderers/deepseek_v3.py +2 -3
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/renderers/default.py +2 -3
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/renderers/gemma4.py +5 -6
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/renderers/glm45.py +2 -3
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/renderers/glm5.py +2 -3
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/renderers/gpt_oss.py +2 -3
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/renderers/hy3.py +2 -3
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/renderers/inkling.py +5 -6
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/renderers/kimi_k2.py +2 -3
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/renderers/kimi_k25.py +9 -9
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/renderers/laguna_s21.py +2 -3
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/renderers/laguna_xs2.py +4 -5
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/renderers/llama_3.py +2 -3
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/renderers/minimax_m2.py +2 -3
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/renderers/nemotron3.py +2 -3
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/renderers/prime_qwen3.py +3 -4
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/renderers/qwen3.py +2 -3
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/renderers/qwen35.py +5 -6
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/renderers/qwen3_vl.py +5 -6
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/tests/test_renderer_config.py +0 -26
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/uv.lock +14 -1
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/.github/workflows/publish-dev.yml +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/.github/workflows/publish.yml +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/.github/workflows/style.yml +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/.github/workflows/test.yml +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/.gitignore +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/.pre-commit-config.yaml +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/LICENSE +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/examples/README.md +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/examples/sglang/multiturn_generate_sglang.py +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/examples/sglang/online_multiturn_sglang.py +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/examples/tinker/multiturn_generate_tinker.py +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/examples/transformers/multiturn_generate_transformers.py +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/examples/vllm/multiturn_generate_vllm.py +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/renderers/configs.py +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/renderers/deepseek_r1.py +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/renderers/parsers.py +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/renderers/parsing.py +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/renderers/qwen36.py +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/renderers/qwen38.py +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/tests/conftest.py +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/tests/test_bridge.py +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/tests/test_build_helpers.py +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/tests/test_client.py +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/tests/test_deepseek_r1.py +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/tests/test_disabled_thinking_stability.py +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/tests/test_gemma4.py +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/tests/test_glm_tool_name_validation.py +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/tests/test_gpt_oss_harmony_parity.py +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/tests/test_hy3.py +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/tests/test_incremental.py +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/tests/test_inkling.py +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/tests/test_is_content.py +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/tests/test_kimi_k25_tool_schema.py +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/tests/test_laguna_m1.py +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/tests/test_laguna_s21.py +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/tests/test_laguna_xs21.py +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/tests/test_llama_3.py +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/tests/test_load_tokenizer.py +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/tests/test_message_indices.py +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/tests/test_message_tool_names.py +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/tests/test_multimodal.py +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/tests/test_nemotron3_parity.py +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/tests/test_nemotron3_ultra.py +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/tests/test_parse_response.py +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/tests/test_parse_response_robustness.py +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/tests/test_parsers.py +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/tests/test_preserve_thinking.py +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/tests/test_prime_qwen3_parity.py +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/tests/test_qwen35_size_coverage.py +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/tests/test_qwen38.py +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/tests/test_render_ids.py +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/tests/test_renderer_config_parity.py +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/tests/test_roundtrip.py +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/tests/test_sampled_mask.py +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/tests/test_tokens_per_message.py +0 -0
- {renderers-0.1.11.dev1 → renderers-0.1.11.dev2}/tests/test_tool_arg_type_preservation.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: renderers
|
|
3
|
-
Version: 0.1.11.
|
|
3
|
+
Version: 0.1.11.dev2
|
|
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
|
|
@@ -11,7 +11,11 @@ Requires-Dist: openai-harmony>=0.0.4
|
|
|
11
11
|
Requires-Dist: openai>=1.108.1
|
|
12
12
|
Requires-Dist: prime-pydantic-config>=0.3.0.dev83
|
|
13
13
|
Requires-Dist: tiktoken
|
|
14
|
-
|
|
14
|
+
Provides-Extra: multimodal
|
|
15
|
+
Requires-Dist: pillow>=12.2.0; extra == 'multimodal'
|
|
16
|
+
Requires-Dist: transformers>=4.50.0; extra == 'multimodal'
|
|
17
|
+
Provides-Extra: transformers
|
|
18
|
+
Requires-Dist: transformers>=4.50.0; extra == 'transformers'
|
|
15
19
|
Description-Content-Type: text/markdown
|
|
16
20
|
|
|
17
21
|
# renderers
|
|
@@ -26,13 +30,28 @@ Standalone on PyPI, and portable across training and inference stacks (transform
|
|
|
26
30
|
uv add renderers
|
|
27
31
|
```
|
|
28
32
|
|
|
33
|
+
The base install supports text renderers with a bring-your-own tokenizer. Add
|
|
34
|
+
the Hugging Face integration for the tokenizer-loading helpers, or the complete
|
|
35
|
+
media stack for image/audio rendering:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
uv add 'renderers[transformers]'
|
|
39
|
+
uv add 'renderers[multimodal]'
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
A BYO tokenizer must expose `encode`, `decode`, `convert_tokens_to_ids`, token
|
|
43
|
+
IDs such as `eos_token_id`, and `return_offsets_mapping=True` through its call
|
|
44
|
+
interface. `DefaultRenderer` additionally requires `apply_chat_template`.
|
|
45
|
+
This includes text-only Inkling training: `InklingRenderer` loads its
|
|
46
|
+
Transformers processor only when image or audio content is actually rendered.
|
|
47
|
+
|
|
29
48
|
## At a glance
|
|
30
49
|
|
|
31
50
|
```python
|
|
32
|
-
from transformers import AutoTokenizer
|
|
33
51
|
from renderers import create_renderer
|
|
52
|
+
from renderers.base import load_tokenizer
|
|
34
53
|
|
|
35
|
-
tok =
|
|
54
|
+
tok = load_tokenizer("Qwen/Qwen3-8B") # renderers[transformers]
|
|
36
55
|
r = create_renderer(tok) # → Qwen3Renderer (auto-resolved)
|
|
37
56
|
|
|
38
57
|
prompt_ids = r.render_ids(
|
|
@@ -92,17 +111,10 @@ r = create_renderer(tok) # AutoRendererConfig is the implicit def
|
|
|
92
111
|
|
|
93
112
|
Auto-detect matches `tokenizer.name_or_path` against `MODEL_RENDERER_MAP` by **exact match**. Prefix matching is intentionally off — same architecture can ship different chat templates (base vs instruct, fine-tune renames). Fine-tunes must pass an explicit typed config (e.g. `Qwen3RendererConfig()`). Unknown text-only names fall back to `DefaultRenderer`, unless `AutoRendererConfig(thinking_retention=...)` was set; the default renderer cannot implement that bridge policy.
|
|
94
113
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
pool = create_renderer_pool("Qwen/Qwen3-8B", size=16)
|
|
101
|
-
with pool.checkout() as r:
|
|
102
|
-
ids = r.render_ids(messages)
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
Each slot owns its own tokenizer copy. Construction fans out across a thread pool so a 32-slot pool doesn't serially eat ~10–15s of `from_pretrained` calls at startup.
|
|
114
|
+
Without the `transformers` extra, exact-match registered models still
|
|
115
|
+
auto-resolve. For an unknown name, renderers cannot safely probe `AutoConfig`
|
|
116
|
+
to distinguish a text model from an unknown VLM; pass an explicit typed config
|
|
117
|
+
such as `DefaultRendererConfig()` for a known text-only model.
|
|
106
118
|
|
|
107
119
|
## Why use a renderer
|
|
108
120
|
|
|
@@ -125,7 +137,7 @@ Each break fragments a rollout into multiple training samples — every fragment
|
|
|
125
137
|
|
|
126
138
|
## Typed renderer configs
|
|
127
139
|
|
|
128
|
-
Each renderer accepts a typed pydantic config at construction. Some fields mirror chat-template kwargs; others configure renderer-only behavior such as image caching, parsers, or Harmony preamble construction. `create_renderer`
|
|
140
|
+
Each renderer accepts a typed pydantic config at construction. Some fields mirror chat-template kwargs; others configure renderer-only behavior such as image caching, parsers, or Harmony preamble construction. `create_renderer` takes one positional `config` argument and an optional keyword-only `chat_template_kwargs` mapping:
|
|
129
141
|
|
|
130
142
|
```python
|
|
131
143
|
from renderers import (
|
|
@@ -175,7 +187,7 @@ Fallback for unsupported text-only models. Wraps `apply_chat_template` and accep
|
|
|
175
187
|
|
|
176
188
|
## Roadmap
|
|
177
189
|
|
|
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`.
|
|
190
|
+
- **VLM expansion.** `ImagePart` support exists for Qwen3-VL, Qwen3.5-family, Gemma 4, and Kimi K2.5 / K2.6 multimodal templates. Install `renderers[multimodal]` for Pillow and the Hugging Face processors. Remaining work: audio/video support, broader VLM coverage, and more RL validation. Gemma 4 image preprocessing requires a Transformers release that provides `Gemma4Processor`.
|
|
179
191
|
- **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
192
|
|
|
181
193
|
## Testing
|
|
@@ -10,13 +10,28 @@ Standalone on PyPI, and portable across training and inference stacks (transform
|
|
|
10
10
|
uv add renderers
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
+
The base install supports text renderers with a bring-your-own tokenizer. Add
|
|
14
|
+
the Hugging Face integration for the tokenizer-loading helpers, or the complete
|
|
15
|
+
media stack for image/audio rendering:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
uv add 'renderers[transformers]'
|
|
19
|
+
uv add 'renderers[multimodal]'
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
A BYO tokenizer must expose `encode`, `decode`, `convert_tokens_to_ids`, token
|
|
23
|
+
IDs such as `eos_token_id`, and `return_offsets_mapping=True` through its call
|
|
24
|
+
interface. `DefaultRenderer` additionally requires `apply_chat_template`.
|
|
25
|
+
This includes text-only Inkling training: `InklingRenderer` loads its
|
|
26
|
+
Transformers processor only when image or audio content is actually rendered.
|
|
27
|
+
|
|
13
28
|
## At a glance
|
|
14
29
|
|
|
15
30
|
```python
|
|
16
|
-
from transformers import AutoTokenizer
|
|
17
31
|
from renderers import create_renderer
|
|
32
|
+
from renderers.base import load_tokenizer
|
|
18
33
|
|
|
19
|
-
tok =
|
|
34
|
+
tok = load_tokenizer("Qwen/Qwen3-8B") # renderers[transformers]
|
|
20
35
|
r = create_renderer(tok) # → Qwen3Renderer (auto-resolved)
|
|
21
36
|
|
|
22
37
|
prompt_ids = r.render_ids(
|
|
@@ -76,17 +91,10 @@ r = create_renderer(tok) # AutoRendererConfig is the implicit def
|
|
|
76
91
|
|
|
77
92
|
Auto-detect matches `tokenizer.name_or_path` against `MODEL_RENDERER_MAP` by **exact match**. Prefix matching is intentionally off — same architecture can ship different chat templates (base vs instruct, fine-tune renames). Fine-tunes must pass an explicit typed config (e.g. `Qwen3RendererConfig()`). Unknown text-only names fall back to `DefaultRenderer`, unless `AutoRendererConfig(thinking_retention=...)` was set; the default renderer cannot implement that bridge policy.
|
|
78
93
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
pool = create_renderer_pool("Qwen/Qwen3-8B", size=16)
|
|
85
|
-
with pool.checkout() as r:
|
|
86
|
-
ids = r.render_ids(messages)
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
Each slot owns its own tokenizer copy. Construction fans out across a thread pool so a 32-slot pool doesn't serially eat ~10–15s of `from_pretrained` calls at startup.
|
|
94
|
+
Without the `transformers` extra, exact-match registered models still
|
|
95
|
+
auto-resolve. For an unknown name, renderers cannot safely probe `AutoConfig`
|
|
96
|
+
to distinguish a text model from an unknown VLM; pass an explicit typed config
|
|
97
|
+
such as `DefaultRendererConfig()` for a known text-only model.
|
|
90
98
|
|
|
91
99
|
## Why use a renderer
|
|
92
100
|
|
|
@@ -109,7 +117,7 @@ Each break fragments a rollout into multiple training samples — every fragment
|
|
|
109
117
|
|
|
110
118
|
## Typed renderer configs
|
|
111
119
|
|
|
112
|
-
Each renderer accepts a typed pydantic config at construction. Some fields mirror chat-template kwargs; others configure renderer-only behavior such as image caching, parsers, or Harmony preamble construction. `create_renderer`
|
|
120
|
+
Each renderer accepts a typed pydantic config at construction. Some fields mirror chat-template kwargs; others configure renderer-only behavior such as image caching, parsers, or Harmony preamble construction. `create_renderer` takes one positional `config` argument and an optional keyword-only `chat_template_kwargs` mapping:
|
|
113
121
|
|
|
114
122
|
```python
|
|
115
123
|
from renderers import (
|
|
@@ -159,7 +167,7 @@ Fallback for unsupported text-only models. Wraps `apply_chat_template` and accep
|
|
|
159
167
|
|
|
160
168
|
## Roadmap
|
|
161
169
|
|
|
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`.
|
|
170
|
+
- **VLM expansion.** `ImagePart` support exists for Qwen3-VL, Qwen3.5-family, Gemma 4, and Kimi K2.5 / K2.6 multimodal templates. Install `renderers[multimodal]` for Pillow and the Hugging Face processors. Remaining work: audio/video support, broader VLM coverage, and more RL validation. Gemma 4 image preprocessing requires a Transformers release that provides `Gemma4Processor`.
|
|
163
171
|
- **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
172
|
|
|
165
173
|
## Testing
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
# Renderer config
|
|
2
2
|
|
|
3
|
-
`renderers.RendererConfig` is the typed input to `create_renderer
|
|
4
|
-
|
|
5
|
-
construction time.
|
|
3
|
+
`renderers.RendererConfig` is the typed input to `create_renderer`. It pins the
|
|
4
|
+
renderer choice and its config at construction time.
|
|
6
5
|
|
|
7
6
|
```python
|
|
8
7
|
from renderers import create_renderer, Qwen35RendererConfig
|
|
@@ -77,10 +76,6 @@ r = create_renderer(
|
|
|
77
76
|
tokenizer,
|
|
78
77
|
chat_template_kwargs={"enable_thinking": False},
|
|
79
78
|
)
|
|
80
|
-
pool = create_renderer_pool(
|
|
81
|
-
"Qwen/Qwen3-8B",
|
|
82
|
-
chat_template_kwargs={"enable_thinking": False},
|
|
83
|
-
)
|
|
84
79
|
```
|
|
85
80
|
|
|
86
81
|
Renderers resolves auto configs before applying `chat_template_kwargs`, so the
|
|
@@ -19,11 +19,6 @@ dependencies = [
|
|
|
19
19
|
"openai>=1.108.1",
|
|
20
20
|
"tiktoken",
|
|
21
21
|
"jinja2",
|
|
22
|
-
# Keep this floor compatible with prime-rl's transformers pin. Inkling's
|
|
23
|
-
# tokenizer and text-only renderer work on older releases; image/audio
|
|
24
|
-
# processing fails lazily with an upgrade message when InklingProcessor is
|
|
25
|
-
# unavailable (native support starts in transformers 5.14).
|
|
26
|
-
"transformers>=4.50.0",
|
|
27
22
|
# Used by GptOssRenderer to render and parse harmony tokens. Vendoring
|
|
28
23
|
# OpenAI's reference implementation keeps us byte-identical with vLLM
|
|
29
24
|
# (which also uses it) and saves us mirroring a 330-line Jinja template.
|
|
@@ -42,6 +37,25 @@ dependencies = [
|
|
|
42
37
|
"prime-pydantic-config>=0.3.0.dev83",
|
|
43
38
|
]
|
|
44
39
|
|
|
40
|
+
[project.optional-dependencies]
|
|
41
|
+
# Tokenizer loading uses Hugging Face. Text-only renderers can instead be
|
|
42
|
+
# constructed with an offset-capable BYO tokenizer and do not import this
|
|
43
|
+
# dependency.
|
|
44
|
+
transformers = [
|
|
45
|
+
# Keep this floor compatible with prime-rl's transformers pin. Inkling's
|
|
46
|
+
# tokenizer and text-only renderer work on older releases; image/audio
|
|
47
|
+
# processing fails lazily with an upgrade message when InklingProcessor is
|
|
48
|
+
# unavailable (native support starts in transformers 5.14).
|
|
49
|
+
"transformers>=4.50.0",
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
# Image/audio renderers also need Pillow to resolve media inputs. Keep this as
|
|
53
|
+
# a separate convenience extra so tokenizer-only users do not pull it in.
|
|
54
|
+
multimodal = [
|
|
55
|
+
"pillow>=12.2.0",
|
|
56
|
+
"transformers>=4.50.0",
|
|
57
|
+
]
|
|
58
|
+
|
|
45
59
|
[tool.hatch.version]
|
|
46
60
|
source = "vcs"
|
|
47
61
|
# Tags look like ``renderers-v0.1.8`` (prefix matches the publish.yml
|
|
@@ -90,6 +104,8 @@ dev = [
|
|
|
90
104
|
"torch>=2.11.0",
|
|
91
105
|
"torchvision>=0.26.0",
|
|
92
106
|
"ty>=0.0.1a29,<0.0.22",
|
|
107
|
+
# Optional for consumers, but required by tokenizer/parity/VLM tests.
|
|
108
|
+
"transformers>=4.50.0",
|
|
93
109
|
]
|
|
94
110
|
|
|
95
111
|
[tool.uv]
|
|
@@ -7,6 +7,7 @@ except ImportError:
|
|
|
7
7
|
__version__ = "0+unknown"
|
|
8
8
|
|
|
9
9
|
from renderers.base import (
|
|
10
|
+
ChatTemplateTokenizer,
|
|
10
11
|
Content,
|
|
11
12
|
ContentPart,
|
|
12
13
|
ImagePart,
|
|
@@ -21,9 +22,9 @@ from renderers.base import (
|
|
|
21
22
|
RenderedTokens,
|
|
22
23
|
RenderedTrainingSample,
|
|
23
24
|
Renderer,
|
|
24
|
-
RendererPool,
|
|
25
25
|
TextPart,
|
|
26
26
|
ThinkingPart,
|
|
27
|
+
Tokenizer,
|
|
27
28
|
ToolCall,
|
|
28
29
|
ToolCallFunction,
|
|
29
30
|
ToolCallParseStatus,
|
|
@@ -33,7 +34,6 @@ from renderers.base import (
|
|
|
33
34
|
build_training_sample,
|
|
34
35
|
build_trajectory_step,
|
|
35
36
|
create_renderer,
|
|
36
|
-
create_renderer_pool,
|
|
37
37
|
extract_message_tool_names,
|
|
38
38
|
is_multimodal,
|
|
39
39
|
reject_assistant_in_extension,
|
|
@@ -74,17 +74,13 @@ from renderers.configs import (
|
|
|
74
74
|
RendererConfig,
|
|
75
75
|
)
|
|
76
76
|
|
|
77
|
-
# Concrete renderer classes are lazy-loaded so that consumers needing
|
|
78
|
-
#
|
|
79
|
-
#
|
|
80
|
-
#
|
|
81
|
-
#
|
|
82
|
-
#
|
|
83
|
-
#
|
|
84
|
-
# import DefaultRenderer`` and ``renderers.DefaultRenderer`` both work
|
|
85
|
-
# transparently. ``create_renderer`` doesn't depend on these eager
|
|
86
|
-
# imports — ``renderers.base._populate_registry`` lazy-imports the
|
|
87
|
-
# concrete classes itself when a renderer is instantiated.
|
|
77
|
+
# Concrete renderer classes are lazy-loaded so that consumers needing only the
|
|
78
|
+
# config layer (``RendererConfig`` discriminated union) don't import every
|
|
79
|
+
# renderer module. Renderer tokenizer annotations use the local ``Tokenizer``
|
|
80
|
+
# protocols, so resolving a text renderer remains safe when the optional
|
|
81
|
+
# ``transformers`` dependency is absent. ``__getattr__`` (PEP 562) resolves the
|
|
82
|
+
# names on first attribute access, while ``renderers.base._populate_registry``
|
|
83
|
+
# handles lazy registration for ``create_renderer``.
|
|
88
84
|
_LAZY_RENDERERS: dict[str, str] = {
|
|
89
85
|
"DeepSeekR1Renderer": "renderers.deepseek_r1",
|
|
90
86
|
"DeepSeekV3Renderer": "renderers.deepseek_v3",
|
|
@@ -134,6 +130,7 @@ def __dir__() -> list[str]:
|
|
|
134
130
|
__all__ = [
|
|
135
131
|
"AutoRendererConfig",
|
|
136
132
|
"BaseRendererConfig",
|
|
133
|
+
"ChatTemplateTokenizer",
|
|
137
134
|
"Content",
|
|
138
135
|
"ContentPart",
|
|
139
136
|
"DeepSeekR1Renderer",
|
|
@@ -205,9 +202,9 @@ __all__ = [
|
|
|
205
202
|
"RenderedTrainingSample",
|
|
206
203
|
"Renderer",
|
|
207
204
|
"RendererConfig",
|
|
208
|
-
"RendererPool",
|
|
209
205
|
"TextPart",
|
|
210
206
|
"ThinkingPart",
|
|
207
|
+
"Tokenizer",
|
|
211
208
|
"ToolCall",
|
|
212
209
|
"ToolCallFunction",
|
|
213
210
|
"ToolCallParseStatus",
|
|
@@ -219,7 +216,6 @@ __all__ = [
|
|
|
219
216
|
"build_trajectory_step",
|
|
220
217
|
"config_from_name",
|
|
221
218
|
"create_renderer",
|
|
222
|
-
"create_renderer_pool",
|
|
223
219
|
"extract_message_tool_names",
|
|
224
220
|
"is_multimodal",
|
|
225
221
|
"reject_assistant_in_extension",
|
|
@@ -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.11.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 1, 11, '
|
|
21
|
+
__version__ = version = '0.1.11.dev2'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 11, 'dev2')
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -2,10 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import enum
|
|
4
4
|
import logging
|
|
5
|
-
import queue
|
|
6
|
-
import threading
|
|
7
5
|
from collections.abc import Mapping
|
|
8
|
-
from contextlib import contextmanager
|
|
9
6
|
from dataclasses import dataclass, field
|
|
10
7
|
from typing import (
|
|
11
8
|
TYPE_CHECKING,
|
|
@@ -663,6 +660,37 @@ class RenderedConversation:
|
|
|
663
660
|
)
|
|
664
661
|
|
|
665
662
|
|
|
663
|
+
@runtime_checkable
|
|
664
|
+
class Tokenizer(Protocol):
|
|
665
|
+
"""Structural tokenizer surface used by hand-coded renderers.
|
|
666
|
+
|
|
667
|
+
Hugging Face tokenizers satisfy this protocol, as can lightweight BYO
|
|
668
|
+
adapters around ``tokenizers.Tokenizer`` or another tokenizer backend.
|
|
669
|
+
Keeping the renderer-facing contract here makes ``transformers`` optional
|
|
670
|
+
for text rendering. Offset-capable ``__call__`` behavior is required by
|
|
671
|
+
:func:`attribute_text_segments` to preserve BPE boundary attribution.
|
|
672
|
+
"""
|
|
673
|
+
|
|
674
|
+
name_or_path: str
|
|
675
|
+
unk_token_id: int | None
|
|
676
|
+
eos_token_id: int | None
|
|
677
|
+
|
|
678
|
+
def encode(self, text: str, *args: Any, **kwargs: Any) -> list[int]: ...
|
|
679
|
+
|
|
680
|
+
def decode(self, token_ids: Any, *args: Any, **kwargs: Any) -> str: ...
|
|
681
|
+
|
|
682
|
+
def convert_tokens_to_ids(self, tokens: Any) -> Any: ...
|
|
683
|
+
|
|
684
|
+
def __call__(self, *args: Any, **kwargs: Any) -> Any: ...
|
|
685
|
+
|
|
686
|
+
|
|
687
|
+
@runtime_checkable
|
|
688
|
+
class ChatTemplateTokenizer(Tokenizer, Protocol):
|
|
689
|
+
"""Tokenizer surface required by :class:`DefaultRenderer`."""
|
|
690
|
+
|
|
691
|
+
def apply_chat_template(self, *args: Any, **kwargs: Any) -> Any: ...
|
|
692
|
+
|
|
693
|
+
|
|
666
694
|
@runtime_checkable
|
|
667
695
|
class Renderer(Protocol):
|
|
668
696
|
"""Owns message ↔ token conversion for a specific model family."""
|
|
@@ -679,10 +707,9 @@ class Renderer(Protocol):
|
|
|
679
707
|
Behaviour around historical ``reasoning_content`` is owned by the
|
|
680
708
|
renderer instance — the ``thinking_retention`` level is resolved at
|
|
681
709
|
construction, not passed per call. To render with a different
|
|
682
|
-
configuration, build a different renderer
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
history-retention knobs.
|
|
710
|
+
configuration, build a different renderer. When ``thinking_retention``
|
|
711
|
+
is left unset, full renders follow the model's chat template and bridge
|
|
712
|
+
policy is derived from that template's own history-retention knobs.
|
|
686
713
|
"""
|
|
687
714
|
...
|
|
688
715
|
|
|
@@ -839,9 +866,7 @@ class MultimodalRenderer(Renderer, Protocol):
|
|
|
839
866
|
# Per-type cache for ``is_multimodal``. The ``runtime_checkable`` Protocol
|
|
840
867
|
# isinstance check walks every protocol member via ``hasattr`` on each
|
|
841
868
|
# call; per-type caching collapses that to a single dict lookup on the
|
|
842
|
-
# hot path (e.g. per-bridge dispatch).
|
|
843
|
-
# directly as a snapshot attribute (different pools share a class but
|
|
844
|
-
# wrap different renderer types), so we don't need to special-case them.
|
|
869
|
+
# hot path (e.g. per-bridge dispatch).
|
|
845
870
|
_IS_MULTIMODAL_BY_TYPE: dict[type, bool] = {}
|
|
846
871
|
|
|
847
872
|
|
|
@@ -863,127 +888,6 @@ def is_multimodal(r: object) -> bool:
|
|
|
863
888
|
return cached
|
|
864
889
|
|
|
865
890
|
|
|
866
|
-
class RendererPool:
|
|
867
|
-
"""Pool of Renderer instances that itself satisfies the Renderer protocol.
|
|
868
|
-
|
|
869
|
-
Callers treat a pool like a single renderer — ``pool.render_ids(...)``,
|
|
870
|
-
``pool.bridge_to_next_turn(...)``, ``isinstance(pool, MultimodalRenderer)``
|
|
871
|
-
all work via structural delegation. The pool internally serializes
|
|
872
|
-
access to its inner renderers (each wraps its own tokenizer copy).
|
|
873
|
-
|
|
874
|
-
Concurrency model:
|
|
875
|
-
- ``size == 1``: a single inner renderer guarded by a ``threading.Lock``.
|
|
876
|
-
Avoids the queue's per-call overhead on the common default config.
|
|
877
|
-
- ``size > 1``: a ``queue.Queue`` of independent renderers, checked out
|
|
878
|
-
one at a time. HuggingFace fast tokenizers release the GIL during
|
|
879
|
-
Rust encoding, so threads achieve real parallelism.
|
|
880
|
-
|
|
881
|
-
Construction parallelism for ``size > 1``: ``AutoTokenizer.from_pretrained``
|
|
882
|
-
takes hundreds of ms per call (JSON parse + Rust tokenizer build + HF
|
|
883
|
-
cache lookup), so populating a 32-slot pool serially costs ~10-15s on
|
|
884
|
-
startup and shows up directly as a step-0 stall. We fan the factory out
|
|
885
|
-
across a short-lived thread pool; the GIL-bound Python portion stops
|
|
886
|
-
scaling past ~8 workers, so we clamp there.
|
|
887
|
-
"""
|
|
888
|
-
|
|
889
|
-
def __init__(self, factory: Callable[[], Renderer], size: int):
|
|
890
|
-
from concurrent.futures import ThreadPoolExecutor
|
|
891
|
-
|
|
892
|
-
self._factory = factory
|
|
893
|
-
self._size = size
|
|
894
|
-
|
|
895
|
-
if size == 1:
|
|
896
|
-
renderer = factory()
|
|
897
|
-
self._sole: Renderer | None = renderer
|
|
898
|
-
self._lock: threading.Lock | None = threading.Lock()
|
|
899
|
-
self._pool: queue.Queue[Renderer] | None = None
|
|
900
|
-
sample: Renderer = renderer
|
|
901
|
-
else:
|
|
902
|
-
self._sole = None
|
|
903
|
-
self._lock = None
|
|
904
|
-
self._pool = queue.Queue(maxsize=size)
|
|
905
|
-
workers = min(size, 8)
|
|
906
|
-
with ThreadPoolExecutor(max_workers=workers) as executor:
|
|
907
|
-
for renderer in executor.map(lambda _: factory(), range(size)):
|
|
908
|
-
self._pool.put(renderer)
|
|
909
|
-
# Peek without removing — safe at construction time before any
|
|
910
|
-
# checkout has been served.
|
|
911
|
-
sample = self._pool.queue[0]
|
|
912
|
-
|
|
913
|
-
# Snapshot the protocol-shaped attributes from a sample renderer.
|
|
914
|
-
# They are constant per renderer class, so resolving them once at
|
|
915
|
-
# construction (a) eliminates per-call ``getattr``/``isinstance``
|
|
916
|
-
# overhead and (b) lets a future out-of-process pool variant skip
|
|
917
|
-
# holding a live tokenizer in the parent process.
|
|
918
|
-
self._renderer_cls: type[Renderer] = type(sample)
|
|
919
|
-
self.supports_tools: bool = getattr(sample, "supports_tools", True)
|
|
920
|
-
self.is_multimodal: bool = is_multimodal(sample)
|
|
921
|
-
# ``mm_token_type_id_map`` is set ONLY on pools wrapping a
|
|
922
|
-
# ``MultimodalRenderer``. We deliberately don't expose this as a
|
|
923
|
-
# class-level property: ``runtime_checkable`` Protocol's
|
|
924
|
-
# isinstance check uses ``inspect.getattr_static``, which finds
|
|
925
|
-
# property descriptors on the class regardless of whether their
|
|
926
|
-
# fget raises. Conditional instance attributes (present in
|
|
927
|
-
# ``self.__dict__`` only when applicable) are the only way to
|
|
928
|
-
# make ``isinstance(pool, MultimodalRenderer)`` reflect the
|
|
929
|
-
# inner renderer's actual protocol conformance.
|
|
930
|
-
if isinstance(sample, MultimodalRenderer):
|
|
931
|
-
self.mm_token_type_id_map: dict[int, int] = sample.mm_token_type_id_map
|
|
932
|
-
|
|
933
|
-
@contextmanager
|
|
934
|
-
def checkout(self):
|
|
935
|
-
if self._sole is not None:
|
|
936
|
-
assert self._lock is not None
|
|
937
|
-
with self._lock:
|
|
938
|
-
yield self._sole
|
|
939
|
-
return
|
|
940
|
-
assert self._pool is not None
|
|
941
|
-
renderer = self._pool.get()
|
|
942
|
-
try:
|
|
943
|
-
yield renderer
|
|
944
|
-
finally:
|
|
945
|
-
self._pool.put(renderer)
|
|
946
|
-
|
|
947
|
-
@property
|
|
948
|
-
def size(self) -> int:
|
|
949
|
-
return self._size
|
|
950
|
-
|
|
951
|
-
@property
|
|
952
|
-
def renderer_cls(self) -> type[Renderer]:
|
|
953
|
-
"""Class of the renderers in this pool (uniform across all slots)."""
|
|
954
|
-
return self._renderer_cls
|
|
955
|
-
|
|
956
|
-
# ── Renderer protocol delegation ────────────────────────────────────
|
|
957
|
-
# Pool structurally satisfies ``Renderer`` (and ``MultimodalRenderer``
|
|
958
|
-
# when its slots wrap multimodal renderers). Callers can call methods
|
|
959
|
-
# directly and dispatch with ``isinstance(pool, MultimodalRenderer)``
|
|
960
|
-
# without reaching into ``checkout()``.
|
|
961
|
-
|
|
962
|
-
def render(self, *args: Any, **kwargs: Any) -> "RenderedTokens":
|
|
963
|
-
with self.checkout() as r:
|
|
964
|
-
return r.render(*args, **kwargs)
|
|
965
|
-
|
|
966
|
-
def render_ids(self, *args: Any, **kwargs: Any) -> list[int]:
|
|
967
|
-
with self.checkout() as r:
|
|
968
|
-
return r.render_ids(*args, **kwargs)
|
|
969
|
-
|
|
970
|
-
def parse_response(self, *args: Any, **kwargs: Any) -> "ParsedResponse":
|
|
971
|
-
with self.checkout() as r:
|
|
972
|
-
return r.parse_response(*args, **kwargs)
|
|
973
|
-
|
|
974
|
-
def get_stop_token_ids(self) -> list[int]:
|
|
975
|
-
with self.checkout() as r:
|
|
976
|
-
return r.get_stop_token_ids()
|
|
977
|
-
|
|
978
|
-
def bridge_to_next_turn(self, *args: Any, **kwargs: Any) -> "RenderedTokens | None":
|
|
979
|
-
with self.checkout() as r:
|
|
980
|
-
return r.bridge_to_next_turn(*args, **kwargs)
|
|
981
|
-
|
|
982
|
-
# ``mm_token_type_id_map`` (the MultimodalRenderer protocol attribute)
|
|
983
|
-
# is set in ``__init__`` only for pools wrapping multimodal renderers;
|
|
984
|
-
# see the comment there for why this isn't a class-level property.
|
|
985
|
-
|
|
986
|
-
|
|
987
891
|
RENDERER_REGISTRY: dict[str, type] = {}
|
|
988
892
|
|
|
989
893
|
# Exact canonical HF model names → renderer. We do NOT use prefix
|
|
@@ -1156,6 +1060,25 @@ MULTIMODAL_MODELS: dict[str, set[str]] = {
|
|
|
1156
1060
|
}
|
|
1157
1061
|
|
|
1158
1062
|
|
|
1063
|
+
_TRANSFORMERS_INSTALL_HINT = (
|
|
1064
|
+
"Install the optional dependency with "
|
|
1065
|
+
"`pip install 'renderers[transformers]'` (or "
|
|
1066
|
+
"`uv add 'renderers[transformers]'`). Text-only renderers work without "
|
|
1067
|
+
"it when constructed with an offset-capable tokenizer object."
|
|
1068
|
+
)
|
|
1069
|
+
|
|
1070
|
+
|
|
1071
|
+
def _require_transformers(feature: str) -> Any:
|
|
1072
|
+
"""Return ``transformers`` or raise an actionable optional-extra error."""
|
|
1073
|
+
try:
|
|
1074
|
+
import transformers
|
|
1075
|
+
except ImportError as exc:
|
|
1076
|
+
raise ImportError(
|
|
1077
|
+
f"{feature} requires Transformers. {_TRANSFORMERS_INSTALL_HINT}"
|
|
1078
|
+
) from exc
|
|
1079
|
+
return transformers
|
|
1080
|
+
|
|
1081
|
+
|
|
1159
1082
|
def _model_has_vision_config(model_name: str) -> bool:
|
|
1160
1083
|
"""Return True if the HF config for ``model_name`` declares vision inputs.
|
|
1161
1084
|
|
|
@@ -1166,13 +1089,26 @@ def _model_has_vision_config(model_name: str) -> bool:
|
|
|
1166
1089
|
match what the trainer reconstructs — a class of bug the renderer
|
|
1167
1090
|
abstraction exists to prevent.
|
|
1168
1091
|
|
|
1169
|
-
Returns False on
|
|
1170
|
-
|
|
1092
|
+
Returns False on remote/config failures so a flaky HF probe never blocks a
|
|
1093
|
+
legitimate text-only fine-tune. When Transformers itself is unavailable,
|
|
1094
|
+
however, auto-resolution cannot safely distinguish an unknown text model
|
|
1095
|
+
from an unknown VLM; callers must install the extra or choose an explicit
|
|
1096
|
+
renderer config.
|
|
1171
1097
|
"""
|
|
1172
1098
|
try:
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1099
|
+
transformers = _require_transformers("Auto-resolving an unregistered model")
|
|
1100
|
+
except ImportError as exc:
|
|
1101
|
+
raise ImportError(
|
|
1102
|
+
f"Cannot auto-resolve unregistered model {model_name!r} without "
|
|
1103
|
+
"checking whether it is multimodal. Install "
|
|
1104
|
+
"`renderers[transformers]`, or pass an explicit typed renderer "
|
|
1105
|
+
"config such as `DefaultRendererConfig()` for a known text-only "
|
|
1106
|
+
"model."
|
|
1107
|
+
) from exc
|
|
1108
|
+
try:
|
|
1109
|
+
cfg = transformers.AutoConfig.from_pretrained(
|
|
1110
|
+
model_name, trust_remote_code=False
|
|
1111
|
+
)
|
|
1176
1112
|
except Exception:
|
|
1177
1113
|
return False
|
|
1178
1114
|
# Most VLM configs nest a vision tower as ``vision_config`` (Qwen-VL,
|
|
@@ -1196,8 +1132,8 @@ def _model_has_vision_config(model_name: str) -> bool:
|
|
|
1196
1132
|
# Pinning the revision keeps the trust narrow: even with
|
|
1197
1133
|
# ``trust_remote_code=True``, transformers downloads / executes the
|
|
1198
1134
|
# tokenizer Python from this exact commit only. A future malicious push
|
|
1199
|
-
# to the Moonshot HF repo doesn't auto-propagate to
|
|
1200
|
-
# ``
|
|
1135
|
+
# to the Moonshot HF repo doesn't auto-propagate to callers of
|
|
1136
|
+
# ``load_tokenizer``. Bump these SHAs deliberately, with review.
|
|
1201
1137
|
TRUSTED_REVISIONS: dict[str, str] = {
|
|
1202
1138
|
"moonshotai/Kimi-K2-Instruct": "fd1984e2b7a3350dbf7305fe73a4ede25c14de50",
|
|
1203
1139
|
"moonshotai/Kimi-K2.5": "4d01dfe0332d63057c186e0b262165819efb6611",
|
|
@@ -1330,7 +1266,9 @@ def load_tokenizer(model_name_or_path: str):
|
|
|
1330
1266
|
those exact IDs we load tokenizer files from the audited unrestricted
|
|
1331
1267
|
``unsloth`` mirrors instead, then restore ``tokenizer.name_or_path`` to
|
|
1332
1268
|
the requested Meta ID so auto-resolution still selects ``Llama3Renderer``.
|
|
1269
|
+
Requires the ``renderers[transformers]`` extra.
|
|
1333
1270
|
"""
|
|
1271
|
+
_require_transformers("Loading a tokenizer")
|
|
1334
1272
|
load_name_or_path = _tokenizer_source_for(model_name_or_path)
|
|
1335
1273
|
kwargs = _tokenizer_load_kwargs(load_name_or_path)
|
|
1336
1274
|
tok = _load_tokenizer_via_auto(load_name_or_path, **kwargs)
|
|
@@ -1408,44 +1346,6 @@ def _populate_registry():
|
|
|
1408
1346
|
)
|
|
1409
1347
|
|
|
1410
1348
|
|
|
1411
|
-
def create_renderer_pool(
|
|
1412
|
-
tokenizer_name_or_path: str,
|
|
1413
|
-
config: RendererConfig | None = None,
|
|
1414
|
-
*,
|
|
1415
|
-
size: int = 16,
|
|
1416
|
-
chat_template_kwargs: Mapping[str, Any] | None = None,
|
|
1417
|
-
) -> RendererPool:
|
|
1418
|
-
"""Create a RendererPool with *size* independent tokenizer copies.
|
|
1419
|
-
|
|
1420
|
-
Each slot loads its own tokenizer so threads never share mutable
|
|
1421
|
-
state. HuggingFace fast tokenizers release the GIL during Rust
|
|
1422
|
-
encoding, so threads achieve real parallelism.
|
|
1423
|
-
|
|
1424
|
-
``config`` is the typed renderer config (one of the variants of
|
|
1425
|
-
:data:`renderers.RendererConfig`). Defaults to
|
|
1426
|
-
:class:`AutoRendererConfig`, which resolves to a concrete renderer
|
|
1427
|
-
via ``MODEL_RENDERER_MAP`` at construction time using the loaded
|
|
1428
|
-
tokenizer's name. ``chat_template_kwargs`` are merged into the
|
|
1429
|
-
resolved concrete config and validated before renderer construction.
|
|
1430
|
-
Every slot in the pool shares the same config; to run a different
|
|
1431
|
-
config, build a different pool.
|
|
1432
|
-
|
|
1433
|
-
Tokenizers load via ``load_tokenizer`` — see its docstring for the
|
|
1434
|
-
``trust_remote_code`` policy (default off; Moonshot Kimi-K2 family
|
|
1435
|
-
opts in with a pinned ``revision``).
|
|
1436
|
-
"""
|
|
1437
|
-
|
|
1438
|
-
def factory() -> Renderer:
|
|
1439
|
-
tokenizer = load_tokenizer(tokenizer_name_or_path)
|
|
1440
|
-
return create_renderer(
|
|
1441
|
-
tokenizer,
|
|
1442
|
-
config,
|
|
1443
|
-
chat_template_kwargs=chat_template_kwargs,
|
|
1444
|
-
)
|
|
1445
|
-
|
|
1446
|
-
return RendererPool(factory, size=size)
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
1349
|
def create_renderer(
|
|
1450
1350
|
tokenizer,
|
|
1451
1351
|
config: RendererConfig | None = None,
|
|
@@ -1455,7 +1355,8 @@ def create_renderer(
|
|
|
1455
1355
|
"""Create a Renderer from a typed config.
|
|
1456
1356
|
|
|
1457
1357
|
Args:
|
|
1458
|
-
tokenizer:
|
|
1358
|
+
tokenizer: An object satisfying :class:`Tokenizer`; the generic
|
|
1359
|
+
fallback additionally requires :class:`ChatTemplateTokenizer`.
|
|
1459
1360
|
config: Typed renderer config — one of the variants of
|
|
1460
1361
|
:data:`renderers.RendererConfig`. ``None`` defaults to
|
|
1461
1362
|
:class:`AutoRendererConfig`, which resolves to a concrete
|
|
@@ -1471,10 +1372,11 @@ def create_renderer(
|
|
|
1471
1372
|
config from ``tokenizer.name_or_path`` and then validates these
|
|
1472
1373
|
kwargs against that config.
|
|
1473
1374
|
|
|
1474
|
-
Selecting the auto-renderer for a model without a registered
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1375
|
+
Selecting the auto-renderer for a model without a registered renderer
|
|
1376
|
+
probes Hugging Face ``AutoConfig`` before falling back to
|
|
1377
|
+
:class:`DefaultRenderer`, so unknown VLMs fail instead of silently dropping
|
|
1378
|
+
media. Without the ``transformers`` extra, pass an explicit renderer config
|
|
1379
|
+
for unregistered model names.
|
|
1478
1380
|
"""
|
|
1479
1381
|
_populate_registry()
|
|
1480
1382
|
|