Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
ab9dc79
feat: add renderers support for RL — rendering proxy + /v1/generate e…
hallerite Apr 7, 2026
63b61e9
feat: remove token-client training path
hallerite Apr 8, 2026
b489632
fix: restore serving chat type imports
hallerite Apr 8, 2026
f34982a
feat: forward lora adapters through generate
hallerite Apr 8, 2026
600348b
fix: preserve renderer proxy client routing
hallerite Apr 8, 2026
82e929b
fix: restore external rollout model routing
hallerite Apr 8, 2026
82fcbeb
refactor: use direct renderer rollout clients
hallerite Apr 9, 2026
4efa271
feat: add new renderer options to config docstring
hallerite Apr 14, 2026
7a7dcc6
fix: point verifiers dep at renderers branch instead of local path
hallerite Apr 14, 2026
84c3852
merge: origin/main into hallerite/renderers-v2
hallerite Apr 14, 2026
d1506f5
chore: remove debug smoke configs
hallerite Apr 14, 2026
b532428
fix: remove old TITO endpoint, fix merge import gaps
hallerite Apr 14, 2026
67ffcb5
refactor: remove OpenAIServingChatWithRoutedExperts
hallerite Apr 14, 2026
c26a0ae
merge: origin/main into hallerite/renderers-v2
hallerite Apr 14, 2026
753d616
fix: update tests for new train/eval client type split
hallerite Apr 14, 2026
9f84ce3
feat: default train_client_type to renderer
hallerite Apr 14, 2026
920f381
fix: elastic test uses train_clients instead of removed clients property
hallerite Apr 14, 2026
208a9b3
chore: pin verifiers renderer revision
hallerite Apr 15, 2026
bcbdbb3
Merge remote-tracking branch 'origin/main' into hallerite/renderers-v2
hallerite Apr 21, 2026
55d44d0
chore: bump verifiers to c4e50e4 (Qwen3-VL image-pad fix)
hallerite Apr 21, 2026
e9cb682
chore: bump verifiers to 2d8cc29 (trainer/vLLM image-pad fix)
hallerite Apr 21, 2026
e781e9c
fix(vlm): expand <|image_pad|> in trainer samples in sync with parall…
hallerite Apr 21, 2026
160d4c9
refactor(vlm): model-agnostic image-pad expansion + overlap preproces…
hallerite Apr 21, 2026
03865c6
feat(vlm): route VLMs to MITO, keep renderers for plain LMs
hallerite Apr 21, 2026
fd9d0f5
chore(orchestrator): bump verifiers to pool-size-1 default
hallerite Apr 21, 2026
ca3dcdd
perf(serving_generate): stop polling is_disconnected per decode step
hallerite Apr 21, 2026
5093054
fix(serving_generate): default max_tokens to context room, not 4096
hallerite Apr 21, 2026
9cf49b0
fix(serving_generate): use engine model_config attr, not async getter
hallerite Apr 21, 2026
b1843b4
refactor(renderer): drop VLM residue from renderer path
hallerite Apr 22, 2026
d54ff30
fix(serving_generate): use TokensInput shape to silence vLLM deprecation
hallerite Apr 22, 2026
05a3516
feat(config): pluggable tool_parser / reasoning_parser
hallerite Apr 22, 2026
10d3d98
feat(config): expose model.renderer_pool_size
hallerite Apr 22, 2026
fcaa834
chore: bump verifiers to f47ba32 (gated synthesize-close bridge)
hallerite Apr 22, 2026
858aa45
chore: bump verifiers to fb6ecb9 (bridge anchor fix + synth-close knob)
hallerite Apr 22, 2026
cd56374
chore: bump verifiers to 891da50 (per-renderer bridge_to_next_turn)
hallerite Apr 22, 2026
c06a0af
chore: bump verifiers to 9020f70 (kimi/gpt-oss parse fixes + wider ro…
hallerite Apr 22, 2026
6e99c2e
chore: bump verifiers to 7d2f7f7 (full render parity across the matrix)
hallerite Apr 23, 2026
4aee0c6
chore: bump verifiers to 6f89c38 (hand-coded per-renderer bridges)
hallerite Apr 23, 2026
62946a7
fix(serving_generate): plumb cache_salt through /v1/generate
hallerite Apr 23, 2026
67f9e54
chore: bump verifiers to c499099 (cache_salt forwarding on /v1/generate)
hallerite Apr 23, 2026
889d15e
chore: bump verifiers to fafea87 (exact-name model→renderer map)
hallerite Apr 23, 2026
7bbf985
fix(ring_attn): adapt FA3 varlen wrappers to flash_attn_3 3.0.0 kwargs
hallerite Apr 24, 2026
e099a9f
chore: bump verifiers to c58da4a
hallerite Apr 24, 2026
9641871
chore: bump verifiers to 7136de5
hallerite Apr 24, 2026
8e7b68f
chore: bump verifiers to 7b86d14 (merge of main into renderers)
hallerite Apr 24, 2026
3ba3dcd
[fix] patch gdn with varlen for qwen (#2346)
Jackmin801 Apr 23, 2026
8089985
chore(deps): bump verifiers to 17358ee
hallerite Apr 28, 2026
01f9453
chore(inference): restore /v1/chat/completions/tokens (TITO) alongsid…
hallerite Apr 28, 2026
5d96a3e
Merge origin/main into hallerite/renderers-v2 + add use_renderer toggle
hallerite Apr 28, 2026
515c48f
chore(orchestrator): cleanup-review fixes + bump verifiers
hallerite Apr 28, 2026
c61d842
chore(deps): bump verifiers to ee6cdb5
hallerite Apr 29, 2026
519f61e
chore(deps): bump verifiers to b7dd85f (renderers merged to main)
hallerite Apr 29, 2026
76c8590
test(orchestrator): set use_renderer/tool_parser/reasoning_parser/ren…
hallerite Apr 29, 2026
540500d
revert: restore docs/on_policy_distillation.md and skills/entrypoints…
hallerite Apr 29, 2026
717c6e0
address reviews
hallerite Apr 29, 2026
5b6f42b
fix(config): drop orphan renderer fields from SharedModelConfig
hallerite Apr 29, 2026
d5ea636
fix(server): validate /v1/generate body via FastAPI
hallerite Apr 30, 2026
c97ecfd
style: ruff isort
hallerite Apr 30, 2026
3ece39f
Merge remote-tracking branch 'origin/main' into hallerite/renderers-v2
hallerite Apr 30, 2026
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
1 change: 0 additions & 1 deletion docs/on_policy_distillation.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ Use this mode when you want to train from teacher-generated completions directly
type = "sft"

[orchestrator]
use_token_client = false

[orchestrator.teacher_rollout_model.client]
base_url = ["https://your-openai-compatible-endpoint/v1"]
Expand Down
1 change: 0 additions & 1 deletion examples/alphabet_sort/sft_distill_hard.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ save_adapter_separately = true
[orchestrator]
batch_size = 256
rollouts_per_example = 4
use_token_client = false

[orchestrator.sampling]
max_tokens = 512
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ override-dependencies = [

[tool.uv.sources]
torch = { index = "pytorch-cu128" }
verifiers = { git = "https://github.com/PrimeIntellect-ai/verifiers.git", rev = "0760204" }
verifiers = { git = "https://github.com/PrimeIntellect-ai/verifiers.git", branch = "renderers" }
torchtitan = { git = "https://github.com/pytorch/torchtitan", rev = "a1fdd7e" }
dion = { git = "https://github.com/samsja/dion.git", rev = "d891eeb" }
transformers = { git = "https://github.com/huggingface/transformers.git", rev = "c1c3424" }
Expand Down
16 changes: 15 additions & 1 deletion skills/entrypoints/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,20 @@ uv run rl @ examples/reverse_text/rl.toml @ examples/reverse_text/slurm_rl.toml
uv run rl @ examples/reverse_text/rl.toml --dry-run # generate scripts without running
```

When running from a fresh worktree, include optional extras required by the config. For example, VLM color-codeword debugging needs FlashAttention and the env packages:

```bash
uv run --extra flash-attn --extra envs rl @ configs/multimodal/rl_color_codeword_test.toml
```

Short debug runs can write large filesystem weight-broadcast snapshots. If `/` is low on space, put the debug `--output-dir` on `/dev/shm` and clean it up afterward.

For hard-distillation with `orchestrator.teacher_rollout_model`, omit `[inference]`. That mode talks directly to the external OpenAI-compatible rollout endpoint and reconstructs training tokens from messages; it does not use the local direct renderer client or `/v1/generate`.

For LoRA RL smoke runs against local vLLM, set `VLLM_ALLOW_RUNTIME_LORA_UPDATING=True` in the environment or adapter load/update requests will fail.

For ad-hoc `CliAgentEnv`/`ComposableEnv` sandbox smoke tests that call back through Prime Tunnel using Python stdlib `urllib`, set an explicit non-default `User-Agent` header such as `curl/7.81.0`. The tunnel can reject Python's default `urllib` user agent from inside Prime sandboxes with HTTP 403 / code 1010. Prefer uploading an agent script into the sandbox and running `python3 /tmp/agent.py` over passing a heredoc directly as a `start_background_job` command.

- **Config:** `RLConfig` (`src/prime_rl/configs/rl.py`)
- **Entrypoint:** `src/prime_rl/entrypoints/rl.py`
- **SLURM:** yes — single-node and multi-node
Expand Down Expand Up @@ -49,7 +63,7 @@ uv run inference --model.name Qwen/Qwen3-0.6B --model.enforce-eager
Always use the `inference` entrypoint — never `vllm serve` directly.

Custom endpoints beyond standard OpenAI API:
- `/v1/chat/completions/tokens` — accepts token IDs as prompt input
- `/v1/generate` — accepts token IDs as prompt input, with optional images
- `/update_weights` — hot-reload model weights from the trainer
- `/load_lora_adapter` — load LoRA adapters at runtime
- `/init_broadcaster` — initialize weight broadcast for distributed training
Expand Down
5 changes: 0 additions & 5 deletions src/prime_rl/configs/rl.py
Original file line number Diff line number Diff line change
Expand Up @@ -441,11 +441,6 @@ def validate_external_rollout_mode(self):
"External rollout mode does not use the local inference server."
)

if self.orchestrator.use_token_client:
raise ValueError(
"orchestrator.use_token_client must be false when orchestrator.teacher_rollout_model is configured."
)

return self

### Auto-setup and validate shared configs
Expand Down
12 changes: 12 additions & 0 deletions src/prime_rl/configs/shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,18 @@ class BaseModelConfig(BaseConfig):
),
] = False

renderer: Annotated[
str,
Field(
description=(
"Renderer to use for chat template tokenization. "
"Options: 'auto' (detect from tokenizer), 'qwen3', 'qwen3_vl', "
"'qwen3.5', 'glm5', 'glm4.5', 'minimax-m2', 'deepseek_v3', "
"'kimi_k2', 'kimi_k25', 'nemotron3', 'gpt_oss', 'default'."
),
),
] = "auto"

vlm: Annotated[
"VLMConfig | None",
Field(
Expand Down
72 changes: 32 additions & 40 deletions src/prime_rl/inference/vllm/server.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
from argparse import Namespace
from http import HTTPStatus
from typing import Any

import uvloop
from fastapi import APIRouter, Depends, Request
from fastapi.responses import JSONResponse, StreamingResponse
from fastapi import APIRouter, Request
from fastapi.responses import JSONResponse
from starlette.datastructures import State
from vllm.engine.protocol import EngineClient
from vllm.entrypoints.openai.api_server import init_app_state
from vllm.entrypoints.openai.chat_completion.protocol import ChatCompletionResponse
from vllm.entrypoints.openai.cli_args import make_arg_parser, validate_parsed_serve_args
from vllm.entrypoints.openai.engine.protocol import ErrorResponse
from vllm.entrypoints.openai.engine.serving import OpenAIServing
from vllm.entrypoints.openai.models.serving import OpenAIServingModels
from vllm.entrypoints.openai.utils import validate_json_request
from vllm.entrypoints.serve.lora.protocol import LoadLoRAAdapterRequest
from vllm.entrypoints.utils import load_aware_call, with_cancellation
from vllm.logger import init_logger
Expand Down Expand Up @@ -139,9 +136,8 @@ def resolve_tool_call_parser(model_name: str, tool_call_parser: str | None) -> s
monkey_patch_load_lora_adapter,
monkey_patch_tokenize_params_validation,
)
from prime_rl.inference.vllm.serving_chat_with_tokens import (
ChatCompletionRequestWithTokens,
OpenAIServingChatWithTokens,
from prime_rl.inference.vllm.serving_chat_with_routed_experts import (
OpenAIServingChatWithRoutedExperts,
)

# NOTE: Fix harmony stop token propagation for GPT-OSS models
Expand Down Expand Up @@ -178,8 +174,25 @@ def models(request: Request) -> OpenAIServingModels:
}


def chat_with_tokens(request: Request) -> OpenAIServingChatWithTokens | None:
return request.app.state.openai_serving_chat_with_tokens
def generate_handler(request: Request):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

do we need this function ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

it's just FastAPI-style Dependency Injection, same as chat_with_tokens()

return request.app.state.openai_serving_generate


@router.post("/v1/generate")
@with_cancellation
@load_aware_call
async def _generate(raw_request: Request):
from prime_rl.inference.vllm.serving_generate import GenerateRequest

body = await raw_request.json()
request = GenerateRequest(**body)
handler = generate_handler(raw_request)
if handler is None:
return JSONResponse({"error": "generate endpoint not available"}, status_code=503)
result = await handler.generate(request, raw_request)
if isinstance(result, dict) and "error" in result:
return JSONResponse(result, status_code=500)
return JSONResponse(content=result.model_dump())
Comment thread
cursor[bot] marked this conversation as resolved.
Resolved


@router.post("/pause")
Expand Down Expand Up @@ -234,32 +247,6 @@ async def init_broadcaster(request: Request):
return {"status": "ok"}


@router.post(
"/v1/chat/completions/tokens",
dependencies=[Depends(validate_json_request)],
responses={
HTTPStatus.OK.value: {"content": {"text/event-stream": {}}},
HTTPStatus.BAD_REQUEST.value: {"model": ErrorResponse},
HTTPStatus.NOT_FOUND.value: {"model": ErrorResponse},
HTTPStatus.INTERNAL_SERVER_ERROR.value: {"model": ErrorResponse},
},
)
@with_cancellation
@load_aware_call
async def _chat_with_tokens(request: ChatCompletionRequestWithTokens, raw_request: Request):
handler = chat_with_tokens(raw_request)
if handler is None:
return base(raw_request).create_error_response(message="The model does not support Chat Completions API")
generator = await handler.create_chat_completion_with_tokens(request, raw_request)
if isinstance(generator, ErrorResponse):
return JSONResponse(content=generator.model_dump(), status_code=generator.error.code)

elif isinstance(generator, ChatCompletionResponse):
return JSONResponse(content=generator.model_dump())

return StreamingResponse(content=generator, media_type="text/event-stream")


async def custom_init_app_state(
engine_client: EngineClient,
state: State,
Expand All @@ -269,18 +256,23 @@ async def custom_init_app_state(
"""
Modifies init_app_state:
1. Call the original init_app_state to set up standard state.
2. Replace the serving_chat with our OpenAIServingChatWithTokens wrapper.
Comment thread
cursor[bot] marked this conversation as resolved.
Resolved
2. Replace the serving_chat with our routed-experts wrapper so chat responses
can still expose routed experts when requested.
"""
await init_app_state(engine_client, state, args, supported_tasks)

if "generate" in supported_tasks and state.openai_serving_chat is not None:
original_chat = state.openai_serving_chat
serving_chat = object.__new__(OpenAIServingChatWithTokens)
serving_chat = object.__new__(OpenAIServingChatWithRoutedExperts)
serving_chat.__dict__.update(original_chat.__dict__)
state.openai_serving_chat = serving_chat
state.openai_serving_chat_with_tokens = serving_chat
else:
state.openai_serving_chat_with_tokens = None
serving_chat = None

# /v1/generate endpoint — tokens + optional images, no chat template
from prime_rl.inference.vllm.serving_generate import OpenAIServingGenerate

state.openai_serving_generate = OpenAIServingGenerate(engine_client, chat_handler=serving_chat)


import vllm.entrypoints.openai.api_server
Expand Down
73 changes: 73 additions & 0 deletions src/prime_rl/inference/vllm/serving_chat_with_routed_experts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import base64
from collections.abc import AsyncGenerator, AsyncIterator

import numpy as np
from vllm.entrypoints.openai.chat_completion.protocol import ChatCompletionRequest, ChatCompletionResponse
from vllm.entrypoints.openai.chat_completion.serving import OpenAIServingChat
from vllm.entrypoints.openai.engine.protocol import ErrorResponse, RequestResponseMetadata
from vllm.logger import init_logger
from vllm.outputs import RequestOutput
from vllm.reasoning import ReasoningParser

logger = init_logger(__name__)


class _RoutedExpertsCapture:
def __init__(self, generator: AsyncGenerator[RequestOutput, None]):
self._generator = generator
self.routed_experts: dict[int, dict] = {}

def _encode_routed_experts(self, arr: np.ndarray) -> dict:
return {
"data": base64.b85encode(arr.tobytes()).decode("ascii"),
"shape": list(arr.shape),
}

async def __aiter__(self):
async for request_output in self._generator:
for output in request_output.outputs:
if output.routed_experts is not None:
self.routed_experts[output.index] = self._encode_routed_experts(output.routed_experts)
yield request_output

def post_process(self, response: ChatCompletionResponse):
for choice in response.choices:
if choice.index in self.routed_experts:
choice.routed_experts = self.routed_experts[choice.index]


class OpenAIServingChatWithRoutedExperts(OpenAIServingChat):
"""OpenAI chat serving wrapper that preserves routed expert metadata."""

async def chat_completion_full_generator(
self,
request: ChatCompletionRequest,
result_generator: AsyncIterator[RequestOutput],
request_id: str,
model_name: str,
conversation,
tokenizer,
request_metadata: RequestResponseMetadata,
reasoning_parser: ReasoningParser | None = None,
) -> ErrorResponse | ChatCompletionResponse:
if self.model_config.enable_return_routed_experts:
capture = _RoutedExpertsCapture(result_generator)
result_generator = capture
else:
capture = None

response = await super().chat_completion_full_generator(
request,
result_generator,
request_id,
model_name,
conversation,
tokenizer,
request_metadata,
reasoning_parser,
)

if capture and isinstance(response, ChatCompletionResponse):
capture.post_process(response)

return response
Loading
Loading