Restructure remote_backend per @zw0610 review (stacked on #3) - #4
Conversation
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>
…e-AI-Research#2/Snowflake-AI-Research#3/Snowflake-AI-Research#4) Addresses @zw0610's structural review on verl-project#6422: - Snowflake-AI-Research#2: verl/trainer/ppo/arctic_rl_client.py -> verl/workers/remote_client/arctic_rl_client.py - Snowflake-AI-Research#3: verl/workers/rollout/arctic_rollout/ -> verl/workers/rollout/remote_rollout/arctic_rollout/ - Snowflake-AI-Research#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>
…Snowflake-AI-Research#5/Snowflake-AI-Research#6/Snowflake-AI-Research#7) Addresses the remaining @zw0610 structural comments on verl-project#6422: - Snowflake-AI-Research#1 (split remote_backend out of ppo_trainer.yaml): * New verl/trainer/config/remote_backend/arctic.yaml with the arctic block content. * In verl/trainer/config/ppo_trainer.yaml, replace the inline remote_backend: {} block with a Hydra optional defaults entry (`- optional remote_backend@remote_backend: null`). Users opt in with `remote_backend=arctic` (standard Hydra option group syntax). * Example script (run_gsm8k_grpo_arl_zorro_yes.sh) updated accordingly. - Snowflake-AI-Research#5 (trim RemoteBackend ABC to lifecycle only): * Drop `compute_log_prob`, `update_actor`, `generate` from verl/remote_backend/base.py. ABC now enforces only: `from_config`, `reconnect_handle`, `destroy`, `update_weights`, `save_checkpoint`, `requires_single_forwarder`. * Concrete compute/update/generate methods stay on the Arctic adapter as plain methods (called from the Arctic-specific worker, not via the ABC contract). - Snowflake-AI-Research#6 (empty MODULES, explicit registration): * Drop the lazy `RemoteBackendRegistry.MODULES` table from base.py so no transitive deps (vLLM, arctic-training, tinker, ...) get pulled in eagerly. * main_ppo.py now imports the adapter module explicitly (`from verl.workers.remote_client import arctic_rl_client`) when `trainer.remote_backend=arctic`; the import side-effect registers the class. - Snowflake-AI-Research#7 (per-backend worker, not a generic parallel class): * New verl/remote_backend/workers/arctic_rl/ package. * Rename `RemoteBackendActorRolloutRefWorker` to `ArcticRLActorRolloutRefWorker` (the worker.py move happened in the previous commit). * main_ppo.py selects the per-backend worker class explicitly (currently arctic; extends backend-by-backend, with a clear error message on unknown backend names). * NOTE: this PR does NOT yet make the worker inherit from `verl.workers.engine_workers.ActorRolloutRefWorker` (Wang's full Snowflake-AI-Research#7 prescription). Doing so requires first decoupling `ActorRolloutRefWorker.__init__` from megatron-specific config fields (e.g. `config.actor.megatron.router_replay`) that the Arctic config tree doesn't carry; left as a follow-up RFC item. Verified end-to-end via 6-step GSM8K GRPO smoke (Qwen3-0.6B, single-GPU, zorro=True); convergence and MFU unchanged. Co-authored-by: Cursor <cursoragent@cursor.com>
Ray child procs don't inherit the driver's import side-effects, so the
`@RemoteBackendRegistry.register("arctic")` decorator never ran in the
WorkerDict actor and `RemoteBackendRegistry.get("arctic")` raised
`KeyError`. Fix by eager-importing the adapter at the top of the
per-backend worker module — every process that loads this worker now
also loads (and registers) its adapter. The driver still imports the
adapter explicitly in `verl.trainer.main_ppo`; this is the matching
import in the worker process.
Caught by the 6-step GSM8K GRPO smoke after the registry/MODULES trim.
Co-authored-by: Cursor <cursoragent@cursor.com>
| @@ -159,7 +158,7 @@ def requires_single_forwarder(self) -> bool: | |||
| return True | |||
|
|
|||
| # ------------------------------------------------------------------ # | |||
| # Core RL ops — called by `RemoteBackendActorRolloutRefWorker` | |||
| # Core RL ops — called by `ArcticRLActorRolloutRefWorker` | |||
There was a problem hiding this comment.
Given new file location, rename to remote_client/arctic_rl.py
There was a problem hiding this comment.
Renamed verl/workers/remote_client/arctic_rl_client.py → arctic_rl.py in 9fc25fb, and updated all 8 callsites (main_ppo.py, arctic_rollout.py, remote_backend/base.py, remote_backend/workers/arctic_rl/worker.py + __init__.py, remote_backend/__init__.py, plus the yaml docstring).
| # defaults (`+remote_backend@remote_backend=arctic`). Companion adapter: | ||
| # `verl.workers.remote_client.arctic_rl_client.ArcticRLClientWrapper`. | ||
| # Selected at runtime by `trainer.remote_backend=arctic`. | ||
| arctic: |
There was a problem hiding this comment.
Is the top-level arctic: namespace still needed given this is arctic yaml?
There was a problem hiding this comment.
You're right — flattened in 9fc25fb. Dropped the top-level arctic: key (file name already names the backend) and removed the _BACKEND_CONFIG_KEY = "arctic" indirection in the adapter so it now reads config.remote_backend directly. Also updated the example Hydra overrides (remote_backend.arctic.X=Y → remote_backend.X=Y) in run_gsm8k_grpo_arl_zorro_yes.sh.
…ctic.yaml (Tunji PR#4) Addresses 2 outstanding @sfc-gh-truwase comments on PR Snowflake-AI-Research#4: 1. `verl/workers/remote_client/arctic_rl_client.py` -> `arctic_rl.py` The new directory `remote_client/` already names the role, so the `_rl_client` suffix is redundant. Updated all 8 callsites (main_ppo.py, arctic_rollout.py, base.py, worker.py, __init__.py files, and adapter self-references in docstrings). 2. `trainer/config/remote_backend/arctic.yaml`: dropped the top-level `arctic:` namespace — the file name (`arctic.yaml`) is already the backend name, so nesting was redundant. Adapter now reads `config.remote_backend` directly (removed `_BACKEND_CONFIG_KEY` indirection). Updated the example shell script Hydra overrides (`remote_backend.arctic.X=Y` -> `remote_backend.X=Y`). Verified end-to-end: `verl.remote_backend`, the renamed adapter, the per-backend worker, and the rollout chain all import cleanly under the arctic_rl venv. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Status update — upstream RFC for the broader abstraction is now at verl-project#6537. Both of your inline comments on this PR addressed in 9fc25fb (rename + arctic.yaml flatten + adapter unwrap). |
|
@zw0610 ready for re-review — both of @sfc-gh-truwase's May 23 inline comments are addressed in 9fc25fb99f:
Import chain verified end-to-end under |
bf8ea8f
into
Snowflake-AI-Research:arctic_rl_share_v0.7.1
Drive-by correctness fix surfaced by the 4-step GSM8K smoke against
the CI-fix head: `destroy()` is async (line 461) and the upstream
ArcticTraining-dss `shutdown()` is async, but the call was missing
`await`, producing:
RuntimeWarning: coroutine 'ArcticRLRayClient.shutdown'
was never awaited
Regression introduced by the wang-restructure merge (the await was
present on the pre-#4 branches; rename + move dropped it). Matches
the `save_checkpoint` / `update_weights` async pattern in the same
file.
Co-authored-by: Cursor <cursoragent@cursor.com>
Why
Addresses all 7 of @zw0610's structural comments on
verl-project/verl#6422
in one place so Tunji + Wang can review the direction before it lands
upstream. Stacks on top of #3 (the wang-rename layer); see the
restructure-only diff here:
(this PR's full diff is a superset because GitHub can't target a fork
branch as base on SAR).
What changes (mapped 1:1 to Wang's comments)
remote_backendconfig out ofppo_trainer.yamlverl/trainer/config/remote_backend/arctic.yaml; inline block inppo_trainer.yamlreplaced by a Hydra optional defaults entry (- optional remote_backend@remote_backend: null). Users opt in withremote_backend=arctic(standard Hydra option-group syntax).arctic_rl_client.pytoverl/workers/remote_client/git mv verl/trainer/ppo/arctic_rl_client.py verl/workers/remote_client/arctic_rl_client.py+ new__init__.py.arctic_rollout/toverl/workers/rollout/remote_rollout/arctic_rollout/git mv verl/workers/rollout/arctic_rollout verl/workers/rollout/remote_rollout/arctic_rollout+ new__init__.py.replica.py:360for lazy-init eachRolloutReplicaRegistryoptionRolloutReplicaRegistry.register("vllm" …)block, citing dependency-conflict risk and noting the_load_*callables already defer imports.RemoteBackendABC droppedcompute_log_prob,update_actor,generate. Remaining abstracts:from_config,reconnect_handle,destroy,update_weights,save_checkpoint,requires_single_forwarder. Compute/update/generate stay on the Arctic adapter as plain (non-abstract) methods, called from the Arctic-specific worker.MODULESempty, register explicitlyRemoteBackendRegistry.MODULEStable. Adapter modules are now imported explicitly: bymain_ppo.pyon the driver and by the per-backend worker module on Ray child procs (one line at the top ofworkers/arctic_rl/worker.py).verl/remote_backend/workers/arctic_rl/package.RemoteBackendActorRolloutRefWorkerrenamed toArcticRLActorRolloutRefWorker.main_ppo.pyselects the per-backend worker class with an explicitif backend_name == "arctic"branch and a clear error message on unknown names.NOT yet done (carve-out): making this worker actually inherit from
verl.workers.engine_workers.ActorRolloutRefWorker(Wang's full prescription withinit_modeloverride) requires first decouplingActorRolloutRefWorker.__init__from megatron-specific config fields (e.g.config.actor.megatron.router_replay) that the Arctic config tree doesn't carry. Left as a follow-up.Commits
9e9f6afb— move adapter + rollout into per-backend paths + add TODO (Wang Generic RemoteBackend abstraction + Arctic adapter (parallel to PR #1) #2/[refactor] Rename arctic_rl config namespace to generic remote_backend.arctic #3/Restructure remote_backend per @zw0610 review (stacked on #3) #4)9b82e95a— ABC trim + registry trim + external YAML + per-backend worker rename +main_ppo.pyselector (Wang Arctic RL: VeRL integration #1/[arctic-rl] align adapter with upcoming zorro_train config grouping #5/[arctic_rl] backport rl-correctness payload-encoding deltas onto arctic_rl_share_v0.7.1 #6/verl abstract remote backend #7)e4aaee0c— eager-import adapter in arctic_rl worker for Ray child procs (caught during smoke after the MODULES trim)Verified
6-step GSM8K GRPO smoke (Qwen3-0.6B, single-GPU, zorro=True, gbs=16, rollout n=5):
All within the established 186–218 baseline; convergence and MFU
unchanged.
Open questions for the reviewers
class unchanged) acceptable as an intermediate step, with the full
ActorRolloutRefWorkerinheritance as a follow-up that also touchesthe megatron config coupling? If you want both in one PR, I can
extend.
RFC ask on [trainer] feat: generic remote backend abstraction for RL Training verl-project/verl#6422 modulo the follow-up above.
Made with Cursor