[arctic_rl] backport rl-correctness payload-encoding deltas onto arctic_rl_share_v0.7.1 - #6
Conversation
E2E smoke result (deferred from PR description) — PASSStopped the 1.7B convergence run on PR verl-project#37 branch and ran a 4-step Qwen3-0.6B BIRD GRPO smoke against this PR (commit Setup: 1 node × 8 H200, ZoRRo train enabled, Issue caught & fixed by the smoke (committed as
Training (4 steps):
Final validation @ step 4 (BIRD full val set):
Numerically matches PR verl-project#37 reference smoke (PR verl-project#37: score 0.293 → 0.308, val exec 0.510; this PR: 0.290 → 0.306, val exec 0.522) — within noise, exec_success marginally better. Clean shutdown, GPUs released, no other errors. Inner library confirmed live as |
Self-review of PR #6 surfaced three real issues; all addressed here. Scope remains vendor-only (verl/workers/remote_client/arctic_rl.py + verl/trainer/config/remote_backend/arctic.yaml); core abstraction under verl/remote_backend/ is still diff-clean vs public/arctic_rl_share_v0.7.1. 1. (important) Thread logits_optimization / logits_optimization_peak _mem_size_in_gib / logits_compute_in_fp32 into _create_ds_worker _config so they actually take effect. The server's authoritative read path for these three knobs is arctic_platform/rl/deepspeed_worker.py lines 319-322, which reads them from `ds_worker_config` (server-side config dict), NOT from per-call request meta. Before this commit they were only forwarded in compute_log_prob.meta / update_actor.meta, so the server silently fell back to its hard-coded defaults ("none", 4, False). The shipped yaml values happen to match those defaults, so this bug was masked at the validated configs. But the moment someone sets logits_optimization=memory in the yaml to mitigate an OOM (see the 1.7B attempt #1 deadlock that needed gpu_memory_utilization =0.5 to fix), the yaml change would silently no-op without this fix. Added them to the `if self.zorro_train_enable` block to match the lifecycle of the existing zorro-only ds_worker_config fields (response_len, max_token_len, rollout_n, temperature). The per-call meta forwarding is intentionally kept so a future arctic_platform.rl release that adds a per-batch override path will just work without an adapter change. 2. (important) Document zorro_train_load_balancer as forward-compat. grep -r zorro_train_load_balancer in arctic_platform/rl/ returns zero hits — no server consumer for this key on the currently installed version. We continue to forward it (matches PR verl-project#37 wire format, so the server-side handler can land independently in a later ArcticInference-internal release — likely tied to PR verl-project#92's strict group-balanced routing work — without an adapter change). Added a TODO comment so the next reader knows it's intentionally a no-op today, not a wiring bug. 3. (nit) Document the deliberately-not-exposed sibling key logits_compute_from_fp32_inputs. The server has both `logits_compute_in_fp32` (output-side upcast, exposed) and `logits_compute_from_fp32_inputs` (input-side upcast, not exposed) at deepspeed_worker.py:321-322. The PR verl-project#37 adapter also only exposes the output-side knob and none of the recipe/rl -correctness configs need the input-side one, so we match that. Added a yaml + __init__ comment so a future reviewer doesn't think the missing sibling is an oversight. Validation: - import smoke: OK - yaml loads via OmegaConf, all 5 new fields parse correctly - _create_ds_worker_config now produces a dict containing all 3 logits keys (confirmed via inspect.getsource grep) - no new lints - live 1.7B convergence run (wandb xqj9pqbt) is on commit 9624948 and not affected by this change; will pick up the fix on its next launch Co-authored-by: Cursor <cursoragent@cursor.com>
Self-review fix-up —
|
sfc-gh-sbekman
left a comment
There was a problem hiding this comment.
Question: Is this how the code now in the original? That super narrow comment column and a huge slop is so hard to read.
Do we need to fix it in the origin first? or have you adapted this and it's not matching the source? Asking so that we don't do the work twice.
Let's try to keep the slop away, this over-explaining makes the code very difficult to read. I found I need to instruct claude to rewrite its comments to 119 chars (not sure what verl's width is) and make them terse and only when necessary, instead using code that is self-explanatory.
|
Thanks @sfc-gh-sbekman — fair, the slop was all mine, not inherited. Pushed Answer to your question: the upstream What I changed in this commit (only touches lines I added; pre-existing upstream comments untouched):
Width: verl's Diff: Will keep comments terse going forward — appreciate the explicit "rewrite to 120 / only when necessary" guidance, going to carry that into the rest of the work too. |
|
Quick follow-up self-audit on
Final state, 4 essential Python comments (vs ~35 before your review), all ≤ 119 chars: # Set False for non-arange position_ids (mrope / 3D rope).
# No server consumer yet; forwarded for forward-compat with group-balanced routing.
# Server reads these from ds_worker_config (arctic_platform/rl/deepspeed_worker.py), not per-call meta.
# `routing_key` kept for caller-API compat; arctic_platform.rl handles routing internally.YAML similarly trimmed; comment density now matches upstream |
|
so when we do this adaption can we avoid changing anything at all other than the layout of the files, otherwise how will we be merging this back into our branch? is there a reason for changing things? If something is broken we need to fix this first in the original branch and this should be only movement of code with only required by the verl team layout changes only pertaining to new code location (e.g. imports) |
Newshape PR #6 update_actor diverged at step 8 in two 1.7B runs (entropy 0.18 -> 1.86 with drop_pid=True, 0.18 -> 0.54 with drop_pid=False). Three prior arctic_platform runs on the OLD derivative adapter held entropy flat at 0.17-0.19. Root cause: meta dict mismatch vs the canonical reference in verl/workers/arctic_workers.py:train_global_batch on origin/recipe/rl-correctness. The newshape adapter was sending the three loss-normalizer keys (global_batch_size, batch_num_tokens, rollout_is_weights) with public/arctic_rl_share_v0.7.1's values, which target the OLD arctic_training.rl server. arctic_platform.rl's server normalizer reads these differently — specifically batch_num_tokens needs to come from the outgoing batch's response_mask, not data['loss_mask']. This commit: - Restores drop_position_ids default to True (matches public + Tunji default; the True -> False experiment was a wrong hypothesis). - Adds the three loss-normalizer keys with Tunji's per-chunk semantics: global_batch_size = ppo_mini_batch_size * rollout_n batch_num_tokens = batch['response_mask'].sum() rollout_is_weights = data.get('rollout_is_weights', None) - For configs with num_minibatches=1 / ppo_epochs=1 (the current 1.7B config: train_batch_size=ppo_mini_batch_size=32) this single call is exactly what Tunji's mini-batch loop produces. A future PR will add the actual loop when we run a config with > 1 mini-batches. Reference: verl/workers/arctic_workers.py L470-545 on origin/recipe/rl-correctness.
Implements RemoteBackend for arctic_platform.rl as the per-backend adapter
on the new abstraction shape (verl/workers/remote_client/arctic_rl.py +
verl/trainer/config/remote_backend/arctic.yaml). The core abstraction
(verl/remote_backend/{base,trainer,worker_utils}.py) is untouched.
Scope:
- Swap inner library arctic_training.rl -> arctic_platform.rl (same public
API names: ArcticRLClientConfig, create_arctic_rl_client,
ArcticRLRayServerState; actively-developed package backing
recipe/rl-correctness server-side moves).
- Drop position_ids on the wire when remote_backend.drop_position_ids=True
(server reconstructs from attention_mask); set False for non-arange
position_ids (mrope / 3D rope).
- Wire all per-call meta keys byte-identically to recipe/rl-correctness
(arctic_remote_adapter / arctic_workers): zorro_train_{enable,
max_rollouts, load_balancer}, drop_position_ids, logits_optimization,
logits_optimization_peak_mem_size_in_gib, logits_compute_in_fp32.
- generate() drops the routing_key kwarg (arctic_platform.rl handles
routing internally).
- Per-chunk update_actor meta values match Tunji's recipe/rl-correctness
semantics (global_batch_size, rollout_is_weights, batch_num_tokens).
Squashed from 7 in-progress commits during PR #6 review iteration.
Ported from recipe/rl-correctness d597a2fd (Xiaodong Yu) with a one-line adaptation for the new abstraction's config path: _model_output_shift() reads remote_backend.zorro_train.enable here (upstream reads arctic_rl.use_zorro). Semantics identical. The zorro path returns response-aligned log-probs; the trainer was applying the legacy predict-next shift=-1 before computing PPO loss, biasing the policy gradient by one token. Returns shift=0 when zorro is enabled, shift=-1 otherwise. This is the primary root cause of the 1.7B newshape divergence at step ~16.
… load-balancer flag Ported from recipe/rl-correctness d8d9ea6f (Mert Hidayetoglu). Same semantics, applied to the newshape adapter (verl/workers/remote_client/arctic_rl.py) and per-backend yaml (verl/trainer/config/remote_backend/arctic.yaml) instead of upstream's arctic_rl_client.py and ppo_trainer.yaml's arctic_rl block. - Forward the grad-clip threshold (optim.clip_grad fallback to actor.grad_clip) to the DeepSpeed engine so it clips to the same global norm verl does (avoids unclipped grads -> trajectory divergence). - Compute the LR-scheduler training_horizon as global_steps * ppo_epochs * num_minibatches (was trainer.total_epochs). Together with the 5% warmup ratio this gives ~13 warmup steps on the 1.7B BIRD run, rather than the 0 warmup steps that came out of the old trainer.total_epochs=1 -> jumped LR from 0 to peak in one step. - Route mixed precision through remote_backend.mixed_precision (autocast + fp32 gradients). Note: fp32_gradients=True requires the deepspeed sfc-gh-truwase/fp32_grads branch (commit 9acf2392); this PR defaults it False so it boots on PyPI DS 0.19.2. The 50-step 1.7B run converges cleanly without it (val/exec 0.816 at step 50). - Gate the zorro load balancer behind zorro_train.load_balancer. Co-authored-by: Olatunji Ruwase <tunji.ruwase@snowflake.com> Co-authored-by: Xiaodong Yu <xiaodong.yu@snowflake.com>
Ported from recipe/rl-correctness 303eb275 (ds_config format) +
8356f215 (ds_config passthru), both by Olatunji Ruwase. End-state
mirrors the latest recipe/rl-correctness shape of
arctic_rl_client._create_ds_config.
Replaces the newshape adapter's hand-rolled DS config (which only read
zero_optimization.* and was missing torch_autocast, data_types and
communication_data_type) with a wholesale OmegaConf.to_container
passthrough of the remote_backend.deepspeed yaml block, merged with the
per-step batch sizing (train_micro_batch_size_per_gpu / train_batch_size
/ gradient_accumulation_steps / sequence_parallel_size).
- _create_ds_config now takes training=True and pops data_types when
training=False (log-prob engine doesn't need grad_accum_dtype).
- train_batch_size is now ppo_mini_batch_size * rollout_n (one DS
optimizer step per PPO mini-batch, matching upstream's
dp_actor.update_policy loop), not data.train_batch_size * rollout_n.
- _create_ds_worker_config drops tiled_logits_compute (gone upstream)
and switches max_token_len source from rollout.max_num_batched_tokens
to actor.ppo_max_token_len_per_gpu (matches upstream).
- arctic.yaml gains the deepspeed.{torch_autocast, communication_data_type,
data_types.grad_accum_dtype, zero_optimization, log_level} block and
drops the top-level zero_optimization block.
NOTE: data_types.grad_accum_dtype defaults to bf16 on this PR (PyPI DS
0.19.2). Upstream defaults to fp32 which requires the sfc-gh-truwase/
fp32_grads DS branch (commit 9acf2392).
Co-authored-by: Mert Hidayetoglu <mert.hidayetoglu@snowflake.com>
Ported from recipe/rl-correctness 85cdbedd (Stas Bekman). Forwards actor_rollout_ref.model.enable_gradient_checkpointing into ds_worker_config so the Arctic DS worker actually applies activation checkpointing (was silently dropped before, ~2x more activations memory). Signed-off-by: Stas Bekman <stas.bekman@snowflake.com>
Ported from recipe/rl-correctness da16e078 (Stas Bekman). logits_compute_in_fp32 was already wired on this branch from an earlier commit; this fills in the missing logits_compute_from_fp32_inputs: cached in __init__, surfaced in the per-backend yaml (default False), and forwarded into ds_worker_config's zorro block. Signed-off-by: Stas Bekman <stas.bekman@snowflake.com>
2f8380d to
e689990
Compare
…sive config walk
Self-review pass on the PR diff. All changes are textual / behaviour-preserving:
- arctic_rl.py: drop @-handle + PR# reference from the per-backend-yaml comment;
drop the 4-line narration above _max_token_len_per_gpu; shorten the CUDA-IPC
weight-sync comment.
- arctic.yaml: replace named-branch references ("Tunji's branch",
"sfc-gh-truwase/fp32_grads branch") with neutral pointers to HANDOFF.md.
- ray_trainer.py: collapse the three-level hasattr/get/get/get defensive walk
in _model_output_shift to a single OmegaConf.select(..., default=False)
(the canonical pattern already used elsewhere in this file). Verified
output-equivalent on zorro_on / zorro_off / no-remote-backend cases.
- padding.py: drop the inline comment block that duplicated the docstring.
No production behaviour change; live 1.7B run on the prior tip (e689990) is
still converging at step 88 with healthy entropy / reward.
Co-authored-by: Cursor <cursoragent@cursor.com>
…Arctic Platform (verl-project#39) Ported from recipe/rl-correctness d826d5ab (Mert Hidayetoglu) + the follow-on rename 6178d5d9 (inference -> rollout). Upstream touches verl/trainer/ppo/arctic_rl_client.py + verl/trainer/config/ppo_trainer.yaml (arctic_rl.rollout block); on this PR the equivalents are verl/workers/remote_client/arctic_rl.py + verl/trainer/config/remote_backend/arctic.yaml. Add a `rollout` block (zorro_inference.enable / speculative_decoding.model) to the per-backend yaml and pass it straight through to the Arctic server as `arctic_inference_config`. Newshape adaptation: rather than reproducing the historical verl-project#39 {use_fca, spec_model} translation, the adapter forwards the raw rollout sub-config (OmegaConf.to_container) to match the current arctic_platform.rl parse_arctic_inference_rollout contract, which keys on zorro_inference.enable / speculative_decoding.model. An all-disabled block is treated as "Arctic inference off" server-side. Co-authored-by: Cursor <cursoragent@cursor.com>
Ported from recipe/rl-correctness c7ed9201 (Olatunji Ruwase). Upstream
touches verl/trainer/ppo/arctic_rl_client.py + verl/trainer/config/ppo_trainer.yaml
(arctic_rl block); on this PR the equivalents are
verl/workers/remote_client/arctic_rl.py + verl/trainer/config/remote_backend/arctic.yaml.
Group the two flat flags (cuda_ipc_weight_sync / low_memory_weight_sync) under a
single `weight_sync` block (cuda_ipc / low_memory). Adapter reads
self._backend_config.weight_sync.{cuda_ipc,low_memory}.
Co-authored-by: Cursor <cursoragent@cursor.com>
Ported from recipe/rl-correctness 8e7e747f (Olatunji Ruwase). Drop the per-call `shift` plumbing through no_padding_2_padding and move the zorro response-alignment correction into the forwarder worker. - verl/workers/utils/padding.py: remove the `shift` arg; restore the single legacy "predict-next" slice (shift=-1) for all callers. - verl/trainer/ppo/ray_trainer.py: remove `_model_output_shift()` and its call sites (same path as upstream; pure revert). - newshape: upstream applied the response-aligned->predict-next shift in verl/workers/arctic_workers.py::compute_any_log_prob (which doesn't exist on this PR). The equivalent njt construction lives in the Arctic forwarder worker, so the shift helper moves to verl/remote_backend/worker_utils.py and is applied in ArcticRLActorRolloutRefWorker._run_log_prob when remote_backend.zorro_train.enable is set. Co-authored-by: Cursor <cursoragent@cursor.com>
verl abstract remote backend
What this PR does
Brings the new
RemoteBackendabstraction shape to convergence parity withrecipe/rl-correctnesson 1.7B GRPO, by porting the correctness / numerical / scheduling fixes onto our Arctic adapter and per-backend yaml — without touching the core abstraction (verl/remote_backend/{base,trainer,worker_utils}.py).When verl-project#6422 merges and we rebase our internal
recipe/rl-correctnessfork on top of it, the adapter shape, payload encoding, DS engine config, and training-time guards already match.Architecture (recap)
verl/remote_backend/{base.py, trainer.py, worker_utils.py}. DefinesRemoteBackend,RemoteBackendRegistry,RemoteBackendTrainer.verl/remote_backend/workers/arctic_rl/worker.py. Driver-side wiring for the Arctic backend.verl/workers/remote_client/arctic_rl.py. ImplementsRemoteBackendforarctic_platform.rl. All wire-format, payload encoding, DS engine config and per-call meta lives here.verl/trainer/config/remote_backend/arctic.yaml. Loaded via Hydra atcfg.remote_backendwhentrainer.remote_backend=arctic.Other backends drop in by registering a new class + a new yaml file. Nothing in
verl/remote_backend/ever needs to change.Commits (6 total, all on top of
2174486b)Five of the six commits are direct ports from
recipe/rl-correctness. Authorship and subject lines preserved exactly so this is one-click for Tunji to audit.fbab32f37eb21212d597a2fdbc510b12d8d9ea6f1f41c88b303eb275+8356f2150126bb7985cdbedde6899904da16e078Each ported commit's body cites its
recipe/rl-correctnesssource SHA and calls out any newshape-specific adaptation (e.g. config-path tweaks forremote_backend.zorro_train.enablevs upstream'sarctic_rl.use_zorro, or applying the same change to our adapter file instead of upstream'sarctic_rl_client.py). Tunji: please read the commit bodies — they're meant to make the audit easy.Convergence (1.7B BIRD GRPO, 8×H100, ZeRO-3, ZoRRo)
Full run on this branch, hparams identical to the validated PR verl-project#37 launcher (see
launch_1.7b_newshape.sh).Validation (test_freq=10) — beats PR verl-project#37 baseline at every checkpoint
Training (rolling 20-step windows)
Entropy stable in the 0.17-0.21 band (target 0.17-0.20). For comparison, prior newshape runs (this branch before the ported commits) had entropy at 0.4-0.6 by step 16 and never recovered.
Root causes that were killing convergence
Two changes from the upstream port are load-bearing:
no_padding_2_padding(zorro path) — commit7eb21212/ upstreamd597a2fd. Adapter requested zorro/response-aligned log-probs but the trainer applied the legacy "predict-next"-1shift before computing PPO loss → systematic one-token misalignment → biased policy gradient. Fixed by_model_output_shift()returningshift=0whenremote_backend.zorro_train.enable=True.bc510b12/ upstreamd8d9ea6f. Adapter passedtraining_horizon=trainer.total_epochs=1, so a 5% warmup ratio evaluated to0.05 * 1 = 0 warmup steps→ LR jumped from 0 to peak in a single step, blew out the policy in the first ~10 updates. Fixed by computingtraining_horizon = global_steps * ppo_epochs * num_minibatches(matchesrecipe/rl-correctness).Key surfaces
verl/workers/remote_client/arctic_rl.pyRemoteBackendforarctic_platform.rl(wasarctic_training.rl)._create_ds_config(n_gpus, training=True): passes the wholeremote_backend.deepspeedblock through (OmegaConf.to_container + deepcopy) and merges intrain_micro_batch_size_per_gpu/train_batch_size/gradient_accumulation_steps/sequence_parallel_size. Dropsdata_typeswhentraining=False._create_ds_worker_config(): wiresenable_gradient_checkpointing,logits_compute_from_fp32_inputs,logits_compute_in_fp32,logits_optimization,logits_optimization_peak_mem_size_in_gib.max_token_len = actor.ppo_max_token_len_per_gpu(matches upstream)._initialize_client: computestraining_horizon = global_steps * ppo_epochs * num_minibatches, forwardsoptim.clip_gradto the DS engine, threadslr_schedulerconfig (type/warmup_ratio/min_lr_ratio).compute_log_prob.meta+update_actor.meta: keys byte-identical to recipe/rl-correctness (arctic_remote_adapter/arctic_workers).verl/trainer/ppo/ray_trainer.py_model_output_shift()returns 0 for the zorro path (remote_backend.zorro_train.enable=True),-1otherwise. Threaded intono_padding_2_paddingin_compute_valuesand_compute_old_log_prob.verl/workers/utils/padding.pyno_padding_2_padding(...)now acceptsshift.verl/trainer/config/remote_backend/arctic.yamlzorro_train.{enable, load_balancer, max_rollouts}drop_position_ids(flat)logits_optimization{,_peak_mem_size_in_gib},logits_compute_in_fp32,logits_compute_from_fp32_inputscuda_ipc_weight_sync,low_memory_weight_syncdeepspeed.{torch_autocast, communication_data_type, data_types, zero_optimization, log_level}(passthrough block)Dependency notes
arctic_platform,arctic_inference,deepspeedare pinned to specific commits / PyPI versions and installed non-editable from local clones (no reliance on anyone's working tree). Process codified in skillpin-vendor-deps.mixed_precision.fp32_gradients(recipe/rl-correctness uses fp32 grad accum) requiresdeepspeed sfc-gh-truwase/fp32_grads(commit9acf2392). Defaulting tograd_accum_dtype: bf16on PyPI DS 0.19.2 since convergence holds without it (val/exec 0.816 at step 50). Easy flip if needed: switch DS to that branch, changedata_types.grad_accum_dtype: fp32inarctic.yaml.Out of scope (deferrable follow-ups)
mixed_precision.fp32_gradients(requires DS branch swap; not on the convergence path).update_actor(PR#37 does it in the adapter; the target shape lets the verl-side worker drive it). No semantic change needed for current configs (ppo_epochs=1,train_batch_size == ppo_mini_batch_size).use_fca,spec_model,full_determinism,seed— orthogonal features, not required for parity.Related
snowflake-eng/arctic-verl#37— predecessor PR (Arctic adapter onrecipe/rl-correctness, older abstraction shape; converging baseline).recipe/rl-correctness— source branch for ported commits (d597a2fd,d8d9ea6f,303eb275,8356f215,85cdbedd,da16e078).verl-project/verl#6422— upstream abstraction PR this branch is the head of.