Skip to content
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
034e83e
Support v1 raw multimodal offload
eligotts Jun 24, 2026
b7903f6
Update raw image renderer pin
eligotts Jun 25, 2026
6c0157a
Simplify v1 raw multimodal serving: always materialize refs
S1ro1 Jun 27, 2026
5ec8148
serving: use canonical split_raw_mm_ref + bump renderers pin
S1ro1 Jun 27, 2026
bcc93c2
Bump renderers pin (drop orphaned image_cache_max)
S1ro1 Jun 27, 2026
2c5af36
Merge remote-tracking branch 'origin/main' into feat/v1-raw-mm-offload
eligotts Jun 28, 2026
75b9e7c
Bump raw image dependency pins
eligotts Jun 28, 2026
99b590a
feat: support inline multimodal image storage
eligotts Jun 28, 2026
3518763
fix: preserve launcher image asset env
eligotts Jun 28, 2026
f37ceac
Simplify raw multimodal offload path
eligotts Jun 29, 2026
a66f1b1
Merge remote-tracking branch 'origin/main' into feat/v1-raw-mm-offload
eligotts Jun 29, 2026
80dc59f
Simplify raw multimodal validation
eligotts Jun 29, 2026
bf2bc8e
Clarify raw image asset env wiring
eligotts Jun 29, 2026
805486f
Use URI-complete raw multimodal refs
eligotts Jun 29, 2026
2f000f3
Merge branch 'main' into feat/v1-raw-mm-offload
eligotts Jun 29, 2026
dce2759
Drop raw multimodal descriptor version
eligotts Jun 29, 2026
0b0f1fb
Bump verifiers raw multimodal client prep
eligotts Jun 29, 2026
d8218c3
Merge remote-tracking branch 'origin/main' into feat/v1-raw-mm-offload
eligotts Jun 29, 2026
d18ed10
Bump renderers for processed multimodal output
eligotts Jun 30, 2026
0da60a8
Bump renderers lock cleanup
eligotts Jun 30, 2026
c7deb3b
Bump verifiers for main sync
eligotts Jul 1, 2026
89fcbc1
Merge remote-tracking branch 'origin/main' into feat/v1-raw-mm-offload
eligotts Jul 1, 2026
36b5042
fix: truncate raw multimodal refs safely
eligotts Jul 3, 2026
53d9ed8
Tighten raw multimodal validation and trainer mm plumbing
eligotts Jul 4, 2026
a13a214
Bump renderers and verifiers for raw multimodal hardening
eligotts Jul 4, 2026
7ea71a9
Merge remote-tracking branch 'origin/main' into feat/v1-raw-mm-offload
eligotts Jul 5, 2026
1f290dc
Document multimodal offload monitoring in the monitor-run skill
eligotts Jul 5, 2026
a3f5251
Bump renderers and verifiers for main reconciliation
eligotts Jul 23, 2026
2119bdb
Merge remote-tracking branch 'origin/main' into feat/v1-raw-mm-offload
eligotts Jul 23, 2026
a2fdf6a
Default SFT renderers to processed multimodal output
eligotts Jul 23, 2026
7676155
Cache materialized raw image refs in the serving layer
eligotts Jul 23, 2026
d00ddac
Bump verifiers: drop the orphaned prepare_messages hook
eligotts Jul 23, 2026
30cdce0
Merge remote-tracking branch 'origin/main' into feat/v1-raw-mm-offload
eligotts Jul 23, 2026
e0c22ee
Audit fixes: SFT mm position ids, cache cancellation, CI test adaptat…
eligotts Jul 23, 2026
6a1a54b
Pack raw-ref multimodal samples like main packs eager ones
eligotts Jul 24, 2026
23e5980
Merge remote-tracking branch 'origin/main' into feat/v1-raw-mm-offload
eligotts Jul 24, 2026
3bfaf11
Bump renderers for the unwrapped ref payload; harden the cache key
eligotts Jul 24, 2026
fd3f379
Inherit the parent env explicitly at the orchestrator launch
eligotts Jul 24, 2026
2c3f6d6
Merge remote-tracking branch 'origin/main' into feat/v1-raw-mm-offload
eligotts Jul 31, 2026
3b2dffd
Relock against the merged research-environments pin; bump verifiers
eligotts Jul 31, 2026
95afd1f
Adapt orchestrator timing metrics to the verifiers agent-span rename
eligotts Jul 31, 2026
37ff96a
Bump renderers: raw mode accepts file:// URLs only
eligotts Aug 1, 2026
743a022
refactor: derive processor fingerprints from renderer layout specs
eligotts Aug 3, 2026
ccb0cd2
Merge remote-tracking branch 'origin/main' into feat/v1-raw-mm-offload
eligotts Aug 4, 2026
4b4f9e3
refactor: trust the train-time checkpoint contract at materialize
eligotts Aug 4, 2026
52adc59
refactor: prune RawMMItem to the fields materialize reads
eligotts Aug 4, 2026
f753800
chore: bump renderers for shared mm_image helpers
eligotts Aug 4, 2026
21e0430
chore: bump renderers after dropping image cache
eligotts Aug 4, 2026
8e00291
chore: bump renderers for local-only processed image sources
eligotts Aug 4, 2026
97c08a2
Merge remote-tracking branch 'origin/main' into feat/v1-raw-mm-offload
eligotts Aug 5, 2026
0e54032
chore: bump renderers for the Pillow-only vision extra
eligotts Aug 6, 2026
98480ba
chore: bump verifiers to latest raw-image-offload head
eligotts Aug 7, 2026
8bcd811
refactor: env servers resolve their own image-asset dir
eligotts Aug 7, 2026
3888738
chore: relock for the verifiers pin; exclude nemo-gym-weather-v1
eligotts Aug 7, 2026
b92dbd5
chore: bump verifiers for the nemo-gym relock
eligotts Aug 7, 2026
ab09483
refactor(trainer): always fail-fast on missing MM images
eligotts Aug 7, 2026
bbb9dce
fix(inference): retain strong refs to mm materialize tasks
eligotts Aug 7, 2026
49fa709
docs: drop advanced.md VLM limitation churn from this PR
eligotts Aug 7, 2026
0ea481a
docs: correct VF_RENDERER_IMAGE_OFFLOAD_DIR wiring notes
eligotts Aug 7, 2026
454b438
docs: drop configuration.md multimodal offload note
eligotts Aug 7, 2026
17b1735
docs: shorten SFT multimodal_output validator messaging
eligotts Aug 7, 2026
4cde3d8
refactor: lean PROTECTED_ENV_VARS change for image offload dir
eligotts Aug 7, 2026
f92bb05
docs: drop monitor-run multimodal offload skill section
eligotts Aug 7, 2026
b57c4ac
refactor(mm): share image load/verify; trim serving_tokens helpers
eligotts Aug 7, 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
4 changes: 2 additions & 2 deletions docs/advanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ To add a new model family permanently, append an entry to `VLM_REGISTRY` in `src
### Limitations

- **Vision encoder frozen by default.** Set `freeze_vision_encoder = false` to fine-tune it; in that case it's FSDP-sharded per block. The combination `freeze_vision_encoder = false` + LoRA is rejected by a config validator — LoRA freezes everything non-adapter, so unfreezing the encoder under LoRA would be a silent no-op.
- **No multimodal-safe truncation.** Token sequences are truncated to `seq_len`, but `pixel_values` and `image_grid_thw` pass through unchanged. If a sample's tokens overflow, image tokens may get dropped while image tensors still describe the full image set. Set `seq_len` to cover your longest sample.
- **No multimodal truncation.** Raw multimodal samples that exceed `seq_len` are rejected before packing, because truncating image placeholder spans would desynchronize tokens from the image descriptors. Set `seq_len` to cover your longest sample.
- **bfloat16 mandatory.** The trainer config validator refuses any other `optimization_dtype` / `reduce_dtype` for VLMs — vLLM serves VLMs in bfloat16 and a mismatch breaks the importance ratio.
- **Higher KL mismatch with multi-image inputs.** Expect noisier `mismatch_kl` than text-only; this is from minor numerical differences between the trainer's and vLLM's image processing.
- **Images aren't logged to monitors.** Sample logging captures the prompt text but not the actual images.
Expand Down Expand Up @@ -146,4 +146,4 @@ salloc -N 1 --gres=gpu:1 bash -c 'bash scripts/install_nixl_from_source.sh'
uv pip install --reinstall --no-deps deps/nixl_cu12-*.whl
```

The script writes UCX 1.19 to `third_party/ucx/`; the bundled sbatch templates prepend it to `LD_LIBRARY_PATH` so it overrides the system version. Re-run both commands after every `uv sync`, since the lock pins the wheel.
The script writes UCX 1.19 to `third_party/ucx/`; the bundled sbatch templates prepend it to `LD_LIBRARY_PATH` so it overrides the system version. Re-run both commands after every `uv sync`, since the lock pins the wheel.
2 changes: 1 addition & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ The `rl` launcher applies these the same way in both single-node and multi-node
1. The launcher's own defaults — **your `env_vars` override these**.
2. Your top-level `[env_vars]`.
3. Your `[component.env_vars]`.
4. Orchestration-critical vars the launcher always sets last — `CUDA_VISIBLE_DEVICES` (GPU partitioning) and `WANDB_SHARED_*` (the single shared W&B run) — **these cannot be overridden** from `env_vars`.
4. Orchestration-critical vars the launcher always sets last — `CUDA_VISIBLE_DEVICES` (GPU partitioning), `WANDB_SHARED_*` (the single shared W&B run), and `VF_RENDERER_IMAGE_OFFLOAD_DIR` (raw multimodal image asset path) — **these cannot be overridden** from `env_vars`.

For standalone `sft` and `inference` configs, `[env_vars]` applies to that entrypoint's process(es). For disaggregated P/D inference, the role-specific [`deployment.{prefill,decode}_env_vars`](inference.md) layer on top of any shared inference env vars.

Expand Down
5 changes: 4 additions & 1 deletion packages/prime-rl-configs/src/prime_rl/configs/inference.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from pydantic import Field, model_validator
from pydantic_config import BaseConfig

from prime_rl.configs.shared import BaseModelConfig, EnvVars, LogConfig, SlurmConfig
from prime_rl.configs.shared import BaseModelConfig, EnvVars, LogConfig, MultimodalConfig, SlurmConfig
from prime_rl.utils.config import find_package_resource, rgetattr, rsetattr
from prime_rl.utils.parsers import resolve_reasoning_parser, resolve_tool_call_parser

Expand Down Expand Up @@ -438,6 +438,9 @@ class InferenceConfig(BaseConfig):

experimental: InferenceExperimentalConfig = InferenceExperimentalConfig()

multimodal: MultimodalConfig = MultimodalConfig()
"""Raw multimodal image offload settings shared with trainer and orchestrator."""

@model_validator(mode="after")
def validate_multi_node_requires_slurm(self):
if self.deployment.type in ("multi_node", "disaggregated") and self.slurm is None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
FileSystemTransportConfig,
HeartbeatConfig,
LogConfig,
MultimodalConfig,
PrimeMonitorConfig,
TransportConfig,
WandbWithExtrasConfig,
Expand Down Expand Up @@ -565,6 +566,9 @@ class OrchestratorConfig(BaseConfig):

experimental: OrchestratorExperimentalConfig = OrchestratorExperimentalConfig()

multimodal: MultimodalConfig = MultimodalConfig()
"""Raw multimodal image offload settings shared with trainer and inference."""

@model_validator(mode="before")
@classmethod
def _env_to_train(cls, data: Any) -> Any:
Expand Down
4 changes: 4 additions & 0 deletions packages/prime-rl-configs/src/prime_rl/configs/rl.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
)
from prime_rl.configs.shared import (
EnvVars,
MultimodalConfig,
SlurmConfig,
VLMConfig,
)
Expand Down Expand Up @@ -223,6 +224,9 @@ class RLConfig(BaseConfig):

weight_broadcast: SharedWeightBroadcastConfig | None = None

multimodal: MultimodalConfig = MultimodalConfig()
"""Shared raw multimodal image offload settings. Propagated to trainer, orchestrator, and inference."""

bench: bool = False
"""Benchmark mode. Sets trainer and orchestrator to benchmark mode and, when set, suffixes the W&B project with ``-bench``."""

Expand Down
13 changes: 12 additions & 1 deletion packages/prime-rl-configs/src/prime_rl/configs/shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@
# and the single shared W&B run. The launcher always sets these last, so allowing them in
# `env_vars` would be a silent no-op (or, on multi-node, a footgun) — reject them instead.
PROTECTED_ENV_VARS = frozenset(
{"CUDA_VISIBLE_DEVICES", "WANDB_SHARED_MODE", "WANDB_SHARED_RUN_ID", "WANDB_SHARED_LABEL"}
{
"CUDA_VISIBLE_DEVICES",
"VF_RENDERER_IMAGE_OFFLOAD_DIR",
"WANDB_SHARED_MODE",
"WANDB_SHARED_RUN_ID",
"WANDB_SHARED_LABEL",
}
)


Expand Down Expand Up @@ -82,6 +88,11 @@ def resolve_project_dir(self):
ServerType = Literal["vllm", "openai"]


class MultimodalConfig(BaseConfig):
offload_dir: Path | None = None
"""Directory for offloaded image assets. Supports environment expansion such as ``/data/outputs/run_${RUN_ID}/assets/images``. When unset, prime-rl resolves a run-scoped default."""


class VLMConfig(BaseConfig):
vision_encoder_attr: str
"""Dotted attribute path to the vision encoder module (e.g. ``model.visual``)."""
Expand Down
8 changes: 8 additions & 0 deletions packages/prime-rl-configs/src/prime_rl/configs/trainer.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
FileSystemTransportConfig,
HeartbeatConfig,
MetricsServerConfig,
MultimodalConfig,
TrainerLogConfig,
TransportConfig,
WandbConfig,
Expand All @@ -20,6 +21,7 @@

AttnImplementation: TypeAlias = Literal["eager", "sdpa", "flash_attention_2", "flash_attention_3", "fa4"]
EPCommBackend: TypeAlias = Literal["torch", "deepep"]
MissingMMImagePolicy: TypeAlias = Literal["error", "placeholder_zero_loss"]

# User-facing name -> internal name. Users set `flash_attention_4` in configs,
# which gets rewritten to `fa4` before pydantic validation.
Expand Down Expand Up @@ -566,6 +568,12 @@ class TrainerConfig(BaseConfig):
max_concurrent_runs: int = Field(1, ge=1)
"""Maximum number of concurrent runs to allow. If 1, only one run may run at a time."""

missing_mm_image_policy: MissingMMImagePolicy = "placeholder_zero_loss"
Comment thread
eligotts marked this conversation as resolved.
Outdated
"""Policy when raw multimodal image files disappear before trainer materialization. ``placeholder_zero_loss`` warns, synthesizes zero-valued image tensors with the original descriptor geometry, and masks out the affected microbatch loss; ``error`` preserves fail-fast behavior."""

multimodal: MultimodalConfig = MultimodalConfig()
"""Raw multimodal image offload settings shared with orchestrator and inference."""

enable_token_export: bool = False
"""Opt-in per-token JSONL export for rollout debugging. When enabled, writes token ids and aligned trainer metrics after each forward pass."""

Expand Down
1 change: 1 addition & 0 deletions packages/prime-rl-configs/src/prime_rl/utils/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ def propagate(shared_path: str, *targets: str) -> None:
# Top-level scalars.
propagate("max_steps", "trainer.max_steps", "orchestrator.max_steps")
propagate("seq_len", "trainer.model.seq_len", "orchestrator.seq_len")
propagate("multimodal", "trainer.multimodal", "orchestrator.multimodal", "inference.multimodal")

# [slurm] → inference: a multi-node RL run drives its inference deployment under
# the same SLURM allocation, so the nested inference inherits [slurm]. This is
Expand Down
2 changes: 2 additions & 0 deletions src/prime_rl/entrypoints/orchestrator.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@
from prime_rl.configs.orchestrator import OrchestratorConfig
from prime_rl.utils.config import cli
from prime_rl.utils.process import set_proc_title
from prime_rl.utils.run_assets import configure_run_asset_env


def main():
set_proc_title("Orchestrator")
config = cli(OrchestratorConfig)
configure_run_asset_env(config.output_dir, config.multimodal)
from prime_rl.orchestrator.orchestrator import run_orchestrator

asyncio.run(run_orchestrator(config))
Expand Down
14 changes: 11 additions & 3 deletions src/prime_rl/entrypoints/rl.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
monitor_process,
set_proc_title,
)
from prime_rl.utils.run_assets import run_asset_env

RL_TOML = "rl.toml"
RL_SBATCH = "rl.sbatch"
Expand Down Expand Up @@ -125,6 +126,7 @@ def rl_local(config: RLConfig):
"WANDB_SHARED_MODE": "1",
"WANDB_SHARED_RUN_ID": os.environ.get("WANDB_SHARED_RUN_ID", uuid.uuid4().hex),
}
shared_run_asset_env = run_asset_env(config.orchestrator.output_dir, multimodal=config.multimodal)

# Validate client port matches inference server port
if config.inference is not None and not config.orchestrator.model.client.is_elastic:
Expand Down Expand Up @@ -170,7 +172,7 @@ def sigterm_handler(signum, frame):
inference_process = Popen(
inference_cmd,
env={
**os.environ,
**shared_run_asset_env,
**DEFAULT_COMMON_ENV_VARS,
**DEFAULT_INFERENCE_ENV_VARS,
**config.env_vars,
Expand Down Expand Up @@ -225,7 +227,7 @@ def sigterm_handler(signum, frame):
stdout=log_file,
stderr=log_file,
env={
**os.environ,
**shared_run_asset_env,
**DEFAULT_COMMON_ENV_VARS,
"LOGURU_FORCE_COLORS": "1",
"WANDB_PROGRAM": "uv run rl",
Expand Down Expand Up @@ -274,7 +276,7 @@ def sigterm_handler(signum, frame):
trainer_process = Popen(
trainer_cmd,
env={
**os.environ,
**shared_run_asset_env,
**DEFAULT_COMMON_ENV_VARS,
**DEFAULT_TRAINER_ENV_VARS,
"LOGURU_FORCE_COLORS": "1",
Expand Down Expand Up @@ -372,6 +374,9 @@ def write_slurm_script(config: RLConfig, config_dir: Path, script_path: Path) ->
kv_offload_disk_path=str(offload.disk.path) if (is_mooncake and offload.disk is not None) else "",
kv_offload_device_name=offload.device_name if is_mooncake else "",
)
image_offload_dir = (
os.path.expanduser(str(config.multimodal.offload_dir)) if config.multimodal.offload_dir is not None else ""
)
Comment thread
cursor[bot] marked this conversation as resolved.

# Per-component env vars: launcher defaults (shared + multi-node-specific) with the
# user's config merged on top. Runtime wiring stays in the template.
Expand All @@ -393,6 +398,7 @@ def write_slurm_script(config: RLConfig, config_dir: Path, script_path: Path) ->
**config.slurm.template_vars,
config_path=config_dir / RL_TOML,
output_dir=config.output_dir,
image_offload_dir=image_offload_dir,
gpus_per_node=config.deployment.gpus_per_node,
)
elif config.inference is not None and config.inference.deployment.type == "disaggregated":
Expand All @@ -404,6 +410,7 @@ def write_slurm_script(config: RLConfig, config_dir: Path, script_path: Path) ->
config_dir=config_dir,
output_dir=config.output_dir,
orchestrator_output_dir=config.orchestrator.output_dir,
image_offload_dir=image_offload_dir,
num_train_nodes=config.deployment.num_train_nodes,
num_infer_nodes=infer_deploy.num_nodes * config.deployment.num_infer_replicas,
nodes_per_infer_replica=infer_deploy.num_nodes,
Expand Down Expand Up @@ -439,6 +446,7 @@ def write_slurm_script(config: RLConfig, config_dir: Path, script_path: Path) ->
config_dir=config_dir, # TODO: should prob have each subconfig path separately
output_dir=config.output_dir,
orchestrator_output_dir=config.orchestrator.output_dir,
image_offload_dir=image_offload_dir,
num_train_nodes=config.deployment.num_train_nodes,
num_infer_nodes=config.deployment.total_infer_nodes,
nodes_per_infer_replica=config.deployment.num_infer_nodes,
Expand Down
3 changes: 3 additions & 0 deletions src/prime_rl/inference/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@

from prime_rl.configs.inference import InferenceConfig
from prime_rl.utils.config import cli
from prime_rl.utils.run_assets import configure_run_asset_env


def setup_vllm_env(config: InferenceConfig):
"""Set vLLM environment variables based on config. Must be called before importing vLLM."""

configure_run_asset_env(config.output_dir, config.multimodal)

# spawn is more robust in vLLM nightlies and Qwen3-VL (fork can deadlock with multithreaded processes)
os.environ.setdefault("VLLM_WORKER_MULTIPROC_METHOD", "spawn")

Expand Down
Loading
Loading