Skip to content

Add an end-to-end test on a collection that is not the global grid - #8771

Closed
Connor-XY wants to merge 657 commits into
NVIDIA:mainfrom
Connor-XY:yx/pstate-e1-e2e-collection-test
Closed

Connor-XY wants to merge 657 commits into
NVIDIA:mainfrom
Connor-XY:yx/pstate-e1-e2e-collection-test

Conversation

@Connor-XY

@Connor-XY Connor-XY commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Depends on #7991, #8722, #8723, #8736, #8731, #8727, #8739, #8763, #8773 and #8775. Together they contain every other PR of this series except #8777, which sits beside this one on the same base. Please review the diff from their merge: scoped diff.

Land this right before #8777, after everything it contains, #8772, #8773 and #8775 included.

Why. The tests that forbid the global grid each cover one component, mostly with stand-ins. No test ran model build, DDP and the optimizer, training steps, a torch_dist save and load, and the inference build together on a collection whose TP and DP groups differ from the global ones. A component that reads the global grid, or that passes no group (WORLD) to a collective, could hide between those tests.

What. New tests/unit_tests/test_process_group_collection_end_to_end.py. No library change.

  • The global grid is TP=2 in the default rank order, built and registered by Utils.initialize_model_parallel. A second collection comes from build_process_group_collection(order="dp-tp-cp-ep-pp") and is not registered. Its TP and DP groups differ from the global ones on every rank.
  • Each phase runs inside forbid_global_process_groups() with ProcessGroupFallbackWarning and ParallelStateDeprecationWarning as errors. The once-per-process records of fallbacks, accessor call sites and optimizer grad-stats fallbacks are emptied first, so a warning from an earlier test cannot hide a fallback. A failure names the phase, and the chained error names the accessor or fallback.
  • The phases: build a GPT model with the training builder (Float16Module, DDP with the distributed optimizer), build the optimizer with get_megatron_optimizer, run two steps with get_forward_backward_func, save and reload a torch_dist checkpoint, and build the model for inference with get_model_for_inference and run it through GPTInferenceWrapper.
  • Since main's refactor(training): Refactor Tokenizer Initialization #8391, get_model_for_inference reads the run config and passes the tokenizer config to the model builder. The inference test takes the run_config fixture, and its stub builder takes tokenizer_config.
  • The checks:
    • Modules, model wrappers (DDP, Float16Module), DDP buffers and bucket groups, the optimizer and its grad-stats group, and the inference wrapper keep groups with the collection's ranks. None of them keeps WORLD or a group object of the global grid, directly or in a list, tuple, dict or collection. Several groups have the same ranks in both layouts, so only the object tells them apart. The inference context resolves the model's collection.
    • DP replicas are equal after the build and after training. The TP shards of the built model differ across the collection's TP group, which a broadcast over WORLD would break.
    • Per-DP-rank losses match the same run on the global grid. This catches a global group whose ranks differ from the collection's, but not WORLD, which spans the same ranks in both layouts.
    • A replica's loss and unreduced gradients do not change when the other replicas take other batches. A forward or backward collective over WORLD breaks this.
    • The checkpoint's replica ids, offsets and optimizer keys follow the collection. The checkpoint loads into a new model and optimizer, and into a model on the global grid.
    • The inference model reproduces the trained model's logits.
  • Both runs are seeded with training's _set_random_seed(..., pg_collection=...), as initialize_megatron() does, so the CPU generator that initializes the learned position embeddings is seeded too.
  • The unit-test conftest sets NVTE_FLASH_ATTN and NVTE_FUSED_ATTN before every test, and since Make MIMO vision encoder attention backend configurable #7362 set_attention_backend() refuses values that disagree with the config's backend. The test clears them before each model it builds.

Tests.

  • Before the sync, on 4 CPU ranks with Gloo, the 2 CPU tests passed on every rank and the 8 CUDA tests skipped. With the record reset for the grad-stats fallback removed, test_the_block_rejects_global_reads_and_fallbacks failed on every rank.
  • Before the sync, on a CPU stand-in for the CUDA phases (not committed), the identity check reported a module that keeps the global grid's cp group or WORLD, and the shard check reported a broadcast over WORLD.
  • On 4 GPUs against its base, test_process_group_collection_end_to_end.py passes 10 of 10. The file is new, so all 10 are new tests. The same job runs 12 other test files of the series (data paths, dataset providers and ownership, get_batch, Megatron-FSDP MoE, optimizer parameter names, the high-level inference API, example process groups, owner linears and the parallel_state deprecation tests): 637 tests pass here and 637 on the base, and none fail. There are no regressions against the base.
  • In the integration run (main 77391e7 with all 93 PRs of [TRACKING] Deprecate parallel_state: explicit process groups in megatron/core #6307 and 6 main-based fixes merged, against main), this PR's test files show no regressions against main. There test_process_group_collection_end_to_end.py passes 10 of 10 on 4 GPUs.
  • Before the sync, a stacked version of this PR ran on GPUs. Its base also had Use the shared expert's TP group in the fused SwiGLU path #8024, Scatter LLaVA's language-model input over the model's TP group #8025 and a test-only follow-up of Take the FP8/FP4 amax reduction group from a process-group collection #8034, which this base does not have. The 4-GPU run above shows that the test passes without them. On the stacked version the test passed 10 of 10 on 4 GPUs and on 8 GPUs (two nodes), with and without -W error::megatron.core.process_groups_config.ProcessGroupFallbackWarning. Run in one process after test_emerging_optimizers.py, all 113 tests passed. Before the NVTE fix, 3 of the 10 failed there because of the conftest variables.

Compatibility.

  • Test only. It needs CUDA and an even world size of at least 4, and skips otherwise. CI's catch-all unit bucket runs it on 8 GPUs, as TP2 x DP4.
  • The tolerances assume repeatable kernels for identical shapes: rel 1e-5 for the step-0 loss across layouts, abs 1e-3 for the later losses, rel 1e-5 for a replica's loss between two passes, a relative-norm change of 1e-3 for its gradients, and 2e-2 for the inference logits.
  • The result checks gather per-rank problem lists and fail on every rank. A phase that raises on only some ranks still leaves the others in a collective until the NCCL timeout.
  • determinism-exempt is needed only while the PRs below this one are unlanded. CI diffs against main, and the base contains registered kernel files. This PR's own diff passes the coverage check.

Synced with main 77391e7 on 2026-11-01 by a merge commit that lists each conflict and its resolution, so existing commits and review links stay valid.

Later on 2026-11-01 the branch also merged main bfab98f. Its #8471 moves FullyShardedDataParallelV1 and FullyShardedDataParallelV2 from mcore_fsdp_adapter.py into mcore_adapter_v1.py and mcore_adapter_v2.py, and keeps mcore_fsdp_adapter.py as the factory that re-exports them. The merge carries its bases' edits to those classes where the code moved; this PR's own diff does not change. On 4 GPUs against its new base, the Megatron-FSDP adapter tests (7 files) show no regressions: 81 passed here, 81 on the base. The two CUDA-graph end-to-end cases of test_mcore_fully_sharded_data_parallel.py fail on both sides, as they do on main.

Part of #6307.

🤖 Generated with Claude Code

@copy-pr-bot

copy-pr-bot Bot commented Oct 30, 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.

Connor-XY and others added 26 commits October 30, 2026 15:27
NVIDIA#8755 lets log_config_to_disk take the logging owner's process groups,
through its pg_collection keyword, and adds
test_config_logger_process_groups.py. The config-logger change that
follows passes the optimizers' collection to that keyword and extends
that test, so merge it here.

Only the process-group usage allowlist conflicts. It is regenerated on
the merged tree: 167 reads (136 accessor, 16 shim, 15 guarded).
Against NVIDIA#8755's file, NVIDIA#8752's guarded reads in get_megatron_optimizer,
_backfill_gtp_sharded_param_map and clip_qk replace their global reads.
The merged .py files show no new undefined names or redefinitions (ruff
F821/F811). Both branches carry NVIDIA#6303.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Yan Xu <yxu1@nvidia.com>
log_config_to_disk names each dump by the ranks in the logging model's process
groups and falls back, with a warning, to the global parallel state when it is
called without them. The FP32, mixed-precision, distributed and fully sharded
optimizers and both Megatron-FSDP wrappers still called it without their
collection, so stock training with --config-logger-dir reached that fallback,
and strict mode turned it into an error.

The optimizers take a keyword-only pg_collection that get_megatron_optimizer
passes, and the FSDP wrappers pass the collection they already hold.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Yan Xu <yxu1@nvidia.com>
The training scripts' dataset changes that follow sit on the
is_dataset_built_on_rank and get_batch code that NVIDIA#8754 moves to the
model config's virtual-pipeline size, and their tests use NVIDIA#8017's
forbid_global_process_groups and build_test_pg_collection, which NVIDIA#8754
brings. The merge is clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Yan Xu <yxu1@nvidia.com>
The checker classification that follows requires every allowlisted
read in megatron/core to be warned or guarded, and its test lists the
only two reads allowed to stay unwarned. NVIDIA#8752 warns before
get_megatron_optimizer's use_mpu_process_groups() fallback, before
clip_qk's global data-parallel group (with NVIDIA#8043, which NVIDIA#8752 brings)
and describes the GTP shards in _backfill_gtp_sharded_param_map with the
optimizer's own groups. Without it those four reads are unwarned and the
classification test fails.

megatron/core/optimizer/__init__.py conflicts on its imports: NVIDIA#8045's
e09f5de (which NVIDIA#8749 brings) imports get_ep_rank_and_size next to
get_global_unique_param_name, and NVIDIA#8752's 2d4d3cc adds
resolve_process_groups to the process_groups_config import. The import
takes both.

NVIDIA#8752 also carries NVIDIA#6303, which this branch does not have yet, so
tests/unit_tests/export/trtllm/test_trtllm_distributed_gpu_converter.py
conflicts on its imports: NVIDIA#6303 imports ProcessGroupCollection and NVIDIA#8059
adds ProcessGroupFallbackWarning to the same import. The import takes
both. The process-group usage allowlist is regenerated on the merged
tree.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Yan Xu <yxu1@nvidia.com>
The checker classification that follows requires every allowlisted
read in megatron/core to be warned or guarded. NVIDIA#8755 warns the global
fallbacks of the log and param-hash helpers, RoPE, MIMO partitioning,
batch-invariant unpermute, the FP8/FP4 contexts and the CUDA-graph
helpers, and brings NVIDIA#8017, NVIDIA#8032, NVIDIA#8035, NVIDIA#8047, NVIDIA#8049 and NVIDIA#8053 (NVIDIA#6303
already came in with NVIDIA#8752).

megatron/core/extensions/transformer_engine.py conflicts on its
parallel_state import: NVIDIA#8038 drops get_tensor_model_parallel_world_size
and keeps model_parallel_is_initialized, and NVIDIA#8755's eac0b1c drops
get_amax_reduction_group and model_parallel_is_initialized. The merged
file calls none of the three, so the import keeps only
get_context_parallel_group, get_hierarchical_context_parallel_groups and
get_tensor_model_parallel_group. The process-group usage allowlist is
regenerated on the merged tree.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Yan Xu <yxu1@nvidia.com>
The checker classification that follows requires every allowlisted
read in megatron/core to be warned or guarded. NVIDIA#8756 rejects unset tp
and cp fields in the attention and MLP builders, validates the MIMO
checkpoint groups and requires the refit metadata groups, instead of
probing the collection and falling back to the global grid.

megatron/core/extensions/transformer_engine.py conflicts on the
process_groups_config import: NVIDIA#8755 imports amax_reduction_group and
resolve_process_groups, and NVIDIA#8756 imports require_process_groups. The
import takes all three. The process-group usage allowlist is regenerated
on the merged tree.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Yan Xu <yxu1@nvidia.com>
This base holds every other PR of the parallel_state deprecation series
(NVIDIA#6307): the end-to-end test that follows asserts that no process-group
fallback or deprecation warning fires anywhere on a collection that is
not the global grid, and the strict-fallback functional tests turn
every remaining fallback into an error, so both need the whole series
below them. NVIDIA#8763 brings NVIDIA#8752, NVIDIA#8755 (with NVIDIA#6303), NVIDIA#8042 and NVIDIA#8045.

Four files conflict:
- extensions/transformer_engine.py: the process_groups_config import
  adds NVIDIA#8755's resolve_process_groups (eac0b1c).
- models/T5/t5_model.py: drop the self.tp_group line that NVIDIA#6293 kept;
  NVIDIA#6303's b732c67, which NVIDIA#8763 brings, removes it because
  LanguageModule already sets self.tp_group from the collection.
- optimizer/fully_sharded_optimizer.py: keep NVIDIA#8763's
  ProcessGroupCollection import (c17120b) and NVIDIA#8731's
  get_model_ep_rank_and_size import (3877940).
- parallel_state.py, get_data_parallel_group_gloo and
  get_expert_data_parallel_group_gloo: take NVIDIA#6259's check_initialized
  argument and Args section, which NVIDIA#8763 brings, with NVIDIA#7991's
  deprecation paragraph before the Args section.
The process-group usage allowlist is regenerated on the merged tree.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Yan Xu <yxu1@nvidia.com>
The end-to-end seeding test that follows checks that the collection
pretrain builds after initialize_megatron holds the same groups as the
collection the RNG was seeded from. It builds that collection with
training._build_pg_collection_from_parallel_state, the helper that
NVIDIA#8037 adds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Yan Xu <yxu1@nvidia.com>
…8039

Seeding the RNG from the grid that initialize_megatron builds needs the
collection that initialize_model_parallel returns, which NVIDIA#8039 adds.
NVIDIA#8039's head also contains the Gloo fields of the collection (NVIDIA#6259).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Yan Xu <yxu1@nvidia.com>
NVIDIA#8031, NVIDIA#8037 and NVIDIA#8039 each refreshed the allowlist against main.
Merging them keeps the total of 195 that NVIDIA#8031 and NVIDIA#8037 share, since
NVIDIA#8039's total equals main's, while the merged entries record 194 reads.
Regenerate the file so that its total matches its entries. No entry
changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Yan Xu <yxu1@nvidia.com>
initialize_megatron seeds the random number generators from the model's
collection when pretrain passes one. Stock training passes none, so
_set_random_seed and model_parallel_cuda_manual_seed read the pipeline,
data-parallel, tensor, expert and GTP-remat ranks from the global
parallel state, although initialize_model_parallel has just returned
the collection of the grid it built.

_initialize_distributed now returns that collection, or None when it
builds no grid (model-parallel setup skipped, a grid already
initialized, or no CUDA device). initialize_megatron seeds from it
unless the caller passes seed_pg_collection, and falls back to the
global state only when no grid was built. The log lines that report the
grid sizes read the same collection, so initialization reads no global
group, rank or size once the grid exists.

The collection holds the groups that initialize_model_parallel
registers, so the seeds equal those of the global state, and the
collection that pretrain builds afterwards from the registered groups
holds the same group objects. With lazy_mpu_init, the provisional
tensor-parallel rank that initialize_megatron sets to the global rank
before the grid exists no longer replaces the group rank in the
tensor-parallel seed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Yan Xu <yxu1@nvidia.com>
When pretrain() is given a ProcessGroupCollection, it passes it to the
model, the optimizer and the schedules, but the rest of the loop still
read the global parallel state: the packed-sequence FLOPs deduplication,
the logits saver and capture stage checks, the batch-size check, the
skipped-iteration batch reads, the samples-seen reduction, the batch
accounting, the full-validation tensor-parallel rank, the full-dataset
length probe and the GPU sniff test. With a collection whose grid
differs from the global one, these selected other ranks than the model
trains on.

Take these groups from the run's single-grid collection, or from the
model's collection for per-model steps:

- consume_seqlen_stats_in_iteration, dummy_train_step,
  post_training_step_callbacks and the private full-dataset helpers
  take an optional collection; train() and pretrain() pass the run's
  single-grid collection.
- train_step, evaluate, evaluate_and_print_results and the logits hooks
  use the model's collection, then a single-grid run collection, and
  read the global grid only when given neither.
- The batch accounting and the samples-seen reduction use dp_gtp_remat,
  the group get_data_parallel_group() returns by default.

A multi-module collection keeps its run-level steps on the global
parallel state, as before, and counts samples on its model's groups.
Without a caller-provided collection, pretrain() has no run collection,
so the run-level steps keep reading the global grid, and the per-model
steps use the model's collection, which holds the global groups. Callers
that pass no collection keep the previous behavior. The batch-size check
becomes a ValueError whose message reports the size the microbatch
calculator divides by, computed only when it fails.

On the standard grid every step selects the same groups as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Yan Xu <yxu1@nvidia.com>
update_packed_sequence_stats counts each packed microbatch once per
data-parallel replica: on the last pipeline stage, tensor-parallel rank
0 and context-parallel rank 0. It read those ranks from the global
parallel state, so a model on another grid counted samples on the wrong
ranks.

Accept an optional pg_collection and read the pipeline, tensor- and
context-parallel ranks from it. Callers that pass none keep reading the
global parallel state.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Yan Xu <yxu1@nvidia.com>
With restore_modelopt_state and no explicit
use_arbitrary_attention_mask, default_layer_spec enables the arbitrary
attention mask only without context parallelism. It read the context
parallel size from the global parallel state, although
GPTModelBuilder.build_model receives the collection the model is built
on.

Give default_layer_spec an optional pg_collection, read the
context-parallel size from it, and pass the builder's collection.
Callers that pass none keep reading the global parallel state.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Yan Xu <yxu1@nvidia.com>
train() hands the GPU sniff test no collection when it runs on a
MultiModuleProcessGroupCollection, so run_gpu_sniff_test() builds one
from the global parallel state. Heterogeneous MiMo
(examples/mimo/pretrain_mimo.py) never initializes that state, so
--gpu-sniff-test-interval fails there before the first benchmark: the
data-parallel getter asserts that its group is not initialized. A
multi-module run that did build a global grid would test groups its
modules do not train on.

Passing each rank's module collection instead is not safe. The sniff
test all-gathers every metric over WORLD. A rank reports 0 or 2
reduce-scatter metrics and 0 or 2 all-to-all metrics, depending on
whether its TP and EP groups have more than one rank, plus 2 send/recv
metrics per stride of its DP group. MiMo modules size these groups
independently: the encoder always has EP=1, and the language model takes
--mimo-llm-ep. Ranks of different modules would issue different numbers
of WORLD all-gathers, and the surplus would pair with the closing WORLD
barrier or hang.

train() now raises a ValueError when --gpu-sniff-test-interval is set
with a multi-module collection. The check adds no collective. Every
rank of such a run receives a MultiModuleProcessGroupCollection and the
same flag value, so every rank raises at the same point, before any
sniff-test collective. Single-grid runs and runs without a collection
are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Yan Xu <yxu1@nvidia.com>
When pretrain() is given a process-group collection for the run, the
data loaders it builds still read the global grid: the samplers took
their data-parallel rank and size from parallel_state, the TP-rank-0
check that decides which ranks build the data pipeline read the global
TP rank, and full validation agreed on the number of evaluation
iterations over the global DP x CP group.

Pass the run's collection to build_train_valid_test_data_iterators,
build_train_valid_test_data_loaders and build_pretraining_data_loader.
The samplers shard over its GTP-inclusive data-parallel group
(dp_gtp_remat, or dp for collections without the GTP fields), the
pipeline is built on the first rank of its tensor-parallel group, and
the evaluation iterations are reduced over dp_cp_gtp_remat, matching
the default group of get_data_parallel_group(with_context_parallel=
True). A MIMO run keeps the global defaults because its collection has
one collection per module and its providers build their own loaders.
The new pg_collection arguments default to None, which keeps the
global groups for existing callers. Without a caller-provided
collection, pretrain() has none to pass, and the loaders keep that
global fallback.

On the standard grid the collection holds the global groups, so the
samplers and reductions are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Yan Xu <yxu1@nvidia.com>
NVIDIA#8017 adds test helpers that forbid global grid reads and build
process-group collections; its head also carries NVIDIA#6259's Gloo fields of
the collection. The Flextron router weight-sync tests that follow import
forbid_global_process_groups, so merge NVIDIA#8017 here. The merge is clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Yan Xu <yxu1@nvidia.com>


NVIDIA#8049 adds ProcessGroupFallbackWarning, resolve_process_groups and
require_process_groups, on top of NVIDIA#6293's deprecated global fallbacks.
The post-training summary and the Flextron model provider resolve their
groups through these helpers, the hybrid Flextron router requires them,
and the new tests expect the warning class. The merge is clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Yan Xu <yxu1@nvidia.com>
…8742

The Flextron get_batch that follows calls
is_first_or_last_pipeline_stage(vp_stage, pp_group=..., vp_size=...),
whose pp_group and vp_size parameters NVIDIA#8742 adds.

NVIDIA#8742 is based on main, so its get_batch and import lines conflict with
NVIDIA#6293's, which NVIDIA#6303 and NVIDIA#8049 bring in. All three are resolved to
NVIDIA#8742's side:
- pretrain_gpt.py, pretrain_hybrid.py: get_batch passes the model's
  pp_group and vp_size instead of NVIDIA#6293's
  mpu.get_pipeline_model_parallel_group(), and computes
  is_first_or_last_stage with them.
- pretrain_vlm.py: keep NVIDIA#8742's is_pp_first_stage/is_pp_last_stage
  import before the ProcessGroupCollection import that both sides add.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Yan Xu <yxu1@nvidia.com>


initialize_megatron now seeds the model-parallel RNG from the
collection that initialize_model_parallel returns (NVIDIA#8039, NVIDIA#8031), and
the training utilities' fallback warnings that follow change the same
seeding code in initialize.py. The merge is clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Yan Xu <yxu1@nvidia.com>
The merge of NVIDIA#8017 took its optimizer entries (one
get_megatron_optimizer shim read, from NVIDIA#6259, in place of the two shim
reads in _get_megatron_emerging_optimizer and
_get_megatron_optimizer_based_on_param_groups) and kept NVIDIA#6303's total
of 172, while the merged entries record 171 reads. Regenerate the file
so that its total matches its entries. No entry changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Yan Xu <yxu1@nvidia.com>
The ModelOpt loss function read the global tensor-parallel grid: a
sequence-parallel distillation loss took its slice of the loss mask by
the global TP rank and size, and every distillation loss split across
TP ranks was all-reduced over the global TP group.
print_distributed_quant_summary chose the ranks that print by the
global data-, tensor-, expert- and pipeline-parallel ranks. A model
built on a collection whose groups differ from the global grid
therefore reduced its distillation losses over the wrong ranks.

loss_func passes the tp group of the model's pg_collection to
_mask_loss, which slices the mask by the rank and size of that group
and all-reduces over it. The group is a new tp_group argument of
_mask_loss; a caller that omits it keeps the global group with a
ProcessGroupFallbackWarning (deprecated in 0.21, removed in 0.23), and
only when a distillation loss needs a group. loss_func still accepts
model=None, which pretrain_gpt.loss_func forwards when ModelOpt is
enabled and the caller, such as pretrain_vlm.py, binds no model: the
plain loss needs no group, and a split loss takes the warned fallback.

print_distributed_quant_summary reads the dp_cp_gtp_remat, tp, ep and
pp groups of the model's collection. This diagnostic runs whenever a
quantized checkpoint is loaded, so a model without a collection that
sets those groups, such as MimoModel, keeps printing by the global
ranks with the same warning instead of failing.

On the standard grid the collection holds the global groups, so the
losses and the printing ranks are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Yan Xu <yxu1@nvidia.com>
Flextron training read the global parallel grid throughout. The loss
split and reduced distillation losses over the global TP group and
gathered the per-budget report over the global data-parallel group.
The Mamba and grouped-MLP elasticity hooks sized and sliced their masks
by the global TP and expert-TP ranks. The router was named by the
global pipeline rank, seeded its Gumbel noise and counted
gradient-accumulation steps by the global DP rank and size, and built
its linear layers without a group, so they fell back to the global TP
group. The injected forward checked the global last stage, and
pretrain_hybrid_flex.py read, broadcast and sharded batches, drew
budgets and chose the dataset-building ranks on the global grid. A
Flextron model built on groups other than the global ones therefore
masked, reduced and seeded over the wrong ranks.

Take every group from the collection of the model or the run:
- loss_func passes the model's tp group to _mask_loss and gathers the
  distillation report over its dp_gtp_remat group;
- the Mamba hook uses the mixer's tp_group, and the grouped-MLP hook
  the expert tensor-parallel tp_group of the module;
- FlextronModelManager builds the router on the model's collection and
  names it by the rank in its pp group. FlextronRouter takes a
  pg_collection, seeds and counts accumulation steps over its
  dp_gtp_remat group and gives its tp group and the collection to its
  linear layers; the collection also supplies the groups of the
  router's sharded state dict. The injected forward checks the last
  stage of the model's pp group;
- get_batch, get_grad_acc_based_random_choice, is_dataset_built_on_rank
  and train_valid_test_datasets_provider take a pg_collection.
  forward_step passes the model's collection, and get_batch reads its
  tp, pp, cp and dynamic_cp groups as pretrain_hybrid.py does.

The new pg_collection and tp_group arguments default to None, which
keeps the global groups with a ProcessGroupFallbackWarning (deprecated
in 0.21, removed in 0.23); the silent global fallback of model_provider
now warns too. get_batch and is_dataset_built_on_rank raise ValueError
for a collection whose groups they need are None: torch.distributed
treats a None group as WORLD, and a None tp group would build the
datasets on every rank. The dataset provider gets the run's
collection from training once training passes it to providers with a
pg_collection parameter. The router and grouped-MLP tests pass their
groups explicitly.

On the standard grid every group is the same object as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Yan Xu <yxu1@nvidia.com>
FlextronRouter._sync_router_weights broadcast every router parameter
from global rank 0 over WORLD. The router's linear layers are sharded
over the model's tp group, so at TP > 1 every TP rank received TP rank
0's shard. The full router weight became TP copies of one shard. The
copies get identical gradients, so the router trains with 1/TP of its
hidden width. On a job whose model spans fewer ranks than WORLD, the
broadcast also mixed the routers of different models, or hung when the
other ranks built no router. This affects Flextron runs with TP > 1 and
Flextron models on a sub-grid.

Broadcast each shard only among its replicas: over the model's pp group
from its first rank, then over its dp_cp group from its first rank.
Every pipeline stage builds a router and finalize all-reduces its
gradients over pp, so the pp peers must start equal. dp_cp is the replica
group of dense weights, the group that DDP's broadcast_params uses. It is
not dp_cp_gtp_remat: with GTP active, the router's linear layers are also
sharded over gtp_remat and record dp_cp as their replica group, so a
broadcast over dp_cp_gtp_remat would overwrite distinct GTP shards.

Which ranks enter the broadcasts: every rank of the model builds the same
routers (one per model chunk, from the same config and full layer
pattern), with the same parameters in the same order. So all members of a
rank's pp group and of its dp_cp group enter the same broadcasts, in the
same order, and no rank outside the model enters them. A group of size 1,
or an axis set to None, is skipped by all of its members alike, so None
never turns into WORLD. The router now requires pp and dp_cp in its
collection, and raises ValueError without them.

Tests: two CPU tests on 4 ranks check that each TP shard comes from the
lowest of its PP x DP-CP replicas (TP2 x DP2 and TP2 x PP2), and that two
2-rank models in one job keep their own weights. Both fail on the parent
commit. A third test checks that a collection without pp or dp_cp is
rejected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Yan Xu <yxu1@nvidia.com>
NVIDIA#8738 passes the training collection to the optimizer with Gloo groups
on, on top of NVIDIA#6259, NVIDIA#8037 and NVIDIA#8055 (which carries NVIDIA#8049 and NVIDIA#6293).
The training loop's changes that follow call NVIDIA#8037's collection
builder, warn through NVIDIA#6293's and NVIDIA#8049's fallback helpers, and extend
NVIDIA#8037's training collection test.

NVIDIA#8737, merged above, now carries NVIDIA#8037 as well, so the allowlist
conflicts on its total: NVIDIA#8737's side records 194 reads and NVIDIA#8738's 158.
It takes 158, which matches the merged entries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Yan Xu <yxu1@nvidia.com>
The data-parallel group helper that follows replaces the DP x CP helper
that NVIDIA#8741 adds to the training loop, and the training utilities'
warnings extend NVIDIA#8741's packed-sequence statistics and GPT builder code.

NVIDIA#8741 is based on main, without NVIDIA#8037's and NVIDIA#8048's neighbouring lines,
so three files conflict. They are resolved as follows:
- training.py: keep NVIDIA#8037's _build_pg_collection_from_parallel_state and
  _get_forward_backward_func next to NVIDIA#8741's _get_model_pg_collection
  and _get_data_parallel_world_size. train_step's pg_collection
  docstring takes NVIDIA#8037's text plus NVIDIA#8741's sentence on the single-grid
  fallback; its p2p_communicator docstring and evaluate's keep NVIDIA#8037's
  text. train's docstring also keeps NVIDIA#8037's text, with NVIDIA#8741's sentence
  that a multi-module collection rejects the GPU sniff test and its
  Raises section.
- models/gpt.py: default_layer_spec takes (config, vp_stage,
  pp_rank=None, pg_collection=None), and GPTModelBuilder passes both
  NVIDIA#8048's pp_rank and NVIDIA#8741's pg_collection.
- test_gpt_builder.py: keep NVIDIA#8741's ModelOpt CP test and expect
  default_layer_spec(config, 3, 1, pg_collection=pg).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Yan Xu <yxu1@nvidia.com>
Connor-XY and others added 26 commits November 1, 2026 09:00
…ir update to main

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Yan Xu <yxu1@nvidia.com>
Conflicts:
- megatron/core/distributed/fsdp/mcore_fsdp_adapter.py: Main adds the
  ShortcutMoEBlock import (NVIDIA#8312) and the PR adds the
  resolve_process_groups import. The result keeps both in isort order,
  along with the PR's _init_dist_index rewrite, which no longer reads
  parallel_state.
- megatron/core/models/common/embeddings/rotary_pos_embedding.py: Main
  adds the register_capture_constant import for its embedding cache
  (NVIDIA#8358) and the PR adds the warn_global_process_group_fallback
  import. The result keeps both, plus the PR's warned cp_group fallback.
- pretrain_gpt.py, pretrain_hybrid.py: Main moves
  is_dataset_built_on_rank and core_gpt_dataset_config_from_args into
  megatron/training/datasets/providers.py (NVIDIA#8341, NVIDIA#8592). The result
  takes main's side and keeps the PR's pp_group in get_batch.
- examples/t5/pretrain_t5.py: The result keeps main's tokenizer
  refactor (NVIDIA#8391) and the PR's pg_collection default passed to
  T5Model. The unused build_tokenizer import that main dropped stays
  dropped.

Adapted to main outside the conflicts:
- megatron/training/datasets/providers.py: The PR's pp_group argument
  to mtp_on_this_rank is re-applied in is_dataset_built_on_rank_explicit,
  where main moved the code.

The process-group allowlist records the reads main added since this PR
was written.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Yan Xu <yxu1@nvidia.com>
…NVIDIA#8749, NVIDIA#8735, NVIDIA#8059, NVIDIA#8038, NVIDIA#8724 and NVIDIA#8725 after their update to main

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Yan Xu <yxu1@nvidia.com>
Conflicts:
- megatron/core/transformer/moe/batch_invariant.py: Main (NVIDIA#8559)
  splits unpermute into a fused path, a reference _unpermute_loop that
  takes ep_size, and a new public unpermute() that reads the global EP
  world size. The result keeps main's split and re-applies the PR's
  optional ep_size argument and warned resolve_process_groups fallback
  to the new unpermute(), so no new parallel_state read remains. Main's
  unpermute tests in test_moe_batch_invariant_unpermute.py and
  test_moe_kernels.py pass ep_size explicitly instead of setting the
  global EP world-size override.
- megatron/core/transformer/cuda_graphs.py: Main adds
  CudaGraphManager._initialize_mempool and the PR adds
  _resolve_reuse_cudagraphs. The result keeps both, with reuse decided
  lazily from the pp group as the PR does.
- megatron/core/tensor_parallel/random.py: CheckpointFunction.forward
  keeps the PR's explicit tp_group and main's new autocast capture.
- tests/unit_tests/dist_checkpointing/utils.py: Both helpers keep main's
  config.finalize_optimizer_config() call (NVIDIA#8213) and then the PR's
  get_megatron_optimizer call with an explicit pg_collection.
- pretrain_gpt.py, pretrain_hybrid.py: The dataset provider keeps the
  PR's pg_collection parameter, which its body passes to
  is_dataset_built_on_rank.
- megatron/core/distributed/fsdp/mcore_fsdp_adapter.py,
  tests/unit_tests/ssm/test_gated_delta_product.py,
  tests/unit_tests/transformer/test_attention_variant_dsa_gqa.py: Import
  conflicts. The result keeps the imports of both sides.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Yan Xu <yxu1@nvidia.com>
…ir update to main

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Yan Xu <yxu1@nvidia.com>
Conflicts:
- megatron/core/extensions/transformer_engine.py: In
  split_te_layernorm_column_parallel_linear, the TEColumnParallelLinear
  call gets pg_collection=fused_layer._pg_collection from the base PR,
  and main (NVIDIA#8426) appends .to(device=fused_layer.weight.device). The
  result keeps both. The other changes from main merge without
  conflict: TENorm .to(device), the RMSNorm is_rmsnorm_weight markers
  (NVIDIA#8246) and the mark_grouped_tensor helpers (NVIDIA#6828).
- tests/unit_tests/extension/test_te_layernorm_column_parallel_linear.py:
  The imports combine the PR's process-group imports with main's
  parenthesized TENorm import. Main's new test_rmsnorm_gamma_marker
  (NVIDIA#8246) stays where main put it, and the PR's _all_ranks helper and
  TestShardedStateDictDpCpGroup class follow it unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Yan Xu <yxu1@nvidia.com>
…main

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Yan Xu <yxu1@nvidia.com>
…ir update to main

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Yan Xu <yxu1@nvidia.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Yan Xu <yxu1@nvidia.com>
Conflicts:
- examples/bert/pretrain_bert.py, examples/t5/pretrain_t5.py: Main
  moves both scripts to get_tokenizer and padded_vocab_size and drops
  build_tokenizer. The PR passes ProcessGroupCollection groups to the
  model provider. The result keeps both and removes the unused
  build_tokenizer import.
- megatron/core/models/common/embeddings/rotary_pos_embedding.py,
  tests/unit_tests/pipeline_parallel/test_fine_grained_activation_offloading.py,
  tests/unit_tests/ssm/test_gated_delta_product.py: Import conflicts
  only. The result keeps the imports from both sides.
- megatron/core/transformer/cuda_graphs.py: Main adds the
  _initialize_mempool staticmethod. The PR adds _resolve_reuse_cudagraphs,
  which decides graph reuse from the pipeline group. Both are kept, in
  the method order of NVIDIA#8755.
- megatron/core/transformer/moe/batch_invariant.py: Main splits
  unpermute into a Triton kernel and a reference loop. The PR's ep_size
  keyword and its warned EP-group fallback move into main's new public
  unpermute.
- pretrain_gpt.py, pretrain_hybrid.py: Main moves the dataset helpers
  into megatron/training. The result takes main's code and keeps the
  PR's pp_group in get_batch.
- tests/unit_tests/dist_checkpointing/utils.py: The result calls main's
  new finalize_optimizer_config() and passes the PR's pg_collection to
  get_megatron_optimizer.

Adapted to main outside the conflicts:
- megatron/training/datasets/providers.py: The PR's pp_group argument
  to mtp_on_this_rank is re-applied where main moved
  is_dataset_built_on_rank.
- tests/unit_tests/transformer/moe/test_moe_batch_invariant_unpermute.py:
  Main's new test passes ep_size explicitly, since unpermute no longer
  reads the global EP world size.

The process-group allowlist records the reads main added since this PR
was written.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Yan Xu <yxu1@nvidia.com>
…NVIDIA#8731, NVIDIA#8722, NVIDIA#8723 and NVIDIA#8727 after their update to main

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Yan Xu <yxu1@nvidia.com>
Conflicts:
- tests/unit_tests/distributed/mfsdp_v2/test_mcore_moe.py: Main (NVIDIA#8312)
  and the PR each add a new test after test_build_train_step_and_clip.
  The result keeps all of main's version: the grouped/sequential
  standard MoE test, the shortcut MoE test, their helpers and imports.
  It also keeps the PR's fully_sharded_optimizer import and its
  test_optimizer_names_experts_by_the_model_expert_layout unchanged.
- pretrain_gpt.py, pretrain_hybrid.py: The conflicts come only from
  the criss-cross merge history. Main makes no net change to these
  files. The result keeps the PR's pg_collection parameter on
  train_valid_test_datasets_provider and its is_dataset_built_on_rank
  predicate bound to that collection.

Adapted to main outside the conflicts:
- tests/unit_tests/test_process_group_collection_end_to_end.py: Main's
  get_model_for_inference reads the run config and passes the tokenizer
  config to the model builder (NVIDIA#8391). The inference test takes the
  run_config fixture, and its stub builder takes the tokenizer config.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Yan Xu <yxu1@nvidia.com>
Main's NVIDIA#8471 moves FullyShardedDataParallelV1 and FullyShardedDataParallelV2 out of
mcore_fsdp_adapter.py into mcore_adapter_v1.py and mcore_adapter_v2.py, and keeps
mcore_fsdp_adapter.py as the factory that re-exports them. This branch's edits to the
two classes and their helpers are applied unchanged where the code moved, and the
process-group allowlist follows the move.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Yan Xu <yxu1@nvidia.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Yan Xu <yxu1@nvidia.com>
Main's NVIDIA#8471 moves FullyShardedDataParallelV1 and FullyShardedDataParallelV2 out of
mcore_fsdp_adapter.py into mcore_adapter_v1.py and mcore_adapter_v2.py, and keeps
mcore_fsdp_adapter.py as the factory that re-exports them. This branch's edits to the
two classes and their helpers are applied unchanged where the code moved, and the
process-group allowlist follows the move.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Yan Xu <yxu1@nvidia.com>
Main's NVIDIA#8471 moves FullyShardedDataParallelV1 and FullyShardedDataParallelV2 out of
mcore_fsdp_adapter.py into mcore_adapter_v1.py and mcore_adapter_v2.py, and keeps
mcore_fsdp_adapter.py as the factory that re-exports them. This branch's edits to the
two classes and their helpers are applied unchanged where the code moved, and the
process-group allowlist follows the move.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Yan Xu <yxu1@nvidia.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Yan Xu <yxu1@nvidia.com>
Main's NVIDIA#8471 moves FullyShardedDataParallelV1 and FullyShardedDataParallelV2 out of
mcore_fsdp_adapter.py into mcore_adapter_v1.py and mcore_adapter_v2.py, and keeps
mcore_fsdp_adapter.py as the factory that re-exports them. This branch's edits to the
two classes and their helpers are applied unchanged where the code moved, and the
process-group allowlist follows the move.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Yan Xu <yxu1@nvidia.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Yan Xu <yxu1@nvidia.com>
Main's NVIDIA#8471 moves FullyShardedDataParallelV1 and FullyShardedDataParallelV2 out of
mcore_fsdp_adapter.py into mcore_adapter_v1.py and mcore_adapter_v2.py, and keeps
mcore_fsdp_adapter.py as the factory that re-exports them. This branch's edits to the
two classes and their helpers are applied unchanged where the code moved, and the
process-group allowlist follows the move.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Yan Xu <yxu1@nvidia.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Yan Xu <yxu1@nvidia.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Yan Xu <yxu1@nvidia.com>
Main's NVIDIA#8471 moves FullyShardedDataParallelV1 and FullyShardedDataParallelV2 out of
mcore_fsdp_adapter.py into mcore_adapter_v1.py and mcore_adapter_v2.py, and keeps
mcore_fsdp_adapter.py as the factory that re-exports them. This branch's edits to the
two classes and their helpers are applied unchanged where the code moved, and the
process-group allowlist follows the move.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Yan Xu <yxu1@nvidia.com>
Main's NVIDIA#8471 moves FullyShardedDataParallelV1 and FullyShardedDataParallelV2 out of
mcore_fsdp_adapter.py into mcore_adapter_v1.py and mcore_adapter_v2.py, and keeps
mcore_fsdp_adapter.py as the factory that re-exports them. This branch's edits to the
two classes and their helpers are applied unchanged where the code moved, and the
process-group allowlist follows the move.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Yan Xu <yxu1@nvidia.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Yan Xu <yxu1@nvidia.com>
Main's NVIDIA#8471 moves FullyShardedDataParallelV1 and FullyShardedDataParallelV2 out of
mcore_fsdp_adapter.py into mcore_adapter_v1.py and mcore_adapter_v2.py, and keeps
mcore_fsdp_adapter.py as the factory that re-exports them. This branch's edits to the
two classes and their helpers are applied unchanged where the code moved, and the
process-group allowlist follows the move.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Yan Xu <yxu1@nvidia.com>
@Connor-XY

Copy link
Copy Markdown
Contributor Author

Superseded by #8910, which carries this change as commit 472f170 under the same title, with "Originally #8771." in its message. Closing so review happens in one place. The branch stays on the fork.

@Connor-XY Connor-XY closed this Nov 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant