Skip to content

feat(automodel): make the process-group timeout configurable - #4876

Open
linmuchuiyang wants to merge 3 commits into
NVIDIA-NeMo:mainfrom
linmuchuiyang:feat/automodel-distributed-timeout
Open

linmuchuiyang wants to merge 3 commits into
NVIDIA-NeMo:mainfrom
linmuchuiyang:feat/automodel-distributed-timeout

Conversation

@linmuchuiyang

@linmuchuiyang linmuchuiyang commented Oct 23, 2026 •

Copy link
Copy Markdown

What does this PR do ?

Adds policy.automodel_cfg.distributed_timeout_minutes (and value.automodel_cfg.… for PPO), the Automodel-path counterpart of the megatron_cfg.distributed_timeout_minutes knob that #4481 added for the Megatron workers. setup_distributed passes timeout=datetime.timedelta(minutes=…) to torch.distributed.init_process_group, so the default group and every FSDP2 / expert-parallel sub-group derived from it use the configured collective timeout instead of torch's default (10 minutes for NCCL). Unset / null keeps the default; non-positive values are rejected with the same message pattern as #4481.

Why: on the Automodel/DTensor path the first training step JIT-compiles kernels (HybridEP / DeepEP dispatch, TileLang attention) and loads large MoE checkpoints; on cold nodes those phases can keep ranks apart for more than 10 minutes, and the only way to survive it today is an environment-variable patch of setup.py. The knob was needed to run the DeepSeek V4.1 Flash recipe (deepseek-v4.1-support) on H100 (NeMo RL #4246, data-point comment) and GLM-5.2 / DeepSeek-V4 Automodel fine-tuning on H100 and GB200.

Changelog

  • nemo_rl/models/automodel/setup.py: _distributed_timeout_minutes() (validation) and _init_process_group() (backend + optional timeout); setup_distributed calls it.
  • nemo_rl/models/policy/__init__.py: AutomodelConfig.distributed_timeout_minutes: NotRequired[float | None].
  • examples/configs/{dpo,grpo_math_1B,grpo_math_1B_megatron,ppo_math_1B,rm,sft,vlm_mpo}.yaml: distributed_timeout_minutes: null with a comment under every automodel_cfg block (same files fix(megatron): make the NCCL collective timeout configurable #4481 annotated for megatron_cfg, plus rm.yaml).
  • tests/unit/models/automodel/test_automodel_setup.py: TestDistributedTimeoutMinutes (forwarded with and without CPU offload backend, unset keeps the default, non-positive rejected).

Issues

Related: #4481 (Megatron counterpart), #2229 / #2390 (exposing hard-coded infrastructure parameters), #4246.

Usage

policy:
  automodel_cfg:
    distributed_timeout_minutes: 45   # null = torch default (10 min for NCCL)

Before your PR is "Ready for review"

  • Unit tests added (tests/unit/models/automodel/test_automodel_setup.py::TestDistributedTimeoutMinutes). Locally the two helpers were exercised with the same four cases in a standalone harness (7 passed); the module-level test needs main's full environment (our container is the older deepseek-v4.1-support stack), so please run it in CI
  • Signed-off commits (DCO)

@copy-pr-bot

copy-pr-bot Bot commented Oct 23, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

Add policy.automodel_cfg.distributed_timeout_minutes (and value.automodel_cfg for PPO), the
Automodel-path counterpart of megatron_cfg.distributed_timeout_minutes (NVIDIA-NeMo#4481). setup_distributed
passes timeout=datetime.timedelta(minutes=...) to torch.distributed.init_process_group, so the
default group and the FSDP2 / expert-parallel groups derived from it use the configured collective
timeout instead of torch's 10-minute NCCL default. Unset keeps the default; non-positive values are
rejected with the same message pattern as NVIDIA-NeMo#4481.

On the Automodel/DTensor path the first training step JIT-compiles kernels (HybridEP / DeepEP,
TileLang) and loads large MoE checkpoints; on cold nodes that keeps ranks apart for more than
10 minutes, and until now the only remedy was patching setup.py.

Signed-off-by: linmuchuiyang <6169320+linmuchuiyang@users.noreply.github.com>
@linmuchuiyang
linmuchuiyang force-pushed the feat/automodel-distributed-timeout branch from dd0b3dd to b02cbd1 Compare October 23, 2026 07:55
@svcnvidia-nemo-ci svcnvidia-nemo-ci added the waiting-on-maintainers Waiting on maintainers to respond label Oct 25, 2026
@sharonyu-115
sharonyu-115 self-requested a review October 30, 2026 09:05

@sharonyu-115 sharonyu-115 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for contributing this configurable timeout for Automodel training.

This PR adds automodel_cfg.distributed_timeout_minutes, including support for PPO value workers, to accommodate long initialization, kernel compilation, and checkpoint-loading delays. Unset or null preserves PyTorch’s default; non-positive values are rejected.

Three P2 issues need attention:

  1. C1: Forward the timeout to newly created training subgroups and add a regression test for that forwarding.
  2. C2: Update the five reference YAML snapshots affected by the new default.
  3. C3: Mark the new timeout tests so the Automodel CI lane selects them.

Validation used isolated CPU harnesses and pinned upstream source. The config failures and test-selection gap were reproduced; full repository tests and GPU/NCCL execution were not run.

Generated by Codex

torch.distributed.init_process_group(backend=backend)
else:
torch.distributed.init_process_group(
backend=backend, timeout=datetime.timedelta(minutes=timeout_minutes)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C1 · 2 action items.
TL;DR — newly created training subgroups retain the backend timeout despite the new setting.

The default-group initialization receives the timeout, but the mesh construction call omits it. Pinned Automodel documents that mesh subgroups do not inherit this value, so those collectives can still time out during the long first-step delays this option targets.

AI-1

Pass timeout_minutes= with the result of _distributed_timeout_minutes(config) to the mesh construction call. Automodel already forwards this parameter. The call is outside this diff, so an in-place suggestion cannot cover it.

AI-2

Extend the existing distributed-setup test to check that an explicit timeout reaches both default-group initialization and mesh construction, and that absent/null settings preserve default behavior. Require the explicit-timeout case to fail before the fix and pass afterward. The new helper-only tests do not exercise mesh construction. The existing test is outside this diff, so an in-place suggestion cannot cover the edit.

# Collective timeout (minutes) for the default process group and the FSDP2 /
# expert-parallel groups derived from it. null = torch default (10 for NCCL).
# Raise it when the first step JIT-compiles kernels (MoE dispatchers, TileLang) or I/O is slow.
distributed_timeout_minutes: null

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C2 · 1 action item.
TL;DR — the new YAML defaults break five reference-config checks.

The added key is absent from the reference snapshots. An isolated run of the existing parity check, using the real config loader, reproduced five failures for missing timeout keys.

AI-1

Add the policy Automodel timeout default (null) to dpo.yaml, grpo_math_1B.yaml, rm.yaml, sft.yaml, and ppo_math_1B_megatron.yaml. Add the same default to PPO's value Automodel configuration. These snapshots are outside the diff, so an in-place suggestion cannot cover this change.

)


class TestDistributedTimeoutMinutes:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C3 · 1 action item.
TL;DR — the Automodel CI lane filters out all seven new timeout cases.

The new test class lacks the marker required by the Automodel lane and its collection filter. An isolated run with that filter selected none; adding the marker ran all seven successfully.

AI-1

Add the class marker:

Suggested change
class TestDistributedTimeoutMinutes:
@pytest.mark.automodel
class TestDistributedTimeoutMinutes:

Address the review on NVIDIA-NeMo#4876:

- C1: pass timeout_minutes to MeshContext.build. DeviceMesh axes create their
  own NCCL groups and do not inherit the default group's timeout; Automodel
  applies the value through a per-axis backend override. Correct the
  _init_process_group docstring that claimed otherwise.
- C1 test: TestSetupDistributed now checks that an explicit timeout reaches
  both init_process_group and MeshContext.build, and that null / absent keeps
  torch's default for both. The explicit case fails on the previous commit
  (assert None == 45) and passes with the fix.
- C2: add the automodel_cfg.distributed_timeout_minutes default to the five
  reference config snapshots (policy and value for PPO), fixing the five
  test_reference_configs_up_to_date failures.
- C3: mark TestDistributedTimeoutMinutes with @pytest.mark.automodel so the
  Automodel CI lane (--automodel-only) selects it.
- Sort the test module's import block (ruff I001).

Signed-off-by: linmuchuiyang <6169320+linmuchuiyang@users.noreply.github.com>
@svcnvidia-nemo-ci svcnvidia-nemo-ci removed the waiting-on-maintainers Waiting on maintainers to respond label Nov 2, 2026
@linmuchuiyang

Copy link
Copy Markdown
Author

Thanks for the careful review — all three points were right, and they are addressed in bd9d948:

C1 — timeout now reaches the mesh sub-groups. setup_distributed passes timeout_minutes=_distributed_timeout_minutes(config) to MeshContext.build, so the per-axis NCCL groups get the configured timeout through Automodel's backend override instead of keeping torch's default. The _init_process_group docstring that claimed the sub-groups inherit the default group's timeout was wrong and is corrected. TestSetupDistributed gained test_setup_distributed_forwards_timeout_to_process_group_and_mesh (parametrized explicit / null / absent), which asserts that an explicit value reaches both init_process_group and MeshContext.build and that null / absent keep torch's default for both. Against the previous commit the explicit case fails (assert None == 45) and the other two pass; with the fix all three pass.

C2 — reference snapshots updated. automodel_cfg.distributed_timeout_minutes: null is added to dpo.yaml, grpo_math_1B.yaml, rm.yaml, sft.yaml, and both policy and value in ppo_math_1B_megatron.yaml. test_reference_configs_up_to_date now passes for all seven reference files (it failed for exactly those five before).

C3 — tests are in the Automodel lane. TestDistributedTimeoutMinutes is marked @pytest.mark.automodel; with --automodel-only the seven cases are collected (previously 0). The import block of the test module is also re-sorted, which ruff's I001 had flagged on the earlier commit.

Validation (CPU, Python 3.13 venv with torch 2.13 and the pinned Automodel submodule, init_process_group / MeshContext mocked as in the existing tests):

  • pytest tests/unit/models/automodel/test_automodel_setup.py -k "TestSetupDistributed or TestDistributedTimeoutMinutes" --automodel-only → 15 passed
  • pytest tests/unit/test_config_v2.py -k test_reference_configs_up_to_date → 7 passed (5 failed on the previous commit)
  • full tests/unit/models/automodel/test_automodel_setup.py --automodel-only → 113 passed
  • ruff format --check, ruff check, ruff check --select I on the two changed Python files → clean

GPU / NCCL execution was not run here; the sub-group timeout path is exercised by Automodel's own _nccl_backend_override.

@sharonyu-115 sharonyu-115 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@linmuchuiyang Thanks for addressing the review comments. A further review exposed another issue:

Blocked pending Automodel #4601. The new mesh-timeout forwarding exposes a reproducible CPU-offload failure during GRPO initialization. Once the upstream fix lands, this PR can proceed with an updated Automodel pin and regression coverage.

ep_size=ep_size,
),
world_size=world_size,
timeout_minutes=_distributed_timeout_minutes(config),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The mesh-timeout forwarding depends on fixing Automodel #4601. With CPU offload enabled, an explicit timeout currently creates NCCL-only mesh groups, causing prepare_refit_info() → DTensor.full_tensor() to fail with No backend type associated with device type cpu.

Reproduced with Qwen2.5-1.5B on two H100s: timeout 45 fails before training; timeout null completes one step.

Please hold this PR until the upstream fix lands, then update the Automodel pin and add a two-GPU regression test covering CPU offload with an explicit timeout.

@linmuchuiyang

Copy link
Copy Markdown
Author

Agreed, thanks for catching it — forwarding the timeout to the mesh exposes _nccl_backend_override dropping the Gloo half of cuda:nccl,cpu:gloo, which breaks the CPU-offload path that this very knob is meant to help. Holding this PR until Automodel #4602 (the fix for #4601) lands. Once it is in, I will bump the Automodel pin here and add the two-GPU regression test covering CPU offload with an explicit timeout.

…timeout

Spawn two ranks through the real setup_distributed with cpu_offload=True and
distributed_timeout_minutes=45 (default group cuda:nccl,cpu:gloo) and check
that the default group and every mesh axis keep both backends with the
45-minute timeout, that CPU and CUDA all-reduces work on every axis, and
that a CPU-offloaded FSDP parameter sharded on device_mesh['dp'] comes back
through full_tensor() - the prepare_refit_info() path of
NVIDIA-NeMo/Automodel#4601.

With the current Automodel pin the rank bodies fail with 'No backend type
associated with device type cpu'; with NVIDIA-NeMo/Automodel#4602 applied
the test passes. Until the pin includes that fix the test skips with that
reason; the guard can go once the pin is bumped.

Signed-off-by: linmuchuiyang <6169320+linmuchuiyang@users.noreply.github.com>
@linmuchuiyang

Copy link
Copy Markdown
Author

Automodel #4602 is merged (d1af15d), thanks for pushing that through. On this PR:

Two-GPU regression test — added tests/unit/models/automodel/test_setup_distributed_gpu.py in 3fd4203. It spawns two ranks, runs the real setup_distributed with cpu_offload=True and distributed_timeout_minutes=45 (default group cuda:nccl,cpu:gloo), then checks that the default group and every mesh axis carry the 45-minute timeout on both the CPU and CUDA backends, that CPU and CUDA all-reduces work on every axis, and that a CPU-offloaded FSDP parameter sharded on device_mesh["dp"] reconstructs through full_tensor() — the prepare_refit_info() path from #4601. On two H100s with the current pin (b916107) the rank bodies fail with No backend type associated with device type cpu; with the #4602 change applied on top of the pin the test passes (CPU and CUDA all-reduces on every axis, both backends at 45 min, full_tensor() matches). The test is in the Automodel lane (@pytest.mark.automodel, under tests/unit/models/automodel/).

Automodel pin — I did not bump it in this PR. The pin would move from Automodel 0.6.1 to 0.8.0 (68 commits), and Automodel's own requirements moved with it: transformers==5.18.0 (NeMo RL caps at <=5.16.1), transformer-engine>=2.19.0 (this repo pins v2.14.1), quack-kernels==0.6.4, a new deep_ep revision and Python >= 3.11 — i.e. a relock of the automodel extra, not a submodule-only change. That seems better done by the maintainers in a dedicated bump, where its CI lanes can be watched. To keep this PR green until then, the new test skips with the reason "Automodel pin predates NVIDIA-NeMo/Automodel#4602" when _nccl_backend_override still hard-codes NCCL, and becomes active automatically on the bump; the guard can be deleted at that point. If you would rather have the bump in this PR anyway, tell me and I will do it, but I wanted to flag the scope first.

@svcnvidia-nemo-ci svcnvidia-nemo-ci added the waiting-on-maintainers Waiting on maintainers to respond label Nov 5, 2026
zpqiu added a commit that referenced this pull request Nov 6, 2026
…PO recipe

Apply the performance suggestions from @linmuchuiyang in #4246 to the 12-node
V4.1 Flash DAPO recipe:
- Generation: enforce_eager false with compilation_config.backend eager
  (CUDA graphs without Inductor) and max_num_seqs 32 instead of 8.
- Training: the HybridEP MoE dispatcher instead of torch.

HybridEP needs two DTensor policy options, both opt-in:
- dtensor_cfg.hybridep_max_tokens_per_rank allocates Automodel's shared
  HybridEP buffer for the micro-batch token budget before the first forward.
  Automodel otherwise sizes it from the first dispatch and reallocates it when
  a larger micro-batch arrives, a mid-step collective that has failed across
  nodes with "Failed to create QP".
- dtensor_cfg.distributed_timeout_minutes sets the NCCL timeout of the default
  process group and of the device-mesh groups (ported from the setup part of
  #4876). HybridEP's first-use JIT and in-step TileLang compiles can keep ranks
  waiting longer than the 10-minute default.

Before colocated generation wakes up, the policy worker now also drops the
per-dispatch tensors that flex MoE dispatch managers keep (ported from #4756);
after activation-checkpointed backward they hold recompute graphs alive and
pin allocator segments that empty_cache() cannot release.

HybridEP requires all EP ranks in one NVLink domain, so the 12 nodes must come
from one GB200 NVL72 rack (Slurm --segment=12 on Lyris).

Verified on Lyris GB200, 12 nodes:
- 30 steps (job 3594829): generation about 28 s/step vs 640-920 s in the
  recipe's torch-dispatcher reference run; steps 11-30 averaged 77 s
  policy_training and 59 s logprobs vs 133 s and 100 s. AIME 0.433/0.492/0.563
  at steps 10/20/30 vs 0.450/0.529/0.579; generation KL 0.0004-0.0005.
- Checkpoint save/resume (jobs 3611584 + 3611585): saves at steps 2/4/6/8 took
  66-116 s, the resumed job continued at step 5, and KL stayed at 0.0004-0.0005.

Co-authored-by: linmuchuiyang <6169320+linmuchuiyang@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: alexchiu <qiuzhaopeng@foxmail.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

community-request waiting-on-maintainers Waiting on maintainers to respond

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants