Skip to content

Commit 3ee5fa3

Browse files
yuki-97terrykongRayenTian
authored
feat(sc): setup + entrypoint (#3266)
Signed-off-by: Yuki Huang <yukih@nvidia.com> Signed-off-by: Terry Kong <terryk@nvidia.com> Signed-off-by: ruit <ruit@nvidia.com> Co-authored-by: Terry Kong <terryk@nvidia.com> Co-authored-by: ruit <ruit@nvidia.com>
1 parent 4f2ef67 commit 3ee5fa3

33 files changed

Lines changed: 2163 additions & 435 deletions

.github/workflows/cicd-main.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -731,6 +731,8 @@ jobs:
731731
runner: ${{ needs.org-member-pre-flight.outputs.runner_prefix }}
732732
- script: L1_Functional_Tests_PPO
733733
runner: ${{ needs.org-member-pre-flight.outputs.runner_prefix }}
734+
- script: L1_Functional_Tests_SingleController
735+
runner: ${{ needs.org-member-pre-flight.outputs.runner_prefix }}
734736
- script: L1_Functional_Tests_Eval
735737
runner: ${{ needs.org-member-pre-flight.outputs.runner_prefix }}
736738
- script: L1_Functional_Tests_Other_1
@@ -800,6 +802,8 @@ jobs:
800802
runner: ${{ vars.GB200_RUNNER }}
801803
- script: L1_Functional_Tests_PPO
802804
runner: ${{ vars.GB200_RUNNER }}
805+
- script: L1_Functional_Tests_SingleController
806+
runner: ${{ vars.GB200_RUNNER }}
803807
- script: L1_Functional_Tests_Eval
804808
runner: ${{ vars.GB200_RUNNER }}
805809
- script: L1_Functional_Tests_Other_1
@@ -873,6 +877,8 @@ jobs:
873877
runner: ${{ needs.org-member-pre-flight.outputs.runner_prefix }}
874878
- script: L1_Functional_Tests_PPO
875879
runner: ${{ needs.org-member-pre-flight.outputs.runner_prefix }}
880+
- script: L1_Functional_Tests_SingleController
881+
runner: ${{ needs.org-member-pre-flight.outputs.runner_prefix }}
876882
- script: L1_Functional_Tests_Eval
877883
runner: ${{ needs.org-member-pre-flight.outputs.runner_prefix }}
878884
- script: L1_Functional_Tests_Other_1
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# SingleController variant of grpo_math_1B.yaml.
2+
defaults: grpo_math_1B.yaml
3+
4+
# SC does not support validation yet.
5+
grpo:
6+
val_period: 0
7+
8+
async_rl:
9+
sampler:
10+
name: in_order
11+
# How far generation may run ahead of the trainer.
12+
max_lookahead_versions: 1
13+
# Recompute generation KV caches after each weight update.
14+
recompute_kv_cache_after_weight_updates: false
15+
# Min ready groups the streaming trainer waits for before dispatching a batch.
16+
min_groups_for_streaming_train: ${grpo.num_prompts_per_step}
17+
# Cap on in-flight generate_and_push calls in the rollout pump.
18+
max_inflight_prompts: ${grpo.num_prompts_per_step}
19+
# Cap on unconsumed rollout groups buffered in the DataPlane (backpressure).
20+
max_buffered_rollouts: 64
21+
# Enable per-rollout diagnostic prints (prompt content / completion previews).
22+
diagnostics: false
23+
24+
# SC does not support checkpointing yet.
25+
checkpointing:
26+
enabled: false
27+
checkpoint_dir: results/grpo-single-controller
28+
29+
policy:
30+
dtensor_cfg:
31+
enabled: false
32+
megatron_cfg:
33+
enabled: true
34+
35+
generation:
36+
port_range_low: 11001
37+
port_range_high: 15000
38+
mcore_generation_config:
39+
unified_memory_level: 0
40+
vllm_cfg:
41+
async_engine: true
42+
colocated:
43+
enabled: false
44+
resources:
45+
gpus_per_node: 1
46+
num_nodes: 1
47+
48+
logger:
49+
wandb:
50+
name: grpo-single-controller-dev
51+
swanlab:
52+
name: grpo-single-controller-dev
53+
mlflow:
54+
run_name: grpo-single-controller-dev
55+
56+
data_plane:
57+
enabled: true
58+
59+
cluster:
60+
gpus_per_node: 2
61+
master_port_range_low: 25000
62+
master_port_range_high: 28000
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# SingleController variant of grpo-llama3.1-8b-instruct-2n8g-async-1off.yaml.
2+
defaults: ./performance/grpo-llama3.1-8b-instruct-2n8g-async-1off.yaml
3+
4+
logger:
5+
log_dir: logs/grpo-llama3.1-8b-instruct-2n8g-async-1off-sc
6+
wandb:
7+
name: grpo-llama3.1-8b-instruct-2n8g-async-1off-sc
8+
9+
# SC does not support validation and checkpointing yet.
10+
grpo:
11+
val_period: 0
12+
13+
checkpointing:
14+
enabled: false
15+
checkpoint_dir: results/grpo-llama3.1-8b-instruct-2n8g-async-1off-sc
16+
17+
# TransferQueue data plane is mandatory for the SingleController path.
18+
data_plane:
19+
enabled: true
20+
21+
# SC async-RL runtime knobs.
22+
async_rl:
23+
sampler:
24+
name: windowed
25+
# Matches grpo.async_grpo.max_trajectory_age_steps=1.
26+
max_staleness_versions: 1
27+
# Min ready groups the streaming trainer waits for before dispatching a batch.
28+
min_groups_for_streaming_train: 16
29+
max_inflight_prompts: ${grpo.num_prompts_per_step} # match grpo-llama3.1-8b-instruct-2n8g-async-1off
30+
max_buffered_rollouts: 128
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# SingleController + Megatron variant of grpo-qwen2.5-math-1.5b-instruct-1n8g-fsdp2tp1.v3.yaml.
2+
defaults: ./grpo-qwen2.5-math-1.5b-instruct-1n8g-fsdp2tp1.v3.yaml
3+
4+
logger:
5+
log_dir: logs/grpo-qwen2.5-math-1.5b-instruct-1n8g-megatron-sc
6+
wandb:
7+
name: grpo-qwen2.5-math-1.5b-instruct-1n8g-megatron-sc
8+
9+
# SC does not support validation and checkpointing yet.
10+
grpo:
11+
val_period: 0
12+
13+
checkpointing:
14+
enabled: false
15+
checkpoint_dir: results/grpo-qwen2.5-math-1.5b-instruct-1n8g-megatron-sc
16+
17+
# TransferQueue data plane is mandatory for the SingleController path.
18+
data_plane:
19+
enabled: true
20+
21+
# SC async-RL runtime knobs.
22+
async_rl:
23+
sampler:
24+
name: in_order
25+
max_lookahead_versions: 0
26+
# Preserve sync-mode cache invalidation after every weight update.
27+
recompute_kv_cache_after_weight_updates: true
28+
# Min ready groups the streaming trainer waits for before dispatching a batch.
29+
min_groups_for_streaming_train: ${grpo.num_prompts_per_step}
30+
max_inflight_prompts: ${grpo.num_prompts_per_step}
31+
# In-order with zero lookahead requires one prompt batch of capacity.
32+
max_buffered_rollouts: 32
33+
34+
policy:
35+
dtensor_cfg:
36+
enabled: false
37+
megatron_cfg:
38+
enabled: true
39+
scheduler:
40+
lr_warmup_iters: 50
41+
generation:
42+
vllm_cfg:
43+
async_engine: true
44+
colocated:
45+
enabled: false
46+
resources:
47+
# 4 GPUs for inference; remaining 4 GPUs on the node go to training.
48+
gpus_per_node: 4
49+
num_nodes: 1
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
# Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Async GRPO launcher driven by the SingleController actor.
16+
17+
Builds the full SC actor args driver-side via setup_single_controller and hands them
18+
to SingleControllerActor. Mirrors run_grpo.py for config loading so the same YAML
19+
files apply. data_plane.enabled=true is mandatory.
20+
"""
21+
22+
import argparse
23+
import os
24+
import pprint
25+
import sys
26+
27+
import ray
28+
from omegaconf import OmegaConf
29+
30+
from nemo_rl.algorithms.single_controller import SingleControllerActor
31+
from nemo_rl.algorithms.single_controller_utils import (
32+
MasterConfig,
33+
setup_single_controller,
34+
)
35+
from nemo_rl.algorithms.utils import get_tokenizer
36+
from nemo_rl.distributed.virtual_cluster import init_ray
37+
from nemo_rl.models.generation import configure_generation_config
38+
from nemo_rl.utils.config import (
39+
load_config,
40+
parse_hydra_overrides,
41+
register_omegaconf_resolvers,
42+
)
43+
from nemo_rl.utils.logger import get_next_experiment_dir
44+
45+
# Drop examples/ from sys.path so examples/nemo_gym/ (no __init__.py) doesn't
46+
# shadow the real nemo_gym package as a namespace package.
47+
current_dir = os.path.dirname(os.path.abspath(__file__))
48+
while current_dir in sys.path:
49+
sys.path.remove(current_dir)
50+
51+
52+
def parse_args() -> tuple[argparse.Namespace, list[str]]:
53+
"""Parse command line arguments."""
54+
parser = argparse.ArgumentParser(
55+
description="Run async GRPO training via SingleController"
56+
)
57+
parser.add_argument(
58+
"--config", type=str, default=None, help="Path to YAML config file"
59+
)
60+
args, overrides = parser.parse_known_args()
61+
return args, overrides
62+
63+
64+
def main() -> None:
65+
"""Main entry point."""
66+
register_omegaconf_resolvers()
67+
args, overrides = parse_args()
68+
69+
if not args.config:
70+
args.config = os.path.join(
71+
os.path.dirname(__file__),
72+
"configs",
73+
"grpo_math_1B_megatron_single_controller.yaml",
74+
)
75+
76+
config = load_config(args.config)
77+
print(f"Loaded configuration from: {args.config}")
78+
79+
if overrides:
80+
print(f"Overrides: {overrides}")
81+
config = parse_hydra_overrides(config, overrides)
82+
83+
config = OmegaConf.to_container(config, resolve=True)
84+
config = MasterConfig(**config)
85+
print("Applied CLI overrides")
86+
87+
dp_cfg = config.data_plane
88+
if not dp_cfg.get("enabled", False):
89+
raise ValueError(
90+
"run_grpo_single_controller requires data_plane.enabled=true. "
91+
"Use examples/run_grpo.py for the legacy / sync paths."
92+
)
93+
94+
print("Final config:")
95+
pprint.pprint(config)
96+
97+
config.logger["log_dir"] = get_next_experiment_dir(config.logger["log_dir"])
98+
print(f"📊 Using log directory: {config.logger['log_dir']}")
99+
if config.checkpointing["enabled"]:
100+
print(
101+
f"📊 Using checkpoint directory: {config.checkpointing['checkpoint_dir']}"
102+
)
103+
104+
init_ray()
105+
106+
tokenizer = get_tokenizer(config.policy["tokenizer"])
107+
assert config.policy["generation"] is not None, (
108+
"A generation config is required for SC-driven async GRPO"
109+
)
110+
has_refit_draft_weights = bool(config.policy["draft"]["enabled"])
111+
megatron_cfg = config.policy.get("megatron_cfg") or {}
112+
trains_mtp = bool(megatron_cfg.get("mtp_num_layers"))
113+
config.policy["generation"] = configure_generation_config(
114+
config.policy["generation"],
115+
tokenizer,
116+
has_refit_draft_weights=has_refit_draft_weights,
117+
trains_mtp=trains_mtp,
118+
)
119+
120+
actor_args = setup_single_controller(config, tokenizer)
121+
122+
print("🚀 Launching SingleControllerActor")
123+
sc = SingleControllerActor.remote(master_config=config, actor_args=actor_args)
124+
try:
125+
result = ray.get(sc.run.remote())
126+
print(f"SC run complete: {result}")
127+
finally:
128+
# Drain env actors before generation to avoid in-flight requests during shutdown.
129+
for env_name, handle in actor_args.env_handles.items():
130+
try:
131+
ray.get(handle.shutdown.remote())
132+
except Exception as e:
133+
print(f"Env {env_name!r} shutdown failed: {e}")
134+
135+
for resource_name, resource in (
136+
("Generation", actor_args.gen_handle),
137+
("Trainer", actor_args.trainer_handle),
138+
):
139+
try:
140+
resource.shutdown()
141+
except Exception as e:
142+
print(f"{resource_name} shutdown failed: {e}")
143+
144+
145+
if __name__ == "__main__":
146+
main()

0 commit comments

Comments
 (0)