Skip to content

Commit 9e85ccf

Browse files
committed
support for external dynamo deployment
Signed-off-by: jthomson04 <jwillthomson19@gmail.com>
1 parent 2d19863 commit 9e85ccf

4 files changed

Lines changed: 55 additions & 4 deletions

File tree

nemo_rl/algorithms/grpo.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,10 @@ def init_train_dataloader(dataset, suffix: str = ""):
395395
# ==========================
396396
print("\n▶ Setting up compute cluster...", flush=True)
397397
colocated_inference = generation_config["colocated"]["enabled"]
398+
is_external_dynamo = (
399+
generation_config.get("backend") == "dynamo"
400+
and bool(generation_config.get("dynamo_cfg", {}).get("external_url"))
401+
)
398402

399403
env_name_list = extract_necessary_env_names(data_config)
400404
rm_env_enabled = "reward_model" in env_name_list
@@ -417,7 +421,29 @@ def init_train_dataloader(dataset, suffix: str = ""):
417421
f"policy_nodes:{policy_nodes} + rm_nodes:{rm_nodes} = total_nodes:{total_nodes}"
418422
)
419423

420-
if colocated_inference:
424+
if is_external_dynamo:
425+
# External Dynamo: no local inference resources — all GPUs go to training.
426+
if total_nodes == 1:
427+
train_gpus_per_node = cluster_config["gpus_per_node"] - rm_gpus_per_node
428+
else:
429+
train_gpus_per_node = cluster_config["gpus_per_node"]
430+
431+
train_cluster = RayVirtualCluster(
432+
name="grpo_train_cluster",
433+
bundle_ct_per_node_list=[train_gpus_per_node] * policy_nodes,
434+
use_gpus=True,
435+
num_gpus_per_node=train_gpus_per_node,
436+
max_colocated_worker_groups=1,
437+
)
438+
inference_cluster = None
439+
colocated_inference = False
440+
print(
441+
f" ✓ External Dynamo mode — all {policy_nodes} node(s) with "
442+
f"{train_gpus_per_node} GPUs/node allocated to training",
443+
flush=True,
444+
)
445+
446+
elif colocated_inference:
421447
if total_nodes == 1:
422448
policy_gpus_per_node = cluster_config["gpus_per_node"] - rm_gpus_per_node
423449
assert policy_gpus_per_node > 0, (

nemo_rl/models/generation/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ def configure_generation_config(
7373
# dynamo setting — always loads real weights (no refit support)
7474
elif config["backend"] == "dynamo":
7575
config = cast(DynamoVllmConfig, config)
76-
config["vllm_cfg"]["load_format"] = "auto"
76+
if not config.get("dynamo_cfg", {}).get("external_url"):
77+
config["vllm_cfg"]["load_format"] = "auto"
7778

7879
return config

nemo_rl/models/generation/dynamo/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class DynamoSpecificArgs(TypedDict):
3636
class DynamoCfg(TypedDict, total=False):
3737
"""Dynamo infrastructure configuration."""
3838

39+
external_url: str # URL of an external Dynamo deployment (skips all local infra)
3940
frontend_http_port: int # 0 = auto-assign
4041
router_mode: str # "round-robin", "kv", "random", "least-loaded"
4142
etcd_port: int # 0 = auto-assign

nemo_rl/models/generation/dynamo/dynamo_generation.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,15 +76,33 @@ class DynamoVllmGeneration(GenerationInterface):
7676

7777
def __init__(
7878
self,
79-
cluster: RayVirtualCluster,
79+
cluster: Optional[RayVirtualCluster],
8080
config: DynamoVllmConfig,
8181
name_prefix: str = "dynamo_vllm",
8282
workers_per_node: Optional[Union[int, list[int]]] = None,
8383
):
8484
self.cfg = config
85-
vllm_cfg = config["vllm_cfg"]
8685
dynamo_cfg = config.get("dynamo_cfg", {})
8786

87+
# External mode: point at an already-running Dynamo deployment.
88+
external_url = dynamo_cfg.get("external_url")
89+
if external_url:
90+
self._external = True
91+
self._etcd_process = None
92+
self._nats_process = None
93+
self._frontend_process = None
94+
self._planner_process = None
95+
self._pool = None
96+
self.dp_openai_server_base_urls: list[Optional[str]] = [external_url]
97+
print(
98+
f" [Dynamo] External mode — using {external_url}",
99+
flush=True,
100+
)
101+
return
102+
103+
self._external = False
104+
vllm_cfg = config["vllm_cfg"]
105+
88106
self.tp_size = vllm_cfg["tensor_parallel_size"]
89107
assert cluster.world_size() % self.tp_size == 0, (
90108
f"World size {cluster.world_size()} must be divisible by "
@@ -485,6 +503,8 @@ def _handle_scaling_event(self, event: Any) -> None:
485503

486504
def _check_subprocesses(self) -> None:
487505
"""Raise if any managed subprocess has exited unexpectedly."""
506+
if self._external:
507+
return
488508
for attr, name, _ in _SUBPROCESS_REGISTRY:
489509
proc: Optional[subprocess.Popen] = getattr(self, attr)
490510
if proc is not None:
@@ -502,6 +522,9 @@ def finish_generation(self, *args: Any, **kwargs: Any) -> bool:
502522
return True
503523

504524
def shutdown(self) -> bool:
525+
if self._external:
526+
return True
527+
505528
self._stop_virtual_connector_client()
506529

507530
for attr, name, timeout in _SUBPROCESS_REGISTRY:

0 commit comments

Comments
 (0)