renderers 0.1.8.dev57__tar.gz → 0.1.8.dev61__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.8.dev57 → renderers-0.1.8.dev61}/PKG-INFO +1 -1
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/renderers/_version.py +2 -2
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/renderers/base.py +34 -2
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/renderers/client.py +17 -6
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/tests/test_build_helpers.py +28 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/.github/workflows/publish-dev.yml +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/.github/workflows/publish.yml +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/.github/workflows/style.yml +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/.github/workflows/test.yml +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/.gitignore +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/.pre-commit-config.yaml +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/LICENSE +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/README.md +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/docs/renderer-config.md +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/examples/README.md +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/examples/sglang/multiturn_generate_sglang.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/examples/sglang/online_multiturn_sglang.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/examples/tinker/multiturn_generate_tinker.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/examples/transformers/multiturn_generate_transformers.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/examples/vllm/multiturn_generate_vllm.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/pyproject.toml +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/renderers/__init__.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/renderers/configs.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/renderers/deepseek_r1.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/renderers/deepseek_v3.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/renderers/default.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/renderers/glm45.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/renderers/glm5.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/renderers/gpt_oss.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/renderers/hy3.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/renderers/kimi_k2.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/renderers/kimi_k25.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/renderers/laguna_xs2.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/renderers/llama_3.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/renderers/minimax_m2.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/renderers/nemotron3.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/renderers/parsers.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/renderers/parsing.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/renderers/prime_qwen3.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/renderers/qwen3.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/renderers/qwen35.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/renderers/qwen36.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/renderers/qwen3_vl.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/tests/conftest.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/tests/test_bridge.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/tests/test_client.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/tests/test_deepseek_r1.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/tests/test_gpt_oss_harmony_parity.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/tests/test_hy3.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/tests/test_incremental.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/tests/test_is_content.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/tests/test_kimi_k25_tool_schema.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/tests/test_laguna_xs21.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/tests/test_llama_3.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/tests/test_load_tokenizer.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/tests/test_message_indices.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/tests/test_message_tool_names.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/tests/test_multimodal.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/tests/test_nemotron3_parity.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/tests/test_nemotron3_ultra.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/tests/test_parse_response.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/tests/test_parse_response_robustness.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/tests/test_parsers.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/tests/test_preserve_thinking.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/tests/test_prime_qwen3_parity.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/tests/test_qwen35_size_coverage.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/tests/test_render_ids.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/tests/test_renderer_config.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/tests/test_renderer_config_parity.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/tests/test_roundtrip.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/tests/test_sampled_mask.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/tests/test_tokens_per_message.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/tests/test_tool_arg_type_preservation.py +0 -0
- {renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/uv.lock +0 -0
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.1.8.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 1, 8, '
|
|
21
|
+
__version__ = version = '0.1.8.dev61'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 8, 'dev61')
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -1594,6 +1594,7 @@ def build_training_sample(
|
|
|
1594
1594
|
role_to_mask: Callable[[Message], bool] | None = None,
|
|
1595
1595
|
tools: list[ToolSpec] | None = None,
|
|
1596
1596
|
content_sft_roles: "set[str] | frozenset[str] | None" = None,
|
|
1597
|
+
ensure_final_stop: bool = False,
|
|
1597
1598
|
) -> RenderedTrainingSample:
|
|
1598
1599
|
"""Build a :class:`RenderedTrainingSample` for supervised training.
|
|
1599
1600
|
|
|
@@ -1649,6 +1650,17 @@ def build_training_sample(
|
|
|
1649
1650
|
or hand-coded renderers that haven't been wired up yet) ignore
|
|
1650
1651
|
``content_sft_roles`` silently — falling back to the original
|
|
1651
1652
|
``role_to_mask`` + ``sampled_mask`` behaviour.
|
|
1653
|
+
|
|
1654
|
+
``ensure_final_stop`` appends the renderer's canonical stop token
|
|
1655
|
+
when the sample ends with an assistant message that the template
|
|
1656
|
+
leaves unterminated. Some templates close an assistant turn only
|
|
1657
|
+
via the *next* message's role marker (e.g. GLM's ``<|user|>`` /
|
|
1658
|
+
``<|observation|>``), so a final assistant message renders with no
|
|
1659
|
+
stop token at all. No-op when the template already closes the turn
|
|
1660
|
+
in-message (ChatML ``<|im_end|>``, Llama ``<|eot_id|>``); where it
|
|
1661
|
+
fires, the output intentionally diverges from ``apply_chat_template``.
|
|
1662
|
+
Ignored for renderers without ``sampled_mask`` (``DefaultRenderer``) —
|
|
1663
|
+
the close of an opaque template can't be located reliably.
|
|
1652
1664
|
"""
|
|
1653
1665
|
rendered = renderer.render(messages, tools=tools)
|
|
1654
1666
|
has_sampled_info = len(rendered.sampled_mask) == len(rendered.token_ids)
|
|
@@ -1689,6 +1701,25 @@ def build_training_sample(
|
|
|
1689
1701
|
else:
|
|
1690
1702
|
loss_mask.append(role_to_mask(msg))
|
|
1691
1703
|
|
|
1704
|
+
token_ids = list(rendered.token_ids)
|
|
1705
|
+
# Requires sampled_mask (opaque templates hide the assistant close)
|
|
1706
|
+
# and a final assistant message the role filter trains.
|
|
1707
|
+
if (
|
|
1708
|
+
ensure_final_stop
|
|
1709
|
+
and has_sampled_info
|
|
1710
|
+
and messages[-1].get("role") == "assistant"
|
|
1711
|
+
and (role_to_mask is None or role_to_mask(messages[-1]))
|
|
1712
|
+
):
|
|
1713
|
+
stop_ids = set(renderer.get_stop_token_ids())
|
|
1714
|
+
last_trainable = next(
|
|
1715
|
+
(k for k in range(len(loss_mask) - 1, -1, -1) if loss_mask[k]), None
|
|
1716
|
+
)
|
|
1717
|
+
if last_trainable is None or token_ids[last_trainable] not in stop_ids:
|
|
1718
|
+
token_ids.append(renderer.get_stop_token_ids()[0])
|
|
1719
|
+
# loss_mask=True marks the token as trainable — the appended
|
|
1720
|
+
# stop is a training target, like any sampled token.
|
|
1721
|
+
loss_mask.append(True)
|
|
1722
|
+
|
|
1692
1723
|
# Surface the multimodal payload for VLM renderers. ``None`` for text
|
|
1693
1724
|
# renderers and for text-only samples (empty media) so downstream
|
|
1694
1725
|
# ``multi_modal_data is not None`` is a reliable "has media" check.
|
|
@@ -1696,12 +1727,12 @@ def build_training_sample(
|
|
|
1696
1727
|
if mm is not None and mm.is_empty():
|
|
1697
1728
|
mm = None
|
|
1698
1729
|
mm_token_type_ids = (
|
|
1699
|
-
_build_mm_token_type_ids(mm.mm_placeholders, len(
|
|
1730
|
+
_build_mm_token_type_ids(mm.mm_placeholders, len(token_ids))
|
|
1700
1731
|
if mm is not None and mm.mm_placeholders
|
|
1701
1732
|
else None
|
|
1702
1733
|
)
|
|
1703
1734
|
return RenderedTrainingSample(
|
|
1704
|
-
token_ids=
|
|
1735
|
+
token_ids=token_ids,
|
|
1705
1736
|
loss_mask=loss_mask,
|
|
1706
1737
|
multi_modal_data=mm,
|
|
1707
1738
|
mm_token_type_ids=mm_token_type_ids,
|
|
@@ -1975,6 +2006,7 @@ def build_trajectory_step(
|
|
|
1975
2006
|
"completion_mask": [True] * len(completion_ids),
|
|
1976
2007
|
"completion_logprobs": [0.0] * len(completion_ids),
|
|
1977
2008
|
"routed_experts": None,
|
|
2009
|
+
"kept_tokens": None,
|
|
1978
2010
|
}
|
|
1979
2011
|
if (
|
|
1980
2012
|
full_rendered.multi_modal_data is not None
|
|
@@ -32,6 +32,7 @@ from renderers.base import (
|
|
|
32
32
|
|
|
33
33
|
_request_logger = logging.getLogger("renderers.client")
|
|
34
34
|
ROUTED_EXPERTS_DATA_PREFIX = b'"routed_experts":{"data":"'
|
|
35
|
+
KEPT_TOKENS_IDS_PREFIX = b'"kept_tokens":{"ids":"'
|
|
35
36
|
|
|
36
37
|
|
|
37
38
|
class OverlongPromptError(Exception):
|
|
@@ -121,23 +122,31 @@ async def _maybe_offload(renderer: Renderer | RendererPool, fn):
|
|
|
121
122
|
return fn()
|
|
122
123
|
|
|
123
124
|
|
|
124
|
-
def
|
|
125
|
-
|
|
125
|
+
def _strip_base64_field(raw: bytes, prefix: bytes) -> tuple[bytes, memoryview | None]:
|
|
126
|
+
"""Splice a large base64 string field out of raw JSON bytes.
|
|
127
|
+
|
|
128
|
+
Avoids json-decoding megabytes of base64; the returned memoryview
|
|
129
|
+
references ``raw`` and is re-inserted into the parsed payload.
|
|
130
|
+
"""
|
|
131
|
+
data_start = raw.find(prefix)
|
|
126
132
|
if data_start < 0:
|
|
127
133
|
return raw, None
|
|
128
134
|
|
|
129
|
-
data_start += len(
|
|
135
|
+
data_start += len(prefix)
|
|
130
136
|
data_end = raw.index(b'"', data_start)
|
|
131
|
-
|
|
137
|
+
data = memoryview(raw)[data_start:data_end]
|
|
132
138
|
stripped = raw[:data_start] + raw[data_end:]
|
|
133
|
-
return stripped,
|
|
139
|
+
return stripped, data
|
|
134
140
|
|
|
135
141
|
|
|
136
142
|
def parse_generate_response(raw: bytes) -> dict[str, Any]:
|
|
137
|
-
stripped, routed_data =
|
|
143
|
+
stripped, routed_data = _strip_base64_field(raw, ROUTED_EXPERTS_DATA_PREFIX)
|
|
144
|
+
stripped, kept_ids_data = _strip_base64_field(stripped, KEPT_TOKENS_IDS_PREFIX)
|
|
138
145
|
payload: dict[str, Any] = json.loads(stripped)
|
|
139
146
|
if routed_data is not None:
|
|
140
147
|
payload["choices"][0]["routed_experts"]["data"] = routed_data
|
|
148
|
+
if kept_ids_data is not None:
|
|
149
|
+
payload["choices"][0]["kept_tokens"]["ids"] = kept_ids_data
|
|
141
150
|
return payload
|
|
142
151
|
|
|
143
152
|
|
|
@@ -293,6 +302,7 @@ async def generate(
|
|
|
293
302
|
completion_logprobs = [float(c.get("logprob") or 0.0) for c in content_lp or []]
|
|
294
303
|
|
|
295
304
|
routed_experts = choice.get("routed_experts")
|
|
305
|
+
kept_tokens = choice.get("kept_tokens")
|
|
296
306
|
|
|
297
307
|
# /inference/v1/generate returns finish_reason in {"stop","length",...} —
|
|
298
308
|
# never "tool_calls" (a chat-completions concept). Promote stop→tool_calls
|
|
@@ -319,6 +329,7 @@ async def generate(
|
|
|
319
329
|
"tool_calls": parsed.tool_calls,
|
|
320
330
|
"finish_reason": finish_reason,
|
|
321
331
|
"routed_experts": routed_experts,
|
|
332
|
+
"kept_tokens": kept_tokens,
|
|
322
333
|
# The mm sidecar consumed on the request side, surfaced back so
|
|
323
334
|
# callers can persist it on the trajectory step for downstream
|
|
324
335
|
# multi-turn bridging and training-sample construction.
|
|
@@ -64,6 +64,34 @@ def test_build_training_sample_has_trainable_tokens(model_name, tokenizer, rende
|
|
|
64
64
|
assert len(mask) == len(ids)
|
|
65
65
|
|
|
66
66
|
|
|
67
|
+
def test_build_training_sample_ensures_final_stop(model_name, tokenizer, renderer):
|
|
68
|
+
"""The final assistant turn ends at a trainable renderer stop token.
|
|
69
|
+
|
|
70
|
+
Templates whose assistant close is part of the message (ChatML, Llama)
|
|
71
|
+
already satisfy this, so the sample stays byte-identical; templates
|
|
72
|
+
that terminate turns with the next message's role marker (GLM) get the
|
|
73
|
+
canonical stop appended as the training target.
|
|
74
|
+
"""
|
|
75
|
+
if not renderer.render([{"role": "user", "content": "x"}]).sampled_mask:
|
|
76
|
+
return # DefaultRenderer: no sampled_mask, role-only masking
|
|
77
|
+
msgs = [
|
|
78
|
+
{"role": "user", "content": "Hi"},
|
|
79
|
+
{"role": "assistant", "content": "Hello!"},
|
|
80
|
+
]
|
|
81
|
+
sample = build_training_sample(renderer, msgs, ensure_final_stop=True)
|
|
82
|
+
stop_ids = set(renderer.get_stop_token_ids())
|
|
83
|
+
last_trainable = max(k for k, m in enumerate(sample.loss_mask) if m)
|
|
84
|
+
assert sample.token_ids[last_trainable] in stop_ids
|
|
85
|
+
|
|
86
|
+
baseline = build_training_sample(renderer, msgs)
|
|
87
|
+
if (
|
|
88
|
+
baseline.token_ids[max(k for k, m in enumerate(baseline.loss_mask) if m)]
|
|
89
|
+
in stop_ids
|
|
90
|
+
):
|
|
91
|
+
# In-message close: ensure_final_stop must not change the bytes.
|
|
92
|
+
assert sample.token_ids == baseline.token_ids
|
|
93
|
+
|
|
94
|
+
|
|
67
95
|
def test_build_trajectory_step_reconstructs_full(model_name, tokenizer, renderer):
|
|
68
96
|
"""prompt_ids + completion_ids must equal the full rendered sequence."""
|
|
69
97
|
prompt = [
|
|
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
|
{renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/examples/sglang/multiturn_generate_sglang.py
RENAMED
|
File without changes
|
|
File without changes
|
{renderers-0.1.8.dev57 → renderers-0.1.8.dev61}/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|