Skip to content

V1 (verl-project/verl main) plugin shims for the Arctic verl integration - #41

Open
sfc-gh-kganesan wants to merge 6 commits into
mainfrom
karthik/verl-v1-plugin
Open

V1 (verl-project/verl main) plugin shims for the Arctic verl integration#41
sfc-gh-kganesan wants to merge 6 commits into
mainfrom
karthik/verl-v1-plugin

Conversation

@sfc-gh-kganesan

@sfc-gh-kganesan sfc-gh-kganesan commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

V1 (verl-project/verl main) plugin shims for the Arctic verl integration

Summary

Adds V1-compatible wrappers so a single plugin install works against both
the Snowflake fork (V0, Snowflake-AI-Research/verl@release/v0.7.1) and
upstream (V1, verl-project/verl@main). V0 modules are untouched;
register.py auto-detects which verl is installed based on whether
verl.trainer.ppo.v1.trainer_remote_backend imports.

Companion verl-core PR (adds the V1 seam this plugin plugs into): verl-project/verl#7102
after the verl PR is opened._

New files

  • arctic_platform/integrations/verl/v1/worker.py
    ArcticV1ActorRolloutRefWorker, subclass of verl V1's
    ActorRolloutRefWorker. Highlights:
    • Skips base-class megatron/veomni init paths Arctic doesn't populate.
    • _AsyncRunner drives the async backend RPCs on a persistent
      background asyncio loop (compensates for V1's @register decorator
      stack dropping coroutine-function status).
    • _to_v0_padded_batch densifies V1's nested-jagged batches to V0's
      dense-padded layout using config max_prompt_length /
      max_response_length (not batch-local maxes) so ZoRRo's
      Qwen3ModelOncePatcher can derive the correct prompt/response split
      (prompt_len = input_ids.shape[1] - max_response_length on every
      forward — batch-local padding silently corrupts this).
  • arctic_platform/integrations/verl/v1/replica.pyArcticV1Replica
    adapts the V0 ArcticReplica constructor to V1's
    LLMServerManager.replica_init_kwargs forwarding and points to
    ArcticV1LLMServer.
  • arctic_platform/integrations/verl/v1/server.py
    ArcticV1LLMServer publishes
    TokenOutput.extra_fields["global_steps"] (V1's real field; V0 used
    extra_info which pydantic silently drops) and adds a
    set_global_steps hook so CheckpointEngineManager can fan the
    current policy version out to servers after each weight sync.
  • arctic_platform/integrations/verl/v1/examples/run_gsm8k_grpo_arl_v1.sh
    V1-recipe smoke launcher.

Updated files

  • arctic_platform/integrations/verl/register.py — auto-detects V0 vs
    V1 by trying to import verl.trainer.ppo.v1.trainer_remote_backend.

Validation

BIRD text-to-SQL, Qwen3-8B, 8× H200, recipe-aligned with
recipe/skyrl-integration/recipes/rl/verl/txt2sql/run_qwen3_32b_bird_grpo_arl_zorro_yes.sh,
5 steps each.

Metric Stock V1 (vLLM+FSDP2) Arctic V1 + ZoRRo (this PR) Speedup
timing_s/update_actor 380.9 166.0 2.30×
timing_s/gen 200.6 204.7 0.98×
timing_s/step (total) 731.4 438.3 1.67×

Convergence: critic/score/mean matches step-over-step between stock and
Arctic paths (step 1: 0.635 vs 0.635; step 5: 0.63 vs 0.63).

Known follow-up (not in this PR)

Inference (gen) is currently at parity, not ahead. The pre-plugin
recipe forced actor_rollout_ref.rollout.enforce_eager=True, which sets
cudagraph_mode=NONE and lets FCA fire (all 8 inference workers log
Forest Cascade Attention ENABLED (... cudagraph_mode=NONE)); with that
knob flipped, gen drops to ~191 s (~7% inference speedup on 8B BIRD).
The current plugin recipe (recipe/skyrl-integration/.../run_qwen3_32b_bird_grpo_arl_zorro_yes.sh)
uses enforce_eager=False, and vLLM 0.18.0's new default
cudagraph_mode=FULL_AND_PIECEWISE silently disables FCA there. Fixing
this cleanly means having parse_arctic_inference_rollout in
arctic_platform/rl/utils/server_models.py override
compilation_config.cudagraph_mode=PIECEWISE when
zorro_inference.enable=True and enforce_eager=False. Tracking as a
separate PR.

sfc-gh-kganesan and others added 6 commits August 19, 2026 18:20
Adapts the Cortex backend for SkyRL and verl to the client/config
refactor Mike landed in #75 (minimal-install) and #77 (onprem/remote as
backend types, backend_config -> backend field, protocol renames).

client/config: adds CortexConfig.from_env(), the single call-site
framework adapters use to hydrate CortexConfig from ARCTIC_CORTEX_*
env vars. Removes the previous scattered env-reads in the shim and the
verl adapter.

client/transports/cortex: wake/sleep short-circuit in the transport
(_NOOP_OPS). Cortex sub-jobs are always awake; short-circuiting here
means the shim doesn't have to wrap every wake/sleep — including the
ones sync_weights invokes internally.

rl/config, rl/client: legacy SkyRL config gets "cortex" backend +
_backend_from_env validator (ARCTIC_BACKEND=cortex flips SkyRL's baked-in
"local" without patching SkyRL); rl/client.create_arctic_rl_client
dispatches to the shim lazily.

rl/_cortex_dispatch: legacy -> unified config translation, async facade
over ArcticRLClient, save_weights raises NotImplementedError (Cortex
sub-jobs don't share disk; silent no-op would leave sampling on stale
weights).

integrations/_cortex_shared.to_cortex_fwd_bwd_payload: shared reshape
between SkyRL shim and verl adapter (both build {batch, meta, processing};
Cortex takes {args, kwargs, context, processing}).

integrations/skyrl: install_cortex_driver_shims patches SkyRL's
peer_access_supported probe (hangs a CPU-only driver). Launcher
python -m arctic_platform.integrations.skyrl installs it and forwards
to SkyRL's own entrypoint.

integrations/verl/adapter: ARCTIC_BACKEND=cortex swaps default
OnPremConfig -> CortexConfig.from_env(); _zero_logprob_response fails
loud on use_kl_loss / use_kl_in_reward / ppo_epochs > 1 (Cortex has no
/forward, zero-fill is only correct for single-epoch on-policy GRPO
without KL).

Docs + tests: docs/cortex-integration.md, recipes/rl/skyrl/simple_gsm8k_cortex/,
integrations/verl/examples/README-cortex.md + run_gsm8k_grpo_cortex.sh,
tests/client/test_client_ops.py adds CortexConfig.from_env, transport
noop-op, legacy env-promotion, shared-helper reshape, save_weights
fail-loud coverage.

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

The server-side reason field disambiguates rate-limits, allowlist rejections,
capacity exhaustion, and real sub-job crashes; today _wait_running throws
'reached terminal state failed' with no detail, which forces an out-of-band
job query to make sense of it.

Co-authored-by: Cursor <cursoragent@cursor.com>
Adds V1-compatible wrappers for the Arctic verl backend so a single
plugin install works against both Snowflake-AI-Research/verl (V0) and
verl-project/verl main (V1). V0 modules are untouched.

New:

* integrations/verl/v1/worker.py: ArcticV1ActorRolloutRefWorker.
  Subclasses verl V1's ActorRolloutRefWorker; skips megatron/veomni
  init paths Arctic doesn't populate, drives async backend RPCs on a
  persistent background asyncio loop (compensates for @register
  losing coroutine-function status), and densifies V1
  nested-jagged batches to V0's dense-padded layout via
  _to_v0_padded_batch. Uses CONFIG-level max_prompt_length /
  max_response_length (not batch-local maxes) so ZoRRo's
  Qwen3ModelOncePatcher can derive the correct prompt/response split.

* integrations/verl/v1/replica.py: ArcticV1Replica. Adapts the V0
  ArcticReplica constructor to V1's
  LLMServerManager.replica_init_kwargs forwarding and swaps in
  ArcticV1LLMServer.

* integrations/verl/v1/server.py: ArcticV1LLMServer. Publishes
  TokenOutput.extra_fields['global_steps'] (V1's field; V0 used the
  now-silently-dropped extra_info kwarg) and exposes set_global_steps
  so the replica can fan the current policy version out after each
  CheckpointEngineManager weight sync.

* integrations/verl/v1/examples/run_gsm8k_grpo_arl_v1.sh: V1-recipe
  smoke test.

Updated:

* integrations/verl/register.py: auto-detect V0 vs V1 based on
  whether verl.trainer.ppo.v1.trainer_remote_backend imports.

Companion verl-core PR:
  https://github.com/verl-project/verl/pull/<TBD>.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Covers layout, source clones, env creation, install order (with the
dss-client-before-arctic-training and torch-reinstall-after-flashinfer
gotchas), data paths, launcher invocation, and the env vars the plugin
keys on. Verified on 8xH200 with torch 2.10.0+cu129 / vllm 0.18.0 /
flashinfer 0.6.6.

Co-authored-by: Cursor <cursoragent@cursor.com>
@sfc-gh-kganesan

Copy link
Copy Markdown
Collaborator Author

Rebased on latest main + the Cortex-shim rebase branch (companion PR#55). One merge conflict in arctic_platform/integrations/verl/register.py (V0/V1 detect split vs. require_any_dep_group("verl") guard) — kept both. All four commits reapplied cleanly.

CPU-only V1 seam smoke against the rebased verl PR (verl-project/verl#7102, HEAD 6329d45b):

  • verl.trainer.ppo.v1.trainer_remote_backend.PPOTrainerRemoteBackend imports
  • RemoteBackendRegistry["arctic"] = ArcticRLClientWrapper (auto-detect selects V1)
  • V1 lazy worker loader → arctic_platform.integrations.verl.v1.worker.ArcticV1ActorRolloutRefWorker
  • V1 replica registered → arctic_platform.integrations.verl.v1.replica.ArcticV1Replica
  • get_trainer_cls("remote_backend")PPOTrainerRemoteBackend

GPU E2E on the rebased stack is queued (Cortex QA6 currently occupied by the PR#55 SkyRL run); will post GSM8K convergence numbers once the slot frees.

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.

1 participant