Repository navigation
[refactor] Rename arctic_rl config namespace to generic remote_backend.arctic - #3
Conversation
…kend.arctic` Addresses @zw0610's review on verl-project#6422 asking that core paths use generic names so other backends can dock without colliding with `arctic_rl`. Changes: * `verl/trainer/config/ppo_trainer.yaml`: top-level `arctic_rl:` block becomes `remote_backend.arctic:` (nested under a generic `remote_backend:` parent). Comment explains that new backends drop in as sibling blocks (`remote_backend.<name>:`) with no other changes required. * `verl/trainer/ppo/arctic_rl_client.py`: adapter now caches `self._backend_config = config.remote_backend[self._BACKEND_CONFIG_KEY]` in `__init__` and reads all backend-specific fields through it. `_BACKEND_CONFIG_KEY = "arctic"` is the single source of truth a new backend would override. * `examples/arctic_rl/run_gsm8k_grpo_arl_zorro_yes.sh`: 9 hydra overrides switched from `arctic_rl.*` to `remote_backend.arctic.*`. Adapter-side filenames/classes (`arctic_rl_client.py`, `ArcticRLClientWrapper`, `verl/workers/rollout/arctic_rollout/`) and the `"arctic"` rollout-replica registry entry are intentionally left alone since they're adapter-scoped and follow the same shape as the existing `vllm`/`sglang`/`trtllm` entries. Smoke: 6-step GSM8K GRPO on H200, MFU 148-174, matches pre-rename baseline. Co-authored-by: Cursor <cursoragent@cursor.com>
|
|
||
| # ray or http: ray would be much faster for payload comms in the en-prem use case | ||
| comm_protocol: ray | ||
| # only set to True if running into OOM, otherwise it'll be faster not to tile (extra forward replay and smaller chunks to compute) |
There was a problem hiding this comment.
Remove this and modify corresponding code to handle missing key and use default True for this key.
There was a problem hiding this comment.
Done in b9d2914 — removed the yaml line; adapter reads it via self._backend_config.get("tiled_logits_compute", True) so the default is True when absent. Also dropped the now-redundant remote_backend.arctic.tiled_logits_compute=True override from the GSM8K example.
|
Can you cherry-pick changes to arctic_rl_client.py and arctic_rollout.py in this commit: |
| n_training_gpus = self._backend_config.get("training_gpus", self.config.trainer.n_gpus_per_node) | ||
| n_sampling_gpus = self._backend_config.get("sampling_gpus", self.config.trainer.n_gpus_per_node) | ||
| n_log_prob_gpus = self._backend_config.get("log_prob_gpus", self.config.trainer.n_gpus_per_node) | ||
| colocate = self._backend_config.get("colocate", False) |
There was a problem hiding this comment.
Make the default True
There was a problem hiding this comment.
Done in b9d2914 — colocate = self._backend_config.get("colocate", True) now defaults to True.
… tokenizer
* `ppo_trainer.yaml`: drop `tiled_logits_compute` (advanced OOM-only
knob); adapter now reads it via
`self._backend_config.get("tiled_logits_compute", True)`.
* `arctic_rl_client.py`: flip in-code default for `colocate` from
`False` to `True` per Tunji's comment.
* Cherry-pick of arctic-verl@662e421b — adapted for the version of
`arctic_training` shipping in this PR (its client RPCs are still
sync; only `sync_weights` is async). Plumbs `async`/`await` through
the full stack so the forwarder's Ray event loop stays responsive,
also addressing the gemini-bot blocking-call comment on
verl-project#6422:
- `RemoteBackend.compute_log_prob`, `update_actor`,
`save_checkpoint` declared `async` on the ABC.
- `ArcticRLClientWrapper` implements them `async` and dispatches
the underlying sync `self._client.fwd_no_grad` /
`fwd_bwd` / `step` / `save_checkpoint` via
`asyncio.to_thread`, so the worker thread blocks instead of the
asyncio loop.
- `RemoteBackendActorRolloutRefWorker._run_log_prob` /
`_run_update_actor` made async and `await` the backend.
`save_checkpoint` / `destroy` likewise await.
- `RemoteBackendTrainer.destroy` (sync shutdown path) bridges
with `asyncio.run` to await the now-async `backend.destroy`.
* `arctic_rollout.py`: drop hardcoded `Qwen/Qwen3-0.6B` tokenizer
load. `ArcticLLMEngine.__init__` takes a tokenizer, and
`ArcticLLMServer` passes `self.model_config.tokenizer` (already
loaded by `HFModelConfig.__post_init__`), avoiding both a hardcoded
model name and a redundant download.
* `run_gsm8k_grpo_arl_zorro_yes.sh`: drop the now-redundant
`remote_backend.arctic.tiled_logits_compute=True` override (the
in-code default already gives `True`).
Smoke: 6-step GSM8K GRPO on H200, MFU 149-170 (matches pre-change
baseline 148-174); no regressions.
Co-authored-by: Cursor <cursoragent@cursor.com>
|
Cherry-picked in b9d2914 ( Heads up: the version of I kept the same end goal — the forwarder's Ray asyncio loop must not block on backend RPCs (this is also the gemini-bot blocking-call comment on verl-project#6422) — but went through async def _send_compute_log_prob(self, payload):
response = await asyncio.to_thread(self._client.fwd_no_grad, payload, reference_model=False)
...Stack-wide changes:
Smoke (6-step GSM8K GRPO, H200): MFU 149-170, matches the pre-change 148-174 baseline. |
| async def _send_compute_log_prob(self, payload: dict): | ||
| payload["processing"] = {"post": ["compute_entropy_and_logprobs"], "loss_fn": None} | ||
| response = self._client.fwd_no_grad(payload, reference_model=False) | ||
| response = await asyncio.to_thread(self._client.fwd_no_grad, payload, reference_model=False) |
There was a problem hiding this comment.
Use simpler await
| response = await asyncio.to_thread(self._client.fwd_no_grad, payload, reference_model=False) | |
| response = await self._client.fwd_no_grad(payload, reference_model=False) |
There was a problem hiding this comment.
Tried bare await self._client.fwd_no_grad(...) first — crashes with TypeError: object dict can't be used in 'await' expression.
I checked the latest arctic_training (HEAD on the verl-integration branch) and verified the runtime signatures:
fwd_no_grad sync
fwd_bwd sync
step sync
save_checkpoint async ← newly async
sync_weights async
So bare await works for save_checkpoint (line 464) but not for fwd_no_grad (this line), fwd_bwd (L458), or step (L459) — they're still sync upstream.
Two paths:
- Now: keep
asyncio.to_thread(self._client.fwd_no_grad, ...)so the forwarder's asyncio loop stays unblocked (gemini-bot G3 on [trainer] feat: generic remote backend abstraction for RL Training verl-project/verl#6422 made the same point) and the verl PR ships against the currentarctic_training. - Later: when
fwd_no_grad/fwd_bwd/stepgo async upstream, I dropto_threadin a small follow-up — happy to do that as soon as you point me at the commit.
I switched save_checkpoint to bare await in 188338b since that one is already async upstream.
|
|
||
| fwd_bwd_response = self._client.fwd_bwd(payload) | ||
| step_response = self._client.step() | ||
| fwd_bwd_response = await asyncio.to_thread(self._client.fwd_bwd, payload) |
There was a problem hiding this comment.
Just await
| fwd_bwd_response = await asyncio.to_thread(self._client.fwd_bwd, payload) | |
| fwd_bwd_response = await self._client.fwd_bwd(payload) |
There was a problem hiding this comment.
Same as the fwd_no_grad thread — fwd_bwd is still sync upstream, so kept the asyncio.to_thread wrapper to avoid TypeError: object dict can't be used in 'await' expression. Happy to switch to bare await once it goes async upstream.
There was a problem hiding this comment.
Retry await. It should now work after ArcticTraining-dss commit: af1ab8d7727e2611ae8eaf9059f5c04170ad02cd
There was a problem hiding this comment.
Done in 8aec8a6 — bumped arctic_training to 983d58c (which includes your af1ab8d) in the smoke env, verified the new signatures (fwd_no_grad / fwd_bwd / step all async), and switched to bare await self._client.fwd_bwd(payload). Also did the same for _send_compute_log_prob / _send_compute_ref_log_prob for symmetry, and dropped the now-unused import asyncio from arctic_rl_client.py.
Smoke (6-step GSM8K GRPO on H200): MFU 186-218, matches the prior asyncio.to_thread baseline (199-224) — no perf regression from dropping the thread hop.
| fwd_bwd_response = self._client.fwd_bwd(payload) | ||
| step_response = self._client.step() | ||
| fwd_bwd_response = await asyncio.to_thread(self._client.fwd_bwd, payload) | ||
| step_response = await asyncio.to_thread(self._client.step) |
There was a problem hiding this comment.
Same as the fwd_no_grad thread — step is still sync upstream, so kept the asyncio.to_thread wrapper. Happy to switch to bare await once it goes async upstream.
There was a problem hiding this comment.
Retry await. It should now work after ArcticTraining-dss commit: af1ab8d7727e2611ae8eaf9059f5c04170ad02cd
There was a problem hiding this comment.
Done in 8aec8a6 — same pass as the fwd_bwd thread, step is now await self._client.step() (it's async on arctic_training @ 983d58c).
| def save_checkpoint(self): | ||
| return self._client.save_checkpoint() | ||
| async def save_checkpoint(self): | ||
| return await asyncio.to_thread(self._client.save_checkpoint) |
There was a problem hiding this comment.
Done in 188338b — save_checkpoint is async upstream, so bare await self._client.save_checkpoint() works (dropped the to_thread wrapper).
Smoke (6-step GSM8K GRPO on H200, post-arctic_training bump): MFU 199-224.
Closing `}` of the returned dict was indented at column 4 instead of column 8 — visually breaks the dict nesting, even though Python accepts it. Trivial cosmetic fix flagged by gemini-bot on verl-project#6422. Smoke: 6-step GSM8K GRPO on H200, MFU 146-175, matches baseline. Co-authored-by: Cursor <cursoragent@cursor.com>
`arctic_training.arctic_rl.client.ArcticRLRayClient.save_checkpoint` is `async` on the current `tunji/verl_integration` HEAD, so the `asyncio.to_thread` wrapper is unnecessary. `fwd_no_grad` / `fwd_bwd` / `step` are still sync upstream and continue to use `to_thread` (per inline-reply on PR Snowflake-AI-Research#3 explaining the test). Smoke: 6-step GSM8K GRPO on H200, MFU 199-224 (post-arctic_training bump), no regressions. Co-authored-by: Cursor <cursoragent@cursor.com>
17d2aeb
into
Snowflake-AI-Research:arctic_rl_share_v0.7.1
Now that ArcticTraining-dss commit af1ab8d (`Async+bf16 opt`) makes `fwd_no_grad`, `fwd_bwd`, and `step` async on `ArcticRLRayClient`, `asyncio.to_thread` is no longer needed for these three. Switching to bare `await self._client.<op>(...)` per Tunji's retry-await comments on PR Snowflake-AI-Research#3 (lines 458, 459) — symmetry with the already-await'd `save_checkpoint` and `sync_weights`. Also drops the now-unused `import asyncio` from `arctic_rl_client.py`. `RemoteBackendTrainer.destroy` still uses `asyncio.run` to bridge the sync shutdown path to the async `backend.destroy`. Smoke: 6-step GSM8K GRPO on H200, MFU 186-218; matches the `asyncio.to_thread` baseline (199-224), no perf regression from dropping the thread hop. Co-authored-by: Cursor <cursoragent@cursor.com>
Addresses @zw0610's structural review on verl-project#6422: - #2: verl/trainer/ppo/arctic_rl_client.py -> verl/workers/remote_client/arctic_rl_client.py - #3: verl/workers/rollout/arctic_rollout/ -> verl/workers/rollout/remote_rollout/arctic_rollout/ - #4: TODO(@zw0610) on RolloutReplicaRegistry registrations in verl/workers/rollout/replica.py:360 about lazy-init each option to avoid pulling in vLLM + SGLang + TRT-LLM transitive deps eagerly. Pure code-motion + a TODO; no behavior change. Imports inside the moved files and in workers/rollout/replica.py updated. Co-authored-by: Cursor <cursoragent@cursor.com>
Summary
Addresses @zw0610's review on verl-project/verl#6422 asking that core paths use generic names so other remote backends can dock without colliding with the Arctic-specific
arctic_rlnamespace.Changes (3 files, +55/-41)
verl/trainer/config/ppo_trainer.yaml— top-levelarctic_rl:block becomesremote_backend.arctic:(nested under a genericremote_backend:parent). A header comment explains that new backends drop in as sibling blocks (remote_backend.<name>:) with no other changes required.verl/trainer/ppo/arctic_rl_client.py— adapter now caches `self._backend_config = config.remote_backend[self._BACKEND_CONFIG_KEY]` in `init` and reads all backend-specific fields through it. `_BACKEND_CONFIG_KEY = "arctic"` is the single source of truth a new backend would override.Intentionally not renamed
Adapter-side filenames/classes (`arctic_rl_client.py`, `ArcticRLClientWrapper`, `verl/workers/rollout/arctic_rollout/`) and the `"arctic"` rollout-replica registry entry are left alone since they're adapter-scoped and follow the same shape as the existing `vllm`/`sglang`/`trtllm` entries.
Test
6-step GSM8K GRPO smoke on H200:
MFU matches the pre-rename baseline (170-ish); no perf regression.
Once merged
PR verl-project#6422's head on `verl-project/verl` picks this up automatically, addressing @zw0610's comment without any further work.
Made with Cursor