From ed2e65e2ba41a2324593bc116d0ac2ba00108d7b Mon Sep 17 00:00:00 2001 From: Jinyan Chen Date: Mon, 6 Jul 2026 15:52:07 +0800 Subject: [PATCH 01/15] feat(dynamo): integrate ThunderAgent with verl training Integrate ThunderAgent program-aware routing with the PR #110 Dynamo backend, wire agent lifecycle and finalization, pin the tested verl revision, and add documentation and targeted tests. --- README.md | 1 + dynamo/README.md | 105 +++++ dynamo/REQUIRED_VERL.txt | 5 + dynamo/config/dynamo_trainer.yaml | 5 + dynamo/dynamo_agent_loop.py | 64 ++- dynamo/dynamo_thunderagent.py | 296 +++++++++++++ dynamo/main_dynamo.py | 16 +- dynamo/register.py | 4 +- dynamo/tests/test_dynamo_thunderagent.py | 509 +++++++++++++++++++++++ dynamo/tests/test_thunderagent.py | 123 ++++++ dynamo/thunderagent.py | 96 +++++ 11 files changed, 1215 insertions(+), 9 deletions(-) create mode 100644 dynamo/REQUIRED_VERL.txt create mode 100644 dynamo/dynamo_thunderagent.py create mode 100644 dynamo/tests/test_dynamo_thunderagent.py create mode 100644 dynamo/tests/test_thunderagent.py create mode 100644 dynamo/thunderagent.py diff --git a/README.md b/README.md index ef7a006b..55af1c57 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,7 @@ The script requires only `bash`, `git`, `awk`, and `pip`/`pip3` on `PATH`. It do | collabllm | [`recipe/collabllm/REQUIRED_VERL.txt`](collabllm/REQUIRED_VERL.txt) | | dapo | [`recipe/dapo/REQUIRED_VERL.txt`](dapo/REQUIRED_VERL.txt) | | deepeyes | [`recipe/deepeyes/REQUIRED_VERL.txt`](deepeyes/REQUIRED_VERL.txt) | +| dynamo | [`recipe/dynamo/REQUIRED_VERL.txt`](dynamo/REQUIRED_VERL.txt) | | entropy | [`recipe/entropy/REQUIRED_VERL.txt`](entropy/REQUIRED_VERL.txt) | | fapo | [`recipe/fapo/REQUIRED_VERL.txt`](fapo/REQUIRED_VERL.txt) | | fault_recover | [`recipe/fault_recover/REQUIRED_VERL.txt`](fault_recover/REQUIRED_VERL.txt) | diff --git a/dynamo/README.md b/dynamo/README.md index 634898b1..d4286f43 100644 --- a/dynamo/README.md +++ b/dynamo/README.md @@ -261,3 +261,108 @@ python3 -m verl.trainer.main_ppo \ > python one-liners from fighting the outer shell's quoting. Export the paths the > runfile needs (a `--export` list, or write them into the runfile as a header) > so the in-container shell sees them. + +## ThunderAgent extension + +This recipe extends the Dynamo rollout backend from +[verl-recipe PR #110](https://github.com/verl-project/verl-recipe/pull/110) +with program-aware scheduling for multi-turn agent trajectories. It does not +modify core `verl` or Dynamo. + +### Required versions + +- Core `verl`: see [`REQUIRED_VERL.txt`](REQUIRED_VERL.txt). +- Dynamo: source commit `48632da9c77c5a7647b50cf1ba2a729dcdca7aea`. + +ThunderAgent is experimental and is not included in a released Dynamo Python +package. Build or install Dynamo from the commit above; `etcd`, `nats-server`, +`dynamo.vllm`, `dynamo.thunderagent_router`, and `dynamo.frontend` must be +available in the runtime image. + +### Request lifecycle + +Each `AgentLoopBase.run()` is one ThunderAgent program: + +1. The recipe creates one random session ID when a trajectory starts. +2. Every LLM turn sends the same `X-Dynamo-Session-ID`. +3. Concurrent trajectories use task-local, isolated session IDs. +4. On success, error, or cancellation, admitted requests drain before one + `X-Dynamo-Session-Final: true` request releases the program. + +The final request must return no model choices. A non-empty completion is +treated as a routing bypass and fails closed. + +### Topology + +With ThunderAgent enabled, PR #110 launches processes in this order: + +```text +etcd -> NATS -> Dynamo vLLM workers -> ThunderAgent router -> frontend +``` + +The frontend uses round-robin only to reach the registered ThunderAgent +handler. ThunderAgent owns the internal KV router and forwards to the worker +endpoint `.backend.generate`. Shutdown reverses the consumer side: +frontend, ThunderAgent, workers, NATS, then etcd. + +### Configuration + +The default recipe enables ThunderAgent: + +```yaml +actor_rollout_ref: + rollout: + agent: + agent_loop_manager_class: recipe.dynamo.dynamo_agent_loop.DynamoAgentLoopManager + engine_kwargs: + dynamo: + thunderagent: + enabled: true + router_block_size: 16 +``` + +`router_block_size` is applied to both vLLM and ThunderAgent. Pass scheduler +CLI options without hard-coding them in the recipe: + +```yaml +thunderagent: + enabled: true + router_block_size: 16 + extra_args: + - --pause-threshold + - "0.95" +``` + +Optional finalization controls are `finalize_max_attempts` (default `3`) and +`finalize_retry_delay_s` (default `0.1`). Set `thunderagent.enabled=false` for +the PR #110 KV-router baseline. + +### Run + +From a verl checkout containing this repository at `recipe/`: + +```bash +VERL_USE_EXTERNAL_MODULES=recipe.dynamo.register \ +python -m recipe.dynamo.main_dynamo \ + actor_rollout_ref.model.path=/path/to/model \ + actor_rollout_ref.rollout.tensor_model_parallel_size=1 +``` + +Supply the remaining dataset, trainer, and resource overrides required by the +standard verl PPO configuration. + +### Tests + +```bash +pytest -q recipe/dynamo/tests +ruff check recipe/dynamo/thunderagent.py recipe/dynamo/dynamo_thunderagent.py \ + recipe/dynamo/dynamo_agent_loop.py recipe/dynamo/main_dynamo.py \ + recipe/dynamo/register.py recipe/dynamo/tests +ruff format --check recipe/dynamo/thunderagent.py recipe/dynamo/dynamo_thunderagent.py \ + recipe/dynamo/dynamo_agent_loop.py recipe/dynamo/main_dynamo.py \ + recipe/dynamo/register.py recipe/dynamo/tests +``` + +GPU validation should run the same model, prompts, concurrency, warmup, and +measurement window twice, changing only `thunderagent.enabled`. Record +throughput, latency, KV-cache hit rate, errors, and router pause/resume logs. diff --git a/dynamo/REQUIRED_VERL.txt b/dynamo/REQUIRED_VERL.txt new file mode 100644 index 00000000..18e78fa2 --- /dev/null +++ b/dynamo/REQUIRED_VERL.txt @@ -0,0 +1,5 @@ +# Dynamo ThunderAgent — tested core verl commit +UPSTREAM=https://github.com/verl-project/verl.git +MODE=pinned_commit +COMMIT=d82d2777b5dc3e96a8a45168d02660312707ab98 +PIP_INSTALL=pip install verl@git+https://github.com/verl-project/verl.git@d82d2777b5dc3e96a8a45168d02660312707ab98 diff --git a/dynamo/config/dynamo_trainer.yaml b/dynamo/config/dynamo_trainer.yaml index 9bfe75fc..6c96885b 100644 --- a/dynamo/config/dynamo_trainer.yaml +++ b/dynamo/config/dynamo_trainer.yaml @@ -23,8 +23,13 @@ actor_rollout_ref: rollout: name: dynamo mode: async + agent: + agent_loop_manager_class: recipe.dynamo.dynamo_agent_loop.DynamoAgentLoopManager engine_kwargs: dynamo: + thunderagent: + enabled: true + router_block_size: 16 # All keys optional; sane defaults applied in DynamoHttpServer. # namespace: verl_dynamo # router_mode: kv # round-robin | kv | random | least-loaded diff --git a/dynamo/dynamo_agent_loop.py b/dynamo/dynamo_agent_loop.py index 83044e36..a6392347 100644 --- a/dynamo/dynamo_agent_loop.py +++ b/dynamo/dynamo_agent_loop.py @@ -21,6 +21,7 @@ from __future__ import annotations +from contextlib import asynccontextmanager from typing import Any, Optional from uuid import uuid4 @@ -32,6 +33,9 @@ from verl.workers.rollout.replica import TokenOutput from verl.workers.rollout.utils import update_prometheus_config +from .thunderagent import current_program +from .thunderagent import program_scope as bind_program + class DynamoServerManager: """Direct manager for the shared Dynamo frontend actor. @@ -41,10 +45,28 @@ class DynamoServerManager: frontend, so verl should only call the single shared Dynamo actor. """ - def __init__(self, servers: list[tuple[str, ray.actor.ActorHandle]]): + def __init__( + self, + servers: list[tuple[str, ray.actor.ActorHandle]], + *, + thunderagent_enabled: bool = False, + ): if len(servers) != 1: raise ValueError(f"DynamoServerManager expects exactly one shared server, got {len(servers)}") self.server_address, self.server = servers[0] + self.thunderagent_enabled = thunderagent_enabled + + @asynccontextmanager + async def program_scope(self): + """Bind all turns in one agent-loop run to one Dynamo program.""" + if not self.thunderagent_enabled: + yield + return + async with bind_program(uuid4().hex, self._finalize_program): + yield + + async def _finalize_program(self, session_id: str) -> None: + await self.server.finalize_program.remote(session_id=session_id) async def generate( self, @@ -54,9 +76,14 @@ async def generate( sampling_params: dict[str, Any], image_data: Optional[list[Any]] = None, video_data: Optional[list[Any]] = None, + audio_data: Optional[list[Any]] = None, + mm_processor_kwargs: Optional[dict[str, Any]] = None, **kwargs: Any, ) -> TokenOutput: - return await self.server.generate.remote( + if audio_data is not None or mm_processor_kwargs: + raise RuntimeError("Dynamo frontend generate does not support audio inputs or processor options") + + generate_kwargs = dict( request_id=request_id or uuid4().hex, prompt_ids=prompt_ids, sampling_params=sampling_params, @@ -64,6 +91,17 @@ async def generate( video_data=video_data, **kwargs, ) + if not self.thunderagent_enabled: + return await self.server.generate.remote(**generate_kwargs) + + scope = current_program() + if scope is None: + raise RuntimeError("Dynamo generation requires an active ThunderAgent program") + async with scope.request(): + return await self.server.generate.remote( + **generate_kwargs, + thunderagent_session_id=scope.session_id, + ) class DynamoLLMServerManager(LLMServerManager): @@ -73,9 +111,9 @@ async def _initialize_llm_servers(self, start_rank: int = 0): if self.worker_group is None: raise ValueError("Dynamo rollout requires hybrid mode with an actor rollout worker group") - from recipe.dynamo.dynamo_async_server import DynamoReplica + from recipe.dynamo.dynamo_thunderagent import DynamoThunderAgentReplica - replica = DynamoReplica( + replica = DynamoThunderAgentReplica( replica_rank=start_rank, config=self.rollout_config, model_config=self.model_config, @@ -93,9 +131,25 @@ async def _initialize_llm_servers(self, start_rank: int = 0): raise ValueError("PROMETHEUS needs disable_log_stats==False, but it is currently True.") update_prometheus_config(self.rollout_config.prometheus, self.server_addresses, self.rollout_config.name) + async def _init_global_load_balancer(self) -> None: + """Dynamo owns routing behind its single shared frontend.""" + + def get_client(self, client_cls=None, **kwargs) -> DynamoServerManager: + dynamo_config = (self.rollout_config.engine_kwargs or {}).get("dynamo", {}) or {} + thunderagent_config = dynamo_config.get("thunderagent", {}) or {} + servers = list(zip(self.server_addresses, self.server_handles, strict=True)) + return DynamoServerManager( + servers, + thunderagent_enabled=bool(thunderagent_config.get("enabled", False)), + ) + class DynamoAgentLoopWorker(AgentLoopWorker): - """Compatibility wrapper for Dynamo agent loop workers.""" + """Bind each trajectory to one ThunderAgent program.""" + + async def _run_agent_loop(self, *args, **kwargs): + async with self.llm_client.program_scope(): + return await super()._run_agent_loop(*args, **kwargs) class DynamoAgentLoopManager(AgentLoopManager): diff --git a/dynamo/dynamo_thunderagent.py b/dynamo/dynamo_thunderagent.py new file mode 100644 index 00000000..a56e60db --- /dev/null +++ b/dynamo/dynamo_thunderagent.py @@ -0,0 +1,296 @@ +# Copyright 2026 Bytedance Ltd. and/or its affiliates +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""ThunderAgent extension for the PR #110 Dynamo rollout backend.""" + +from __future__ import annotations + +import asyncio +import json +import logging +import os +import subprocess +import sys +import time +from contextvars import ContextVar +from typing import Any, Optional +from uuid import uuid4 + +import ray + +from .dynamo_async_server import DynamoHttpServer, DynamoReplica + +logger = logging.getLogger(__file__) + +_REQUEST_PROGRAM: ContextVar[tuple[str, bool] | None] = ContextVar( + "dynamo_thunderagent_request_program", + default=None, +) + + +class DynamoThunderAgentHttpServer(DynamoHttpServer): + """Add program-aware routing while preserving PR #110's server stack.""" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._thunderagent_process: Optional[subprocess.Popen] = None + self._thunderagent_log_fp = None + if self._thunderagent_enabled(): + self._router_mode = "round-robin" + + def _thunderagent_config(self) -> dict[str, Any]: + config = self._dynamo_cfg().get("thunderagent", {}) or {} + if not isinstance(config, dict): + raise TypeError("rollout.engine_kwargs.dynamo.thunderagent must be a mapping") + return config + + def _thunderagent_enabled(self) -> bool: + value = self._thunderagent_config().get("enabled", False) + if not isinstance(value, bool): + raise TypeError("rollout.engine_kwargs.dynamo.thunderagent.enabled must be a boolean") + return value + + def _thunderagent_router_block_size(self) -> int: + value = int(self._thunderagent_config().get("router_block_size", 16)) + if value <= 0: + raise ValueError("rollout.engine_kwargs.dynamo.thunderagent.router_block_size must be positive") + return value + + def _thunderagent_extra_args(self) -> list[str]: + value = self._thunderagent_config().get("extra_args", []) or [] + if not isinstance(value, list): + raise TypeError("rollout.engine_kwargs.dynamo.thunderagent.extra_args must be a list") + return [str(arg) for arg in value] + + def _build_thunderagent_cmd(self) -> list[str]: + model_name = self._served_model_name or self.model_config.local_path + return [ + sys.executable, + "-m", + "dynamo.thunderagent_router", + "--endpoint", + f"{self._namespace}.backend.generate", + "--model-name", + str(model_name), + "--model-path", + str(self.model_config.local_path), + "--router-block-size", + str(self._thunderagent_router_block_size()), + "--router-reset-states", + *self._thunderagent_extra_args(), + ] + + def _build_vllm_cmd( + self, + served_model_name: str, + tp: int, + kv_events_config_json: str, + ) -> list[str]: + command = super()._build_vllm_cmd(served_model_name, tp, kv_events_config_json) + if not self._thunderagent_enabled(): + return command + + block_size = str(self._thunderagent_router_block_size()) + configured_block_size = None + for index, arg in enumerate(command): + if arg == "--block-size" and index + 1 < len(command): + configured_block_size = command[index + 1] + elif arg.startswith("--block-size="): + configured_block_size = arg.partition("=")[2] + if configured_block_size is not None and configured_block_size != block_size: + raise ValueError( + f"Dynamo vLLM and ThunderAgent router block sizes must match: {configured_block_size} != {block_size}" + ) + if configured_block_size is None: + command.extend(["--block-size", block_size]) + return command + + def _frontend_router_args(self) -> list[str]: + args = super()._frontend_router_args() + if self._thunderagent_enabled() and "--router-reset-states" not in args: + args.append("--router-reset-states") + return args + + def _start_thunderagent(self) -> None: + if not self._thunderagent_enabled() or self._thunderagent_process is not None: + return + env = os.environ.copy() + env.update(self._dynamo_env_vars()) + log_root = os.environ.get("VERL_DYNAMO_LOG_DIR", "/tmp") + os.makedirs(log_root, exist_ok=True) + log_path = os.path.join(log_root, f"verl_dynamo_replica{self.replica_rank}_thunderagent.log") + self._thunderagent_log_fp = open(log_path, "w") + command = self._build_thunderagent_cmd() + logger.info("[DynamoHttpServer] starting ThunderAgent (log=%s): %s", log_path, " ".join(command)) + self._thunderagent_process = subprocess.Popen( + command, + env=env, + stdout=self._thunderagent_log_fp, + stderr=subprocess.STDOUT, + ) + + def _start_frontend(self) -> None: + if self._thunderagent_enabled(): + self._start_thunderagent() + super()._start_frontend() + + def _raise_if_subprocess_died(self) -> None: + super()._raise_if_subprocess_died() + process = self._thunderagent_process + if process is not None and process.poll() is not None: + raise RuntimeError(f"dynamo ThunderAgent exited rc={process.returncode}") + + def _frontend_headers(self, request_id: str) -> dict[str, str]: + headers = {"X-Request-Id": str(request_id)} + request_program = _REQUEST_PROGRAM.get() + if request_program is not None: + session_id, is_final = request_program + headers["X-Dynamo-Session-ID"] = session_id + if is_final: + headers["X-Dynamo-Session-Final"] = "true" + return headers + + def _build_frontend_completion_payload(self, *args, **kwargs) -> dict[str, Any]: + payload = super()._build_frontend_completion_payload(*args, **kwargs) + if not self._thunderagent_enabled(): + return payload + + nvext = payload.setdefault("nvext", {}) + if not isinstance(nvext, dict): + raise TypeError("Dynamo nvext must be a mapping") + # PR #110 launches one DP=1 vLLM process per shard. Supplying its rank + # avoids a startup race when ThunderAgent pins a newly discovered worker. + nvext.setdefault("dp_rank", 0) + return payload + + async def _frontend_post(self, payload: dict[str, Any], request_id: str) -> tuple[int, str]: + import aiohttp + + session = await self._get_http_session() + timeout = aiohttp.ClientTimeout(total=self._frontend_request_timeout_s()) + async with session.post( + self._frontend_completions_url(), + json=payload, + headers=self._frontend_headers(request_id), + timeout=timeout, + ) as response: + return response.status, await response.text() + + async def generate(self, *args, thunderagent_session_id: Optional[str] = None, **kwargs): + if not self._thunderagent_enabled(): + return await super().generate(*args, **kwargs) + if not thunderagent_session_id: + raise RuntimeError("ThunderAgent generation requires a non-empty session ID") + if self._use_direct_generate(): + raise RuntimeError("direct_generate bypasses ThunderAgent and cannot be enabled") + + token = _REQUEST_PROGRAM.set((thunderagent_session_id, False)) + try: + return await super().generate(*args, **kwargs) + finally: + _REQUEST_PROGRAM.reset(token) + + def _finalize_max_attempts(self) -> int: + attempts = int(self._thunderagent_config().get("finalize_max_attempts", 3)) + if attempts <= 0: + raise ValueError("thunderagent.finalize_max_attempts must be positive") + return attempts + + def _finalize_retry_delay(self) -> float: + delay = float(self._thunderagent_config().get("finalize_retry_delay_s", 0.1)) + if delay < 0: + raise ValueError("thunderagent.finalize_retry_delay_s must be non-negative") + return delay + + @staticmethod + def _validate_final_response(status: int, body: str) -> None: + if not 200 <= status < 300: + raise RuntimeError(f"ThunderAgent final request failed status={status} body={body[:2000]!r}") + if not body.strip(): + return + data = json.loads(body) + if data.get("choices"): + raise RuntimeError("ThunderAgent final request was bypassed and reached a model worker") + + async def finalize_program(self, session_id: str) -> None: + if not self._thunderagent_enabled(): + return + if not session_id: + raise ValueError("session_id must be non-empty") + + request_id = f"thunderagent-final-{uuid4().hex}" + payload = { + "model": self._served_model_name or self.model_config.local_path, + "prompt": [0], + "max_tokens": 1, + "stream": False, + } + token = _REQUEST_PROGRAM.set((session_id, True)) + try: + last_error = None + attempts = self._finalize_max_attempts() + for attempt in range(1, attempts + 1): + try: + status, body = await self._frontend_post(payload, request_id) + except Exception as error: + last_error = error + else: + if status not in {408, 429} and status < 500: + self._validate_final_response(status, body) + return + last_error = RuntimeError(f"ThunderAgent final request failed status={status} body={body[:2000]!r}") + if attempt < attempts: + await asyncio.sleep(self._finalize_retry_delay()) + raise RuntimeError(f"ThunderAgent final request failed after {attempts} attempts") from last_error + finally: + _REQUEST_PROGRAM.reset(token) + + async def _healthcheck_frontend(self, expected_workers: int) -> None: + await super()._healthcheck_frontend(expected_workers) + if self._thunderagent_enabled(): + await self.finalize_program(f"thunderagent-health-{time.time_ns()}") + + async def shutdown(self) -> None: + frontend = self._frontend_process + self._frontend_process = None + if frontend is not None: + self._stop_one(frontend, "frontend", 15) + + thunderagent = self._thunderagent_process + self._thunderagent_process = None + if thunderagent is not None: + self._stop_one(thunderagent, "ThunderAgent", 15) + + try: + await super().shutdown() + finally: + if self._thunderagent_log_fp is not None: + self._thunderagent_log_fp.close() + self._thunderagent_log_fp = None + + def __getstate__(self): + state = super().__getstate__() + state["_thunderagent_process"] = None + state["_thunderagent_log_fp"] = None + return state + + +class DynamoThunderAgentReplica(DynamoReplica): + """Use the ThunderAgent-aware HTTP server for the PR #110 replica.""" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.server_class = ray.remote(DynamoThunderAgentHttpServer) + + +__all__ = ["DynamoThunderAgentHttpServer", "DynamoThunderAgentReplica"] diff --git a/dynamo/main_dynamo.py b/dynamo/main_dynamo.py index da9ce41a..4c047cd3 100644 --- a/dynamo/main_dynamo.py +++ b/dynamo/main_dynamo.py @@ -17,15 +17,27 @@ from verl.experimental.reward_loop import migrate_legacy_reward_impl from verl.trainer.main_ppo import run_ppo -from verl.trainer.main_ppo_v0 import TaskRunner from verl.utils.device import auto_set_device +try: + # Current verl keeps the legacy PPO runner in a dedicated module. + from verl.trainer.main_ppo_v0 import TaskRunner +except ModuleNotFoundError as exc: + if exc.name != "verl.trainer.main_ppo_v0": + raise + # The ThunderAgent-pinned verl revision still selects its TaskRunner when + # run_ppo is called without an explicit runner. + TaskRunner = None + @hydra.main(config_path="config", config_name="dynamo_trainer", version_base=None) def main(config): auto_set_device(config) config = migrate_legacy_reward_impl(config) - run_ppo(config, task_runner_class=TaskRunner) + if TaskRunner is None: + run_ppo(config) + else: + run_ppo(config, task_runner_class=TaskRunner) if __name__ == "__main__": diff --git a/dynamo/register.py b/dynamo/register.py index 087d56d5..b35fd163 100644 --- a/dynamo/register.py +++ b/dynamo/register.py @@ -9,9 +9,9 @@ def _load_dynamo(): - from recipe.dynamo.dynamo_async_server import DynamoReplica + from recipe.dynamo.dynamo_thunderagent import DynamoThunderAgentReplica - return DynamoReplica + return DynamoThunderAgentReplica RolloutReplicaRegistry.register("dynamo", _load_dynamo) diff --git a/dynamo/tests/test_dynamo_thunderagent.py b/dynamo/tests/test_dynamo_thunderagent.py new file mode 100644 index 00000000..09e295ee --- /dev/null +++ b/dynamo/tests/test_dynamo_thunderagent.py @@ -0,0 +1,509 @@ +# Copyright 2026 Bytedance Ltd. and/or its affiliates +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import asyncio +import inspect +import json +import sys +from contextlib import asynccontextmanager +from pathlib import Path +from types import SimpleNamespace + +import pytest +import yaml +from recipe.dynamo import dynamo_async_server, dynamo_thunderagent, register +from recipe.dynamo.dynamo_agent_loop import ( + DynamoAgentLoopWorker, + DynamoLLMServerManager, + DynamoServerManager, +) +from recipe.dynamo.dynamo_async_server import DynamoHttpServer +from recipe.dynamo.dynamo_thunderagent import ( + DynamoThunderAgentHttpServer, + DynamoThunderAgentReplica, +) + +from verl.experimental.agent_loop.agent_loop import AgentLoopWorker + +RECIPE_ROOT = Path(__file__).resolve().parents[1] +REPOSITORY_ROOT = RECIPE_ROOT.parent + + +def _make_http_server(thunderagent: dict | None = None) -> DynamoThunderAgentHttpServer: + server = object.__new__(DynamoThunderAgentHttpServer) + server.config = SimpleNamespace(engine_kwargs={"dynamo": {"thunderagent": thunderagent or {"enabled": True}}}) + server.model_config = SimpleNamespace(local_path="/models/test-model") + server._namespace = "verl_dynamo" + server._served_model_name = "test-model" + server._router_mode = "round-robin" + server.replica_rank = 0 + server._frontend_process = None + server._thunderagent_process = None + server._thunderagent_log_fp = None + return server + + +class _RemoteMethod: + def __init__(self, function): + self.function = function + + async def remote(self, *args, **kwargs): + result = self.function(*args, **kwargs) + if inspect.isawaitable(result): + return await result + return result + + +class _FakeServer: + def __init__(self): + self.generate_calls = [] + self.finalize_calls = [] + self.generate = _RemoteMethod(self._generate) + self.finalize_program = _RemoteMethod(self._finalize_program) + + def _generate(self, **kwargs): + self.generate_calls.append(kwargs) + return kwargs + + def _finalize_program(self, session_id: str) -> None: + self.finalize_calls.append(session_id) + + +def _generate_kwargs(prompt_id: int) -> dict: + return { + "request_id": None, + "prompt_ids": [prompt_id], + "sampling_params": {"max_tokens": 1}, + } + + +@pytest.mark.asyncio +async def test_client_reuses_program_id_for_all_turns_and_finalizes_once() -> None: + server = _FakeServer() + manager = DynamoServerManager([("frontend:8000", server)], thunderagent_enabled=True) + + async with manager.program_scope(): + first = await manager.generate(**_generate_kwargs(1)) + second = await manager.generate(**_generate_kwargs(2)) + + session_id = first["thunderagent_session_id"] + assert session_id + assert second["thunderagent_session_id"] == session_id + assert server.finalize_calls == [session_id] + + +@pytest.mark.asyncio +async def test_client_isolates_concurrent_programs() -> None: + server = _FakeServer() + manager = DynamoServerManager([("frontend:8000", server)], thunderagent_enabled=True) + + async def run(prompt_id: int) -> str: + async with manager.program_scope(): + output = await manager.generate(**_generate_kwargs(prompt_id)) + return output["thunderagent_session_id"] + + first_id, second_id = await asyncio.gather(run(1), run(2)) + assert first_id != second_id + assert sorted(server.finalize_calls) == sorted([first_id, second_id]) + + +@pytest.mark.asyncio +async def test_enabled_client_fails_closed_without_program_scope() -> None: + manager = DynamoServerManager([("frontend:8000", _FakeServer())], thunderagent_enabled=True) + + with pytest.raises(RuntimeError, match="active ThunderAgent program"): + await manager.generate(**_generate_kwargs(1)) + + +@pytest.mark.asyncio +async def test_disabled_client_preserves_pr110_request_path() -> None: + server = _FakeServer() + manager = DynamoServerManager([("frontend:8000", server)], thunderagent_enabled=False) + + async with manager.program_scope(): + await manager.generate(**_generate_kwargs(1)) + + assert "thunderagent_session_id" not in server.generate_calls[0] + assert server.finalize_calls == [] + + +@pytest.mark.asyncio +async def test_client_omits_empty_audio_arguments_unsupported_by_pr110() -> None: + server = _FakeServer() + manager = DynamoServerManager([("frontend:8000", server)], thunderagent_enabled=False) + + await manager.generate( + **_generate_kwargs(1), + audio_data=None, + mm_processor_kwargs=None, + ) + + assert "audio_data" not in server.generate_calls[0] + assert "mm_processor_kwargs" not in server.generate_calls[0] + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + ("unsupported_argument", "value"), + [("audio_data", [b"audio"]), ("mm_processor_kwargs", {"sampling_rate": 16_000})], +) +async def test_client_rejects_unsupported_audio_arguments(unsupported_argument, value) -> None: + server = _FakeServer() + manager = DynamoServerManager([("frontend:8000", server)], thunderagent_enabled=False) + + with pytest.raises(RuntimeError, match="does not support audio inputs"): + await manager.generate(**_generate_kwargs(1), **{unsupported_argument: value}) + + assert server.generate_calls == [] + + +@pytest.mark.asyncio +async def test_agent_loop_worker_wraps_parent_run_in_program_scope(monkeypatch) -> None: + class ScopeClient: + active = False + exits = 0 + + @asynccontextmanager + async def program_scope(self): + self.active = True + try: + yield + finally: + self.active = False + self.exits += 1 + + client = ScopeClient() + worker = object.__new__(DynamoAgentLoopWorker) + worker.llm_client = client + + async def parent_run(self, *args, **kwargs): + assert self.llm_client.active + assert args == ("sampling", "trajectory") + assert kwargs == {"agent_name": "tool_agent"} + return "output" + + monkeypatch.setattr(AgentLoopWorker, "_run_agent_loop", parent_run) + + assert await worker._run_agent_loop("sampling", "trajectory", agent_name="tool_agent") == "output" + assert not client.active + assert client.exits == 1 + + +@pytest.mark.asyncio +async def test_agent_loop_worker_closes_scope_when_parent_fails(monkeypatch) -> None: + class ScopeClient: + exits = 0 + + @asynccontextmanager + async def program_scope(self): + try: + yield + finally: + self.exits += 1 + + client = ScopeClient() + worker = object.__new__(DynamoAgentLoopWorker) + worker.llm_client = client + + async def parent_run(_self, *_args, **_kwargs): + raise ValueError("agent failed") + + monkeypatch.setattr(AgentLoopWorker, "_run_agent_loop", parent_run) + + with pytest.raises(ValueError, match="agent failed"): + await worker._run_agent_loop("sampling", "trajectory", agent_name="tool_agent") + assert client.exits == 1 + + +@pytest.mark.asyncio +async def test_server_manager_returns_direct_thunderagent_client() -> None: + assert "_init_global_load_balancer" in DynamoLLMServerManager.__dict__ + manager = object.__new__(DynamoLLMServerManager) + manager.server_addresses = ["frontend:8000"] + manager.server_handles = [_FakeServer()] + manager.rollout_config = SimpleNamespace(engine_kwargs={"dynamo": {"thunderagent": {"enabled": True}}}) + + await manager._init_global_load_balancer() + client = manager.get_client() + + assert isinstance(client, DynamoServerManager) + assert client.thunderagent_enabled is True + assert not hasattr(manager, "global_load_balancer") + + +def test_thunderagent_command_derives_endpoint_model_and_block_size() -> None: + server = _make_http_server() + + assert server._build_thunderagent_cmd() == [ + sys.executable, + "-m", + "dynamo.thunderagent_router", + "--endpoint", + "verl_dynamo.backend.generate", + "--model-name", + "test-model", + "--model-path", + "/models/test-model", + "--router-block-size", + "16", + "--router-reset-states", + ] + + +def test_thunderagent_command_passes_configured_router_options() -> None: + server = _make_http_server( + { + "enabled": True, + "router_block_size": 32, + "extra_args": ["--pause-threshold", "0.9"], + } + ) + + command = server._build_thunderagent_cmd() + + assert command[command.index("--router-block-size") + 1] == "32" + assert command[-2:] == ["--pause-threshold", "0.9"] + + +def test_thunderagent_extra_args_must_be_a_list() -> None: + server = _make_http_server({"enabled": True, "extra_args": "--pause-threshold 0.9"}) + + with pytest.raises(TypeError, match="extra_args must be a list"): + server._build_thunderagent_cmd() + + +def test_vllm_and_thunderagent_share_block_size(monkeypatch) -> None: + server = _make_http_server({"enabled": True, "router_block_size": 32}) + monkeypatch.setattr( + DynamoHttpServer, + "_build_vllm_cmd", + lambda _self, _model, _tp, kv_events_config_json: ["python", "-m", "dynamo.vllm"], + ) + + command = server._build_vllm_cmd("test-model", 1, "{}") + + assert command[-2:] == ["--block-size", "32"] + + +def test_thunderagent_payload_supplies_worker_dp_rank(monkeypatch) -> None: + server = _make_http_server() + monkeypatch.setattr( + DynamoHttpServer, + "_build_frontend_completion_payload", + lambda _self, _prompt, _sampling, _request_id: {"nvext": {"extra_fields": ["engine_data"]}}, + ) + + payload = server._build_frontend_completion_payload([1], {}, "request-a") + + assert payload["nvext"] == {"extra_fields": ["engine_data"], "dp_rank": 0} + + +def test_frontend_start_starts_thunderagent_first(monkeypatch) -> None: + server = _make_http_server() + events = [] + monkeypatch.setattr(server, "_start_thunderagent", lambda: events.append("thunderagent")) + monkeypatch.setattr(DynamoHttpServer, "_start_frontend", lambda _self: events.append("frontend")) + + server._start_frontend() + + assert events == ["thunderagent", "frontend"] + + +@pytest.mark.asyncio +async def test_generation_adds_program_header(monkeypatch) -> None: + server = _make_http_server() + monkeypatch.setattr(server, "_use_direct_generate", lambda: False) + + async def base_generate(self, *_args, **_kwargs): + return self._frontend_headers("request-a") + + monkeypatch.setattr(DynamoHttpServer, "generate", base_generate) + + headers = await server.generate( + prompt_ids=[1], + sampling_params={"max_tokens": 1}, + request_id="request-a", + thunderagent_session_id="program-a", + ) + + assert headers == { + "X-Request-Id": "request-a", + "X-Dynamo-Session-ID": "program-a", + } + + +@pytest.mark.asyncio +async def test_generation_requires_program_and_rejects_direct_bypass(monkeypatch) -> None: + server = _make_http_server() + + with pytest.raises(RuntimeError, match="session ID"): + await server.generate(prompt_ids=[1], sampling_params={}, request_id="request-a") + + monkeypatch.setattr(server, "_use_direct_generate", lambda: True) + with pytest.raises(RuntimeError, match="direct_generate"): + await server.generate( + prompt_ids=[1], + sampling_params={}, + request_id="request-a", + thunderagent_session_id="program-a", + ) + + +@pytest.mark.asyncio +async def test_finalize_retries_and_accepts_empty_choices(monkeypatch) -> None: + server = _make_http_server( + { + "enabled": True, + "finalize_max_attempts": 2, + "finalize_retry_delay_s": 0, + } + ) + responses = [(503, "temporarily unavailable"), (200, json.dumps({"choices": []}))] + calls = [] + + async def frontend_post(_payload, request_id): + calls.append(server._frontend_headers(request_id)) + return responses.pop(0) + + monkeypatch.setattr(server, "_frontend_post", frontend_post) + + await server.finalize_program("program-a") + + assert len(calls) == 2 + assert calls[-1]["X-Dynamo-Session-ID"] == "program-a" + assert calls[-1]["X-Dynamo-Session-Final"] == "true" + + +@pytest.mark.asyncio +async def test_finalize_rejects_nonempty_choices_as_router_bypass(monkeypatch) -> None: + server = _make_http_server({"enabled": True, "finalize_max_attempts": 1}) + + async def frontend_post(_payload, _request_id): + return 200, json.dumps({"choices": [{"text": "model answered"}]}) + + monkeypatch.setattr(server, "_frontend_post", frontend_post) + + with pytest.raises(RuntimeError, match="bypassed"): + await server.finalize_program("program-a") + + +def test_watchdog_detects_thunderagent_exit(monkeypatch) -> None: + server = _make_http_server() + server._thunderagent_process = SimpleNamespace(poll=lambda: 9, returncode=9) + monkeypatch.setattr(DynamoHttpServer, "_raise_if_subprocess_died", lambda _self: None) + + with pytest.raises(RuntimeError, match="ThunderAgent.*rc=9"): + server._raise_if_subprocess_died() + + +@pytest.mark.asyncio +async def test_shutdown_stops_frontend_then_thunderagent_then_base(monkeypatch) -> None: + server = _make_http_server() + server._frontend_process = object() + server._thunderagent_process = object() + events = [] + + monkeypatch.setattr(server, "_stop_one", lambda _process, name, _timeout: events.append(name)) + + async def base_shutdown(_self): + events.append("base-workers-and-infra") + + monkeypatch.setattr(DynamoHttpServer, "shutdown", base_shutdown) + + await server.shutdown() + + assert events == ["frontend", "ThunderAgent", "base-workers-and-infra"] + + +def test_registered_replica_uses_thunderagent_server_class() -> None: + assert DynamoThunderAgentReplica.__name__ == "DynamoThunderAgentReplica" + + +def test_recipe_registry_loads_thunderagent_replica() -> None: + assert register._load_dynamo() is DynamoThunderAgentReplica + + +@pytest.mark.asyncio +async def test_llm_server_manager_constructs_thunderagent_replica(monkeypatch) -> None: + constructed = [] + + class FakeReplica: + def __init__(self, **kwargs): + constructed.append(kwargs) + self._server_handle = object() + self._server_address = "frontend:8000" + + async def init_hybrid_worker_pool(self, worker_group): + assert worker_group == "worker-group" + + def reject_base_replica(**_kwargs): + raise AssertionError("base PR #110 replica bypasses ThunderAgent") + + monkeypatch.setattr(dynamo_thunderagent, "DynamoThunderAgentReplica", FakeReplica) + monkeypatch.setattr(dynamo_async_server, "DynamoReplica", reject_base_replica) + manager = object.__new__(DynamoLLMServerManager) + manager.worker_group = "worker-group" + manager.rollout_config = SimpleNamespace( + n_gpus_per_node=8, + prometheus=SimpleNamespace(enable=False), + name="dynamo", + ) + manager.model_config = SimpleNamespace(local_path="test-model") + + await manager._initialize_llm_servers() + + assert constructed == [ + { + "replica_rank": 0, + "config": manager.rollout_config, + "model_config": manager.model_config, + "gpus_per_node": 8, + } + ] + + +def test_recipe_config_enables_thunderagent_agent_loop() -> None: + config = yaml.safe_load((RECIPE_ROOT / "config" / "dynamo_trainer.yaml").read_text()) + rollout = config["actor_rollout_ref"]["rollout"] + + assert rollout["agent"]["agent_loop_manager_class"] == ("recipe.dynamo.dynamo_agent_loop.DynamoAgentLoopManager") + assert rollout["engine_kwargs"]["dynamo"]["thunderagent"] == { + "enabled": True, + "router_block_size": 16, + } + + +def test_recipe_pins_tested_verl_and_dynamo_revisions() -> None: + required_verl = (RECIPE_ROOT / "REQUIRED_VERL.txt").read_text() + readme = (RECIPE_ROOT / "README.md").read_text() + repository_readme = (REPOSITORY_ROOT / "README.md").read_text() + + assert "MODE=pinned_commit" in required_verl + assert "COMMIT=d82d2777b5dc3e96a8a45168d02660312707ab98" in required_verl + assert "48632da9c77c5a7647b50cf1ba2a729dcdca7aea" in readme + assert "dynamo/REQUIRED_VERL.txt" in repository_readme + + +def test_training_entrypoint_supports_pinned_and_current_verl_runner(monkeypatch) -> None: + from recipe.dynamo import main_dynamo + + calls = [] + monkeypatch.setattr(main_dynamo, "auto_set_device", lambda _config: None) + monkeypatch.setattr(main_dynamo, "migrate_legacy_reward_impl", lambda config: config) + monkeypatch.setattr(main_dynamo, "run_ppo", lambda config, **kwargs: calls.append((config, kwargs))) + + main_dynamo.main.__wrapped__("config") + + expected_kwargs = {} if main_dynamo.TaskRunner is None else {"task_runner_class": main_dynamo.TaskRunner} + assert calls == [("config", expected_kwargs)] diff --git a/dynamo/tests/test_thunderagent.py b/dynamo/tests/test_thunderagent.py new file mode 100644 index 00000000..47d46824 --- /dev/null +++ b/dynamo/tests/test_thunderagent.py @@ -0,0 +1,123 @@ +# Copyright 2026 Bytedance Ltd. and/or its affiliates +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import asyncio + +import pytest + +try: + from recipe.dynamo.thunderagent import ProgramScope, current_program, program_scope +except ModuleNotFoundError: + from dynamo.thunderagent import ProgramScope, current_program, program_scope + + +async def _hold_request(scope: ProgramScope, entered: asyncio.Event, release: asyncio.Event) -> None: + async with scope.request(): + entered.set() + await release.wait() + + +@pytest.mark.asyncio +async def test_program_scope_binds_id_and_finalizes_once() -> None: + finalized = [] + + async with program_scope("program-a", finalized.append): + assert current_program() is not None + assert current_program().session_id == "program-a" + + assert current_program() is None + assert finalized == ["program-a"] + + +@pytest.mark.asyncio +async def test_program_scope_finalizes_after_body_error() -> None: + finalized = [] + + with pytest.raises(ValueError, match="agent failed"): + async with program_scope("program-a", finalized.append): + raise ValueError("agent failed") + + assert finalized == ["program-a"] + + +@pytest.mark.asyncio +async def test_close_waits_for_inflight_request_and_is_idempotent() -> None: + scope = ProgramScope("program-a") + entered = asyncio.Event() + release = asyncio.Event() + finalized = [] + request = asyncio.create_task(_hold_request(scope, entered, release)) + await entered.wait() + + scope.stop_accepting_requests() + first_close = asyncio.create_task(scope.close(finalized.append)) + second_close = asyncio.create_task(scope.close(finalized.append)) + await asyncio.sleep(0) + assert finalized == [] + + release.set() + await request + await asyncio.gather(first_close, second_close) + assert finalized == ["program-a"] + + +@pytest.mark.asyncio +async def test_closing_scope_rejects_late_request() -> None: + scope = ProgramScope("program-a") + scope.stop_accepting_requests() + + with pytest.raises(RuntimeError, match="closing"): + async with scope.request(): + pass + + +@pytest.mark.asyncio +async def test_contextvars_isolate_concurrent_programs() -> None: + finalized = [] + both_entered = asyncio.Barrier(2) + + async def run(session_id: str) -> str: + async with program_scope(session_id, finalized.append): + await both_entered.wait() + assert current_program() is not None + return current_program().session_id + + assert await asyncio.gather(run("program-a"), run("program-b")) == ["program-a", "program-b"] + assert sorted(finalized) == ["program-a", "program-b"] + + +@pytest.mark.asyncio +async def test_cancellation_waits_for_finalizer() -> None: + body_entered = asyncio.Event() + finalizer_entered = asyncio.Event() + allow_finalizer = asyncio.Event() + + async def finalize(_session_id: str) -> None: + finalizer_entered.set() + await allow_finalizer.wait() + + async def run() -> None: + async with program_scope("program-a", finalize): + body_entered.set() + await asyncio.Event().wait() + + task = asyncio.create_task(run()) + await body_entered.wait() + task.cancel() + await finalizer_entered.wait() + assert not task.done() + + allow_finalizer.set() + with pytest.raises(asyncio.CancelledError): + await task diff --git a/dynamo/thunderagent.py b/dynamo/thunderagent.py new file mode 100644 index 00000000..20c6af31 --- /dev/null +++ b/dynamo/thunderagent.py @@ -0,0 +1,96 @@ +# Copyright 2026 Bytedance Ltd. and/or its affiliates +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Task-local lifecycle for one Dynamo ThunderAgent program.""" + +from __future__ import annotations + +import asyncio +import inspect +from collections.abc import AsyncIterator, Awaitable, Callable +from contextlib import asynccontextmanager +from contextvars import ContextVar + +Finalize = Callable[[str], Awaitable[None] | None] + + +class ProgramScope: + """Track requests belonging to one agent trajectory.""" + + def __init__(self, session_id: str): + if not session_id: + raise ValueError("session_id must be non-empty") + self.session_id = session_id + self._accepting_requests = True + self._inflight = 0 + self._idle = asyncio.Event() + self._idle.set() + self._finalized = False + self._finalize_lock = asyncio.Lock() + + @asynccontextmanager + async def request(self) -> AsyncIterator[None]: + """Admit one request, or reject it once program shutdown starts.""" + if not self._accepting_requests: + raise RuntimeError(f"ThunderAgent program {self.session_id!r} is closing") + self._inflight += 1 + self._idle.clear() + try: + yield + finally: + self._inflight -= 1 + if self._inflight == 0: + self._idle.set() + + def stop_accepting_requests(self) -> None: + self._accepting_requests = False + + async def close(self, finalize: Finalize) -> None: + """Drain admitted requests and finalize the program exactly once.""" + self.stop_accepting_requests() + await self._idle.wait() + async with self._finalize_lock: + if self._finalized: + return + result = finalize(self.session_id) + if inspect.isawaitable(result): + await result + self._finalized = True + + +_CURRENT_PROGRAM: ContextVar[ProgramScope | None] = ContextVar("dynamo_program", default=None) + + +def current_program() -> ProgramScope | None: + return _CURRENT_PROGRAM.get() + + +@asynccontextmanager +async def program_scope(session_id: str, finalize: Finalize) -> AsyncIterator[ProgramScope]: + """Bind a program to this task and finalize it on every exit path.""" + scope = ProgramScope(session_id) + token = _CURRENT_PROGRAM.set(scope) + try: + yield scope + finally: + scope.stop_accepting_requests() + _CURRENT_PROGRAM.reset(token) + close_task = asyncio.create_task(scope.close(finalize)) + try: + await asyncio.shield(close_task) + except asyncio.CancelledError: + await close_task + raise + + +__all__ = ["ProgramScope", "current_program", "program_scope"] From 922793ff8f6eaae120ff331ad6ffae014c67cb72 Mon Sep 17 00:00:00 2001 From: Sophia Yang Date: Mon, 20 Jul 2026 10:18:09 -0700 Subject: [PATCH 02/15] modify TA readme --- dynamo/dynamo_thunderagent.py | 11 +- dynamo/run_uniagent_variant.sh | 330 +++++++++++++++++++++++++++++++++ 2 files changed, 339 insertions(+), 2 deletions(-) create mode 100644 dynamo/run_uniagent_variant.sh diff --git a/dynamo/dynamo_thunderagent.py b/dynamo/dynamo_thunderagent.py index a56e60db..7e312c6f 100644 --- a/dynamo/dynamo_thunderagent.py +++ b/dynamo/dynamo_thunderagent.py @@ -36,6 +36,7 @@ "dynamo_thunderagent_request_program", default=None, ) +_THUNDERAGENT_BACKEND_MODEL_SUFFIX = "--verl-thunderagent-backend" class DynamoThunderAgentHttpServer(DynamoHttpServer): @@ -96,8 +97,14 @@ def _build_vllm_cmd( tp: int, kv_events_config_json: str, ) -> list[str]: - command = super()._build_vllm_cmd(served_model_name, tp, kv_events_config_json) - if not self._thunderagent_enabled(): + thunderagent_enabled = self._thunderagent_enabled() + worker_model_name = ( + f"{served_model_name}{_THUNDERAGENT_BACKEND_MODEL_SUFFIX}" + if thunderagent_enabled + else served_model_name + ) + command = super()._build_vllm_cmd(worker_model_name, tp, kv_events_config_json) + if not thunderagent_enabled: return command block_size = str(self._thunderagent_router_block_size()) diff --git a/dynamo/run_uniagent_variant.sh b/dynamo/run_uniagent_variant.sh new file mode 100644 index 00000000..fb63fe66 --- /dev/null +++ b/dynamo/run_uniagent_variant.sh @@ -0,0 +1,330 @@ +#!/bin/bash +# Launch one arm of the uni-agent SWE x ThunderAgent benchmark. +# +# Usage: +# VARIANT=global|dynamo|ta RUN_NAME= [STEPS=3] [BATCH=32] [N=8] \ +# [MAX_TURNS=50] [CONCURRENCY=256] [MAX_NUM_SEQS=128] [GATE=0] \ +# [ROLLOUT_ONLY=0] [DYNAMO_REQUEST_TIMEOUT_S=1800] \ +# [MODEL_DIR=models/Qwen3-Coder-30B-A3B-Instruct] bash run_uniagent_variant.sh +# +# GATE=1 shrinks to a 1-step correctness gate (BATCH=8 N=2 unless overridden). +set -euo pipefail + +BENCH=${BENCH:?set BENCH to this reproduction package directory} +VERL_WT=${VERL_WT:?set VERL_WT to the Verl worktree} +RECIPE_WT=${RECIPE_WT:?set RECIPE_WT to the verl-recipe worktree} +UNIAGENT=${UNIAGENT:?set UNIAGENT to the uni-agent source checkout} +IMAGE=${IMAGE:-verl-recipe-thunderagent:pr11185-fa2.8.3-uni-agent-docker} +DYNAMO_WT=${DYNAMO_WT:-} +DOCKER_GID=$(stat -c '%g' /var/run/docker.sock) +NETWORK=${NETWORK:-uniagent-bench} + +VARIANT=${VARIANT:?global|dynamo|ta} +RUN_NAME=${RUN_NAME:?} + +# Temporary benchmark queue hold: the current priority is the matched +# GlobalLoadBalancer vs ThunderAgent comparison. Do not create a run directory +# for Dynamo KV Router arms, so they can be resumed cleanly later. +if [ "$VARIANT" = "dynamo" ] && [ "${ALLOW_DYNAMO_KV:-0}" != "1" ]; then + echo "Dynamo KV Router arm is on hold: $RUN_NAME" + exit 0 +fi + +GATE=${GATE:-0} +ROLLOUT_ONLY=${ROLLOUT_ONLY:-0} +DYNAMO_REQUEST_TIMEOUT_S=${DYNAMO_REQUEST_TIMEOUT_S:-1800} +STEPS=${STEPS:-3} +BATCH=${BATCH:-32} +N=${N:-8} +if [ "$GATE" = "1" ]; then STEPS=1; BATCH=${BATCH_OVERRIDE:-8}; N=${N_OVERRIDE:-2}; fi +if [ "$ROLLOUT_ONLY" = "1" ]; then STEPS=1; fi +MAX_TURNS=${MAX_TURNS:-50} +CONCURRENCY=${CONCURRENCY:-256} +MAX_NUM_SEQS=${MAX_NUM_SEQS:-128} +MODEL_DIR=${MODEL_DIR:?set MODEL_DIR to Qwen3-Coder-30B-A3B-Instruct} +DATA=${DATA:-/bench/uni-agent/data/swe_bench_verified_local32.parquet} +MAX_PROMPT=${MAX_PROMPT:-4096} +MAX_RESP=${MAX_RESP:-28672} +MAX_MODEL_LEN=$((MAX_PROMPT + MAX_RESP)) +TEMP=${TEMP:-0.8} +TOP_P=${TOP_P:-0.9} +DYNAMO_SESSION_AFFINITY=${DYNAMO_SESSION_AFFINITY:-$( [ "$VARIANT" = dynamo ] && echo 1 || echo 0 )} +DYNAMO_SESSION_AFFINITY_TTL_S=${DYNAMO_SESSION_AFFINITY_TTL_S:-7200} +SESSION_AFFINITY_OVERLAY=${SESSION_AFFINITY_OVERLAY:-$BENCH/uniagent-concurrency-sweep-20260713/dynamo_async_server_session_affinity.py} + +RUN_DIR=$BENCH/runs/$RUN_NAME +if [ -e "$RUN_DIR/command.txt" ]; then + echo "Run directory already contains command.txt: $RUN_DIR" >&2 + exit 4 +fi +mkdir -p "$RUN_DIR" "$RUN_DIR/agent-logs" "$RUN_DIR/home" +chmod -R 777 "$RUN_DIR" +docker network inspect "$NETWORK" >/dev/null 2>&1 || docker network create "$NETWORK" + +# Refuse to start while other benchmark trainers hold the GPUs. +if docker ps --format '{{.Names}}' | grep -qE 'retool32-|uniagent-swe'; then + echo "GPUs busy (retool32/uniagent container running); aborting launch." >&2 + exit 3 +fi + +# Render an agent config with this run's turn/concurrency/parser knobs. +TOOL_PARSER=${TOOL_PARSER:-qwen3_coder} +sed -e "s/max_turns: 50/max_turns: $MAX_TURNS/" \ + -e "s/concurrency: 256/concurrency: $CONCURRENCY/" \ + -e "s/tool_parser: qwen3_coder/tool_parser: $TOOL_PARSER/" \ + "$BENCH/uni-agent/agent_config_local_swe.yaml" > "$RUN_DIR/agent_config.yaml" + +CONTAINER_PYTHONPATH=/workspace/verl:/workspace:/workspace/uni-agent:/bench +if [ -n "$DYNAMO_WT" ]; then + CONTAINER_PYTHONPATH=/workspace/dynamo/components/src:$CONTAINER_PYTHONPATH +fi +if [ "$ROLLOUT_ONLY" = "1" ]; then + CONTAINER_PYTHONPATH=/run:$CONTAINER_PYTHONPATH +fi + +COMMON_ENV=( + -e HOME=/run/home + -e PYTHONPATH="$CONTAINER_PYTHONPATH" + -e PYTHONDONTWRITEBYTECODE=1 + -e TOKENIZERS_PARALLELISM=true + -e RAY_DEDUP_LOGS=0 + -e VERL_FILE_LOGGER_PATH=/run/metrics.jsonl +) +MOUNTS=( + -v /var/run/docker.sock:/var/run/docker.sock + -v "$VERL_WT":/workspace/verl:ro + -v "$RECIPE_WT":/workspace/recipe:ro + -v "$UNIAGENT":/workspace/uni-agent:ro + -v "$BENCH":/bench:ro + -v "$RUN_DIR":/run + -v "$MODEL_DIR":/model:ro +) +if [ -n "$DYNAMO_WT" ]; then + MOUNTS+=(-v "$DYNAMO_WT":/workspace/dynamo:ro) +fi +if [ "$VARIANT" = dynamo ] && [ "$DYNAMO_SESSION_AFFINITY" = 1 ]; then + if [ ! -f "$SESSION_AFFINITY_OVERLAY" ]; then + echo "Missing Dynamo session-affinity overlay: $SESSION_AFFINITY_OVERLAY" >&2 + exit 5 + fi + MOUNTS+=(-v "$SESSION_AFFINITY_OVERLAY":/workspace/recipe/dynamo/dynamo_async_server.py:ro) +fi +if [ "$ROLLOUT_ONLY" = "1" ]; then + COMMON_ENV+=( + -e ROLLOUT_BENCHMARK_AUTO_INSTALL=1 + -e ROLLOUT_BENCHMARK_PATH=/run/rollout-benchmark.json + ) + cp "$BENCH/uni-agent/rollout_benchmark_entry.py" "$RUN_DIR/rollout_benchmark_entry.py" +fi + +COMMON_ARGS=( + algorithm.adv_estimator=grpo + algorithm.use_kl_in_reward=false + algorithm.kl_ctrl.kl_coef=0.0 + data.train_files="$DATA" + data.val_files="$DATA" + data.return_raw_chat=true + data.train_batch_size="$BATCH" + data.max_prompt_length="$MAX_PROMPT" + data.max_response_length="$MAX_RESP" + data.filter_overlong_prompts=true + data.filter_overlong_prompts_workers=1 + data.truncation=error + data.shuffle=false + actor_rollout_ref.model.path=/model + actor_rollout_ref.model.use_remove_padding=true + actor_rollout_ref.model.enable_gradient_checkpointing=true + actor_rollout_ref.actor.optim.lr=1e-6 + actor_rollout_ref.actor.use_kl_loss=false + actor_rollout_ref.actor.kl_loss_coef=0.0 + actor_rollout_ref.actor.use_dynamic_bsz=true + actor_rollout_ref.actor.ppo_mini_batch_size="$BATCH" + actor_rollout_ref.actor.ppo_max_token_len_per_gpu="$MAX_MODEL_LEN" + actor_rollout_ref.actor.ulysses_sequence_parallel_size="${SP:-8}" + actor_rollout_ref.actor.fsdp_config.param_offload=true + actor_rollout_ref.actor.fsdp_config.optimizer_offload=true + actor_rollout_ref.rollout.mode=async + actor_rollout_ref.rollout.tensor_model_parallel_size=4 + actor_rollout_ref.rollout.gpu_memory_utilization="${GPU_UTIL:-0.75}" + actor_rollout_ref.rollout.max_model_len="$MAX_MODEL_LEN" + actor_rollout_ref.rollout.max_num_batched_tokens=32768 + actor_rollout_ref.rollout.max_num_seqs="$MAX_NUM_SEQS" + actor_rollout_ref.rollout.disable_log_stats=false + actor_rollout_ref.rollout.temperature="$TEMP" + actor_rollout_ref.rollout.top_p="$TOP_P" + actor_rollout_ref.rollout.n="$N" + actor_rollout_ref.rollout.agent.num_workers=8 + actor_rollout_ref.rollout.agent.agent_loop_config_path=/run/agent_config.yaml + actor_rollout_ref.rollout.multi_turn.enable=true + trainer.logger='[console,file]' + trainer.project_name=verl-ta-uniagent + trainer.experiment_name="$RUN_NAME" + trainer.nnodes=1 + trainer.n_gpus_per_node=8 + trainer.val_before_train=$( [ "$ROLLOUT_ONLY" = "1" ] && echo true || echo false ) + trainer.test_freq=-1 + trainer.save_freq=-1 + trainer.resume_mode=disable + trainer.default_local_dir=/run/checkpoints + trainer.rollout_data_dir=/run/rollout_data + trainer.total_epochs="$STEPS" + trainer.total_training_steps="$STEPS" + hydra.run.dir=/run/hydra +) + +if [ "$ROLLOUT_ONLY" = "1" ]; then + COMMON_ARGS+=( + data.val_batch_size="$BATCH" + data.val_max_samples="$BATCH" + actor_rollout_ref.rollout.val_kwargs.n="$N" + actor_rollout_ref.rollout.val_kwargs.temperature="$TEMP" + actor_rollout_ref.rollout.val_kwargs.top_p="$TOP_P" + actor_rollout_ref.rollout.val_kwargs.top_k=-1 + actor_rollout_ref.rollout.val_kwargs.do_sample=true + trainer.val_only=true + ) +fi + +# Explicit per-GPU KV bytes sidestep vLLM's memory-profiling race with actor +# memory release (known phase-1 issue). Empty = use gpu_memory_utilization. +KV_BYTES=${KV_BYTES:-} + +case "$VARIANT" in + global) + TARGET_MODULE=verl.trainer.main_ppo + EXTRA_ENV=() + if [ "$ROLLOUT_ONLY" = "1" ]; then + EXTRA_ENV+=(-e VERL_USE_EXTERNAL_MODULES=rollout_benchmark_entry) + fi + EXTRA_ARGS=(actor_rollout_ref.rollout.name=vllm) + if [ -n "$KV_BYTES" ]; then + EXTRA_ARGS+=(+actor_rollout_ref.rollout.engine_kwargs.vllm.kv_cache_memory_bytes="$KV_BYTES") + fi + ;; + dynamo|ta) + TARGET_MODULE=recipe.dynamo.main_dynamo + NS=$(echo "$RUN_NAME" | tr '-' '_') + EXTERNAL_MODULES=recipe.dynamo.register + if [ "$ROLLOUT_ONLY" = "1" ]; then + EXTERNAL_MODULES=$EXTERNAL_MODULES,rollout_benchmark_entry + fi + EXTRA_ENV=( + -e VERL_USE_EXTERNAL_MODULES="$EXTERNAL_MODULES" + -e VERL_DYNAMO_LOG_DIR=/run/dynamo + -e VERL_DYNAMO_WORKER_METRICS_DIR=/run/worker_metrics + -e VERL_DYNAMO_KV_EVENT_PORT_BASE=24000 + -e VERL_DYNAMO_REFIT_STRICT=1 + -e VLLM_LOGGING_LEVEL=INFO + ) + TA_ENABLED=false + [ "$VARIANT" = "ta" ] && TA_ENABLED=true + EXTRA_ARGS=( + actor_rollout_ref.rollout.engine_kwargs.dynamo.thunderagent.enabled=$TA_ENABLED + actor_rollout_ref.rollout.engine_kwargs.dynamo.thunderagent.router_block_size=16 + ++actor_rollout_ref.rollout.engine_kwargs.dynamo.request_engine_data=true + ++actor_rollout_ref.rollout.engine_kwargs.dynamo.request_completion_token_ids=true + ++actor_rollout_ref.rollout.engine_kwargs.dynamo.request_timeout_s="$DYNAMO_REQUEST_TIMEOUT_S" + +actor_rollout_ref.rollout.engine_kwargs.dynamo.enable_worker_system_metrics=true + +actor_rollout_ref.rollout.engine_kwargs.dynamo.stable_kv_event_ports=true + +actor_rollout_ref.rollout.engine_kwargs.dynamo.free_engine_on_train=true + +actor_rollout_ref.rollout.engine_kwargs.dynamo.namespace="$NS" + ) + if [ "$VARIANT" = dynamo ] && [ "$DYNAMO_SESSION_AFFINITY" = 1 ]; then + EXTRA_ARGS+=( + +actor_rollout_ref.rollout.engine_kwargs.dynamo.session_affinity_enabled=true + "+actor_rollout_ref.rollout.engine_kwargs.dynamo.frontend_extra_args=[--router-session-affinity-ttl-secs,$DYNAMO_SESSION_AFFINITY_TTL_S]" + ) + fi + if [ -n "$KV_BYTES" ]; then + EXTRA_ARGS+=("+actor_rollout_ref.rollout.engine_kwargs.dynamo.extra_args=[--kv-cache-memory-bytes,$KV_BYTES]") + fi + ;; + *) echo "unknown VARIANT=$VARIANT" >&2; exit 2 ;; +esac + +ENTRY=(python -m "$TARGET_MODULE") + +# GPU telemetry sampler (host side). Epoch timestamps align exactly with +# ThunderAgent scheduler snapshots and worker Prometheus samples. +printf 'timestamp,index,utilization_gpu_pct,memory_used_mib,power_draw_w\n' > "$RUN_DIR/gpu.csv" +( + while true; do + timestamp=$(date +%s.%N) + nvidia-smi \ + --query-gpu=index,utilization.gpu,memory.used,power.draw \ + --format=csv,noheader,nounits | while IFS= read -r row; do + printf '%s,%s\n' "$timestamp" "${row//, /,}" + done + sleep 1 + done +) >> "$RUN_DIR/gpu.csv" 2> "$RUN_DIR/gpu-sampler.stderr" & +SAMPLER=$! + +SIDECAR= +if [ "$VARIANT" != "global" ]; then + mkdir -p "$RUN_DIR/worker_metrics" + chmod 777 "$RUN_DIR/worker_metrics" + python "$RECIPE_WT/dynamo/metrics_sidecar.py" \ + --endpoints-glob "$RUN_DIR/worker_metrics/*.endpoints" \ + --output "$RUN_DIR/worker-metrics.jsonl" \ + --label "$VARIANT" --interval 1 --timeout 1 \ + > "$RUN_DIR/metrics-sidecar.log" 2>&1 & + SIDECAR=$! +fi + +cleanup() { + kill "$SAMPLER" 2>/dev/null || true + if [ -n "$SIDECAR" ]; then + kill "$SIDECAR" 2>/dev/null || true + fi +} +trap cleanup EXIT + +set -o pipefail +git_diff_sha() { + git -C "$1" diff --binary | sha256sum | awk '{print $1}' +} +IMAGE_ID=$(docker image inspect "$IMAGE" --format '{{.Id}}') +VERL_SHA=$(git -C "$VERL_WT" rev-parse HEAD) +VERL_DIFF_SHA=$(git_diff_sha "$VERL_WT") +RECIPE_SHA=$(git -C "$RECIPE_WT" rev-parse HEAD) +RECIPE_DIFF_SHA=$(git_diff_sha "$RECIPE_WT") +UNIAGENT_SHA=$(git -C "$UNIAGENT" rev-parse HEAD) +UNIAGENT_DIFF_SHA=$(git_diff_sha "$UNIAGENT") +DYNAMO_SHA=${DYNAMO_WT:+$(git -C "$DYNAMO_WT" rev-parse HEAD)} +DYNAMO_DIFF_SHA=${DYNAMO_WT:+$(git_diff_sha "$DYNAMO_WT")} +HARNESS_SHA=$(sha256sum "$0" | awk '{print $1}') +ROLLOUT_PROBE_SHA=$(sha256sum "$BENCH/uni-agent/rollout_benchmark_entry.py" | awk '{print $1}') +DATA_SHA=$(sha256sum "$BENCH/uni-agent/data/swe_bench_verified_local32.parquet" | awk '{print $1}') +MODEL_INDEX_SHA=$(sha256sum "$MODEL_DIR/model.safetensors.index.json" | awk '{print $1}') +SANDBOX_MANIFEST_SHA=$(sha256sum "$BENCH/uni-agent/data/sandbox-images-manifest.txt" | awk '{print $1}') +SANDBOX_IMAGE_COUNT=$(wc -l < "$BENCH/uni-agent/data/sandbox-images-manifest.txt" | tr -d ' ') +{ + echo "VARIANT=$VARIANT GATE=$GATE ROLLOUT_ONLY=$ROLLOUT_ONLY STEPS=$STEPS BATCH=$BATCH N=$N MAX_TURNS=$MAX_TURNS CONCURRENCY=$CONCURRENCY MAX_NUM_SEQS=$MAX_NUM_SEQS MODEL_DIR=$MODEL_DIR DYNAMO_WT=$DYNAMO_WT DYNAMO_REQUEST_TIMEOUT_S=$DYNAMO_REQUEST_TIMEOUT_S DYNAMO_SESSION_AFFINITY=$DYNAMO_SESSION_AFFINITY DYNAMO_SESSION_AFFINITY_TTL_S=$DYNAMO_SESSION_AFFINITY_TTL_S NGPUS_PER_NODE=8 ROLLOUT_TP=4 ACTOR_SP=${SP:-8} AGENT_WORKERS=8 GPU_DEVICES=0,1,2,3,4,5,6,7" + echo "HOSTNAME=$(hostname) IMAGE_ID=$IMAGE_ID VERL_SHA=$VERL_SHA VERL_DIFF_SHA=$VERL_DIFF_SHA RECIPE_SHA=$RECIPE_SHA RECIPE_DIFF_SHA=$RECIPE_DIFF_SHA UNIAGENT_SHA=$UNIAGENT_SHA UNIAGENT_DIFF_SHA=$UNIAGENT_DIFF_SHA DYNAMO_SHA=${DYNAMO_SHA:-none} DYNAMO_DIFF_SHA=${DYNAMO_DIFF_SHA:-none} HARNESS_SHA=$HARNESS_SHA ROLLOUT_PROBE_SHA=$ROLLOUT_PROBE_SHA DATA_SHA=$DATA_SHA MODEL_INDEX_SHA=$MODEL_INDEX_SHA SANDBOX_MANIFEST_SHA=$SANDBOX_MANIFEST_SHA SANDBOX_IMAGE_COUNT=$SANDBOX_IMAGE_COUNT" + if [ "$VARIANT" = dynamo ] && [ "$DYNAMO_SESSION_AFFINITY" = 1 ]; then + echo "SESSION_AFFINITY_OVERLAY_SHA=$(sha256sum "$SESSION_AFFINITY_OVERLAY" | awk '{print $1}')" + fi + echo GPU_LAYOUT_BEGIN + nvidia-smi -L + echo GPU_LAYOUT_END + echo "docker run ... ${ENTRY[*]} ${COMMON_ARGS[*]} ${EXTRA_ARGS[*]}" +} > "$RUN_DIR/command.txt" + +set +e +docker run $( [ "${KEEP:-0}" = "1" ] || echo --rm ) --name "$RUN_NAME" -e HYDRA_FULL_ERROR=1 \ + --network "$NETWORK" \ + --gpus all \ + --ipc=host \ + --group-add "$DOCKER_GID" \ + "${COMMON_ENV[@]}" "${EXTRA_ENV[@]}" \ + "${MOUNTS[@]}" \ + -w /workspace/verl \ + "$IMAGE" \ + "${ENTRY[@]}" "${COMMON_ARGS[@]}" "${EXTRA_ARGS[@]}" ${EXTRA:-} \ + 2>&1 | tee "$RUN_DIR/console.log" +DOCKER_STATUS=${PIPESTATUS[0]} +set -e +printf '%s\n' "$DOCKER_STATUS" > "$RUN_DIR/exit_code" +python "$BENCH/uni-agent/validity.py" "$RUN_DIR" || true +exit "$DOCKER_STATUS" From 01b1db79c47cd142e794dadea25d0e77d06a4d3b Mon Sep 17 00:00:00 2001 From: Jinyan Chen Date: Thu, 23 Jul 2026 21:48:06 +0800 Subject: [PATCH 03/15] fix(dynamo): wait for ThunderAgent workers --- dynamo/README.md | 3 +- dynamo/dynamo_thunderagent.py | 6 ++-- dynamo/tests/test_dynamo_thunderagent.py | 38 +++++++++++++++++++++++- 3 files changed, 41 insertions(+), 6 deletions(-) diff --git a/dynamo/README.md b/dynamo/README.md index d4286f43..f0e9df5e 100644 --- a/dynamo/README.md +++ b/dynamo/README.md @@ -272,7 +272,8 @@ modify core `verl` or Dynamo. ### Required versions - Core `verl`: see [`REQUIRED_VERL.txt`](REQUIRED_VERL.txt). -- Dynamo: source commit `48632da9c77c5a7647b50cf1ba2a729dcdca7aea`. +- Dynamo: source commit `59d614641837e593f0567b79d75394aae5f864e0`, including + [PR #11185](https://github.com/ai-dynamo/dynamo/pull/11185). ThunderAgent is experimental and is not included in a released Dynamo Python package. Build or install Dynamo from the commit above; `etcd`, `nats-server`, diff --git a/dynamo/dynamo_thunderagent.py b/dynamo/dynamo_thunderagent.py index 7e312c6f..a244f2e4 100644 --- a/dynamo/dynamo_thunderagent.py +++ b/dynamo/dynamo_thunderagent.py @@ -99,9 +99,7 @@ def _build_vllm_cmd( ) -> list[str]: thunderagent_enabled = self._thunderagent_enabled() worker_model_name = ( - f"{served_model_name}{_THUNDERAGENT_BACKEND_MODEL_SUFFIX}" - if thunderagent_enabled - else served_model_name + f"{served_model_name}{_THUNDERAGENT_BACKEND_MODEL_SUFFIX}" if thunderagent_enabled else served_model_name ) command = super()._build_vllm_cmd(worker_model_name, tp, kv_events_config_json) if not thunderagent_enabled: @@ -263,7 +261,7 @@ async def finalize_program(self, session_id: str) -> None: _REQUEST_PROGRAM.reset(token) async def _healthcheck_frontend(self, expected_workers: int) -> None: - await super()._healthcheck_frontend(expected_workers) + await super()._healthcheck_frontend(expected_workers + int(self._thunderagent_enabled())) if self._thunderagent_enabled(): await self.finalize_program(f"thunderagent-health-{time.time_ns()}") diff --git a/dynamo/tests/test_dynamo_thunderagent.py b/dynamo/tests/test_dynamo_thunderagent.py index 09e295ee..20f362ad 100644 --- a/dynamo/tests/test_dynamo_thunderagent.py +++ b/dynamo/tests/test_dynamo_thunderagent.py @@ -296,6 +296,22 @@ def test_vllm_and_thunderagent_share_block_size(monkeypatch) -> None: assert command[-2:] == ["--block-size", "32"] +def test_thunderagent_backend_workers_use_internal_model_name(monkeypatch) -> None: + server = _make_http_server() + captured = {} + + def build_vllm(_self, model, _tp, _kv_events_config_json): + captured["model"] = model + return ["python", "-m", "dynamo.vllm"] + + monkeypatch.setattr(DynamoHttpServer, "_build_vllm_cmd", build_vllm) + + server._build_vllm_cmd("test-model", 1, "{}") + + assert captured["model"] == "test-model--verl-thunderagent-backend" + assert server._served_model_name == "test-model" + + def test_thunderagent_payload_supplies_worker_dp_rank(monkeypatch) -> None: server = _make_http_server() monkeypatch.setattr( @@ -320,6 +336,26 @@ def test_frontend_start_starts_thunderagent_first(monkeypatch) -> None: assert events == ["thunderagent", "frontend"] +@pytest.mark.asyncio +@pytest.mark.parametrize(("enabled", "expected_workers"), [(True, 5), (False, 4)]) +async def test_frontend_health_waits_for_router_and_workers(monkeypatch, enabled, expected_workers) -> None: + server = _make_http_server({"enabled": enabled}) + observed = [] + + async def healthcheck(_self, workers): + observed.append(workers) + + async def finalize(_session_id): + pass + + monkeypatch.setattr(DynamoHttpServer, "_healthcheck_frontend", healthcheck) + monkeypatch.setattr(server, "finalize_program", finalize) + + await server._healthcheck_frontend(4) + + assert observed == [expected_workers] + + @pytest.mark.asyncio async def test_generation_adds_program_header(monkeypatch) -> None: server = _make_http_server() @@ -491,7 +527,7 @@ def test_recipe_pins_tested_verl_and_dynamo_revisions() -> None: assert "MODE=pinned_commit" in required_verl assert "COMMIT=d82d2777b5dc3e96a8a45168d02660312707ab98" in required_verl - assert "48632da9c77c5a7647b50cf1ba2a729dcdca7aea" in readme + assert "59d614641837e593f0567b79d75394aae5f864e0" in readme assert "dynamo/REQUIRED_VERL.txt" in repository_readme From 0d7289f5af3ba30f71d1af8817858c5e57188424 Mon Sep 17 00:00:00 2001 From: Sophia Yang Date: Sat, 25 Jul 2026 10:07:55 -0700 Subject: [PATCH 04/15] modify readme --- dynamo/README.md | 37 +----------------------- dynamo/tests/test_dynamo_thunderagent.py | 23 ++++++++++----- 2 files changed, 16 insertions(+), 44 deletions(-) diff --git a/dynamo/README.md b/dynamo/README.md index f0e9df5e..41df012a 100644 --- a/dynamo/README.md +++ b/dynamo/README.md @@ -271,31 +271,12 @@ modify core `verl` or Dynamo. ### Required versions -- Core `verl`: see [`REQUIRED_VERL.txt`](REQUIRED_VERL.txt). - Dynamo: source commit `59d614641837e593f0567b79d75394aae5f864e0`, including [PR #11185](https://github.com/ai-dynamo/dynamo/pull/11185). -ThunderAgent is experimental and is not included in a released Dynamo Python -package. Build or install Dynamo from the commit above; `etcd`, `nats-server`, -`dynamo.vllm`, `dynamo.thunderagent_router`, and `dynamo.frontend` must be -available in the runtime image. - -### Request lifecycle - -Each `AgentLoopBase.run()` is one ThunderAgent program: - -1. The recipe creates one random session ID when a trajectory starts. -2. Every LLM turn sends the same `X-Dynamo-Session-ID`. -3. Concurrent trajectories use task-local, isolated session IDs. -4. On success, error, or cancellation, admitted requests drain before one - `X-Dynamo-Session-Final: true` request releases the program. - -The final request must return no model choices. A non-empty completion is -treated as a routing bypass and fails closed. - ### Topology -With ThunderAgent enabled, PR #110 launches processes in this order: +With ThunderAgent enabled, verl launches processes in this order: ```text etcd -> NATS -> Dynamo vLLM workers -> ThunderAgent router -> frontend @@ -351,19 +332,3 @@ python -m recipe.dynamo.main_dynamo \ Supply the remaining dataset, trainer, and resource overrides required by the standard verl PPO configuration. - -### Tests - -```bash -pytest -q recipe/dynamo/tests -ruff check recipe/dynamo/thunderagent.py recipe/dynamo/dynamo_thunderagent.py \ - recipe/dynamo/dynamo_agent_loop.py recipe/dynamo/main_dynamo.py \ - recipe/dynamo/register.py recipe/dynamo/tests -ruff format --check recipe/dynamo/thunderagent.py recipe/dynamo/dynamo_thunderagent.py \ - recipe/dynamo/dynamo_agent_loop.py recipe/dynamo/main_dynamo.py \ - recipe/dynamo/register.py recipe/dynamo/tests -``` - -GPU validation should run the same model, prompts, concurrency, warmup, and -measurement window twice, changing only `thunderagent.enabled`. Record -throughput, latency, KV-cache hit rate, errors, and router pause/resume logs. diff --git a/dynamo/tests/test_dynamo_thunderagent.py b/dynamo/tests/test_dynamo_thunderagent.py index 20f362ad..ed976038 100644 --- a/dynamo/tests/test_dynamo_thunderagent.py +++ b/dynamo/tests/test_dynamo_thunderagent.py @@ -298,17 +298,20 @@ def test_vllm_and_thunderagent_share_block_size(monkeypatch) -> None: def test_thunderagent_backend_workers_use_internal_model_name(monkeypatch) -> None: server = _make_http_server() - captured = {} + base_calls = [] - def build_vllm(_self, model, _tp, _kv_events_config_json): - captured["model"] = model - return ["python", "-m", "dynamo.vllm"] + def build_base_command(_self, served_model_name, _tp, _kv_events_config_json): + base_calls.append(served_model_name) + return ["python", "-m", "dynamo.vllm", "--served-model-name", served_model_name] - monkeypatch.setattr(DynamoHttpServer, "_build_vllm_cmd", build_vllm) + monkeypatch.setattr(DynamoHttpServer, "_build_vllm_cmd", build_base_command) - server._build_vllm_cmd("test-model", 1, "{}") + command = server._build_vllm_cmd("test-model", 1, "{}") + thunderagent_command = server._build_thunderagent_cmd() - assert captured["model"] == "test-model--verl-thunderagent-backend" + assert base_calls == ["test-model--verl-thunderagent-backend"] + assert command[command.index("--served-model-name") + 1] == "test-model--verl-thunderagent-backend" + assert thunderagent_command[thunderagent_command.index("--model-name") + 1] == "test-model" assert server._served_model_name == "test-model" @@ -317,11 +320,15 @@ def test_thunderagent_payload_supplies_worker_dp_rank(monkeypatch) -> None: monkeypatch.setattr( DynamoHttpServer, "_build_frontend_completion_payload", - lambda _self, _prompt, _sampling, _request_id: {"nvext": {"extra_fields": ["engine_data"]}}, + lambda _self, _prompt, _sampling, _request_id: { + "model": _self._served_model_name, + "nvext": {"extra_fields": ["engine_data"]}, + }, ) payload = server._build_frontend_completion_payload([1], {}, "request-a") + assert payload["model"] == "test-model" assert payload["nvext"] == {"extra_fields": ["engine_data"], "dp_rank": 0} From b16087aa955a41b62e979190e6ca7dff55b8e587 Mon Sep 17 00:00:00 2001 From: Sophia Yang Date: Sat, 25 Jul 2026 11:05:22 -0700 Subject: [PATCH 05/15] feat(dynamo): add concise launch examples Co-authored-by: OpenAI Codex --- dynamo/run_uniagent_variant.sh | 429 +++++------------- dynamo/scripts/smoke_dynamo_v1.sh | 53 +++ dynamo/train_30b_rl_dynamo_kv_i100_metrics.sh | 103 +++++ 3 files changed, 257 insertions(+), 328 deletions(-) mode change 100644 => 100755 dynamo/run_uniagent_variant.sh create mode 100755 dynamo/scripts/smoke_dynamo_v1.sh create mode 100755 dynamo/train_30b_rl_dynamo_kv_i100_metrics.sh diff --git a/dynamo/run_uniagent_variant.sh b/dynamo/run_uniagent_variant.sh old mode 100644 new mode 100755 index fb63fe66..79a054bb --- a/dynamo/run_uniagent_variant.sh +++ b/dynamo/run_uniagent_variant.sh @@ -1,330 +1,103 @@ -#!/bin/bash -# Launch one arm of the uni-agent SWE x ThunderAgent benchmark. -# -# Usage: -# VARIANT=global|dynamo|ta RUN_NAME= [STEPS=3] [BATCH=32] [N=8] \ -# [MAX_TURNS=50] [CONCURRENCY=256] [MAX_NUM_SEQS=128] [GATE=0] \ -# [ROLLOUT_ONLY=0] [DYNAMO_REQUEST_TIMEOUT_S=1800] \ -# [MODEL_DIR=models/Qwen3-Coder-30B-A3B-Instruct] bash run_uniagent_variant.sh -# -# GATE=1 shrinks to a 1-step correctness gate (BATCH=8 N=2 unless overridden). -set -euo pipefail - -BENCH=${BENCH:?set BENCH to this reproduction package directory} -VERL_WT=${VERL_WT:?set VERL_WT to the Verl worktree} -RECIPE_WT=${RECIPE_WT:?set RECIPE_WT to the verl-recipe worktree} -UNIAGENT=${UNIAGENT:?set UNIAGENT to the uni-agent source checkout} -IMAGE=${IMAGE:-verl-recipe-thunderagent:pr11185-fa2.8.3-uni-agent-docker} -DYNAMO_WT=${DYNAMO_WT:-} -DOCKER_GID=$(stat -c '%g' /var/run/docker.sock) -NETWORK=${NETWORK:-uniagent-bench} - -VARIANT=${VARIANT:?global|dynamo|ta} -RUN_NAME=${RUN_NAME:?} - -# Temporary benchmark queue hold: the current priority is the matched -# GlobalLoadBalancer vs ThunderAgent comparison. Do not create a run directory -# for Dynamo KV Router arms, so they can be resumed cleanly later. -if [ "$VARIANT" = "dynamo" ] && [ "${ALLOW_DYNAMO_KV:-0}" != "1" ]; then - echo "Dynamo KV Router arm is on hold: $RUN_NAME" - exit 0 -fi - -GATE=${GATE:-0} -ROLLOUT_ONLY=${ROLLOUT_ONLY:-0} -DYNAMO_REQUEST_TIMEOUT_S=${DYNAMO_REQUEST_TIMEOUT_S:-1800} -STEPS=${STEPS:-3} -BATCH=${BATCH:-32} -N=${N:-8} -if [ "$GATE" = "1" ]; then STEPS=1; BATCH=${BATCH_OVERRIDE:-8}; N=${N_OVERRIDE:-2}; fi -if [ "$ROLLOUT_ONLY" = "1" ]; then STEPS=1; fi -MAX_TURNS=${MAX_TURNS:-50} -CONCURRENCY=${CONCURRENCY:-256} -MAX_NUM_SEQS=${MAX_NUM_SEQS:-128} -MODEL_DIR=${MODEL_DIR:?set MODEL_DIR to Qwen3-Coder-30B-A3B-Instruct} -DATA=${DATA:-/bench/uni-agent/data/swe_bench_verified_local32.parquet} -MAX_PROMPT=${MAX_PROMPT:-4096} -MAX_RESP=${MAX_RESP:-28672} -MAX_MODEL_LEN=$((MAX_PROMPT + MAX_RESP)) -TEMP=${TEMP:-0.8} -TOP_P=${TOP_P:-0.9} -DYNAMO_SESSION_AFFINITY=${DYNAMO_SESSION_AFFINITY:-$( [ "$VARIANT" = dynamo ] && echo 1 || echo 0 )} -DYNAMO_SESSION_AFFINITY_TTL_S=${DYNAMO_SESSION_AFFINITY_TTL_S:-7200} -SESSION_AFFINITY_OVERLAY=${SESSION_AFFINITY_OVERLAY:-$BENCH/uniagent-concurrency-sweep-20260713/dynamo_async_server_session_affinity.py} - -RUN_DIR=$BENCH/runs/$RUN_NAME -if [ -e "$RUN_DIR/command.txt" ]; then - echo "Run directory already contains command.txt: $RUN_DIR" >&2 - exit 4 -fi -mkdir -p "$RUN_DIR" "$RUN_DIR/agent-logs" "$RUN_DIR/home" -chmod -R 777 "$RUN_DIR" -docker network inspect "$NETWORK" >/dev/null 2>&1 || docker network create "$NETWORK" - -# Refuse to start while other benchmark trainers hold the GPUs. -if docker ps --format '{{.Names}}' | grep -qE 'retool32-|uniagent-swe'; then - echo "GPUs busy (retool32/uniagent container running); aborting launch." >&2 - exit 3 -fi - -# Render an agent config with this run's turn/concurrency/parser knobs. -TOOL_PARSER=${TOOL_PARSER:-qwen3_coder} -sed -e "s/max_turns: 50/max_turns: $MAX_TURNS/" \ - -e "s/concurrency: 256/concurrency: $CONCURRENCY/" \ - -e "s/tool_parser: qwen3_coder/tool_parser: $TOOL_PARSER/" \ - "$BENCH/uni-agent/agent_config_local_swe.yaml" > "$RUN_DIR/agent_config.yaml" - -CONTAINER_PYTHONPATH=/workspace/verl:/workspace:/workspace/uni-agent:/bench -if [ -n "$DYNAMO_WT" ]; then - CONTAINER_PYTHONPATH=/workspace/dynamo/components/src:$CONTAINER_PYTHONPATH -fi -if [ "$ROLLOUT_ONLY" = "1" ]; then - CONTAINER_PYTHONPATH=/run:$CONTAINER_PYTHONPATH -fi - -COMMON_ENV=( - -e HOME=/run/home - -e PYTHONPATH="$CONTAINER_PYTHONPATH" - -e PYTHONDONTWRITEBYTECODE=1 - -e TOKENIZERS_PARALLELISM=true - -e RAY_DEDUP_LOGS=0 - -e VERL_FILE_LOGGER_PATH=/run/metrics.jsonl -) -MOUNTS=( - -v /var/run/docker.sock:/var/run/docker.sock - -v "$VERL_WT":/workspace/verl:ro - -v "$RECIPE_WT":/workspace/recipe:ro - -v "$UNIAGENT":/workspace/uni-agent:ro - -v "$BENCH":/bench:ro - -v "$RUN_DIR":/run - -v "$MODEL_DIR":/model:ro -) -if [ -n "$DYNAMO_WT" ]; then - MOUNTS+=(-v "$DYNAMO_WT":/workspace/dynamo:ro) -fi -if [ "$VARIANT" = dynamo ] && [ "$DYNAMO_SESSION_AFFINITY" = 1 ]; then - if [ ! -f "$SESSION_AFFINITY_OVERLAY" ]; then - echo "Missing Dynamo session-affinity overlay: $SESSION_AFFINITY_OVERLAY" >&2 - exit 5 - fi - MOUNTS+=(-v "$SESSION_AFFINITY_OVERLAY":/workspace/recipe/dynamo/dynamo_async_server.py:ro) -fi -if [ "$ROLLOUT_ONLY" = "1" ]; then - COMMON_ENV+=( - -e ROLLOUT_BENCHMARK_AUTO_INSTALL=1 - -e ROLLOUT_BENCHMARK_PATH=/run/rollout-benchmark.json - ) - cp "$BENCH/uni-agent/rollout_benchmark_entry.py" "$RUN_DIR/rollout_benchmark_entry.py" -fi - -COMMON_ARGS=( - algorithm.adv_estimator=grpo - algorithm.use_kl_in_reward=false - algorithm.kl_ctrl.kl_coef=0.0 - data.train_files="$DATA" - data.val_files="$DATA" - data.return_raw_chat=true - data.train_batch_size="$BATCH" - data.max_prompt_length="$MAX_PROMPT" - data.max_response_length="$MAX_RESP" - data.filter_overlong_prompts=true - data.filter_overlong_prompts_workers=1 - data.truncation=error - data.shuffle=false - actor_rollout_ref.model.path=/model - actor_rollout_ref.model.use_remove_padding=true - actor_rollout_ref.model.enable_gradient_checkpointing=true - actor_rollout_ref.actor.optim.lr=1e-6 - actor_rollout_ref.actor.use_kl_loss=false - actor_rollout_ref.actor.kl_loss_coef=0.0 - actor_rollout_ref.actor.use_dynamic_bsz=true - actor_rollout_ref.actor.ppo_mini_batch_size="$BATCH" - actor_rollout_ref.actor.ppo_max_token_len_per_gpu="$MAX_MODEL_LEN" - actor_rollout_ref.actor.ulysses_sequence_parallel_size="${SP:-8}" - actor_rollout_ref.actor.fsdp_config.param_offload=true - actor_rollout_ref.actor.fsdp_config.optimizer_offload=true - actor_rollout_ref.rollout.mode=async - actor_rollout_ref.rollout.tensor_model_parallel_size=4 - actor_rollout_ref.rollout.gpu_memory_utilization="${GPU_UTIL:-0.75}" - actor_rollout_ref.rollout.max_model_len="$MAX_MODEL_LEN" - actor_rollout_ref.rollout.max_num_batched_tokens=32768 - actor_rollout_ref.rollout.max_num_seqs="$MAX_NUM_SEQS" - actor_rollout_ref.rollout.disable_log_stats=false - actor_rollout_ref.rollout.temperature="$TEMP" - actor_rollout_ref.rollout.top_p="$TOP_P" - actor_rollout_ref.rollout.n="$N" - actor_rollout_ref.rollout.agent.num_workers=8 - actor_rollout_ref.rollout.agent.agent_loop_config_path=/run/agent_config.yaml - actor_rollout_ref.rollout.multi_turn.enable=true - trainer.logger='[console,file]' - trainer.project_name=verl-ta-uniagent - trainer.experiment_name="$RUN_NAME" - trainer.nnodes=1 - trainer.n_gpus_per_node=8 - trainer.val_before_train=$( [ "$ROLLOUT_ONLY" = "1" ] && echo true || echo false ) - trainer.test_freq=-1 - trainer.save_freq=-1 - trainer.resume_mode=disable - trainer.default_local_dir=/run/checkpoints - trainer.rollout_data_dir=/run/rollout_data - trainer.total_epochs="$STEPS" - trainer.total_training_steps="$STEPS" - hydra.run.dir=/run/hydra -) - -if [ "$ROLLOUT_ONLY" = "1" ]; then - COMMON_ARGS+=( - data.val_batch_size="$BATCH" - data.val_max_samples="$BATCH" - actor_rollout_ref.rollout.val_kwargs.n="$N" - actor_rollout_ref.rollout.val_kwargs.temperature="$TEMP" - actor_rollout_ref.rollout.val_kwargs.top_p="$TOP_P" - actor_rollout_ref.rollout.val_kwargs.top_k=-1 - actor_rollout_ref.rollout.val_kwargs.do_sample=true - trainer.val_only=true - ) -fi - -# Explicit per-GPU KV bytes sidestep vLLM's memory-profiling race with actor -# memory release (known phase-1 issue). Empty = use gpu_memory_utilization. -KV_BYTES=${KV_BYTES:-} - -case "$VARIANT" in - global) - TARGET_MODULE=verl.trainer.main_ppo - EXTRA_ENV=() - if [ "$ROLLOUT_ONLY" = "1" ]; then - EXTRA_ENV+=(-e VERL_USE_EXTERNAL_MODULES=rollout_benchmark_entry) - fi - EXTRA_ARGS=(actor_rollout_ref.rollout.name=vllm) - if [ -n "$KV_BYTES" ]; then - EXTRA_ARGS+=(+actor_rollout_ref.rollout.engine_kwargs.vllm.kv_cache_memory_bytes="$KV_BYTES") - fi - ;; - dynamo|ta) - TARGET_MODULE=recipe.dynamo.main_dynamo - NS=$(echo "$RUN_NAME" | tr '-' '_') - EXTERNAL_MODULES=recipe.dynamo.register - if [ "$ROLLOUT_ONLY" = "1" ]; then - EXTERNAL_MODULES=$EXTERNAL_MODULES,rollout_benchmark_entry - fi - EXTRA_ENV=( - -e VERL_USE_EXTERNAL_MODULES="$EXTERNAL_MODULES" - -e VERL_DYNAMO_LOG_DIR=/run/dynamo - -e VERL_DYNAMO_WORKER_METRICS_DIR=/run/worker_metrics - -e VERL_DYNAMO_KV_EVENT_PORT_BASE=24000 - -e VERL_DYNAMO_REFIT_STRICT=1 - -e VLLM_LOGGING_LEVEL=INFO - ) - TA_ENABLED=false - [ "$VARIANT" = "ta" ] && TA_ENABLED=true - EXTRA_ARGS=( - actor_rollout_ref.rollout.engine_kwargs.dynamo.thunderagent.enabled=$TA_ENABLED - actor_rollout_ref.rollout.engine_kwargs.dynamo.thunderagent.router_block_size=16 - ++actor_rollout_ref.rollout.engine_kwargs.dynamo.request_engine_data=true - ++actor_rollout_ref.rollout.engine_kwargs.dynamo.request_completion_token_ids=true - ++actor_rollout_ref.rollout.engine_kwargs.dynamo.request_timeout_s="$DYNAMO_REQUEST_TIMEOUT_S" - +actor_rollout_ref.rollout.engine_kwargs.dynamo.enable_worker_system_metrics=true - +actor_rollout_ref.rollout.engine_kwargs.dynamo.stable_kv_event_ports=true - +actor_rollout_ref.rollout.engine_kwargs.dynamo.free_engine_on_train=true - +actor_rollout_ref.rollout.engine_kwargs.dynamo.namespace="$NS" - ) - if [ "$VARIANT" = dynamo ] && [ "$DYNAMO_SESSION_AFFINITY" = 1 ]; then - EXTRA_ARGS+=( - +actor_rollout_ref.rollout.engine_kwargs.dynamo.session_affinity_enabled=true - "+actor_rollout_ref.rollout.engine_kwargs.dynamo.frontend_extra_args=[--router-session-affinity-ttl-secs,$DYNAMO_SESSION_AFFINITY_TTL_S]" - ) - fi - if [ -n "$KV_BYTES" ]; then - EXTRA_ARGS+=("+actor_rollout_ref.rollout.engine_kwargs.dynamo.extra_args=[--kv-cache-memory-bytes,$KV_BYTES]") - fi - ;; - *) echo "unknown VARIANT=$VARIANT" >&2; exit 2 ;; +#!/usr/bin/env bash +set -xeuo pipefail + +# VARIANT=ta runs ThunderAgent; use dynamo or global for the two baselines. +VARIANT=${VARIANT:-ta} +project_name=${PROJECT_NAME:-verl-ta-uniagent} +exp_name=${EXP_NAME:-uniagent-${VARIANT}} + +max_prompt_length=${MAX_PROMPT_LENGTH:-4096} +max_response_length=${MAX_RESPONSE_LENGTH:-28672} +train_prompt_bsz=${TRAIN_BATCH_SIZE:-32} +n_resp_per_prompt=${N_RESP_PER_PROMPT:-8} +train_prompt_mini_bsz=${MINI_BATCH_SIZE:-32} +total_training_steps=${TOTAL_TRAINING_STEPS:-3} + +NNODES=${NNODES:-1} +NGPUS_PER_NODE=${NGPUS_PER_NODE:-8} +RAY_DATA_HOME=${RAY_DATA_HOME:-"${HOME}/verl"} +MODEL_PATH=${MODEL_PATH:-"${RAY_DATA_HOME}/models/Qwen3-Coder-30B-A3B-Instruct"} +TRAIN_FILE=${TRAIN_FILE:-"${RAY_DATA_HOME}/data/swe_bench_verified_local32.parquet"} +TEST_FILE=${TEST_FILE:-"${TRAIN_FILE}"} +AGENT_CONFIG=${AGENT_CONFIG:-"${RAY_DATA_HOME}/uni-agent/agent_config_local_swe.yaml"} +UNIAGENT_ROOT=${UNIAGENT_ROOT:-"${RAY_DATA_HOME}/uni-agent"} +CKPTS_DIR=${CKPTS_DIR:-"${RAY_DATA_HOME}/ckpts/${project_name}/${exp_name}"} + +export PYTHONPATH="${UNIAGENT_ROOT}:${PYTHONPATH:-}" + +case "${VARIANT}" in + global) + target_module=verl.trainer.main_ppo + backend_args=( + actor_rollout_ref.rollout.name=vllm + ) + ;; + dynamo | ta) + target_module=recipe.dynamo.main_dynamo + export VERL_USE_EXTERNAL_MODULES=recipe.dynamo.register + thunderagent_enabled=false + [[ "${VARIANT}" == "ta" ]] && thunderagent_enabled=true + backend_args=( + actor_rollout_ref.rollout.name=dynamo + "++actor_rollout_ref.rollout.engine_kwargs.dynamo.thunderagent.enabled=${thunderagent_enabled}" + ++actor_rollout_ref.rollout.engine_kwargs.dynamo.thunderagent.router_block_size=16 + ++actor_rollout_ref.rollout.engine_kwargs.dynamo.request_engine_data=true + ++actor_rollout_ref.rollout.engine_kwargs.dynamo.request_completion_token_ids=true + ++actor_rollout_ref.rollout.engine_kwargs.dynamo.request_timeout_s=1800 + ++actor_rollout_ref.rollout.engine_kwargs.dynamo.free_engine_on_train=true + ) + ;; + *) + echo "VARIANT must be global, dynamo, or ta" >&2 + exit 2 + ;; esac -ENTRY=(python -m "$TARGET_MODULE") - -# GPU telemetry sampler (host side). Epoch timestamps align exactly with -# ThunderAgent scheduler snapshots and worker Prometheus samples. -printf 'timestamp,index,utilization_gpu_pct,memory_used_mib,power_draw_w\n' > "$RUN_DIR/gpu.csv" -( - while true; do - timestamp=$(date +%s.%N) - nvidia-smi \ - --query-gpu=index,utilization.gpu,memory.used,power.draw \ - --format=csv,noheader,nounits | while IFS= read -r row; do - printf '%s,%s\n' "$timestamp" "${row//, /,}" - done - sleep 1 - done -) >> "$RUN_DIR/gpu.csv" 2> "$RUN_DIR/gpu-sampler.stderr" & -SAMPLER=$! - -SIDECAR= -if [ "$VARIANT" != "global" ]; then - mkdir -p "$RUN_DIR/worker_metrics" - chmod 777 "$RUN_DIR/worker_metrics" - python "$RECIPE_WT/dynamo/metrics_sidecar.py" \ - --endpoints-glob "$RUN_DIR/worker_metrics/*.endpoints" \ - --output "$RUN_DIR/worker-metrics.jsonl" \ - --label "$VARIANT" --interval 1 --timeout 1 \ - > "$RUN_DIR/metrics-sidecar.log" 2>&1 & - SIDECAR=$! -fi - -cleanup() { - kill "$SAMPLER" 2>/dev/null || true - if [ -n "$SIDECAR" ]; then - kill "$SIDECAR" 2>/dev/null || true - fi -} -trap cleanup EXIT - -set -o pipefail -git_diff_sha() { - git -C "$1" diff --binary | sha256sum | awk '{print $1}' -} -IMAGE_ID=$(docker image inspect "$IMAGE" --format '{{.Id}}') -VERL_SHA=$(git -C "$VERL_WT" rev-parse HEAD) -VERL_DIFF_SHA=$(git_diff_sha "$VERL_WT") -RECIPE_SHA=$(git -C "$RECIPE_WT" rev-parse HEAD) -RECIPE_DIFF_SHA=$(git_diff_sha "$RECIPE_WT") -UNIAGENT_SHA=$(git -C "$UNIAGENT" rev-parse HEAD) -UNIAGENT_DIFF_SHA=$(git_diff_sha "$UNIAGENT") -DYNAMO_SHA=${DYNAMO_WT:+$(git -C "$DYNAMO_WT" rev-parse HEAD)} -DYNAMO_DIFF_SHA=${DYNAMO_WT:+$(git_diff_sha "$DYNAMO_WT")} -HARNESS_SHA=$(sha256sum "$0" | awk '{print $1}') -ROLLOUT_PROBE_SHA=$(sha256sum "$BENCH/uni-agent/rollout_benchmark_entry.py" | awk '{print $1}') -DATA_SHA=$(sha256sum "$BENCH/uni-agent/data/swe_bench_verified_local32.parquet" | awk '{print $1}') -MODEL_INDEX_SHA=$(sha256sum "$MODEL_DIR/model.safetensors.index.json" | awk '{print $1}') -SANDBOX_MANIFEST_SHA=$(sha256sum "$BENCH/uni-agent/data/sandbox-images-manifest.txt" | awk '{print $1}') -SANDBOX_IMAGE_COUNT=$(wc -l < "$BENCH/uni-agent/data/sandbox-images-manifest.txt" | tr -d ' ') -{ - echo "VARIANT=$VARIANT GATE=$GATE ROLLOUT_ONLY=$ROLLOUT_ONLY STEPS=$STEPS BATCH=$BATCH N=$N MAX_TURNS=$MAX_TURNS CONCURRENCY=$CONCURRENCY MAX_NUM_SEQS=$MAX_NUM_SEQS MODEL_DIR=$MODEL_DIR DYNAMO_WT=$DYNAMO_WT DYNAMO_REQUEST_TIMEOUT_S=$DYNAMO_REQUEST_TIMEOUT_S DYNAMO_SESSION_AFFINITY=$DYNAMO_SESSION_AFFINITY DYNAMO_SESSION_AFFINITY_TTL_S=$DYNAMO_SESSION_AFFINITY_TTL_S NGPUS_PER_NODE=8 ROLLOUT_TP=4 ACTOR_SP=${SP:-8} AGENT_WORKERS=8 GPU_DEVICES=0,1,2,3,4,5,6,7" - echo "HOSTNAME=$(hostname) IMAGE_ID=$IMAGE_ID VERL_SHA=$VERL_SHA VERL_DIFF_SHA=$VERL_DIFF_SHA RECIPE_SHA=$RECIPE_SHA RECIPE_DIFF_SHA=$RECIPE_DIFF_SHA UNIAGENT_SHA=$UNIAGENT_SHA UNIAGENT_DIFF_SHA=$UNIAGENT_DIFF_SHA DYNAMO_SHA=${DYNAMO_SHA:-none} DYNAMO_DIFF_SHA=${DYNAMO_DIFF_SHA:-none} HARNESS_SHA=$HARNESS_SHA ROLLOUT_PROBE_SHA=$ROLLOUT_PROBE_SHA DATA_SHA=$DATA_SHA MODEL_INDEX_SHA=$MODEL_INDEX_SHA SANDBOX_MANIFEST_SHA=$SANDBOX_MANIFEST_SHA SANDBOX_IMAGE_COUNT=$SANDBOX_IMAGE_COUNT" - if [ "$VARIANT" = dynamo ] && [ "$DYNAMO_SESSION_AFFINITY" = 1 ]; then - echo "SESSION_AFFINITY_OVERLAY_SHA=$(sha256sum "$SESSION_AFFINITY_OVERLAY" | awk '{print $1}')" - fi - echo GPU_LAYOUT_BEGIN - nvidia-smi -L - echo GPU_LAYOUT_END - echo "docker run ... ${ENTRY[*]} ${COMMON_ARGS[*]} ${EXTRA_ARGS[*]}" -} > "$RUN_DIR/command.txt" - -set +e -docker run $( [ "${KEEP:-0}" = "1" ] || echo --rm ) --name "$RUN_NAME" -e HYDRA_FULL_ERROR=1 \ - --network "$NETWORK" \ - --gpus all \ - --ipc=host \ - --group-add "$DOCKER_GID" \ - "${COMMON_ENV[@]}" "${EXTRA_ENV[@]}" \ - "${MOUNTS[@]}" \ - -w /workspace/verl \ - "$IMAGE" \ - "${ENTRY[@]}" "${COMMON_ARGS[@]}" "${EXTRA_ARGS[@]}" ${EXTRA:-} \ - 2>&1 | tee "$RUN_DIR/console.log" -DOCKER_STATUS=${PIPESTATUS[0]} -set -e -printf '%s\n' "$DOCKER_STATUS" > "$RUN_DIR/exit_code" -python "$BENCH/uni-agent/validity.py" "$RUN_DIR" || true -exit "$DOCKER_STATUS" +python3 -m "${target_module}" \ + algorithm.adv_estimator=grpo \ + algorithm.use_kl_in_reward=False \ + algorithm.kl_ctrl.kl_coef=0.0 \ + data.train_files="${TRAIN_FILE}" \ + data.val_files="${TEST_FILE}" \ + data.return_raw_chat=True \ + data.train_batch_size="${train_prompt_bsz}" \ + data.max_prompt_length="${max_prompt_length}" \ + data.max_response_length="${max_response_length}" \ + data.filter_overlong_prompts=True \ + data.truncation=error \ + actor_rollout_ref.model.path="${MODEL_PATH}" \ + actor_rollout_ref.model.use_remove_padding=True \ + actor_rollout_ref.model.enable_gradient_checkpointing=True \ + actor_rollout_ref.actor.optim.lr=1e-6 \ + actor_rollout_ref.actor.use_kl_loss=False \ + actor_rollout_ref.actor.kl_loss_coef=0.0 \ + actor_rollout_ref.actor.use_dynamic_bsz=True \ + actor_rollout_ref.actor.ppo_mini_batch_size="${train_prompt_mini_bsz}" \ + actor_rollout_ref.actor.ppo_max_token_len_per_gpu=$((max_prompt_length + max_response_length)) \ + actor_rollout_ref.actor.ulysses_sequence_parallel_size=8 \ + actor_rollout_ref.actor.fsdp_config.param_offload=True \ + actor_rollout_ref.actor.fsdp_config.optimizer_offload=True \ + actor_rollout_ref.rollout.mode=async \ + actor_rollout_ref.rollout.tensor_model_parallel_size=4 \ + actor_rollout_ref.rollout.gpu_memory_utilization=0.75 \ + actor_rollout_ref.rollout.max_model_len=$((max_prompt_length + max_response_length)) \ + actor_rollout_ref.rollout.max_num_batched_tokens=32768 \ + actor_rollout_ref.rollout.temperature=0.8 \ + actor_rollout_ref.rollout.top_p=0.9 \ + actor_rollout_ref.rollout.n="${n_resp_per_prompt}" \ + actor_rollout_ref.rollout.agent.num_workers=8 \ + actor_rollout_ref.rollout.agent.agent_loop_config_path="${AGENT_CONFIG}" \ + actor_rollout_ref.rollout.multi_turn.enable=True \ + "${backend_args[@]}" \ + trainer.logger='["console"]' \ + trainer.project_name="${project_name}" \ + trainer.experiment_name="${exp_name}" \ + trainer.n_gpus_per_node="${NGPUS_PER_NODE}" \ + trainer.nnodes="${NNODES}" \ + trainer.val_before_train=False \ + trainer.test_freq=-1 \ + trainer.save_freq=-1 \ + trainer.total_training_steps="${total_training_steps}" \ + trainer.default_local_dir="${CKPTS_DIR}" \ + trainer.resume_mode=auto \ + "$@" diff --git a/dynamo/scripts/smoke_dynamo_v1.sh b/dynamo/scripts/smoke_dynamo_v1.sh new file mode 100755 index 00000000..32e2dd08 --- /dev/null +++ b/dynamo/scripts/smoke_dynamo_v1.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +set -xeuo pipefail + +# One-GPU, validation-only smoke for the Dynamo rollout path. +project_name=${PROJECT_NAME:-verl-dynamo} +exp_name=${EXP_NAME:-dynamo-v1-smoke} + +max_prompt_length=${MAX_PROMPT_LENGTH:-512} +max_response_length=${MAX_RESPONSE_LENGTH:-512} + +NNODES=${NNODES:-1} +NGPUS_PER_NODE=${NGPUS_PER_NODE:-1} +RAY_DATA_HOME=${RAY_DATA_HOME:-"${HOME}/verl"} +MODEL_PATH=${MODEL_PATH:-"${RAY_DATA_HOME}/models/Qwen2.5-0.5B-Instruct"} +TRAIN_FILE=${TRAIN_FILE:-"${RAY_DATA_HOME}/data/dapo-math-17k.parquet"} +TEST_FILE=${TEST_FILE:-"${RAY_DATA_HOME}/data/aime-2024.parquet"} + +export VERL_USE_EXTERNAL_MODULES=recipe.dynamo.register + +python3 -m recipe.dynamo.main_dynamo \ + algorithm.adv_estimator=grpo \ + data.train_files="${TRAIN_FILE}" \ + data.val_files="${TEST_FILE}" \ + data.train_batch_size=1 \ + data.val_batch_size=1 \ + data.train_max_samples=1 \ + data.val_max_samples=1 \ + data.max_prompt_length="${max_prompt_length}" \ + data.max_response_length="${max_response_length}" \ + actor_rollout_ref.model.path="${MODEL_PATH}" \ + actor_rollout_ref.actor.ppo_mini_batch_size=1 \ + actor_rollout_ref.rollout.name=dynamo \ + actor_rollout_ref.rollout.mode=async \ + actor_rollout_ref.rollout.calculate_log_probs=False \ + actor_rollout_ref.rollout.tensor_model_parallel_size=1 \ + actor_rollout_ref.rollout.gpu_memory_utilization=0.5 \ + actor_rollout_ref.rollout.max_model_len=$((max_prompt_length + max_response_length)) \ + actor_rollout_ref.rollout.n=1 \ + actor_rollout_ref.rollout.multi_turn.enable=False \ + ++actor_rollout_ref.rollout.engine_kwargs.dynamo.router_mode=round-robin \ + ++actor_rollout_ref.rollout.engine_kwargs.dynamo.thunderagent.enabled=false \ + trainer.logger='["console"]' \ + trainer.project_name="${project_name}" \ + trainer.experiment_name="${exp_name}" \ + trainer.n_gpus_per_node="${NGPUS_PER_NODE}" \ + trainer.nnodes="${NNODES}" \ + trainer.val_before_train=True \ + trainer.val_only=True \ + trainer.total_training_steps=1 \ + trainer.save_freq=-1 \ + "$@" + +echo "PASS: Dynamo validation smoke completed" diff --git a/dynamo/train_30b_rl_dynamo_kv_i100_metrics.sh b/dynamo/train_30b_rl_dynamo_kv_i100_metrics.sh new file mode 100755 index 00000000..ac53deb0 --- /dev/null +++ b/dynamo/train_30b_rl_dynamo_kv_i100_metrics.sh @@ -0,0 +1,103 @@ +#!/usr/bin/env bash +set -xeuo pipefail + +project_name=${PROJECT_NAME:-retool-30b-dynamo} +exp_name=${EXP_NAME:-dynamo-kv-i100-metrics} + +max_prompt_length=${MAX_PROMPT_LENGTH:-2048} +max_response_length=${MAX_RESPONSE_LENGTH:-16384} +train_prompt_bsz=${TRAIN_BATCH_SIZE:-16} +n_resp_per_prompt=${N_RESP_PER_PROMPT:-16} +train_prompt_mini_bsz=${MINI_BATCH_SIZE:-16} + +NNODES=${NNODES:-2} +NGPUS_PER_NODE=${NGPUS_PER_NODE:-8} +RAY_DATA_HOME=${RAY_DATA_HOME:-"${HOME}/verl"} +MODEL_PATH=${MODEL_PATH:-"${RAY_DATA_HOME}/models/Qwen3-30B-A3B-Base"} +TRAIN_FILE=${TRAIN_FILE:-"${RAY_DATA_HOME}/data/dapo-math-17k.parquet"} +TEST_FILE=${TEST_FILE:-"${RAY_DATA_HOME}/data/aime-2024.parquet"} +TOOL_CONFIG=${TOOL_CONFIG:-recipe/retool/sandbox_fusion_tool_config.yaml} +CKPTS_DIR=${CKPTS_DIR:-"${RAY_DATA_HOME}/ckpts/${project_name}/${exp_name}"} +METRICS_DIR=${METRICS_DIR:-"${RAY_DATA_HOME}/logs/${exp_name}"} +METRICS_PORT=${METRICS_PORT:-9200} + +export VERL_USE_EXTERNAL_MODULES=recipe.dynamo.register +export VERL_DYNAMO_WORKER_METRICS_DIR="${METRICS_DIR}/workers" +mkdir -p "${VERL_DYNAMO_WORKER_METRICS_DIR}" + +python3 recipe/dynamo/metrics_sidecar.py \ + --targets-glob "${METRICS_DIR}/prometheus.yml" \ + --endpoints-glob "${VERL_DYNAMO_WORKER_METRICS_DIR}/*.endpoints" \ + --output "${METRICS_DIR}/kv_metrics.jsonl" \ + --label dynamo_kv --interval 30 & +sidecar_pid=$! +trap 'kill "${sidecar_pid}" 2>/dev/null || true' EXIT + +python3 -m recipe.dynamo.main_dynamo \ + algorithm.adv_estimator=grpo \ + algorithm.use_kl_in_reward=False \ + algorithm.kl_ctrl.kl_coef=0.0 \ + data.train_files="${TRAIN_FILE}" \ + data.val_files="${TEST_FILE}" \ + data.return_raw_chat=True \ + data.train_batch_size="${train_prompt_bsz}" \ + data.max_prompt_length="${max_prompt_length}" \ + data.max_response_length="${max_response_length}" \ + data.filter_overlong_prompts=True \ + data.truncation=error \ + data.custom_cls.path=recipe/retool/retool.py \ + data.custom_cls.name=CustomRLHFDataset \ + custom_reward_function.path=recipe/retool/retool.py \ + custom_reward_function.name=compute_score \ + actor_rollout_ref.model.path="${MODEL_PATH}" \ + actor_rollout_ref.model.use_remove_padding=True \ + actor_rollout_ref.model.enable_gradient_checkpointing=True \ + actor_rollout_ref.actor.optim.lr=1e-6 \ + actor_rollout_ref.actor.use_kl_loss=False \ + actor_rollout_ref.actor.kl_loss_coef=0.0 \ + actor_rollout_ref.actor.clip_ratio_low=0.2 \ + actor_rollout_ref.actor.clip_ratio_high=0.28 \ + actor_rollout_ref.actor.use_dynamic_bsz=True \ + actor_rollout_ref.actor.ppo_mini_batch_size="${train_prompt_mini_bsz}" \ + actor_rollout_ref.actor.ppo_max_token_len_per_gpu=$((max_prompt_length + max_response_length)) \ + actor_rollout_ref.actor.ulysses_sequence_parallel_size=4 \ + actor_rollout_ref.actor.fsdp_config.param_offload=True \ + actor_rollout_ref.actor.fsdp_config.optimizer_offload=True \ + actor_rollout_ref.ref.log_prob_max_token_len_per_gpu=$(((max_prompt_length + max_response_length) * 4)) \ + actor_rollout_ref.rollout.name=dynamo \ + actor_rollout_ref.rollout.mode=async \ + actor_rollout_ref.rollout.calculate_log_probs=True \ + actor_rollout_ref.rollout.tensor_model_parallel_size=2 \ + actor_rollout_ref.rollout.multi_turn.enable=True \ + actor_rollout_ref.rollout.multi_turn.max_user_turns=8 \ + actor_rollout_ref.rollout.multi_turn.max_assistant_turns=8 \ + actor_rollout_ref.rollout.multi_turn.tool_config_path="${TOOL_CONFIG}" \ + actor_rollout_ref.rollout.multi_turn.format=hermes \ + actor_rollout_ref.rollout.gpu_memory_utilization=0.6 \ + actor_rollout_ref.rollout.temperature=1.0 \ + actor_rollout_ref.rollout.top_p=1.0 \ + actor_rollout_ref.rollout.top_k=-1 \ + actor_rollout_ref.rollout.n="${n_resp_per_prompt}" \ + ++actor_rollout_ref.rollout.engine_kwargs.dynamo.router_mode=kv \ + ++actor_rollout_ref.rollout.engine_kwargs.dynamo.thunderagent.enabled=false \ + ++actor_rollout_ref.rollout.engine_kwargs.dynamo.request_engine_data=true \ + ++actor_rollout_ref.rollout.engine_kwargs.dynamo.request_completion_token_ids=true \ + ++actor_rollout_ref.rollout.engine_kwargs.dynamo.request_timeout_s=1800 \ + ++actor_rollout_ref.rollout.engine_kwargs.dynamo.enable_worker_system_metrics=true \ + ++actor_rollout_ref.rollout.engine_kwargs.dynamo.free_engine_on_train=true \ + '++actor_rollout_ref.rollout.engine_kwargs.dynamo.extra_args=["--generation-config","vllm","--stream-interval=100"]' \ + actor_rollout_ref.rollout.prometheus.enable=True \ + actor_rollout_ref.rollout.prometheus.port="${METRICS_PORT}" \ + actor_rollout_ref.rollout.prometheus.file="${METRICS_DIR}/prometheus.yml" \ + trainer.logger='["console"]' \ + trainer.project_name="${project_name}" \ + trainer.experiment_name="${exp_name}" \ + trainer.n_gpus_per_node="${NGPUS_PER_NODE}" \ + trainer.nnodes="${NNODES}" \ + trainer.val_before_train=False \ + trainer.test_freq=999 \ + trainer.save_freq=-1 \ + trainer.total_training_steps=100 \ + trainer.default_local_dir="${CKPTS_DIR}" \ + trainer.resume_mode=auto \ + "$@" From 70b4eb86583c99f9c3a9933ebc5d03e4e42120c2 Mon Sep 17 00:00:00 2001 From: Sophia Yang Date: Sat, 25 Jul 2026 19:14:57 -0700 Subject: [PATCH 06/15] docs(dynamo): document UniAgent variants Co-authored-by: OpenAI Codex --- dynamo/README.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/dynamo/README.md b/dynamo/README.md index 41df012a..66290eef 100644 --- a/dynamo/README.md +++ b/dynamo/README.md @@ -332,3 +332,31 @@ python -m recipe.dynamo.main_dynamo \ Supply the remaining dataset, trainer, and resource overrides required by the standard verl PPO configuration. + +### UniAgent variants + +[`run_uniagent_variant.sh`](run_uniagent_variant.sh) is a concise UniAgent +training example. Select one rollout path with `VARIANT`: + +- `ta` (default): Dynamo with ThunderAgent enabled. +- `dynamo`: the native Dynamo KV-router baseline with ThunderAgent disabled. +- `global`: the native verl vLLM rollout baseline, bypassing Dynamo. The + historical name does not mean that this script explicitly configures a + GlobalLoadBalancer. + +Run it from the verl root: + +```bash +VARIANT=ta RAY_DATA_HOME=/path/to/verl-data \ +bash recipe/dynamo/run_uniagent_variant.sh + +VARIANT=dynamo RAY_DATA_HOME=/path/to/verl-data \ +bash recipe/dynamo/run_uniagent_variant.sh + +VARIANT=global RAY_DATA_HOME=/path/to/verl-data \ +bash recipe/dynamo/run_uniagent_variant.sh +``` + +When `VARIANT` is omitted, the script uses `ta`. Override `MODEL_PATH`, +`TRAIN_FILE`, `TEST_FILE`, `AGENT_CONFIG`, or `UNIAGENT_ROOT` when the files do +not follow the default layout under `RAY_DATA_HOME`. From e57280b2ef61ad3512ffc996906ed9e1dc584e25 Mon Sep 17 00:00:00 2001 From: Sophia Yang Date: Sat, 25 Jul 2026 20:20:40 -0700 Subject: [PATCH 07/15] docs(dynamo): record KV router benchmark Co-authored-by: OpenAI Codex --- dynamo/README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/dynamo/README.md b/dynamo/README.md index 66290eef..3401a4e3 100644 --- a/dynamo/README.md +++ b/dynamo/README.md @@ -117,6 +117,21 @@ variants) live in the top-level scripts: sbatch recipe/dynamo/train_30b_rl_dynamo_kv_i100_metrics.sh # KV router + metrics ``` +## KV-aware routing result + +The matched comparison below keeps only Dynamo KV routing with +`stream-interval=100` and the native vLLM baseline. Lower `ms/token` is better; +the similar response lengths and rewards are sanity checks that generation +behavior stayed comparable. + +| Backend | ms/token | Mean response length | Mean reward | KV-cache hits / queries | KV-cache hit rate | +| --- | ---: | ---: | ---: | ---: | ---: | +| Dynamo KV (`stream-interval=100`) | 1.5956 | 876.1 | -0.8807 | 2,248,368 / 2,520,362 | 89.21% | +| vLLM baseline | 1.7220 | 872.3 | -0.8867 | 1,860,816 / 2,432,064 | 76.51% | + +Dynamo KV is approximately **7.3% faster per generated token** in this +comparison and improves the KV-cache hit rate by **12.70 percentage points**. + ## Running a full agent-loop RL run The quick-start examples above are single-turn smoke tests. A real RL run drives From 5578a5a8a43a20434ef87a131b0fb988381682d7 Mon Sep 17 00:00:00 2001 From: Sophia Yang Date: Sat, 25 Jul 2026 20:24:17 -0700 Subject: [PATCH 08/15] docs(dynamo): add KV benchmark plot Co-authored-by: OpenAI Codex --- dynamo/README.md | 2 ++ dynamo/assets/kv_aware_router_interval100.png | Bin 0 -> 297298 bytes 2 files changed, 2 insertions(+) create mode 100644 dynamo/assets/kv_aware_router_interval100.png diff --git a/dynamo/README.md b/dynamo/README.md index 3401a4e3..05d4a716 100644 --- a/dynamo/README.md +++ b/dynamo/README.md @@ -129,6 +129,8 @@ behavior stayed comparable. | Dynamo KV (`stream-interval=100`) | 1.5956 | 876.1 | -0.8807 | 2,248,368 / 2,520,362 | 89.21% | | vLLM baseline | 1.7220 | 872.3 | -0.8867 | 1,860,816 / 2,432,064 | 76.51% | +![Per-step timing per token from the KV-aware router comparison](assets/kv_aware_router_interval100.png) + Dynamo KV is approximately **7.3% faster per generated token** in this comparison and improves the KV-cache hit rate by **12.70 percentage points**. diff --git a/dynamo/assets/kv_aware_router_interval100.png b/dynamo/assets/kv_aware_router_interval100.png new file mode 100644 index 0000000000000000000000000000000000000000..8717b97952157d792bb0b6c6f68cf14431b9a16c GIT binary patch literal 297298 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