Skip to content

feat(recipe): Add Qwen2.5-7B ACK agentic recipe support - #1

Open
zlaazlaa wants to merge 2 commits into
alibaba:feat/add-agentic-training-examplefrom
zlaazlaa:feat/add-agentic-training-example
Open

feat(recipe): Add Qwen2.5-7B ACK agentic recipe support#1
zlaazlaa wants to merge 2 commits into
alibaba:feat/add-agentic-training-examplefrom
zlaazlaa:feat/add-agentic-training-example

Conversation

@zlaazlaa

Copy link
Copy Markdown

Summary

This PR adds an ACK-oriented Qwen2.5-7B agentic training script and exposes the Harbor ACK base image registry option for SWE-bench runs.

Changes

  • Add agentic-qwen2.5-7b.sh for running the SWE-bench agentic demo with Qwen2.5-7B.
  • Support ACK runtime parameters:
    • REGISTRY
    • IMAGE_PULL_SECRET
    • SERVICE_ACCOUNT
    • USE_BUILDKIT
    • BUILDKIT_ADDRESS
    • sandbox settings
  • Add BASE_IMAGE_REGISTRY and pass it to Harbor as base_image_registry.

Harbor dependency

This PR is intended to be used together with the Harbor PR:

The Harbor PR fixes the ACK Kubernetes stream client isolation issue and adds support for rewriting Dockerfile base image sources during BuildKit builds.

This recipe exposes that capability through:

BASE_IMAGE_REGISTRY=...

For example, users can avoid Docker Hub pull limits by pre-syncing SWE-bench base images into ACR and then running:

BASE_IMAGE_REGISTRY=zlaa-test-registry-vpc.us-east-1.cr.aliyuncs.com/zlaa

In that setup, Harbor uses the ACK IMAGE_PULL_SECRET / ACR credentials and pulls base images through the internal ACR VPC endpoint during BuildKit builds, improving speed and reducing 429 Too Many Requests risk.

Example

  ENVIRONMENT=ack \
  MODEL_PATH=/var/model/Qwen2.5-7B-Instruct \
  HARBOR_TASK_DIR=/var/model-dataset/swebench-verified \
  REGISTRY='zlaa-test-registry-vpc.us-east-1.cr.aliyuncs.com/zlaa' \
  IMAGE_PULL_SECRET=acr-credential-secret-aggregation \
  BASE_IMAGE_REGISTRY='zlaa-test-registry-vpc.us-east-1.cr.aliyuncs.com/zlaa' \
  SERVICE_ACCOUNT=rayclustertest \
  bash agentic/agentic-qwen2.5-7b.sh

KunWuLuan pushed a commit that referenced this pull request Aug 13, 2026
Author: @sophiayyya @hou2lin

## Summary

Add a recipe-side **Dynamo** rollout backend for verl training
(`actor_rollout_ref.rollout.name=dynamo`).

The backend runs [NVIDIA Dynamo](https://github.com/ai-dynamo/dynamo) as
an async rollout engine: it brings up the Dynamo control plane (etcd +
NATS), launches one `dynamo.vllm` worker per rollout GPU, and serves
generation through a single Dynamo **frontend** whose **KV-aware
router** (`router_mode=kv`) picks the worker with the best prefix-cache
overlap. Everything is confined to `recipe/dynamo` and is opt-in via
config — no changes to verl core semantics. Generation, sleep/wake,
KV-cache clearing, global-step propagation, and bucketed weight updates
from the trainer all work in the hybrid (colocated) topology.

## Architecture

```
                         verl trainer (hybrid, colocated on the rollout GPUs)
                                          │
                DynamoAgentLoopManager ──▶ DynamoLLMServerManager ──▶ DynamoServerManager
                                          │        (one shared frontend, no verl-side LB)
                                          ▼
                              Dynamo frontend  (KV-aware router)
                                          │  HTTP  /v1/completions
                    ┌─────────────────────┼─────────────────────┐
                    ▼                     ▼                     ▼
             dynamo.vllm #0        dynamo.vllm #1   ...   dynamo.vllm #N     
             + ZMQ control         + ZMQ control          + ZMQ control      (weight update / sleep / kv-clear)
                    └──────── etcd + NATS (control plane, master node) ──────┘
```

Routing lives inside Dynamo's KV router, **not** in verl's
`GlobalRequestLoadBalancer`; verl only ever talks to the single shared
frontend actor.

## Changes (all under `recipe/dynamo/`)

**Entry point & registration**
- `main_dynamo.py` — training entry point; runs `main_ppo.run_ppo` with
the dynamo Hydra config.
- `config/dynamo_trainer.yaml` — inherits `ppo_trainer`; only deltas are
`rollout.name=dynamo` and `rollout.mode=async`. All dynamo-specific
knobs live under `rollout.engine_kwargs.dynamo.*`.
- `register.py` — for `VERL_USE_EXTERNAL_MODULES`: registers
`DynamoReplica` in `RolloutReplicaRegistry`, wires `(dynamo, async) →
dynamo_rollout.ServerAdapter`, and monkeypatches `LLMServerManager →
DynamoLLMServerManager`.

**Serving / lifecycle (`dynamo_async_server.py`)**
- `DynamoReplica` (`RolloutReplica`) — owns one logical serving replica
across N nodes. `init_hybrid_worker_pool()` takes over all rollout GPUs,
maps workers → nodes/GPU ids with per-shard rank offsets, and launches
one `DynamoHttpServer` actor per node (master starts etcd/NATS/frontend,
slaves wait on the master address, then all wait for frontend
readiness). Enforces `TP ≤ gpus_per_node` (CUDA-IPC constraint).
- `DynamoHttpServer` — Ray actor acting as GPU placeholder + Dynamo
subprocess watchdog. Boots subprocesses in order (etcd → NATS →
`dynamo.vllm` workers → frontend), serves `generate()` over HTTP to the
frontend (aiohttp), and bridges control RPCs (`collective_rpc`,
`wake_up`, `sleep`, `clear_kv_cache`, `set_global_steps`) to the
per-worker ZMQ control sidecars. Optionally publishes per-worker
`/metrics` endpoints for KV telemetry.

**Agent loop / server manager (`dynamo_agent_loop.py`)**
- `DynamoServerManager` — talks to the **single** shared frontend actor
(asserts exactly one server; intentionally bypasses verl's request load
balancer since Dynamo owns routing).
- `DynamoLLMServerManager` (`LLMServerManager`) — builds the
`DynamoReplica` in hybrid mode and, when
`rollout.prometheus.enable=true`, wires the frontend into the prometheus
target (requires `disable_log_stats=False`).
- `DynamoAgentLoopManager` / `DynamoAgentLoopWorker` — thin
`AgentLoopManager` subclasses so the standard agent-loop execution model
runs against the Dynamo server manager.

**Trainer-side rollout adapter (`dynamo_rollout.py`)**
- `ServerAdapter` (extends the vLLM async `ServerAdapter`) — HTTP
generation path is unchanged; overrides only the Ray actor naming so
control RPCs land on `dynamo_server_*`. Gates control RPCs to **one
trainer rank per physical node** (`_is_node_control_rank`), and
implements `update_weights()` via `BucketedWeightSender` racing the
engine-side IPC receiver, followed by per-node `clear_kv_cache` +
`set_global_steps`.

**vLLM worker integration**
- `dynamo_worker_extension.py` — `vLLMDynamoColocateWorkerExtension`
overrides `_get_zmq_handle()` to add `VERL_DYNAMO_RANK_OFFSET` to the
TP-local rank, so trainer-side sender and engine-side receiver agree on
the IPC socket path in the multi-DP-per-node topology. Also wraps
`update_weights_from_ipc` in `set_current_vllm_config` for vLLM 0.20
compatibility.
- `_dynamo_vllm_with_control.py` — wrapper around `python -m
dynamo.vllm` that injects a verl-private **ZMQ control sidecar**: it
captures the `AsyncLLM` engine and serves `collective_rpc` /
`engine_method` / `generate_direct` requests (endpoint from
`VERL_DYNAMO_CONTROL_ZMQ`), enabling weight updates and a
direct-generate fallback.

**Telemetry**
- `metrics_sidecar.py` — stdlib-only scraper that appends per-target
`/metrics` snapshots to JSONL over the whole job lifetime.
`--targets-glob` (prometheus targets, vLLM arm) / `--endpoints-glob`
(dynamo worker `/metrics`, written when
`enable_worker_system_metrics=true`). Captures engine-level
`vllm:prefix_cache_hits_total` / `vllm:prefix_cache_queries_total` so
Dynamo and vLLM KV hit rates are computed from the **same underlying
vLLM engine counters**.

### Key `engine_kwargs.dynamo.*` knobs
| key | default | purpose |
|---|---|---|
| `namespace` | `verl_dynamo` | Dynamo cluster namespace |
| `router_mode` | `kv` | routing strategy (`kv` = KV-aware) |
| `frontend_http_port` / `etcd_port` / `etcd_peer_port` / `nats_port` |
`0` (auto) | control-plane / frontend ports |
| `free_engine_on_train` | `false` | sleep/wake workers around training
steps |
| `enable_worker_system_metrics` | `true` | expose per-worker `/metrics`
(engine-level prefix-cache counters) |
| `frontend_connection_limit` | `0` (unlimited) | aiohttp connector
limit to the frontend |
| `request_timeout_s` | `600` | frontend HTTP request timeout |
| `extra_args` / `frontend_extra_args` | `[]` | pass-through CLI args to
worker / frontend |

## Validation
- Experiment: retool
- Model: Qwen30B
- wandb report:
https://wandb.ai/yangjingyi_algo/verl_Dynamo_compare?nw=nwuseryangjingyi_algo
- Dynamo (kv) is ~7% faster than vLLM ;

<img width="333" height="274" alt="image"
src="https://github.com/user-attachments/assets/a0572271-9da8-4b0a-b556-f0541dc7fddc"
/>

<meta charset="utf-8"><b style="font-weight:normal;"
id="docs-internal-guid-f97569ab-7fff-e09d-4aea-f22d880214aa"><p
dir="ltr"
style="line-height:1.38;background-color:#ffffff;margin-top:0pt;margin-bottom:5pt;"></p><div
dir="ltr" style="margin-left:0pt;" align="left">
Config | ms/token | kv cache hit rate | Resp_len mean
-- | -- | -- | --
Dynamo kv, interval=100 | 1.5956 | 89.21% | 876.1
vLLM baseline | 1.7220 | 76.51% | 872.3


</div></b>

---------

Co-authored-by: Sophia Yang <sopyang@cw-dfw-cs-001-dc-02.cm.cluster>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Sophia Yang <sopyang@r6515-0154.ipp3a1.colossus.nvidia.com>
Co-authored-by: Sophia Yang <sopyang@cw-dfw-cs-001-vscode-01.cm.cluster>
Co-authored-by: OpenAI Codex <codex@openai.com>
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