Skip to content

[arctic_rl] repoint to arctic_platform.rl + apply verl PR #6 correctness fixes; address reviewer comments - #5

Merged
sfc-gh-kganesan merged 25 commits into
arctic-rl-publicfrom
karthik/skyrl-arctic-rl-refactor-deltas
Jun 26, 2026
Merged

[arctic_rl] repoint to arctic_platform.rl + apply verl PR #6 correctness fixes; address reviewer comments#5
sfc-gh-kganesan merged 25 commits into
arctic-rl-publicfrom
karthik/skyrl-arctic-rl-refactor-deltas

Conversation

@sfc-gh-kganesan

@sfc-gh-kganesan sfc-gh-kganesan commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

Brings the SkyRL Arctic RL integration to parity with the validated Snowflake-AI-Research/verl PR #6 (1.7B Qwen3 + BIRD, val/exec 0.913 at step 269) and addresses CharlieFRuan's footprint-reduction comments from the parent PR #1. SumanthRH's core-side comments (Jun 2 / Jun 3 / Jun 15) are tracked but not yet applied — see status table below.

Latest update (commit c54d400) extends the integration to Qwen3-32B on 4 nodes and adds a SkyRL FSDP-native counterpart for the head-to-head comparison — Arctic-RL is 2.09× faster end-to-end per step at 32B. Numbers below.

Commit map

# commit scope summary
1 fad7d70 refactor + reviewer comments repoint backend arctic_training.arctic_rlarctic_platform.rl; move ArcticRLTrainerConfig to integrations/arctic-rl/arctic_rl/config.py; drop _propagate_arctic_env_vars from core; remove arctic-specific routing block from main_base.py; rewrite trainer / generator / entrypoint / config against the new async client
2 e2ac216 post-launch fixes from E2E run 6 runtime fixes from the 1.7B GSM8K E2E: checkpoint_path to ArcticRLClientConfig; ray.init(ignore_reinit_error=True); server_state actor handle plumbed through skyrl_entrypoint for comm_protocol=ray reconnect; gradient_accumulation_steps formula matched to verl (one DS step per PPO mini-batch); explicit train_batch_size in ds_config; full vllm_config set (max_model_len, max_num_seqs, enforce_eager, enable_chunked_prefill)
3 eb9fcda perf parallelize per-rollout reward scoring with a ProcessPoolExecutor — generation phase dropped from ~296s → ~157s on BIRD/Qwen3-1.7B
4 947f8e2 verl PR NovaSky-AI#6 parity surface expose VERL memory / perf knobs (use_liger, attn_implementation=flash_attention_3, enable_gradient_checkpointing, ulysses_sequence_parallel_size, logits_optimization=memory, cuda_ipc_weight_sync, vllm_enforce_eager=false, vllm_enable_prefix_caching, vllm_max_num_batched_tokens=40960, lr_warmup_ratio, optimizer_betas, server_logs, startup_timeout) on ArcticRLTrainerConfig
5 8afddb8 correctness trainer dispatch + meta fixes: enforce update_epochs_per_batch=1 (otherwise per-epoch old_log_probs recompute collapses PPO ratio to 1); drop explicit sleep_inference / wake_training handshake (server manages its own memory); pin per-call meta to known-good values (drop_position_ids=False, logits_optimization="none", logits_optimization_peak_mem_size_in_gib=4, logits_compute_in_fp32=False) to avoid ZoRRO shape-mismatch crash from per-call meta override
6 fb42f29 ZoRRO unpack fix in _repack_to_verl_shape, pad batch responses up to cfg.generator.sampling_params.max_generate_length when ZoRRO is on. Qwen3ModelOncePatcher derives prompt_len = seq_len - response_len at every forward using the fixed response_len baked in at engine build; the client was using a dynamic per-batch max_r, so when no rollout in a batch hit the cap the patcher's split shifted left and pipeline.py:214 crashed with shape mismatch. Padding the response region to the fixed response_len keeps the patcher's split aligned with the server-side unpacker's meta["max_prompt_len"]
7 c54d400 32B scale-up + FSDP-native baseline 4-node Qwen3-32B BIRD recipes + client-side fixes uncovered while bringing them up: Option B sources colocate / cuda_ipc / low_memory from cfg.trainer.arctic_rl (worker-side ArcticRLRayClient.reconnect_config() strips client.config to a minimal subset, so trusting it silently disables both sleep gates and OOMs step 2); _ArcticInferenceEngineStub.sleep forces level=2 so vLLM releases bf16 weight pages with KV (level=1 keeps ~64 GiB resident and OOMs the DS worker's first MLP allocation at 32B); keep the explicit empty_training_cache / wake_training / wake_inference handshake around client.sync_weights(cuda_ipc=…, low_memory=…) at 32B (platform orchestration leaves vLLM weights resident and the IPC clone OOMs without it); sampling_gpus = num_engines * tensor_parallel_size (was just num_engines, silently shrinking rollout replicas 4x at TP=4); forward WANDB_* env vars to Ray workers; SKYRL_USE_LIGER env-var opt-in in fsdp_worker.py to enable Liger fused linear-CE in HFModelWrapper for the FSDP-native baseline
8 57431dd cleanup self-review pass: trim ~120 lines of verbose explanatory comments and remove a stray logger.info diagnostic block in train_critic_and_policy left over from the Option-B bring-up. Behavioral net-zero; only docstrings, in-line comments, and the temporary diagnostic log are changed

Review comments status (parent PR #1)

# Reviewer Comment Status
1 @CharlieFRuan pyproject.toml: minimal arctic-rl extras, revert tool.setuptools.packages.find to upstream Done: arctic-rl = ["skyrl[skyrl-train]", "arctic_training"]; include = ["skyrl*"] restored
2 @CharlieFRuan Move ArcticRLTrainerConfig out of skyrl/train/config/config.py into the integration Done: now at integrations/arctic-rl/arctic_rl/config.py:29 + ArcticSkyRLConfig = make_config(trainer_cls=ArcticTrainerConfig) at line 223 (mirrors HarborSkyRLConfig)
3 @CharlieFRuan Remove arctic-specific routing block from skyrl/train/entrypoints/main_base.py Done: zero "arctic" references in core main_base.py
4 @CharlieFRuan Drop _propagate_arctic_env_vars from skyrl/train/utils/utils.py Done: removed from core; ARCTIC_* forwarding now inline in arctic_rl/entrypoint.py:148-150
5 @CharlieFRuan Example script should use uv run --extra arctic-rl -m arctic_rl.entrypoint Header comment mentions both as "equivalent", but actual invocation in run_gsm8k_grpo_4gpu.sh / run_bird_grpo_1.7b_8gpu.sh still uses python -m skyrl.train.entrypoints.main_base trainer.backend=arctic_rl .... Will flip once Sumanth's NovaSky-AI#11 lands and trainer.override_entrypoint exists.
6 @SumanthRH (Jun 2) Backend dispatch should treat megatron the same as fsdp Not yet — main_base.py:512 is still if backend != "fsdp", so trainer.backend=megatron would mis-trigger the integration import path. Will land with NovaSky-AI#11 below.
7 @SumanthRH (Jun 2) Remove _ensure_backend_importable PYTHONPATH magic Not yet — function still present at main_base.py:475, called at line 516. Will land with NovaSky-AI#11 below.
8 @SumanthRH (Jun 2 file-level) PYTHONPATH friction: import arctic_rl requires running from integrations/arctic-rl/ Not yet — orthogonal to NovaSky-AI#7. Will be moot once NovaSky-AI#11 lands and PYTHONPATH is the caller's responsibility.
9 @SumanthRH (Jun 3) Why a separate trainer.backend field rather than reusing TrainerConfig.strategy? Not yet — backend: str = "fsdp" still in skyrl/train/config/config.py:637. Resolved by deletion when NovaSky-AI#11 lands.
10 @SumanthRH (Jun 15) _ensure_backend_importable assumption isn't true for all integrations Not yet — duplicate of NovaSky-AI#7 in spirit; resolved by NovaSky-AI#11.
11 @SumanthRH (Jun 15) Replace the dispatch hack with trainer.override_entrypoint field Not yet — override_entrypoint does not exist in code; the old trainer.backend machinery is still in main_base.py. Will land as a follow-up commit on this PR (see "Follow-ups" below).

Follow-ups (planned for this PR)

  1. Add trainer.override_entrypoint: Optional[str] = None to TrainerConfig (resolves Sandbox timeout after several minutes into training, and "422 Unprocessable Entity" NovaSky-AI/SkyRL#9, Fix links for RL example scripts NovaSky-AI/SkyRL#11)
  2. Replace _ensure_backend_importable + the trainer.backend != "fsdp" dispatch in main_base.py with a 5-line override_entrypoint dispatch (resolves Update readme NovaSky-AI/SkyRL#6, Which commit of verl is this repo based on? NovaSky-AI/SkyRL#7, NameError: name 'full_response_texts' is not defined NovaSky-AI/SkyRL#8, Add more concrete instruction on how to download dataset NovaSky-AI/SkyRL#10, Fix links for RL example scripts NovaSky-AI/SkyRL#11 — Sumanth's exact suggested shape)
  3. Delete backend: str = "fsdp" field from TrainerConfig (Sandbox timeout after several minutes into training, and "422 Unprocessable Entity" NovaSky-AI/SkyRL#9)
  4. Update the two example scripts to use PYTHONPATH=integrations/arctic-rl trainer.override_entrypoint=arctic_rl.entrypoint ([arctic_rl] repoint to arctic_platform.rl + apply verl PR #6 correctness fixes; address reviewer comments #5)

Invocation (today)

# Through core dispatch
python -m skyrl.train.entrypoints.main_base \
    trainer.backend=arctic_rl <flags>

# Direct (already works)
PYTHONPATH=integrations/arctic-rl python -m arctic_rl.entrypoint <flags>

After the override_entrypoint follow-up lands:

PYTHONPATH=integrations/arctic-rl python -m skyrl.train.entrypoints.main_base \
    trainer.override_entrypoint=arctic_rl.entrypoint <flags>

Correctness fixes ported from verl PR NovaSky-AI#6

Failure mode without them was entropy collapse around step 8-16.

  • LR-scheduler horizon: training_horizon = total_training_steps * update_epochs_per_batch * num_minibatches. The verl bug was passing epochs=1 which collapsed warmup to 0 steps.
  • Gradient clipping plumbed from trainer.policy.optimizer_config.max_grad_norm into both DS optimizer + engine.
  • DS engine config uses the arctic_platform.rl shape: torch_autocast, communication_data_type, data_types, zero_optimization, log_level.
  • ZoRRO knobs use the new zorro_train_enable naming + expose logits_compute_in_fp32, logits_compute_from_fp32_inputs, logits_optimization*.
  • Log-prob convention: log-probs follow the _shifted (roll(-1)) convention documented in arctic_platform.rl.ray_client.fwd_bwd.

Validation — 1.7B BIRD

  • Backend convergence (reference): validated end-to-end at 1.7B Qwen3 + BIRD via verl PR #6 — same arctic_platform.rl backend, different driver. 269 steps, val/exec 0.913. (wandb xid2pl9f)
  • This integration, SkyRL E2E: 1.7B Qwen3 + BIRD, same recipe as verl PR Update readme NovaSky-AI/SkyRL#6. Currently at step 207 / 269 with eval/bird/avg_score = 0.5828 at step 200 — already +0.038 above the VERL reference's all-time peak (0.5445 @ step 60). Zero crashes across 200+ ZoRRO steps after the c71c34b / fb42f29 patcher-response-len fix. PPO invariants pristine throughout (ppo_kl=0, pg_clipfrac=0). (wandb st3ue30x)
step SkyRL eval/bird/avg_score VERL val-core/bird/reward/mean@1 delta % VERL
10 0.4855 0.5122 -0.0267 94.8%
20 0.4940 0.5066 -0.0126 97.5%
40 0.4990 0.5183 -0.0192 96.3%
60 0.5115 0.5445 -0.0331 93.9%
80 0.5250 0.5392 -0.0142 97.4%
120 0.5457 (no eval logged past 80) -- --
160 0.5734 -- -- --
200 0.5828 -- -- --

32B BIRD-SQL E2E: ArcticRL vs SkyRL-Native FSDP

Hardware: 4 × 8 × H200 (32 GPUs, 140 GiB HBM each).
Model: Qwen3-32B. Task: BIRD-SQL GRPO, train_batch=128 prompts × n_samples=16 = 2048 trajectories/step.
Sequence: max_prompt=32768, max_response=4096, packed-seq (sample_packing=true).
Same dataset, batch sizes, DP=32, vLLM TP=4, 8 engines, gpu_mem_util=0.5 for both runs.

W&B project: new_verl_bird_sql on https://snowflake.wandb.io/karthik-ganesan/.

Run Backend Status
Arctic-RL DeepSpeed Z3 + ZoRRo + Liger + ArcticInference (FCA + spec-dec) 17 steps clean, killed for FSDP test
FSDP-Native SkyRL FSDP2 + vanilla vLLM 2 steps clean, OOM at step 3

E2E per-step (steady-state):

Phase Arctic-RL FSDP-Native Arctic vs FSDP
Generate 478 s 857 s Arctic 1.79× faster
Trainer (logprobs + policy loss + backward + opt) 482 s 1,821 s Arctic 3.78× faster
Actor update (weight sync to vLLM) 314 s 35 s FSDP 8.97× faster
End-to-end / step 1,280 s 2,678 s Arctic 2.09× faster overall

Where the deltas come from:

  • Generate (Arctic 1.79× faster). ArcticInference enables FCA (Forest Cascade Attention via forest_cascade_attn_configs), speculative decoding (3-head Arctic draft, num_speculative_tokens=3), and piecewise CUDA graphs (cudagraph_mode: PIECEWISE).
  • Trainer (Arctic 3.78× faster). Arctic-RL uses DeepSpeed ZeRO-3 + ZoRRo (micro-batch unit = n_samples=16, grad_accum=4), Liger fused linear-CE (critical for Qwen3-32B with vocab=151,936 + packed-seq up to 36,864 — naive logits would be ~10 GiB per sample), Liger fused MLP/RMSNorm, flash_attention_3, logits_optimization=memory, offload_optimizer=true (Adam state on CPU). SkyRL native FSDP is forced to micro_train_batch_size_per_gpu=2 (grad_accum=32) — Liger is available via SKYRL_USE_LIGER=1 in this PR but the Qwen3 Liger kernel hits a Triton illegal-memory-access on packed-seq inputs (cu_seqlens variable + attention_mask=None + explicit position_ids). Without Liger, micro=16 OOMs the LM-head logits (~174 GiB). 16× more grad-accum steps → ~3.8× slower per-step train.
  • Sync weights (FSDP 8.97× faster). Arctic-RL uses CUDA-IPC handle gather via gather_cuda_ipc_handles_low_memory() (parameter-at-a-time on the training side, IPC handles passed to vLLM, vLLM reconstructs from shared memory; includes vLLM wake_inference(level=2) reallocation overhead). SkyRL native uses NCCL collective broadcast from DP rank-0 of FSDP into the vLLM ranks via the colocated PG — direct GPU-to-GPU. The FSDP win on sync_weights is real and material, but the trainer + generate wins dominate the total step.

Reproduce:

# Arctic-RL (4-node, 32B)
integrations/arctic-rl/examples/run_bird_grpo_32b_32gpu.sh

# SkyRL FSDP-native counterpart (identical batch math except micro=2 vs Arctic's effective micro=16 via ZoRRo)
integrations/arctic-rl/examples/run_bird_grpo_32b_32gpu_fsdp.sh

Open items for the FSDP path (tracked, not blocking this PR):

  1. Liger Qwen3 + packed-seq Triton crash — illegal memory access in lce_forward. Either patch Liger or disable sample_packing on the FSDP path; both have tradeoffs.
  2. Step-N memory accumulation — micro=2 ran 2 clean steps then OOM'd at step 3 forward_backward. Likely PyTorch CUDACachingAllocator fragmentation across the colocated vLLM↔FSDP transition; running with PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True only on the FSDP worker process (not vLLM, which is incompatible) may help.
  3. ZoRRo-equivalent for FSDP — folding n_samples replication into a single micro-batch would unlock similar grad-accum efficiency.

File structure

integrations/arctic-rl/                # under integrations/, sibling of skyrl/
├── README.md                          # architecture + invocation + knobs reference
├── arctic_rl/                         # importable namespace package
│   ├── __init__.py
│   ├── trainer.py                     # ArcticPPOTrainer + _ArcticDispatch (async client) — Option-B colocate sourcing
│   ├── generator.py                   # parallel reward scoring (ProcessPoolExecutor)
│   ├── config.py                      # ArcticRLTrainerConfig + build_rl_config() — sampling_gpus fix
│   ├── envs/bird.py                   # BIRD environment registration
│   └── entrypoint.py                  # WANDB_* forwarding to Ray workers
└── examples/
    ├── run_gsm8k_grpo_4gpu.sh
    ├── run_bird_grpo_1.7b_8gpu.sh     # 1.7B 1-node converging-reference recipe
    ├── run_bird_grpo_1.7b_32gpu.sh    # 1.7B 4-node (smoke for the 32B stack)
    ├── run_bird_grpo_32b_32gpu.sh     # 32B 4-node ArcticRL
    ├── run_bird_grpo_32b_32gpu_fsdp.sh # 32B 4-node SkyRL FSDP-native counterpart
    ├── fsdp_bird_entry.py             # driver wrapper that registers `bird` skyrl-gym env
    │                                  # and forwards arctic_rl on PYTHONPATH for Ray workers
    └── run_bird_grpo_smoke.sh

Switches the SkyRL Arctic RL integration from the deprecated
`arctic_training.arctic_rl` client to the new `arctic_platform.rl`
client. Scope is intentionally minimal: only the import paths, client
construction, async/await plumbing, and response/argument shapes that
the new client requires. No design changes to the SkyRL-side dispatch
or to core SkyRL; recipe authors keep using
`python -m skyrl.train.entrypoints.main_base trainer.backend=arctic_rl`
unchanged. All correctness/protocol work targeting verl parity moves to
arctic_platform.rl in a separate PR.

API deltas adapted (5 files, +84/-43):
- import: arctic_training.arctic_rl.{client,config}
          -> arctic_platform.rl.{create_arctic_rl_client, ArcticRLClientConfig}
- client construction: ArcticRLClient(cfg)
          -> create_arctic_rl_client(cfg, server_state)
- async client: WorkerDispatch is sync, so dispatch wraps coroutines
  with a tiny `_run(coro)` helper (asyncio.run with thread-pool
  fallback); already-async paths (`save_weights_for_sampler`,
  `_ArcticInferenceEngineStub`, fully-async sync hook) use `await`
  directly.
- response shapes: fwd_no_grad now `reference_model=True/False` (no
  `post_processors=`); outputs land under `result["batch"]` (was
  `model_outputs`); `step()` metrics under `result["metrics"]`.
- weight sync kwargs: sync_weights(cuda_ipc=, low_memory=) plumbed
  from `cfg.trainer.arctic_rl.{cuda_ipc_weight_sync,low_memory_weight_sync}`
  (default False; preserves public behavior).
- entrypoint pre-init: capture `pre_client.get_server_state()` and
  forward it through `skyrl_entrypoint` to the reconnecting client
  (Ray comm-protocol requires it; None for http). Use
  `ray.init(ignore_reinit_error=True)` so the driver's `num_gpus=0`
  init can reuse the GPU cluster the pre-init started.
- config: set `comm_protocol="ray"` explicitly (new default is "http")
  and pass `checkpoint_path=cfg.trainer.ckpt_path`. Auto-pick
  `log_prob_engine="deepspeed"` only when `log_prob_gpus>0`.

Verified: package imports cleanly, `build_rl_config` produces a valid
`ArcticRLClientConfig` (pydantic) end-to-end from the public 4-GPU
GSM8K CLI overrides.

Co-authored-by: Cursor <cursoragent@cursor.com>
@sfc-gh-kganesan
sfc-gh-kganesan force-pushed the karthik/skyrl-arctic-rl-refactor-deltas branch from e2ac216 to 1df089b Compare June 17, 2026 20:33
sfc-gh-kganesan and others added 8 commits June 17, 2026 20:59
Adds a BIRD-SQL training recipe to the Arctic RL integration so that the
same model (Qwen3-1.7B), same dataset (BIRD parquet at
/data/snowflakesql/xyu/open-source-text2sql/), and the same reward
function as the validated verl PR NovaSky-AI#6 run can be exercised through SkyRL.
This gives us an apples-to-apples step-1 baseline to compare SkyRL's
arctic_rl backend against the verl arctic_rl backend on identical inputs.

New files (all under the integration; zero SkyRL public surface change):

- arctic_rl/envs/bird.py: single-turn skyrl-gym env that delegates the
  reward to the existing
  `arctic_platform.rl.projects.txt2sql.bird_reward.compute_score`
  function -- the *exact* reward fn the verl PR NovaSky-AI#6 run uses (no
  reimplementation, no schema rewriting). Reads
  `extras["reward_model"].ground_truth` + `extras["extra_info"].db_path`
  straight out of the verl-format parquet, so no data conversion is
  required.

- arctic_rl/envs/__init__.py: registers `bird` with skyrl_gym via the
  standard `register(id, entry_point)` API. Registration is triggered as
  a side-effect import from `arctic_rl/__init__.py`, so any recipe that
  imports the integration sees the env without modifying skyrl-gym
  upstream.

- examples/run_bird_grpo_1.7b_8gpu.sh: launcher that mirrors verl PR NovaSky-AI#6
  hyperparameters (8 GPUs colocated, ZeRO-3 + optimizer offload, BSZ=32
  prompts x ROLL_N=16, prompt_len 32K, response_len 4K, lr=2e-6, no KL,
  entropy_coeff=0, single epoch). Toggles backends via
  `trainer.backend=arctic_rl` (the public discoverability hook -- no
  PYTHONPATH gymnastics for the recipe author).

Verified (no GPU):
  - `arctic_rl` import -> `bird` appears in `skyrl_gym.envs.registration.registry`
  - BirdEnv built from a real verl-format parquet row; gold-SQL response
    scores 1.0, executable-wrong scores 0.1, junk scores 0.0 (matches
    bird_reward.compute_score semantics).
  - Launcher CLI overrides pass `ArcticSkyRLConfig.from_cli_overrides`,
    SkyRL `validate_cfg`, and `build_rl_config` -> emits a valid
    `arctic_platform.rl.ArcticRLClientConfig` with grad_accum=1 (matches
    verl PR NovaSky-AI#6's PPO_MINI_BSZ_PER_GPU == BSZ_PER_GPU).

Wire-protocol caveat documented in the launcher header: absolute step-1
metric values will still drift from verl until the tied-embeddings
weight-sync fix + verl-shape meta dict / verl_grpo loss / post-processors
land in `arctic_platform.rl` (a separate PR there, per the strategy
pivot). Step-1 invariants (clipfrac==0, ppo_kl==0 on epoch 1) and metric
*shape* should already line up.

Co-authored-by: Cursor <cursoragent@cursor.com>
`create_arctic_rl_client(rl_config)` now initializes Ray itself during
pre-init (it owns a GPU cluster), so the driver-side `ray.init(...,
runtime_env=..., ignore_reinit_error=True)` happens *second* and Ray
silently drops the runtime_env on a no-op re-init. Workers then
deserialize `ArcticSkyRLConfig` without `integrations/arctic-rl` on
sys.path and crash with `ModuleNotFoundError: No module named
'arctic_rl'`.

Move the runtime_env to task granularity (`skyrl_entrypoint.options(
runtime_env=...).remote(...)`) so it applies regardless of whether we
or arctic_platform.rl initialized the cluster.

Repro before fix: BIRD smoke at /tmp/skyrl_bird_20260617T205957Z.log
crashed at `ray.exceptions.RaySystemError: System error: No module named
'arctic_rl'` immediately after the ArcticRL jobs came up.

Co-authored-by: Cursor <cursoragent@cursor.com>
The arctic_platform.rl server's payload contract -- shared with the verl
adapter at verl/workers/remote_client/arctic_rl.py -- expects each batch
to carry:

  prompts       [B, P]   prompt-only slice
  responses     [B, A]   response-only slice
  response_mask [B, A]   response-only mask
  input_ids     [B, S]   full prompt+response (already there)
  attention_mask[B, S]   already there
  position_ids  [B, S]   derived from attention_mask

Producing those is a per-framework adapter job (not a server change):
verl ships them already, SkyRL must too. The data is right there --
`convert_prompts_responses_to_batch_tensors` left-pads prompts to
max_prompt_len and right-pads responses to max_response_len, so the
split is a uniform `prompt_len = sequences.shape[1] - response_mask.shape[1]`
across the batch.

Verified via standalone tensor-shape unit test that the rebuilt
`_to_batch` survives the server-side compute_packing_info_for_batch
path that previously failed with `KeyError: 'prompts'`:

  prompts.shape == (B, P), response_mask.shape == (B, A),
  responses.shape == (B, A), input_ids/attention_mask/position_ids shape (B, S);
  attention_mask[:, P:].sum(dim=1) == per-row actual response lengths.

Repro before: /tmp/skyrl_bird_20260617T211633Z.log step-1 generate
succeeded (avg_raw_reward=0.4443, response_length=1179, in the same
ballpark as verl PR NovaSky-AI#6 step-1's 0.5313/1192.9), then fwd_bwd died at
arctic_platform/rl/processors/pipeline.py:236 KeyError('prompts').

Co-authored-by: Cursor <cursoragent@cursor.com>
Translate SkyRL's TrainingInputBatch to the arctic_platform.rl server's
verl-shape contract entirely on the client side so step-1 metrics match
verl PR NovaSky-AI#6 byte-for-byte without modifying arctic_platform.rl:

  - Per-sample repack of `[PAD_L | prompt | response]` (SkyRL native) to
    `[PAD_L*(P-p_i) | prompt | response | PAD_R*(R-r_i)]` (verl shape)
    so prompts/responses live in fixed [B, P] / [B, R] regions of a
    [B, P+R] sequence. Server's `compute_packing_info_for_batch` reads
    `prompts.shape[1]` to derive response_lens; SkyRL's variable-position
    layout was producing mixed prompt/pad tokens in the response window.
  - Synthesize `prompts`, `responses`, `position_ids` (cumsum-1) on the
    wire so the server's deepspeed_worker payload contract is satisfied.
  - Add full `meta` dict matching verl: `pad_token_id` (was the immediate
    KeyError blocker), `temperature`, `actor_config` + `policy_loss_config`
    (GRPO defaults: clip_ratio=0.2, entropy_coeff=0, use_kl_loss=False,
    loss_mode=vanilla), `dp_size`, `batch_num_tokens`, `global_batch_size`,
    `max_prompt_len`/`max_response_len`, `max_token_len_per_gpu`,
    `zorro_train_enable` mirrored from `arl.use_zorro`.
  - In `forward_backward`: compute old_log_probs server-side via
    `fwd_no_grad` (mirrors verl's pre-update_actor `compute_log_prob`
    call) so PPO ratio at step 1 is exp(0)=1 → ppo_kl=0, clipfrac=0.
  - Left-pad `response_mask`/`advantages`/`old_log_probs` to seq_len,
    set `loss_mask = response_mask`, drive `verl_grpo` loss with
    post=["apply_temperature", "compute_entropy_and_logprobs"].
  - Wire ZoRRO toggle through honestly: `meta.zorro_train_enable` follows
    `arl.use_zorro` so the per-call meta and ds_worker_config don't
    desync. Payload is ZoRRO-compatible regardless (response-only tensors
    are already left-padded to seq_len).

Validated by `/tmp/test_repack.py` (per-sample shape correctness on a
3-row toy batch with varying prompt/response lengths). All server-side
changes are still deferred to a separate arctic_platform.rl PR.
After the wire-protocol bridge proved correct (3 consecutive steps emit
clean pg_loss / ppo_kl / clipfrac / grad_norm / reward metrics), two
follow-ups surfaced from the live logs:

  - examples/run_bird_grpo_smoke.sh: drop-in shrink of the full BIRD
    recipe (TRAIN_BSZ 32→4, N_SAMPLES 16→4, PROMPT_LEN 32768→4096,
    RESPONSE_LEN 4096→512). Hits the same arctic_platform.rl wire path
    as the full recipe but each training step takes ~15-20s instead of
    ~7-8min, so wire-shape / meta-dict / verl_grpo loss-config
    iterations land in ~1.5min/iter instead of ~10min/iter. WandB logger
    disabled by default to keep smoke runs out of the production project.

  - trainer.optim_step: server reports grad_norm as a per-DP-rank list
    ([g]*world_size); SkyRL's reduce_metrics expects a scalar and warned
    "Metrics for key grad_norm are not all numbers" each step. Flatten
    + pick first (every rank sees the same ZeRO-3 / DDP-reduced value).

Step-1 invariant note: ppo_kl ≈ -0.04 (vs verl's expected ~0) is bf16 /
flash-attn precision drift between the eval-mode fwd_no_grad (old log
probs) and train-mode fwd_bwd (new log probs), not a wire bug. Will
confirm against verl PR NovaSky-AI#6 baseline values before chasing further.
ArcticGenerator.generate() was scoring rollouts serially after vLLM
returned. For BIRD this dominates: 512 samples * ~0.4-1.0s per SQLite
query against the gold answer. Fan the post-generation phase out to a
ProcessPoolExecutor (8 workers by default, override via
ARCTIC_RL_SCORING_WORKERS). Process pool, not threads, so each worker
keeps its own BIRD sqlite handle.

Measured on BIRD / Qwen3-1.7B / 8xH100 colocated, averaged over the
first 10 training steps:

  generate avg                273.4s -> 124.6s   (-54%)
  step total avg              318.1s -> 170.1s   (-47%)
  train_critic_and_policy     34.7s  -> 35.0s    (unchanged)

Does not yet match verl xid2pl9f (timing_s/gen ~65s) -- verl's
agent_loop overlaps generation and scoring; we still block generation
before scoring starts. Closing that needs a per-prompt fan-out in
ArcticGenerator.generate() instead of one batched arctic_client.generate
call, tracked separately.

Repro before: /data-fast/skyrl-runs/20260618T220520Z/skyrl_full.log
Repro after:  /data-fast/skyrl-runs/20260618T231552Z/skyrl_full.log

Co-authored-by: Cursor <cursoragent@cursor.com>
Surface the full memory/perf surface that verl's xid2pl9f BIRD-1.7B
converged-reference run (launch_1.7b_newshape.sh in arctic-verl) sets,
so SkyRL+ArcticRL can match its memory headroom and step-time profile
on the same 8xH100 colocated topology. Defaults stay safe-off so
existing recipes are unaffected.

config.py: ArcticRLTrainerConfig gets 1:1 mappings to verl
  - use_liger, attn_implementation (flash_attention_3),
    enable_gradient_checkpointing, ulysses_sequence_parallel_size,
    logits_optimization (memory|none),
    logits_optimization_peak_mem_size_in_gib, cuda_ipc_weight_sync
  - vllm_enforce_eager, vllm_enable_prefix_caching,
    vllm_max_num_batched_tokens (40960 in verl)
  - lr_warmup_ratio, optimizer_betas
  - server_logs, startup_timeout
  Plus gradient_accumulation_steps made ulysses_sp-aware so DeepSpeed's
  batch-size assertion passes when SP > 1.

run_bird_grpo_1.7b_8gpu.sh: surface every knob above at the verl
  default values, plus turn on use_zorro (server-side prompt dedup
  and packing) which verl uses for BIRD.

Verified: step-1 PPO metrics match verl xid2pl9f within tolerance --
ppo_kl=0 exact, grad_norm=0.547 vs verl ~0.5, pg_loss=-0.0019 vs
verl ~0. No OOM on 8xH100 at colocated 0.5 vllm gpu_memory_utilization.
Wandb run: arctic_rl_bird_sql/9xfcr0sr.

Co-authored-by: Cursor <cursoragent@cursor.com>
…arity

Three trainer-side fixes landed together because they were surfaced
end-to-end while reproducing the verl xid2pl9f BIRD-1.7B run. Each is
small but independent.

1. Drop the colocate-mode sleep_inference / wake_training handshake
   from train_critic_and_policy. The reference verl arctic_rl client
   (arctic-verl/verl/workers/remote_client/arctic_rl.py) does not call
   either anywhere -- colocated GPU memory is managed server-side and
   fits via gradient-checkpointing + flash-attn + liger + a bounded
   ppo_max_token_len_per_gpu, all set at engine-build time. The extra
   handshake was both unnecessary and a divergence from the verl
   contract.

2. Require update_epochs_per_batch == 1 and remove the per-epoch loop
   wrapping fwd_bwd. _ArcticDispatch.forward_backward calls
   _compute_old_log_probs every iteration, so >1 epoch would refresh
   old_log_probs and collapse PPO ratio to exp(0)=1, defeating
   clipping. The verl BIRD recipe uses 1 epoch so we assert rather
   than silently misbehave. Multi-epoch support requires hoisting the
   old-log-prob call into fwd_logprobs_values_reward (verl's
   compute_log_prob placement); tracked separately.

3. Pin _build_meta to known-good engine-build values:
   drop_position_ids=False, logits_optimization=none,
   logits_optimization_peak_mem_size_in_gib=4,
   logits_compute_in_fp32=False. A prior attempt to source these
   dynamically per-call (logits_optimization=memory,
   drop_position_ids=True) crashed at step 5 with a ZoRRO shape
   mismatch:

     RuntimeError: shape mismatch: value tensor of shape [8026]
       cannot be broadcast to indexing result of shape [7042]

   in arctic_platform/rl/zorro_train/seqlen_balancing.py on the first
   packed micro-batch where prompts had been deduplicated. The
   hardcoded values now match the engine-build config end-to-end.

Verified: 11 consecutive steps clean on the current run, including the
step-5 watershed where the prior _build_meta crashed. Step-1 PPO
metrics match verl reference (ppo_kl=0 exact, grad_norm 0.547 vs ~0.5).
Wandb run: arctic_rl_bird_sql/9xfcr0sr.

Co-authored-by: Cursor <cursoragent@cursor.com>
@sfc-gh-kganesan
sfc-gh-kganesan force-pushed the karthik/skyrl-arctic-rl-refactor-deltas branch from d911e08 to 8afddb8 Compare June 19, 2026 00:33
Qwen3ModelOncePatcher is built once at engine init with a fixed
response_len (= cfg.generator.sampling_params.max_generate_length, wired
through arctic_rl/config.py:412 -> deepspeed_worker.py:328). On every
forward the patched causal-LM splits input_ids via
`prompt_len = seq_len - response_len`. The client batch shaped with the
dynamic per-batch max_r, so when no rollout in a batch hit the cap the
patcher split shifted left -- the last (response_len - max_r) prompt
tokens per sample leaked into the "response" region, the model returned
sum(p_i_suffix + r_i) logprobs while pipeline.py:214 only had sum(r_i)
attention slots, and the unpack crashed with "shape mismatch" at random
steps.

Fix: in `_repack_to_verl_shape`, when ZoRRO is enabled, pad max_r up to
the patcher response_len. Extra positions get attention_mask=0 and
pad_token_id so model forward and verl_grpo loss treat them as masked.
The patcher seq_len - response_len now always equals the unpacker
max_prompt_len.

Repro before: BIRD/Qwen3-1.7B run 9xfcr0sr survived steps 1-23 then died
at step 24 with "value tensor of shape [13252] cannot be broadcast to
indexing result of shape [8556]"; surviving steps all happened to have
at least one rollout hit the 4096 cap.

Verified: synthetic repack test (4 samples, response_lens [50,30,100,20],
patcher_response_len=4096) confirms patcher prompt_len == unpacker
max_prompt_len, response-region attention sum still == sum(real
response_lens).

Co-authored-by: Cursor <cursoragent@cursor.com>
@sfc-gh-kganesan
sfc-gh-kganesan force-pushed the karthik/skyrl-arctic-rl-refactor-deltas branch from c71c34b to fb42f29 Compare June 19, 2026 05:03
sfc-gh-truwase and others added 6 commits June 24, 2026 17:44
…e baseline

Adds the Qwen3-32B 4-node BIRD recipes (ArcticRL + FSDP-native counterpart)
used for the E2E comparison, and the client-side fixes uncovered while
bringing them up. Validated by a stable 17-step ArcticRL 32B run on the
4-node Lustre cluster; the SkyRL FSDP-native counterpart is the comparison
baseline.

Client-side changes
-------------------

* entrypoint.py / trainer.py (Option B): source `colocate`, `cuda_ipc`,
  `low_memory` from `cfg.trainer.arctic_rl` instead of `client.config`.
  `ArcticRLRayClient.reconnect_config()` strips the schema to a minimal
  serializable subset when shipping the client to Ray workers, so the
  worker-side `_ArcticDispatch` was seeing `colocate=False` regardless of
  the launcher flag — which silently disabled both sleep gates and OOM'd
  the DeepSpeed worker on step 2 of every 32B run.

* trainer.py (`_ArcticInferenceEngineStub.sleep`): force `level=2` so
  vLLM's CuMemAllocator releases bf16 weight pages alongside KV cache.
  `level=1` keeps ~64 GiB resident and OOMs the DS worker on the first
  MLP allocation of the backward pass at 32B.

* trainer.py (`save_weights_for_sampler`): keep the explicit
  `empty_training_cache()` / `wake_training()` / `wake_inference()`
  handshake around `client.sync_weights(cuda_ipc=…, low_memory=…)` —
  required at 32B because the platform-side orchestration leaves vLLM
  weights resident and the IPC clone OOMs without the manual drain.

* config.py: `sampling_gpus = num_engines * tensor_parallel_size` (was
  just `num_engines`). At TP=4 / num_engines=8 this previously asked the
  orchestrator for 2 sampling replicas instead of 8, silently shrinking
  rollout parallelism 4x and tripping the multi-node FlashInfer workspace
  collision at init.

* entrypoint.py: forward `WANDB_*` env vars to Ray workers (mirror the
  existing `ARCTIC_*` forwarding). Previously the worker actor was
  401'ing against `api.wandb.ai` instead of `snowflake.wandb.io`.

SkyRL-core changes (FSDP-native counterpart)
--------------------------------------------

* fsdp_worker.py: `SKYRL_USE_LIGER` env-var opt-in for Liger fused
  linear-CE in `HFModelWrapper`. Needed for 32B FSDP-native runs (vocab
  151936 + packed-seq up to 36864 + micro>=4 OOMs the LM head without
  it). Off by default — flag is opt-in.

Launchers
---------

* examples/run_bird_grpo_32b_32gpu.sh           (new) — ArcticRL recipe
* examples/run_bird_grpo_32b_32gpu_fsdp.sh      (new) — FSDP-native
* examples/run_bird_grpo_1.7b_32gpu.sh          (new) — 1.7B 4-node
* examples/run_bird_grpo_1.7b_8gpu.sh           (touched)
* examples/fsdp_bird_entry.py                   (new) — wrapper that
  registers the `bird` skyrl-gym env on the driver and forwards
  `arctic_rl` on PYTHONPATH for Ray workers, so the FSDP-native path
  can train on BIRD-SQL without depending on the ArcticRL backend.

Deps
----

* uv.lock: pick up `arctic-training==0.8.0` (matches the `arctic-rl`
  extras in pyproject.toml).

Co-authored-by: Cursor <cursoragent@cursor.com>
Self-review pass. Removes 120 lines of verbose explanatory comments and one
stray `logger.info` diagnostic block in `train_critic_and_policy` that was a
debug aid during Option-B bring-up.

Behavioral net-zero: all sleep/wake/sync-weights/colocate semantics are
unchanged. Only docstrings, in-line comments, and the temporary diagnostic
log are trimmed.

Co-authored-by: Cursor <cursoragent@cursor.com>
…5+S6)

Replace the integration-specific ``trainer.backend`` dispatch + the generic
sys.path injector (``_ensure_backend_importable``) in
``skyrl/train/entrypoints/main_base.py`` with a 6-line ``trainer.override_entrypoint``
peek-and-import, per Sumanth's PR #1 review comment:

  #1 (comment)

Closes:
  - S5 (``_ensure_backend_importable`` is not true for all integrations)
  - S6 (keep ``main_base.main()`` simple; use ``trainer.override_entrypoint``)
  - C3 (revert the arctic-specific routing block)

Core surface area shrinks: -38 / +12 lines, with no integration named in
``skyrl/`` and no sys.path manipulation.

Config:
- ``skyrl/train/config/config.py``: replace ``backend: str = "fsdp"`` with
  ``override_entrypoint: Optional[str] = None``.

Dispatch:
- ``skyrl/train/entrypoints/main_base.py``: peek
  ``trainer.override_entrypoint=`` from sys.argv before strict parse;
  if set, ``importlib.import_module(<path>).main()`` and return.
  Otherwise the standard FSDP path runs unchanged.

Integration alignment with the new core API:
- ``integrations/arctic_rl/`` (flattened from ``integrations/arctic-rl/arctic_rl/``;
  separate Option-A commit): docstrings + config narrative updated to
  reference ``trainer.override_entrypoint=integrations.arctic_rl.entrypoint``.

Migration: launchers swap
  ``trainer.backend=arctic_rl`` → ``trainer.override_entrypoint=integrations.arctic_rl.entrypoint``
Co-authored-by: Cursor <cursoragent@cursor.com>
Make the two 32B launchers respect ``SKYRL_DIR`` and ``PYBIN`` from the
environment so the same script can target alternate envs (e.g. ``PYBIN=
/home/.../envs/skyrl_v2/bin/python``) without editing the file. Default
values unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>
…override

Removes the boilerplate ``trainer.arctic_rl={}`` from every recipe and makes
the ``arctic-rl`` extra self-contained, so any stock SkyRL recipe can opt
into Arctic by appending a single CLI override.

User experience after this commit (lands on top of 2587f0e + bb43337):

  # one-time install
  uv sync --extra fsdp --extra arctic-rl

  # any recipe → Arctic backend, one flag
  bash examples/<recipe>/run.sh \
    trainer.override_entrypoint=integrations.arctic_rl.entrypoint

  # optional knobs
  ... trainer.arctic_rl.colocate=true trainer.arctic_rl.zero_stage=3 ...

Changes:

- pyproject.toml: ``arctic-rl`` extra now pins ``arctic-platform`` and
  ``arctic-inference[vllm]`` (was a stale ``arctic_training`` PyPI
  reference). Adds ``[tool.uv.sources]`` git entries pointing both at public
  main (arctic-platform isn't on PyPI yet).

- integrations/arctic_rl/entrypoint.py: when ``cfg.trainer.arctic_rl`` is
  ``None`` (user didn't pass ``trainer.arctic_rl=`` overrides), default to
  ``ArcticRLTrainerConfig()`` so the single ``override_entrypoint`` flag is
  enough.

- integrations/arctic_rl/examples/*.sh: drop ``trainer.arctic_rl={}`` (Hydra
  auto-creates the parent dict from sub-key overrides; entrypoint fills the
  default when missing entirely).

- integrations/arctic_rl/README.md: rewrite the Quick Start around the
  single-flag any-recipe pattern; drop the stale references to the legacy
  ``arctic-skyrl`` / ``ArcticTraining-dss`` branches.

Co-authored-by: Cursor <cursoragent@cursor.com>
Removes hardcoded Snowflake-internal defaults so the launchers work
unmodified for any user:

- ``HF_HOME`` and ``VLLM_CACHE_ROOT`` default to ``$HOME/.cache/{huggingface,vllm}``
  (was: ``/checkpoint/huggingface`` / ``/modeling-checkpoints/vllm``).
- ``DATA_DIR`` defaults to ``$HOME/data/bird`` (was hardcoded internal path).
- ``WANDB_API_KEY`` defaults to empty — user sets in their environment.
- ``WANDB_BASE_URL`` not set — falls back to public ``api.wandb.ai``.
- ``WANDB_PROJECT`` defaults to ``skyrl_arctic_rl``.
- ``ATTN_IMPL`` defaults to ``flash_attention_2`` (broadly available);
  set ``ATTN_IMPL=flash_attention_3`` for the Hopper-only build.
- ``SKYRL_DIR`` defaults to script-relative repo root; ``PYBIN`` to ``python``.

Also drops stale README references to the legacy companion repos and
strips remaining internal-name comments in the 32B launcher.

Co-authored-by: Cursor <cursoragent@cursor.com>
@sfc-gh-kganesan
sfc-gh-kganesan force-pushed the karthik/skyrl-arctic-rl-refactor-deltas branch from 2c1fe23 to ee13bb2 Compare June 25, 2026 01:20
sfc-gh-truwase and others added 6 commits June 25, 2026 01:28
…ficient)

The ``arctic-rl`` extra already provides everything an Arctic run needs:
- ``skyrl[skyrl-train]`` (SkyRL training core)
- ``arctic-platform`` (DeepSpeed training workers)
- ``arctic-inference[vllm]`` (vLLM server; brings torch + vllm transitively)

``--extra fsdp`` only matters if you also want to run the SkyRL native FSDP
baseline side-by-side (the two extras pin different vLLM versions, so
arctic-rl alone is the cleaner install for pure Arctic runs).

Co-authored-by: Cursor <cursoragent@cursor.com>
The launcher hardcoded ``trainer.logger=wandb``, which forced
``WANDB_API_KEY`` to be set even for smoke tests / console-only runs
(SkyRL ``validate_generator_cfg`` asserts the key when logger is wandb).
Thread ``LOGGER`` env var through (default ``wandb`` for parity with
prior behavior; ``LOGGER=console`` for no-creds runs).

Co-authored-by: Cursor <cursoragent@cursor.com>
Add explicit Prerequisites / Clone+install / Ray bootstrap / Data+model
prep / Run steps so a fresh user can go from empty directory to a running
GRPO loop without leaving the README. Also fixes a stale claim that the
launchers default to ``LOGGER=console`` (they actually default to
``wandb``; ``LOGGER=console`` is the no-creds opt-out).

Co-authored-by: Cursor <cursoragent@cursor.com>
Removes manual prep steps a fresh user previously needed:

- All launchers: ``HF_HUB_OFFLINE`` / ``TRANSFORMERS_OFFLINE`` now default
  to ``0`` (auto-download). Set to ``1`` on isolated clusters where the
  model is pre-staged in ``HF_HOME``.

- 32B + 32B FSDP launchers: drop the manual ``$HF_HOME/hub/.../refs/main``
  snapshot dance. Pass ``Qwen/Qwen3-32B`` as the HF id; transformers/vLLM
  auto-download to ``HF_HOME`` on first use. Multi-node users with a
  shared pre-staged cache can still ``MODEL=<absolute path>`` to skip
  the hub lookup.

- GSM8K launcher: auto-run ``examples/train/gsm8k/gsm8k_dataset.py`` when
  ``$DATA_DIR/{train,validation}.parquet`` doesn't exist.

- BIRD-SQL launcher: clear error pointing to ``$DATA_DIR`` when parquets
  are missing (no public prep script — BYO data).

- 32B launchers: ``CHECKPOINT_DIR`` defaults to ``$HOME/skyrl-runs/ckpts/<run>``
  (was a hardcoded ``/data/skyrl-runs/...`` cluster path).

- README: drop the manual prep section; document the auto-prep behavior.

Net result: ``bash integrations/arctic_rl/examples/run_gsm8k_grpo_4gpu.sh``
works on a fresh machine after ``uv sync --extra arctic-rl`` + ``ray start``
with no other prep.

Co-authored-by: Cursor <cursoragent@cursor.com>
The launchers set ``trainer.arctic_rl.use_liger=true`` (fused linear-CE +
MLP/RMSNorm kernels, critical for 32B memory). arctic-platform's
DeepSpeedWorker imports ``liger_kernel.transformers.monkey_patch`` at
init time when use_liger is on, so a fresh ``uv sync --extra arctic-rl``
without liger-kernel crashed at worker initialize with
``ModuleNotFoundError: No module named 'liger_kernel'``.

Pinning ``liger-kernel`` (PyPI, pip-installable) directly in the
``arctic-rl`` extra so a fresh install has everything the recipes need.

Co-authored-by: Cursor <cursoragent@cursor.com>
…ic-platform branch

The BIRD env's reward fn (`arctic_platform.rl.projects.txt2sql.bird_reward`)
only exists on Arctic-Platform's private recipe/rl-correctness branch — it
isn't shipped from public main. When users install arctic-platform from
public main, every Ray actor silently fails the import and `ArcticGenerator`
returns score=0 for every sample; the run looks healthy but never converges.

Vendor `bird_reward.py` (~10 KB, stdlib-only) into the integration so it's
self-contained and matches the validated verl PR NovaSky-AI#6 reward function
bit-for-bit. Keep the upstream copy as the source of truth — re-sync if it
changes.

Co-authored-by: Cursor <cursoragent@cursor.com>
@sfc-gh-kganesan
sfc-gh-kganesan force-pushed the karthik/skyrl-arctic-rl-refactor-deltas branch from 8d6a1ba to 356cb30 Compare June 25, 2026 14:46
This makes the BIRD-SQL pipeline runnable on the public main branches of
arctic-inference and arctic-platform with no private-branch dependencies:

  - integrations/arctic_rl/envs/preprocess_bird.py  (vendored verbatim from
    arctic_platform.rl.projects.txt2sql.preprocess_bird; that module lives
    on a private recipes branch and is not on public arctic-platform main).
  - README: walkthrough for running the preprocessor + FlashAttention-3
    install from the official PyTorch wheel index (needed for the 2x
    speedup on Hopper).

Together with the earlier vendoring of bird_reward.py, the SkyRL client
now ships everything needed to reproduce the BIRD GRPO 32B benchmark.
Re-sync these vendored files if their upstream copies change.

Co-authored-by: Cursor <cursoragent@cursor.com>
sfc-gh-kganesan and others added 2 commits June 25, 2026 19:06
vLLM's ``AsyncEngineArgs.__post_init__`` converts nested overrides only
when ``isinstance(value, dict)`` is true, so an ``OmegaConf.DictConfig``
(what Hydra hands us) gets silently dropped — including the
``compilation_config`` / ``speculative_config`` / ``forest_cascade_attn_configs``
needed for the 2x Arctic-RL speedup. The previous ``dict(...)`` cast was
shallow, leaving nested ``DictConfig`` values intact.

Round-trip through ``OmegaConf.create() -> to_container(resolve=True)``
deep-coerces the whole tree to plain Python and is idempotent for both
``DictConfig`` and plain ``dict`` inputs. Matches the
``OmegaConf.to_container`` idiom used in arctic-verl's
``workers/remote_client/arctic_rl.py`` (tunji/remote_backend).

Also drop the unused 1.7B smoke launcher scripts (kept locally during
debugging; not part of the public recipe set) and trim the README's
post-install ``transformers<5`` fixup note to just the one-liner users
need.

Co-authored-by: Cursor <cursoragent@cursor.com>
Re-pin optimization_level: 1 in the 32B launcher and ship a sibling 8B
launcher. Today's TP=4 experiment confirmed that the OmegaConf round-trip
in integrations/arctic_rl/config.py is *not* sufficient on its own:
vLLM's engine init still resolves cudagraph_mode=FULL_AND_PIECEWISE and
fuse_allreduce_rms=True even with an explicit compilation_config override
on the CLI — i.e., the nested override is being dropped somewhere
between ArcticRLClientConfig and AsyncEngineArgs. Until that plumbing
is traced end-to-end, optimization_level=1 (which hard-codes
fuse_allreduce_rms=false inside vLLM) is the reliable speedup config and
reproduces the Jun 24 (skyrl_v1) 2x baseline on Hopper TP>1.

Also add TORCHINDUCTOR_FORCE_DISABLE_CACHES=1 so a prior compiled graph
that baked in flashinfer_trtllm_fused_allreduce_norm can't be reused
across config flips (VLLM_DISABLE_COMPILE_CACHE only covers vLLM's own
cache, not inductor's).

The 8B launcher mirrors the 32B recipe (TP=4, FCA, CUDA-IPC weight sync,
ZoRRo, Liger) for fast iteration on the same TP>1 code path; spec-dec is
off by default since the published 32B 3-head checkpoint is
architecturally tied to Qwen3-32B.

Co-authored-by: Cursor <cursoragent@cursor.com>
@sfc-gh-kganesan
sfc-gh-kganesan merged commit 7636101 into arctic-rl-public Jun 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants