Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion configs/advanced/minimax-m2.5/swe.toml
Original file line number Diff line number Diff line change
Expand Up @@ -64,5 +64,5 @@ env.agent.runtime.labels = ["minimax-swe", "swe-bench-verified"]

[inference.vllm]
tensor_parallel_size = 8
[orchestrator.renderer]
[inference.renderer]
name = "minimax-m2"
2 changes: 1 addition & 1 deletion configs/advanced/nemotron-3-super/swe.toml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ num_output_tokens_weight = 0.0
num_input_tokens_weight = 0.1
num_turns_weight = 0.1

[orchestrator.renderer]
[inference.renderer]
name = "nemotron-3"
truncate_history_thinking = false

Expand Down
2 changes: 1 addition & 1 deletion configs/ci/integration/alphabet_sort.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,6 @@ env.agent.runtime.type = "subprocess"

# Mirror of Qwen/Qwen3-0.6B; PI's template patch always re-emits prior
# <think> blocks. Match that with renderer thinking_retention.
[orchestrator.renderer]
[inference.renderer]
name = "auto"
thinking_retention = "all"
2 changes: 1 addition & 1 deletion configs/ci/integration/reverse-text-lora/resume.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,5 @@ env.agent.runtime.type = "subprocess"
[inference.vllm]
enable_lora = true
gpu_memory_utilization = 0.7
[orchestrator.renderer]
[inference.renderer]
name = "prime-qwen3"
2 changes: 1 addition & 1 deletion configs/ci/integration/reverse-text-lora/start.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,5 @@ env.agent.runtime.type = "subprocess"
[inference.vllm]
enable_lora = true
gpu_memory_utilization = 0.7
[orchestrator.renderer]
[inference.renderer]
name = "prime-qwen3"
2 changes: 1 addition & 1 deletion configs/ci/integration/reverse-text-moe/start.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,5 @@ env.agent.runtime.type = "subprocess"
[inference.vllm]
max_model_len = 2048
gpu_memory_utilization = 0.7
[orchestrator.renderer]
[inference.renderer]
name = "default"
2 changes: 1 addition & 1 deletion configs/ci/integration/reverse-text-rl-opd/start.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ type = "opd"
name = "PrimeIntellect/Qwen3-0.6B-Reverse-Text-RL"
base_url = "http://localhost:8001/v1"

[orchestrator.renderer]
[inference.renderer]
name = "prime-qwen3"

[orchestrator.train.sampling]
Expand Down
8 changes: 2 additions & 6 deletions configs/ci/integration/reverse-text-rl-sft/start.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,8 @@ type = "sft"
name = "PrimeIntellect/Qwen3-0.6B-Reverse-Text-RL"
base_url = "http://localhost:8001/v1"

# The student's renderer tokenizes the teacher's messages into the student's
# token space (backfill). Use `prime-qwen3` — it matches this PrimeIntellect
# Qwen3 model's chat template token-for-token (single-turn + system), so the CE
# target stays faithful, without the empty `<think></think>` block the stock
# `qwen3` renderer injects (which this model's template never emits).
[orchestrator.renderer]
# Match the policy chat template. The frozen generation server also owns its renderer.
[inference.renderer]
name = "prime-qwen3"
Comment on lines +40 to 42

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Configure the frozen SFT server's renderer

This block configures only the managed policy server, while the frozen teacher at localhost:8001 is started separately by tests/integration/test_reverse_text_rl_sft.py:56-66 without --renderer.name prime-qwen3. Because the teacher model’s -RL suffix does not resolve to the intended renderer, the new server-owned training-record path renders teacher prompts with the auto/default renderer rather than the policy-compatible renderer, so this SFT integration can produce mismatched CE targets. Fresh evidence beyond the existing tokenizer comment is that the equivalent debug launch now explicitly passes --renderer.name prime-qwen3, but the fixture launch was not updated.

Useful? React with 👍 / 👎.


[orchestrator.train.sampling]
Expand Down
2 changes: 1 addition & 1 deletion configs/ci/integration/reverse-text/resume.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,5 @@ env.agent.runtime.type = "subprocess"

# PrimeIntellect Qwen3 fine-tune - select its renderer explicitly (the -SFT
# suffix misses the MODEL_RENDERER_MAP exact match).
[orchestrator.renderer]
[inference.renderer]
name = "prime-qwen3"
2 changes: 1 addition & 1 deletion configs/ci/integration/reverse-text/start.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,5 @@ env.agent.runtime.type = "subprocess"

# PrimeIntellect Qwen3 fine-tune - select its renderer explicitly (the -SFT
# suffix misses the MODEL_RENDERER_MAP exact match).
[orchestrator.renderer]
[inference.renderer]
name = "prime-qwen3"
5 changes: 3 additions & 2 deletions configs/ci/nightly-fft/hendrycks-sanity.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ attn = "flash_attention_2"
[inference.vllm]
max_model_len = 8192
data_parallel_size = 4
[orchestrator.renderer]
reasoning_parser = "qwen3"

[inference.renderer]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Move the migrated reasoning parser to vLLM

For the Hendrycks runs using Qwen/Qwen3-1.7B, migrating this block leaves reasoning_parser = "think" under inference.renderer, but PrimeRlServingChat now parses completions exclusively with self.parser_cls, which is configured by inference.vllm.reasoning_parser; auto-resolution returns no parser for this model. Consequently <think> output remains in message content instead of being separated as reasoning, changing reward inputs and subsequent multi-turn messages. Move this setting (and the identical one in examples/basic/hendrycks-sanity/rl.toml) to [inference.vllm], or explicitly connect renderer parser settings to the native response parser.

Useful? React with 👍 / 👎.

name = "default"
reasoning_parser = "think"
2 changes: 1 addition & 1 deletion configs/ci/nightly-fft/reverse-text.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,5 @@ max_completion_tokens = 128

[inference]

[orchestrator.renderer]
[inference.renderer]
name = "prime-qwen3"
2 changes: 1 addition & 1 deletion configs/ci/nightly-fft/wordle.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,5 @@ max_completion_tokens = 1024

[inference.vllm]
data_parallel_size = 6
[orchestrator.renderer]
[inference.renderer]
name = "prime-qwen3"
2 changes: 1 addition & 1 deletion configs/debug/algo/echo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ env.agent.runtime.type = "subprocess"
max_completion_tokens = 512

# Fine-tune inherits the PrimeIntellect Qwen3 template byte-for-byte.
[orchestrator.renderer]
[inference.renderer]
name = "prime-qwen3"

[trainer.optim]
Expand Down
2 changes: 1 addition & 1 deletion configs/debug/algo/grpo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ group_size = 16
[orchestrator.train.algo]
type = "grpo"

[orchestrator.renderer]
[inference.renderer]
name = "prime-qwen3"

[orchestrator.train.sampling]
Expand Down
2 changes: 1 addition & 1 deletion configs/debug/algo/max_rl.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ group_size = 16
[orchestrator.train.algo]
type = "max_rl"

[orchestrator.renderer]
[inference.renderer]
name = "prime-qwen3"

[orchestrator.train.sampling]
Expand Down
2 changes: 1 addition & 1 deletion configs/debug/algo/mixed_grpo_opd.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ group_size = 16
[orchestrator.train.algo]
type = "grpo"

[orchestrator.renderer]
[inference.renderer]
name = "prime-qwen3"

[orchestrator.train.sampling]
Expand Down
4 changes: 2 additions & 2 deletions configs/debug/algo/opd.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ type = "opd"
name = "PrimeIntellect/Qwen3-0.6B-Reverse-Text-RL"
base_url = "http://localhost:8001/v1"

# Teacher rolls out over plain chat-completions (no tokens); the renderer backfills them.
[orchestrator.renderer]
# The inference server returns exact tokens with the policy chat template.
[inference.renderer]
name = "prime-qwen3"

[orchestrator.train.sampling]
Expand Down
4 changes: 2 additions & 2 deletions configs/debug/algo/opd_lora.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ type = "opd"
name = "PrimeIntellect/Qwen3-0.6B-Reverse-Text-RL"
base_url = "http://localhost:8001/v1"

# Teacher rolls out over plain chat-completions (no tokens); the renderer backfills them.
[orchestrator.renderer]
# The inference server returns exact tokens with the policy chat template.
[inference.renderer]
name = "prime-qwen3"

[orchestrator.train.sampling]
Expand Down
2 changes: 1 addition & 1 deletion configs/debug/algo/rae.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ group_size = 1
[orchestrator.train.algo]
type = "rae"

[orchestrator.renderer]
[inference.renderer]
name = "prime-qwen3"

[orchestrator.train.sampling]
Expand Down
2 changes: 1 addition & 1 deletion configs/debug/algo/self_distill.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ group_size = 1
type = "opsd"
demo_key = "answer"

[orchestrator.renderer]
[inference.renderer]
name = "prime-qwen3"

[orchestrator.train.sampling]
Expand Down
10 changes: 3 additions & 7 deletions configs/debug/algo/sft_distill.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Start the frozen reference server first (on a separate GPU):
# CUDA_VISIBLE_DEVICES=1 uv run inference \
# --vllm.model PrimeIntellect/Qwen3-0.6B-Reverse-Text-RL \
# --vllm.model PrimeIntellect/Qwen3-0.6B-Reverse-Text-RL --renderer.name prime-qwen3 \
# --server.port 8001 --vllm.gpu-memory-utilization 0.5 --vllm.enforce-eager
# Then:
# uv run rl @ configs/debug/algo/sft_distill.toml
Expand Down Expand Up @@ -29,12 +29,8 @@ type = "sft"
name = "PrimeIntellect/Qwen3-0.6B-Reverse-Text-RL"
base_url = "http://localhost:8001/v1"

# The student's renderer tokenizes the teacher's messages into the student's
# token space (backfill). Use `prime-qwen3` — it matches this PrimeIntellect
# Qwen3 model's chat template token-for-token (single-turn + system), keeping the
# CE target faithful, without the empty `<think></think>` block the stock `qwen3`
# renderer injects (which this model's template never emits).
[orchestrator.renderer]
# Match the policy chat template. The frozen generation server also owns its renderer.
[inference.renderer]
name = "prime-qwen3"

[orchestrator.train.sampling]
Expand Down
10 changes: 3 additions & 7 deletions configs/debug/algo/sft_distill_lora.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Start the frozen reference server first (on a separate GPU):
# CUDA_VISIBLE_DEVICES=1 uv run inference \
# --vllm.model PrimeIntellect/Qwen3-0.6B-Reverse-Text-RL \
# --vllm.model PrimeIntellect/Qwen3-0.6B-Reverse-Text-RL --renderer.name prime-qwen3 \
# --server.port 8001 --vllm.gpu-memory-utilization 0.5 --vllm.enforce-eager
# Then:
# uv run rl @ configs/debug/algo/sft_distill_lora.toml
Expand Down Expand Up @@ -29,12 +29,8 @@ type = "sft"
name = "PrimeIntellect/Qwen3-0.6B-Reverse-Text-RL"
base_url = "http://localhost:8001/v1"

# The student's renderer tokenizes the teacher's messages into the student's
# token space (backfill). Use `prime-qwen3` — it matches this PrimeIntellect
# Qwen3 model's chat template token-for-token (single-turn + system), keeping the
# CE target faithful, without the empty `<think></think>` block the stock `qwen3`
# renderer injects (which this model's template never emits).
[orchestrator.renderer]
# Match the policy chat template. The frozen generation server also owns its renderer.
[inference.renderer]
name = "prime-qwen3"

[orchestrator.train.sampling]
Expand Down
2 changes: 1 addition & 1 deletion deps/renderers
2 changes: 1 addition & 1 deletion deps/verifiers
Submodule verifiers updated 91 files
+16 −0 docs/agent.md
+43 −42 docs/env.md
+31 −12 docs/harbor.md
+4 −1 docs/harnesses.md
+1 −0 docs/mint.json
+1 −0 docs/overview.md
+54 −0 docs/runtime.md
+34 −9 environments/interception/interception/taskset.py
+13 −7 tests/integration/conftest.py
+9 −9 tests/integration/fixtures/echo_acp_resume_v1.py
+47 −53 tests/integration/fixtures/network_policy_v1.py
+14 −4 tests/integration/grid.py
+5 −5 tests/integration/test_agentic_judge.py
+169 −0 tests/integration/test_compose_scoring.py
+54 −10 tests/integration/test_examples.py
+57 −17 tests/integration/test_network_policy.py
+0 −4 tests/integration/test_resume_tool.py
+22 −9 tests/integration/test_rubric_judge.py
+29 −0 tests/integration/test_sampling.py
+61 −7 tests/unit/harnesses/test_core.py
+1 −0 tests/unit/test_agent.py
+51 −0 tests/unit/test_dialects.py
+9 −37 tests/unit/test_graph.py
+44 −7 tests/unit/test_trace.py
+1 −3 tests/unit/utils/test_loaders.py
+20 −8 verifiers/v1/__init__.py
+60 −10 verifiers/v1/agent.py
+8 −7 verifiers/v1/cli/debug.py
+6 −5 verifiers/v1/cli/validate.py
+3 −20 verifiers/v1/clients/__init__.py
+11 −6 verifiers/v1/clients/base.py
+242 −52 verifiers/v1/clients/client.py
+0 −222 verifiers/v1/clients/eval.py
+0 −473 verifiers/v1/clients/train.py
+27 −3 verifiers/v1/configs/agent.py
+2 −2 verifiers/v1/configs/cli/eval.py
+5 −50 verifiers/v1/configs/client.py
+2 −2 verifiers/v1/configs/judge.py
+2 −2 verifiers/v1/configs/retries.py
+55 −0 verifiers/v1/configs/scoring.py
+1 −2 verifiers/v1/dialects/__init__.py
+135 −420 verifiers/v1/dialects/anthropic.py
+121 −306 verifiers/v1/dialects/base.py
+160 −374 verifiers/v1/dialects/chat.py
+740 −0 verifiers/v1/dialects/policy.py
+140 −496 verifiers/v1/dialects/responses.py
+2 −2 verifiers/v1/envs/agentic_judge/__init__.py
+70 −75 verifiers/v1/envs/agentic_judge/env.py
+0 −7 verifiers/v1/envs/isolated_verifier/__init__.py
+0 −182 verifiers/v1/envs/isolated_verifier/env.py
+0 −6 verifiers/v1/envs/shared_agentic_judge/__init__.py
+22 −13 verifiers/v1/errors.py
+3 −3 verifiers/v1/gepa/config.py
+205 −0 verifiers/v1/grading.py
+64 −234 verifiers/v1/graph.py
+7 −3 verifiers/v1/harness.py
+1 −2 verifiers/v1/harnesses/hermes_agent/harness.py
+9 −2 verifiers/v1/harnesses/prime_agent/harness.py
+1 −1 verifiers/v1/harnesses/rlm/harness.py
+17 −91 verifiers/v1/harnesses/utils/core.py
+2 −1 verifiers/v1/harnesses/utils/launch.py
+1 −1 verifiers/v1/interception/__init__.py
+223 −372 verifiers/v1/interception/server.py
+23 −14 verifiers/v1/mcp/launch.py
+439 −208 verifiers/v1/rollout.py
+50 −36 verifiers/v1/runtimes/__init__.py
+5 −3 verifiers/v1/runtimes/apptainer.py
+49 −162 verifiers/v1/runtimes/base.py
+120 −74 verifiers/v1/runtimes/compose.py
+69 −53 verifiers/v1/runtimes/container.py
+196 −0 verifiers/v1/runtimes/deployment.py
+64 −52 verifiers/v1/runtimes/docker/__init__.py
+3 −3 verifiers/v1/runtimes/docker/egress.py
+37 −27 verifiers/v1/runtimes/e2b.py
+24 −17 verifiers/v1/runtimes/modal.py
+29 −22 verifiers/v1/runtimes/prime.py
+223 −0 verifiers/v1/runtimes/runtime.py
+64 −0 verifiers/v1/runtimes/strategies.py
+22 −13 verifiers/v1/runtimes/subprocess.py
+24 −18 verifiers/v1/serve/client.py
+1 −1 verifiers/v1/serve/pool.py
+115 −63 verifiers/v1/session.py
+54 −11 verifiers/v1/task.py
+0 −3 verifiers/v1/tasksets/harbor/__init__.py
+0 −138 verifiers/v1/tasksets/harbor/env.py
+76 −24 verifiers/v1/tasksets/harbor/taskset.py
+5 −5 verifiers/v1/tasksets/nemo_gym/taskset.py
+24 −16 verifiers/v1/types.py
+3 −1 verifiers/v1/utils/artifacts.py
+98 −0 verifiers/v1/utils/chat.py
+31 −0 verifiers/v1/utils/env.py
59 changes: 26 additions & 33 deletions docs/algorithms.md
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,7 @@ reject_max = 0.05

## Multi-Turn Trajectories

For multi-turn rollouts (tool use, browser environments, long conversations), `prime-rl` records each LLM request/response as an independent **trajectory step** and merges them at training time using best-effort interleaving — with [renderers](#renderers) as the mechanism that keeps the merge safe by construction.
For multi-turn rollouts (tool use, browser environments, long conversations), `prime-rl` records each LLM request/response as an independent **trajectory step** and merges them at training time using best-effort interleaving. Verifiers checks the server-returned token IDs before sharing a prefix.

### Extension Property

Expand Down Expand Up @@ -518,47 +518,40 @@ steps 4–5: extension holds → merged into Sample 2
result: 2 training samples instead of 5
```

The orchestrator enforces an **exact prefix invariant**: the prompt at turn $t$ must be the exact concatenation of prior messages exactly as the LLM originally generated them. If turn 2's prompt is `U1, A1', U2` while `A1' ≠ A1`, the orchestrator can't safely merge — either choice produces logprob drift between trainer and inference. Starting a fresh sample is the only correct behavior, so that's what happens.
Verifiers enforces an **exact prefix invariant** when merging records: the prompt at turn $t$ must be the exact concatenation of prior messages exactly as the LLM originally generated them. If turn 2's prompt is `U1, A1', U2` while `A1' ≠ A1`, the orchestrator can't safely merge — either choice produces logprob drift between trainer and inference. Starting a fresh sample is the only correct behavior, so that's what happens.

### Renderers

Best-effort interleaving works because the renderer guarantees the exact-prefix invariant *by construction* — it never re-renders prior turns, so it can't lose tokens to chat-template normalization, BPE retokenization drift, or thinking stripping. A renderer turns a model's chat template into a Python object that can:

- `render_ids(messages)` — tokenize messages to ids the inference engine accepts.
- `parse_response(completion_ids)` — recover structured `(content, reasoning_content, tool_calls)` from sampled ids.
- `bridge_to_next_turn(prev_prompt_ids, prev_completion_ids, new_messages)` — extend the previous turn's tokens verbatim with the new environment turn, instead of re-rendering history.

When `bridge_to_next_turn` succeeds, the trainer sees the exact token stream the sampler produced; when it can't be proven safe (e.g. the renderer is `DefaultRenderer` and the template's stop sequence is unknown), it returns `None` and the orchestrator falls back to a full re-render — which triggers the new-sample fallback above.

A common source of breakage in the absence of a hand-coded renderer is models like Qwen3 whose chat templates strip past `<think>` blocks across user turns:

```python
from transformers import AutoTokenizer
tok = AutoTokenizer.from_pretrained("Qwen/Qwen3-0.6B")
messages = [
{"role": "user", "content": "U1"},
{"role": "assistant", "content": "<think>R1</think>A1"},
{"role": "user", "content": "U2"},
]
tok.apply_chat_template(messages[:1], tokenize=False)
# <|im_start|>user
# U1<|im_end|>

tok.apply_chat_template(messages, tokenize=False)
# <|im_start|>user\nU1<|im_end|>\n<|im_start|>assistant\nA1<|im_end|>\n<|im_start|>user\nU2<|im_end|>
# (the <think>R1</think> from turn 2 is gone)
```

Hand-coded renderers ship for `qwen3`, `qwen3-vl`, `qwen3.5`, `glm-5`, `glm-4.5`, `minimax-m2`, `deepseek-v3`, `kimi-k2`, `kimi-k2.5`, `nemotron-3`, `gpt-oss`; anything else falls back to `DefaultRenderer` (a generic `apply_chat_template` wrapper). Pick one via:
Evaluation and training use the same OpenAI-compatible client. Training requests ask the
inference server for exact prompt and completion token IDs, sampled-token logprobs, and
training metadata. Each call is a valid training record in its own right. Verifiers merges
records only when their token prefixes match exactly; a re-rendered history starts a new
sample with its context masked out.

The inference server uses the configured `renderers` implementation for every Chat
Completions prompt, and to attribute prompt tokens to messages
and message bodies. ECHO uses this attribution to select observations, including when
tokenization changes across turns. Multimodal records keep inline image URLs and exact
placeholder ranges; the trainer builds image features from those URLs. Native vLLM handles
sampling, response parsing, tools, reasoning, and streaming for both training and eval.
`return_training_metadata=true` adds exact token IDs and attribution; logprobs remain optional.
Sampling masks and
expert routing travel alongside the tokens for replay in the trainer.

```toml
[orchestrator.renderer]
name = "auto" # detect from tokenizer; pass an explicit name for fine-tunes
[inference.renderer]
name = "auto" # detect from tokenizer; use an explicit name for fine-tunes
```

A renderer outside the `renderers` package loads as a custom renderer: `name = "custom"` with `import_path = "my_module.Class"` (or `path/to/file.py:Class`), see [Custom renderers](training.md#dataset-format).

For the full design rationale (failure modes ruled out, empirical token-identity comparison against `apply_chat_template`, when to write a hand-coded renderer), see [the renderers writeup on the Prime Intellect blog](https://www.primeintellect.ai/blog/renderers) — the canonical reference.
RL and distillation losses require sampling logprobs. CE-only frozen generation can omit
logprobs, but its endpoint must still return exact tokens and training metadata.
Before frozen generation starts, its endpoint's `/v1/tokenizer` vocabulary must match
the policy tokenizer's token-to-ID mapping. Different vocabularies are rejected; frozen
tokens are not retokenized for the policy.
If a frozen endpoint's router does not forward `/v1/tokenizer`, set its `admin_base_url`
to the inference servers so their vocabularies can be checked directly.

### Discontinuous Trajectories

Expand Down
4 changes: 2 additions & 2 deletions docs/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ A `prime-rl` RL run is three cooperating processes:

![Architecture](assets/architecture.png)

- **Inference** — vLLM-backed server (or fleet) holding the current policy. The orchestrator drives rollouts through the token-in `/inference/v1/generate` route via the [`renderers`](https://github.com/PrimeIntellect-ai/renderers) package (OpenAI-compatible chat/completions routes are also exposed for external clients). We are trying to stay up-to-date with the latest vLLM features, you can read more about the supported features and deployment options in the dedicated [inference documentation](inference.md).
- **Orchestrator** — Lightweight CPU process that owns the data plane across many [`verifiers`](https://github.com/PrimeIntellect-ai/verifiers) training and eval environments. Each env runs in an isolated subprocess with a variable-size pool of env workers for scalability. The orchestrator drives multi-turn rollouts against the inference fleet (tool use, browsers, sandboxes, long horizons) without re-tokenizing across turns, computes advantages, packs the rollouts into training batches, and relays new weights from trainer to inference.
- **Inference** — vLLM-backed server (or fleet) holding the current policy. The orchestrator drives rollouts through `/v1/chat/completions`, requesting exact tokens and training attribution from the server. The endpoint's [`renderers`](https://github.com/PrimeIntellect-ai/renderers) configuration controls prompt rendering for both training and eval. See the [inference documentation](inference.md) for supported features and deployment options.
- **Orchestrator** — Lightweight CPU process that owns the data plane across many [`verifiers`](https://github.com/PrimeIntellect-ai/verifiers) training and eval environments. Each env runs in an isolated subprocess with a variable-size pool of env workers for scalability. The orchestrator drives multi-turn rollouts against the inference fleet (tool use, browsers, sandboxes, long horizons), merges exact token prefixes across turns, computes advantages, packs the rollouts into training batches, and relays new weights from trainer to inference.
- **Trainer** — FSDP2 process group that consumes packed rollouts and steps the optimizer. We ship optimized custom modeling code for many MoE / dense / VLM families that unlocks advanced trainer parallelism — expert parallelism (EP, with DeepEP kernels) and context parallelism (CP) for long-sequence training — plus selective activation checkpointing, FP8 training on Hopper+, and LoRA. You can read more in the dedicated [training documentation](training.md).

The three processes communicate through configurable transports — by default the trainer↔orchestrator rollout link uses ZMQ (each trainer rank requests a step before the orchestrator sends it, so the trainer only ever holds the batch it is training on), and weight broadcast uses NCCL for synchronous in-memory transfer (falling back to filesystem when LoRA is enabled or no inference server is configured). Swap the rollout link to the local filesystem (`rollout_transport.type = "filesystem"`) if you want rollouts persisted to disk. See [Scaling](scaling.md) for the deployment options.
Expand Down
2 changes: 1 addition & 1 deletion docs/training.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ import_path = "my_renderers.glm53_no_thinking.NoThinkingGLM53Renderer"
clear_thinking = true
```

The same config selects the renderer for RL under `[orchestrator.renderer]`. The [`renderers` README](https://github.com/PrimeIntellect-ai/renderers#custom-renderers) shows the class behind this example.
The same config selects the renderer for RL under `[inference.renderer]`. The [`renderers` README](https://github.com/PrimeIntellect-ai/renderers#custom-renderers) shows the class behind this example.

**Renderer-backed tokenization.** SFT tokenization is renderer-only. The [`renderers`](algorithms.md#renderers) package owns message-to-token conversion and loss attribution end-to-end, so position-dependent chat templates (for example templates that strip past `<think>` blocks across user turns) do not corrupt the loss mask. `[renderer]` defaults to `name = "auto"`; set a typed renderer config only when you need model-specific template controls. Hand-coded renderers ship for Qwen3, Qwen3.5, GLM-5, GLM-4.5, Kimi K2/K2.5, MiniMax M2, DeepSeek V3, Nemotron 3, GPT-OSS, and VLM families such as Qwen3-VL/Qwen3.5.

Expand Down
8 changes: 4 additions & 4 deletions examples/advanced/glm-4.5-air/ipo-ablation/base.toml
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,6 @@ num_output_tokens_weight = 0.1
num_input_tokens_weight = 0.1
num_turns_weight = 0.1

[orchestrator.renderer]
name = "glm-4.5"
thinking_retention = "all"

[[orchestrator.train.source]]
name = "scaleswe"
env.taskset.id = "scaleswe"
Expand All @@ -89,6 +85,10 @@ env.agent.timeout.rollout = 3600

[inference]

[inference.renderer]
name = "glm-4.5"
thinking_retention = "all"

[inference.vllm]
gpu_memory_utilization = 0.85
max_model_len = 131072
Expand Down
2 changes: 1 addition & 1 deletion examples/advanced/glm-4.5-air/search.toml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ group_size = 16
initial_inflight = 256
max_inflight = 512

[orchestrator.renderer]
[inference.renderer]
name = "glm-4.5"
thinking_retention = "all"

Expand Down
2 changes: 1 addition & 1 deletion examples/advanced/glm-4.5-air/swe-budget.toml
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ num_output_tokens_weight = 0.0
num_input_tokens_weight = 0.1
num_turns_weight = 0.1

[orchestrator.renderer]
[inference.renderer]
name = "glm-4.5"
thinking_retention = "all"

Expand Down
2 changes: 1 addition & 1 deletion examples/advanced/glm-4.5-air/swe.toml
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ num_output_tokens_weight = 0.0
num_input_tokens_weight = 0.1
num_turns_weight = 0.1

[orchestrator.renderer]
[inference.renderer]
name = "glm-4.5"
thinking_retention = "all"

Expand Down
2 changes: 1 addition & 1 deletion examples/advanced/glm-4.5-air/terminal.toml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ group_size = 16
initial_inflight = 256
max_inflight = 512

[orchestrator.renderer]
[inference.renderer]
name = "glm-4.5"
thinking_retention = "all"

Expand Down
Loading
Loading