refactor(nemo-gym): centralize actor construction - #3367
Conversation
|
Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually. Contributors can view more details about this message here. |
1414943 to
a0df64f
Compare
|
Auto-sync is disabled for ready for review pull requests in this repository. Workflows must be run manually. Contributors can view more details about this message here. |
|
/ok to test a0df64f |
|
/ok to test 76447f3 |
76447f3 to
9a7060f
Compare
9a7060f to
9a140b1
Compare
|
/ok to test 9a140b1 |
9a140b1 to
e190399
Compare
|
/ok to test e190399 |
e190399 to
45e305f
Compare
|
/ok to test 45e305f |
45e305f to
a77c04e
Compare
|
/ok to test a77c04e |
terrykong
left a comment
There was a problem hiding this comment.
Three call sites collapse into one factory, and the refactor holds up — this is real dedup, not code moved around. Distillation drops ~40 hand-rolled lines, and the routed-experts dtype resolution that was copy-pasted in grpo.py and setup.py now lives in one place. Nothing here blocks the merge.
The new tests in test_nemo_gym_utils.py are a genuine improvement. The coverage migration is complete, and it adds three cases that existed nowhere before: the num_gpu_nodes == 0 branch, dtype resolution with router replay on, and the default to int16 with it off. The ray.get ordering assertion is stricter than the one it replaces. build_nemo_gym_config also copies env_configs["nemo_gym"] rather than mutating the caller's config, and there is a test pinning that.
Two behavior changes are not mentioned in the PR description. Both look like fixes; no code change needed, but each deserves a line in the description:
VIRTUAL_ENV/UV_PROJECT_ENVIRONMENTfor the grpo and SingleController Gym actors now point at the venv root instead of the python binary, because those paths moved ontomake_actor_runtime_env. Distillation already worked this way, and every consumer in the tree treats these as directories, so the new value is the right one.- Distillation now gets
tokenizer_configandpad_dynamic_image_shapespromoted to top-levelNemoGymConfigfields for the first time, which fixes VLM distillation. To be exact: this was not broken in production — no in-tree config exercises it. All threeis_vlm: trueconfigs underexamples/configs/recipes/vlm/are GRPO recipes, and no distillation config sets it. So it fixes a path nothing currently runs.
Environment caveat, stated once: this review is read from source at the pinned SHAs, with no GPU. The exception is the unit tests, which were actually run in a hand-built CPU-only venv — 21 passed in test_nemo_gym_utils.py, 22 in test_distillation.py, 16 selected in test_grpo.py, and 68 in test_setup.py with one pre-existing failure unrelated to this PR (test_single_controller_mopd_recipe_resolves_to_runtime_contract, which dies on an unset HF_HOME). Ruff is clean: the pinned 0.9.9 hooks (check, check --select I, format --check) all pass. CI is 60/60 green.
Generated by Claude Code
a77c04e to
8aa1cb9
Compare
|
/ok to test 8aa1cb9 |
|
/ok to test 8aa1cb9 |
The NemoGym actor was constructed in three places (grpo.setup, distillation.setup, single_controller_utils.setup) that had drifted apart, each forgetting a different field: - single_controller never passed use_fastokens or routed_experts_dtype - distillation never passed require_routed_experts - grpo and single_controller built runtime_env inline and set VIRTUAL_ENV to the *interpreter path* rather than the venv root Collapse all three onto spinup_nemo_gym_actor(), split out build_nemo_gym_config() so the config split (NeMo-RL knobs vs Gym's global config) is testable on its own, and use the shared make_actor_runtime_env() helper, which resolves VIRTUAL_ENV correctly. This makes the actor-creation path a single place to extend for multi-actor sharding. Distillation keeps require_routed_experts off: it never configures vLLM to emit routed experts, so requiring them would fail every rollout. The construction assertions that lived in the distillation tests move to the factory's own test module, where they cover all three callers at once. Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
The deferred vLLM path reserves one URL slot per data-parallel rank, so the list can contain None until the servers finish loading. Match the shared NeMo-Gym factory's input type instead of narrowing it to list[str]. Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
Update the bottom stack layer's mocks for tokenizer installation and the centralized factory signature so every PR remains independently testable. Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
Keep the concurrent setup double aligned with the actor helper so the test exercises the intended handshake instead of failing in its worker thread. Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
Make router replay and tokenizer behavior explicit at every call site, keep model dtype resolution patchable in the owning module, and document and test the config fields promoted for actor use. Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
8aa1cb9 to
c02605e
Compare
|
/ok to test c02605e |
… lint after main merge The origin/main merge (f6305dc) auto-merged nemo_gym.py without conflict but dropped the token_capture kwarg from the new spinup_nemo_gym_actor wrapper introduced by #3367, while setup.py still passed it. Restore the parameter and forward it to build_nemo_gym_config; assert in the unit test that it reaches the actor config. Also fix the Lint CI job: point pyrefly.toml at the renamed rollout_reassembler*.py modules (the stale blackbox_finalizer / finalizer_actor entries made pyrefly abort), and apply ruff format/isort to the files the merge left unformatted. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Pranav Thombre <pthombre@nvidia.com>
The four generation backends disagreed on what `dp_openai_server_base_urls` holds when there is no HTTP server to report. Megatron returned `[]`; vLLM, TRT-LLM and Dynamo returned a list of `None`s. The placeholder does not stand for anything -- in those branches no server exists, so the length carries no information -- but it does make the value lie to a plain truth test, since `[None]` is truthy. Callers had to know that and write `if not any(urls)` instead of `if not urls`, and a sync run printed `Reserved 1 vLLM server URLs: [None]`. Match Megatron and narrow the type to `list[str]`. `NemoGymConfig.base_urls` and `spinup_nemo_gym_actor` already declare `list[str]`, so the producers now match what the consumers ask for; NVIDIA-NeMo#3367 had to add a `cast(list[str], ...)` in `distillation.py` to bridge the gap. vLLM needed more than swapping the sentinel. A worker leaves `base_url` unset unless `expose_http_server` started a server, so an async engine without one answered `[None] * dp_size` from the workers themselves, not from the guarded branch. Both `_report_dp_openai_server_base_urls` and `_collect_reserved_urls` now report that case as empty. Emptiness is decided for the list as a whole rather than by dropping entries. `expose_http_server` is one config value for every worker, so the answer is all-or-nothing; and `GenerationFleetHealth` indexes these URLs by shard and rejects a list whose length does not match `shard_count`, so filtering entry by entry would turn a degraded shard into a startup failure with a length error that names neither cause. No caller changes needed: `_shard_base_urls` already collapses an all-None list to None and does the same with an empty one, `_maybe_start_generation_router` filters falsy entries before its own emptiness check, and NeMo-Gym is reachable only behind the `should_expose_http_server` assert, so the no-server list never gets there. Nothing reads the length in the no-server case. The redundant `any(...)` guards stay as they are. Signed-off-by: Yupeng Tang <85978465+yupengtang@users.noreply.github.com>
Summary
Centralizes construction of the
NemoGymRay actor innemo_rl/environments/nemo_gym.py.grpo.py,distillation.py, andsingle_controller_utils/setup.pypreviously assembled the actor configuration independently. Those paths could diverge in how they handled NeMo RL integration keys, uv directories, router replay, routed-expert dtype, anduse_fastokens.The shared implementation now separates those responsibilities:
build_nemo_gym_config()removes NeMo RL integration settings from the configuration forwarded to Gym and applies model-dependent rollout settings.spinup_nemo_gym_actor()creates the actor runtime environment, preserves existing placement behavior, starts Gym, and installs the tokenizer.VIRTUAL_ENVandUV_PROJECT_ENVIRONMENTto the virtual-environment root throughmake_actor_runtime_env(), matching the directory values already used by distillation.tokenizer_configandpad_dynamic_image_shapesinto the top-levelNemoGymConfigfields consumed by the actor. This enables the VLM distillation path, although no current in-tree distillation recipe setsis_vlm: true.Stack
This is the first PR in the NeMo-Gym actor-sharding stack. Actor lifetime fixes follow in #3368.