Skip to content

[Core] Add dynamic context-parallel THD scheduling - #5679

Closed
ilml wants to merge 1 commit into
NVIDIA:mainfrom
ilml:codex/dynamic-cp-thd-core
Closed

[Core] Add dynamic context-parallel THD scheduling#5679
ilml wants to merge 1 commit into
NVIDIA:mainfrom
ilml:codex/dynamic-cp-thd-core

Conversation

@ilml

@ilml ilml commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Training loss:
image

Throughput:
image

Summary

  • Add a main-native sequence-packing scheduler for fixed context parallelism (dp_balanced) and dynamic context parallelism (default_dynamic_cp).
  • Add dynamic CP process groups, per-microbatch CP sizing, and pipeline-aware scheduling for packed THD batches.
  • Propagate packed-sequence metadata through attention, RoPE, Transformer Engine, and tensor-parallel broadcasts while restoring the configured process groups after each microbatch.
  • Add tokenizer chat-template keyword forwarding and an SFT mock-data path for scheduler testing.

User-facing flags

Flag Behavior and constraints
--dynamic-context-parallel Enables per-microbatch CP resizing for THD batches. Requires --calculate-per-token-loss, --max-seqlen-per-dp-cp-rank, --dataloader-type single, and --cuda-graph-impl none. Megatron FSDP, MoE, MLA, and hybrid/Mamba entrypoints are not supported.
--min-dynamic-context-parallel-size N Sets the smallest allowed CP size. The DP x CP domain must be a power of two unless N selects the full domain.
--sequence-packing-scheduler default_dynamic_cp Uses dynamic CP scheduling. Packed capacity is DP x CP x --max-seqlen-per-dp-cp-rank. It is selected automatically by --dynamic-context-parallel.
--sequence-packing-scheduler dp_balanced Balances packed work while keeping the configured CP size fixed. MoE requires all-to-all token dispatch.
--hybrid-context-parallel Deprecated compatibility alias for dynamic CP. New recipes should use --dynamic-context-parallel.
--max-seqlen-per-dp-cp-rank N Sets the packed-token capacity assigned to each scheduler rank. It is required whenever a sequence-packing scheduler is active.
--sft-mock-dataset-config-json VALUE Supplies inline JSON or a JSON file for synthetic SFT sequence lengths when using --sft --mock-data.

Dynamic CP requires Transformer Engine 2.9 or newer.

Stack and scope

Validation

  • uv run isort --check-only on changed Python files
  • uv run ruff check on changed Python files
  • Python byte compilation and git diff --check
  • Signed commit verified locally: 533deb7b7e68a7ed5daa988fcfdbe0af73172cc1

The available environment does not have a usable PyTorch test installation and has only two GPUs, so the distributed numerical tests were not run locally. CI was requested on the signed commit.

Summary by CodeRabbit

  • New Features

    • Added sequence-packing schedulers to improve variable-length workload balancing and GPU utilization.
    • Added dynamic context parallelism with configurable scheduling and minimum group sizes.
    • Added support for passing custom chat-template options, including tool definitions, during SFT tokenization.
    • Added configurable mock SFT datasets for testing and development.
  • Bug Fixes

    • Improved padding-mask handling across sequence and tensor parallel execution.
    • Preserved accurate token and sequence-length statistics for packed batches.
  • Documentation

    • Added guidance for sequence-packing schedulers and dynamic context parallelism.

@copy-pr-bot

copy-pr-bot Bot commented Jul 6, 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.

@ilml

ilml commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test

@copy-pr-bot

copy-pr-bot Bot commented Jul 6, 2026

Copy link
Copy Markdown

/ok to test

@ilml, there was an error processing your request: E1

See the following link for more information: https://docs.gha-runners.nvidia.com/cpr/e/1/

@ilml

ilml commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test 554f423

Comment thread megatron/core/datasets/data_schedule.py
Comment thread megatron/core/datasets/data_schedule_utils.py
Comment thread megatron/core/datasets/data_schedule.py
@ilml
ilml marked this pull request as draft July 14, 2026 20:05
@ilml
ilml force-pushed the codex/dynamic-cp-thd-core branch from 554f423 to b38b574 Compare July 14, 2026 20:22
@ilml

ilml commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test b38b574

Add dynamic DPxCP scheduling, per-microbatch process-group selection, and THD real-versus-padded boundary handling. Preserve the existing fixed-CP scheduler and tokenizer API while rejecting unsupported CUDA graph, FSDP, MoE, MLA runtime, and hybrid-model combinations.

Co-authored-by: xiaoyao0115 <1804647152@qq.com>
Signed-off-by: ilml <tolong@nvidia.com>
@ilml
ilml force-pushed the codex/dynamic-cp-thd-core branch from 938e6f6 to 533deb7 Compare July 14, 2026 21:33
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Walkthrough

Adds dynamic context-parallel process groups and configurable sequence-packing schedulers, integrates packed batches through training and attention, extends SFT dataset support, and forwards SFT chat-template arguments.

Changes

Sequence packing and dynamic context parallelism

Layer / File(s) Summary
Dynamic CP topology and configuration
megatron/core/model_parallel_config.py, megatron/core/parallel_state.py, megatron/training/arguments.py, megatron/training/initialize.py, megatron/training/datasets/data_samplers.py, pretrain_hybrid.py, megatron/core/transformer/transformer_config.py
Adds dynamic CP settings, topology validation, process-group creation, deprecated hybrid-CP mapping, scheduler validation, and unsupported-mode guards.
Packing scheduler pipeline
megatron/core/datasets/data_schedule.py, megatron/core/datasets/data_schedule_utils.py, tests/unit_tests/test_sequence_packing.py
Adds scheduler abstractions, DP-balanced and dynamic-CP grouping, all-to-all rerouting, packed microbatch construction, iterator wrapping, and distributed tests.
Packed attention and training integration
pretrain_gpt.py, megatron/training/training.py, megatron/core/transformer/attention.py, megatron/core/extensions/transformer_engine.py, megatron/core/packed_seq_params.py, megatron/core/pipeline_parallel/schedules.py
Passes packed sequence metadata through GPT batching, attention, Transformer Engine, training/evaluation, and global sequence-length accounting.
SFT packed dataset support
megatron/training/datasets/sft_dataset.py, megatron/training/datasets/utils.py, pretrain_gpt.py
Adds dynamic-CP-aware padding, unpadded cu-sequence boundaries, mock SFT datasets, and JSON-configured mock data.
Configuration and regression coverage
megatron/core/datasets/readme.md, tests/unit_tests/test_model_parallel_config.py, tests/unit_tests/test_parallel_state.py, tests/unit_tests/models/test_hybrid_moe_model.py
Documents the scheduler and updates configuration, process-group, and golden-config tests.

SFT tokenizer template arguments

Layer / File(s) Summary
Chat-template argument forwarding
megatron/core/tokenizers/text/libraries/sft_tokenizer.py, megatron/core/tokenizers/text/text_tokenizer.py, tests/unit_tests/tokenizers/test_tokenizer.py
Adds optional chat_template_kwargs and forwards values such as tools to the underlying chat-template renderer.

Padding-mask alignment

Layer / File(s) Summary
Sequence-parallel padding masks
megatron/core/models/gpt/gpt_model.py, megatron/core/transformer/moe/moe_layer.py, tests/unit_tests/transformer/moe/test_moe_layer.py
Updates padding-mask layout and aligns global masks with sequence-parallel hidden states, with focused MoE coverage.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Training
  participant Scheduler
  participant DCP
  participant Attention
  participant TransformerEngine
  Training->>Scheduler: wrap data iterator
  Scheduler->>DCP: reroute scheduled samples
  DCP-->>Scheduler: return packed microbatches
  Scheduler-->>Training: provide iterator and token statistics
  Training->>Attention: forward packed sequence parameters
  Attention->>TransformerEngine: use dynamic CP group
  TransformerEngine-->>Attention: restore original CP group
Loading

Suggested reviewers: chtruong814, dimapihtar

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.17% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main change: dynamic context-parallel THD scheduling.
Description check ✅ Passed The description is detailed and covers summary, flags, scope, and validation, with only template-specific checklist/issue sections omitted.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@ilml

ilml commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test 533deb7

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
megatron/core/extensions/transformer_engine.py (1)

1794-1905: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Restore-on-success only: exception during attention leaves self.cp_group permanently corrupted.

The dynamic-CP group swap (lines 1794-1816) is only reverted at lines 1897-1904, after super().forward(...). If that call raises for any reason, the restore never runs and this persistent module's CP group/stream stays pointed at the dynamic-CP value for every subsequent forward call on this layer (including non-dynamic-CP iterations).

🔧 Proposed fix: guarantee restoration with try/finally
         original_cp_group = self.cp_group
         original_cp_global_ranks = self.cp_global_ranks
 
-        if packed_seq_params is not None:
-            # If Dynamic CP group is provided, update TE DPA CP group
-            if packed_seq_params.local_cp_size is not None:
-                if packed_seq_params.local_cp_size == 1:
-                    super().set_context_parallel_group(None, None, None, self.cp_comm_type)
-                else:
-                    assert (
-                        packed_seq_params.cp_group is not None
-                    ), "cp_group is not set in packed_seq_params for dynamic CP"
-                    self.cp_group = packed_seq_params.cp_group
-                    if TEDotProductAttention.cp_stream is None:
-                        TEDotProductAttention.cp_stream = torch.cuda.Stream()
-                    super().set_context_parallel_group(
-                        self.cp_group,
-                        torch.distributed.get_process_group_ranks(self.cp_group),
-                        TEDotProductAttention.cp_stream,
-                        self.cp_comm_type,
-                    )
-            self.kept_packed_seq_params.discard("cp_group")
-            self.kept_packed_seq_params.discard("local_cp_size")
+        dynamic_cp_active = packed_seq_params is not None and packed_seq_params.local_cp_size is not None
+        if packed_seq_params is not None:
+            # If Dynamic CP group is provided, update TE DPA CP group
+            if dynamic_cp_active:
+                if packed_seq_params.local_cp_size == 1:
+                    super().set_context_parallel_group(None, None, None, self.cp_comm_type)
+                else:
+                    assert (
+                        packed_seq_params.cp_group is not None
+                    ), "cp_group is not set in packed_seq_params for dynamic CP"
+                    self.cp_group = packed_seq_params.cp_group
+                    if TEDotProductAttention.cp_stream is None:
+                        TEDotProductAttention.cp_stream = torch.cuda.Stream()
+                    super().set_context_parallel_group(
+                        self.cp_group,
+                        torch.distributed.get_process_group_ranks(self.cp_group),
+                        TEDotProductAttention.cp_stream,
+                        self.cp_comm_type,
+                    )
+            self.kept_packed_seq_params.discard("cp_group")
+            self.kept_packed_seq_params.discard("local_cp_size")
+
+        try:

...then wrap through the end of both super().forward(...) branches, and change the restore block to:

-        if packed_seq_params is not None and packed_seq_params.local_cp_size is not None:
-            super().set_context_parallel_group(
-                original_cp_group,
-                original_cp_global_ranks,
-                TEDotProductAttention.cp_stream,
-                self.cp_comm_type,
-            )
+        finally:
+            if dynamic_cp_active:
+                super().set_context_parallel_group(
+                    original_cp_group,
+                    original_cp_global_ranks,
+                    TEDotProductAttention.cp_stream,
+                    self.cp_comm_type,
+                )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@megatron/core/extensions/transformer_engine.py` around lines 1794 - 1905,
Guarantee restoration of the original context-parallel state when attention
execution fails. In the forward method, wrap both `super().forward(...)`
branches and all intervening processing through the return in a `try/finally`,
and move the existing restoration using `original_cp_group`,
`original_cp_global_ranks`, and `TEDotProductAttention.cp_stream` into the
`finally` block so dynamic-CP changes are reverted on success and exceptions.
🧹 Nitpick comments (1)
megatron/core/datasets/data_schedule_utils.py (1)

312-328: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Reuse gid2local_id for O(1) membership checks instead of tensor in.

gid2local_id (line 312) already provides O(1) lookup, but send_ids_sorted (and the similar filter in send_seq_lens) test membership via gid in global_ids_this_rank, a tensor scan. For large batches this becomes O(N·M) instead of O(N).

♻️ Proposed refactor
     send_ids_sorted = [
-        gid for d in dp_ranks for gid in combined_sample_id_groups[d] if gid in global_ids_this_rank
+        gid for d in dp_ranks for gid in combined_sample_id_groups[d] if gid in gid2local_id
     ]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@megatron/core/datasets/data_schedule_utils.py` around lines 312 - 328, Update
the `send_ids_sorted` filter and the corresponding filter in `send_seq_lens` to
use `gid2local_id` for membership checks instead of scanning
`global_ids_this_rank` with `gid in`. Preserve the existing ordering and output
values while ensuring each membership test is O(1).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@megatron/core/datasets/data_schedule_utils.py`:
- Around line 857-862: Replace the `assert False` guard in
`align_sample_id_groups` with an explicit exception that remains active under
Python optimization, while preserving the existing failure message and boundary
condition when no tail microbatch can provide enough IDs.
- Around line 522-559: Define or import _DYNAMIC_CP_WORKLOAD_CAP_DELTA at module
scope before next_hdp_group_packing_aware computes cap. Ensure the symbol is
available for every non-empty scheduler call and retains the intended
workload-cap delta value, or source it from the existing configuration mechanism
if one is already established.

In `@megatron/core/parallel_state.py`:
- Around line 428-434: Update the group-size generation in the parallel-group
construction logic around group_sizes and create_group so every generated size
evenly divides len(ranks), preventing partial trailing groups from being stored
under a larger key. Either reject non-power-of-two domains unless min_cp_size
selects the full domain, or filter group_sizes to divisors of len(ranks),
preserving valid full-domain behavior.

In `@megatron/training/arguments.py`:
- Around line 1389-1393: Update the deprecation warning in the
hybrid_context_parallel argument handling to pass stacklevel=2, matching the
existing warning in model_parallel_config.py and ensuring callers receive the
correct warning location.
- Around line 1400-1403: Update the dynamic_context_parallel validation to check
the normalized args.cuda_graph_impl value instead of accessing
args.enable_cuda_graph, which may have been deleted earlier in the
argument-processing function. Preserve the existing assertion message and reject
the combination when CUDA Graph is enabled.

In `@pretrain_gpt.py`:
- Line 129: Initialize is_hybrid_cp from args.dynamic_context_parallel instead
of hardcoding it to False, so get_batch() uses the dynamic CP behavior
consistently with get_batch_on_this_tp_rank(), get_batch_on_this_cp_rank(), and
dummy_train_step().

In `@pretrain_hybrid.py`:
- Around line 455-460: Update the unsupported-feature guard in
pretrain_hybrid.py to reject either args.dynamic_context_parallel or the
deprecated args.hybrid_context_parallel alias before dataset configuration is
built. Preserve the existing ValueError message and behavior for both flags.

---

Outside diff comments:
In `@megatron/core/extensions/transformer_engine.py`:
- Around line 1794-1905: Guarantee restoration of the original context-parallel
state when attention execution fails. In the forward method, wrap both
`super().forward(...)` branches and all intervening processing through the
return in a `try/finally`, and move the existing restoration using
`original_cp_group`, `original_cp_global_ranks`, and
`TEDotProductAttention.cp_stream` into the `finally` block so dynamic-CP changes
are reverted on success and exceptions.

---

Nitpick comments:
In `@megatron/core/datasets/data_schedule_utils.py`:
- Around line 312-328: Update the `send_ids_sorted` filter and the corresponding
filter in `send_seq_lens` to use `gid2local_id` for membership checks instead of
scanning `global_ids_this_rank` with `gid in`. Preserve the existing ordering
and output values while ensuring each membership test is O(1).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Enterprise

Run ID: 0da1dc8d-ba7e-40d4-840a-198bc594de12

📥 Commits

Reviewing files that changed from the base of the PR and between e5344ab and 533deb7.

📒 Files selected for processing (30)
  • megatron/core/datasets/data_schedule.py
  • megatron/core/datasets/data_schedule_utils.py
  • megatron/core/datasets/gpt_dataset.py
  • megatron/core/datasets/readme.md
  • megatron/core/extensions/transformer_engine.py
  • megatron/core/model_parallel_config.py
  • megatron/core/models/gpt/gpt_model.py
  • megatron/core/packed_seq_params.py
  • megatron/core/parallel_state.py
  • megatron/core/pipeline_parallel/schedules.py
  • megatron/core/tokenizers/text/libraries/sft_tokenizer.py
  • megatron/core/tokenizers/text/text_tokenizer.py
  • megatron/core/transformer/attention.py
  • megatron/core/transformer/moe/moe_layer.py
  • megatron/core/transformer/transformer_config.py
  • megatron/elastification/pretrain_hybrid_flex.py
  • megatron/training/arguments.py
  • megatron/training/datasets/data_samplers.py
  • megatron/training/datasets/sft_dataset.py
  • megatron/training/datasets/utils.py
  • megatron/training/initialize.py
  • megatron/training/training.py
  • pretrain_gpt.py
  • pretrain_hybrid.py
  • tests/unit_tests/models/test_hybrid_moe_model.py
  • tests/unit_tests/test_model_parallel_config.py
  • tests/unit_tests/test_parallel_state.py
  • tests/unit_tests/test_sequence_packing.py
  • tests/unit_tests/tokenizers/test_tokenizer.py
  • tests/unit_tests/transformer/moe/test_moe_layer.py
💤 Files with no reviewable changes (1)
  • megatron/core/pipeline_parallel/schedules.py

Comment on lines +522 to +559
def next_hdp_group_packing_aware(
sample_seqlens: List[Tuple[int, int]],
total_gpus: int,
max_seq_len_per_rank: int,
min_cp_size: int = 1,
) -> Tuple[List[List[int]], List[Tuple[int, int]], List[float], List[List[int]]]:
"""Form one DCP microbatch with packing-aware CP group selection.

This differs from the legacy DCP scheduler in two ways:
1. Short sequences may use a larger CP group than their minimum required
CP size when that lowers the critical-path rank workload.
2. Candidate placements are bounded by ``tall * max_seq_len_per_rank``,
the per-rank workload upper bound for packing sequences no longer than
the local tallest sequence in the microbatch.

The scheduler keeps the legacy invariant that each returned microbatch has
no empty DPxCP rank after the fill step. For non-power-of-two DPxCP layouts,
it falls back to the full DPxCP group if power-of-two expansion cannot fill
every rank.
"""
if not sample_seqlens:
return (
[[] for _ in range(total_gpus)],
[],
[0.0 for _ in range(total_gpus)],
[[] for _ in range(total_gpus)],
)

def cp_min_fn(seq_len: int) -> int:
return dcp_gpus_needed(seq_len, max_seq_len_per_rank, min_cp_size)

def workload(seq_len: int, cp_size: int) -> float:
return (seq_len * seq_len) / cp_size

sample_seqlens = sorted(sample_seqlens, key=lambda x: x[1], reverse=True)
local_tall = sample_seqlens[0][1]
cap = float(local_tall) * float(max_seq_len_per_rank) * (1.0 + _DYNAMIC_CP_WORKLOAD_CAP_DELTA)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Searching for _DYNAMIC_CP_WORKLOAD_CAP_DELTA references..."
rg -n "_DYNAMIC_CP_WORKLOAD_CAP_DELTA" .

echo
echo "Inspecting the relevant file region..."
file="megatron/core/datasets/data_schedule_utils.py"
wc -l "$file"
sed -n '520,565p' "$file"

Repository: NVIDIA/Megatron-LM

Length of output: 2395


Define _DYNAMIC_CP_WORKLOAD_CAP_DELTA before using it
next_hdp_group_packing_aware references _DYNAMIC_CP_WORKLOAD_CAP_DELTA, but this module never defines or imports it. Any non-empty call will raise NameError when computing cap, breaking the dynamic-CP scheduler path. Add the missing module-level constant or pass it in from config.

🧰 Tools
🪛 Ruff (0.15.21)

[error] 558-558: Undefined name _DYNAMIC_CP_WORKLOAD_CAP_DELTA

(F821)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@megatron/core/datasets/data_schedule_utils.py` around lines 522 - 559, Define
or import _DYNAMIC_CP_WORKLOAD_CAP_DELTA at module scope before
next_hdp_group_packing_aware computes cap. Ensure the symbol is available for
every non-empty scheduler call and retains the intended workload-cap delta
value, or source it from the existing configuration mechanism if one is already
established.

Source: Linters/SAST tools

Comment on lines +857 to +862
attempts_since_split = 0
while remainder > 0:
if i < 0:
if attempts_since_split >= len(sample_id_groups):
assert False, 'align_sample_id_groups: no tail microbatch has enough ids to split'
i = len(sample_id_groups) - 1

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

assert False is stripped under python -O.

Ruff (B011) flags this. If assertions are disabled, the attempts_since_split >= len(sample_id_groups) guard silently no-ops instead of failing, leaving i negative and changing loop behavior unexpectedly rather than failing loudly.

🛡️ Proposed fix
-            if attempts_since_split >= len(sample_id_groups):
-                assert False, 'align_sample_id_groups: no tail microbatch has enough ids to split'
+            if attempts_since_split >= len(sample_id_groups):
+                raise AssertionError(
+                    'align_sample_id_groups: no tail microbatch has enough ids to split'
+                )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
attempts_since_split = 0
while remainder > 0:
if i < 0:
if attempts_since_split >= len(sample_id_groups):
assert False, 'align_sample_id_groups: no tail microbatch has enough ids to split'
i = len(sample_id_groups) - 1
attempts_since_split = 0
while remainder > 0:
if i < 0:
if attempts_since_split >= len(sample_id_groups):
raise AssertionError(
'align_sample_id_groups: no tail microbatch has enough ids to split'
)
i = len(sample_id_groups) - 1
🧰 Tools
🪛 Ruff (0.15.21)

[warning] 861-861: Do not assert False (python -O removes these calls), raise AssertionError()

Replace assert False

(B011)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@megatron/core/datasets/data_schedule_utils.py` around lines 857 - 862,
Replace the `assert False` guard in `align_sample_id_groups` with an explicit
exception that remains active under Python optimization, while preserving the
existing failure message and boundary condition when no tail microbatch can
provide enough IDs.

Source: Linters/SAST tools

Comment on lines +428 to 434
group_sizes = [
2**i for i in range(int(log2(len(ranks))) + 1) if min_cp_size <= 2**i < len(ranks)
]
for group_size in group_sizes:
for i in range(0, len(ranks), group_size):
group = create_group(
ranks[i : i + group_size],

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Reject domains that cannot be evenly partitioned into every generated group size.

The current evenness check permits a six-rank DP×CP domain. Line 432 then creates a final two-rank slice for group_size=4 and stores it under key 4, so callers requesting CP size 4 receive a size-2 group. Require a power-of-two domain unless the minimum selects the full domain, or only generate sizes that divide len(ranks).

Proposed fix
-            assert len(ranks_with_cp) % 2 == 0 or min_dynamic_context_parallel_size == len(
-                ranks_with_cp
-            ), (
-                "Dynamic context parallel requires an even DP x CP domain unless the "
-                "minimum group size selects the full domain"
-            )
+            is_power_of_two = len(ranks_with_cp) > 0 and (
+                len(ranks_with_cp) & (len(ranks_with_cp) - 1)
+            ) == 0
+            assert (
+                is_power_of_two
+                or min_dynamic_context_parallel_size == len(ranks_with_cp)
+            ), (
+                "Dynamic context parallel requires a power-of-two DP x CP domain "
+                "unless the minimum group size selects the full domain"
+            )

Also applies to: 934-945

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@megatron/core/parallel_state.py` around lines 428 - 434, Update the
group-size generation in the parallel-group construction logic around
group_sizes and create_group so every generated size evenly divides len(ranks),
preventing partial trailing groups from being stored under a larger key. Either
reject non-power-of-two domains unless min_cp_size selects the full domain, or
filter group_sizes to divisors of len(ranks), preserving valid full-domain
behavior.

Comment on lines 1389 to +1393
if args.hybrid_context_parallel:
assert not args.pipeline_model_parallel_size > 1, 'Hybrid context parallelism not supported with pipeline parallelism'
assert not args.enable_cuda_graph, 'Hybrid context parallelism not supported with CUDA Graph'
assert not args.use_megatron_fsdp, 'Hybrid context parallelism not supported with Megatron FSDP'
assert args.dataloader_type == 'single', 'Hybrid context parallelism only supported with single dataloader type'
assert args.calculate_per_token_loss, 'Hybrid context parallelism must be used with --calculate-per-token-loss'
warnings.warn(
'--hybrid-context-parallel is deprecated; use --dynamic-context-parallel.',
DeprecationWarning,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Missing stacklevel on deprecation warning.

Flagged by Ruff (B028). The equivalent deprecation warning in megatron/core/model_parallel_config.py (hybrid_context_paralleldynamic_context_parallel) already uses stacklevel=2; this one should match for consistent, useful warning locations.

🔧 Proposed fix
     if args.hybrid_context_parallel:
         warnings.warn(
             '--hybrid-context-parallel is deprecated; use --dynamic-context-parallel.',
             DeprecationWarning,
+            stacklevel=2,
         )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if args.hybrid_context_parallel:
assert not args.pipeline_model_parallel_size > 1, 'Hybrid context parallelism not supported with pipeline parallelism'
assert not args.enable_cuda_graph, 'Hybrid context parallelism not supported with CUDA Graph'
assert not args.use_megatron_fsdp, 'Hybrid context parallelism not supported with Megatron FSDP'
assert args.dataloader_type == 'single', 'Hybrid context parallelism only supported with single dataloader type'
assert args.calculate_per_token_loss, 'Hybrid context parallelism must be used with --calculate-per-token-loss'
warnings.warn(
'--hybrid-context-parallel is deprecated; use --dynamic-context-parallel.',
DeprecationWarning,
)
if args.hybrid_context_parallel:
warnings.warn(
'--hybrid-context-parallel is deprecated; use --dynamic-context-parallel.',
DeprecationWarning,
stacklevel=2,
)
🧰 Tools
🪛 Ruff (0.15.21)

[warning] 1390-1390: No explicit stacklevel keyword argument found

Set stacklevel=2

(B028)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@megatron/training/arguments.py` around lines 1389 - 1393, Update the
deprecation warning in the hybrid_context_parallel argument handling to pass
stacklevel=2, matching the existing warning in model_parallel_config.py and
ensuring callers receive the correct warning location.

Source: Linters/SAST tools

Comment on lines +1400 to +1403
if args.dynamic_context_parallel:
assert not args.enable_cuda_graph, (
'Dynamic context parallelism is not supported with CUDA Graph'
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

args.enable_cuda_graph may already be deleted here, causing AttributeError instead of a clean assertion.

Earlier in this same function (lines 605-610), when --enable-cuda-graph is set, the code does args.cuda_graph_impl = "local"; del args.enable_cuda_graph. If a user then also passes --dynamic-context-parallel, this line's assert not args.enable_cuda_graph reads a deleted Namespace attribute and raises AttributeError rather than the intended assertion message. args.cuda_graph_impl is already normalized at this point and is safe to check instead.

🔧 Proposed fix
-        assert not args.enable_cuda_graph, (
-            'Dynamic context parallelism is not supported with CUDA Graph'
-        )
+        assert args.cuda_graph_impl == "none", (
+            'Dynamic context parallelism is not supported with CUDA Graph'
+        )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if args.dynamic_context_parallel:
assert not args.enable_cuda_graph, (
'Dynamic context parallelism is not supported with CUDA Graph'
)
if args.dynamic_context_parallel:
assert args.cuda_graph_impl == "none", (
'Dynamic context parallelism is not supported with CUDA Graph'
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@megatron/training/arguments.py` around lines 1400 - 1403, Update the
dynamic_context_parallel validation to check the normalized args.cuda_graph_impl
value instead of accessing args.enable_cuda_graph, which may have been deleted
earlier in the argument-processing function. Preserve the existing assertion
message and reject the combination when CUDA Graph is enabled.

Comment thread pretrain_gpt.py
vp_stage=vp_stage,
)
is_hybrid_cp = args.hybrid_context_parallel
is_hybrid_cp = False

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate relevant files and inspect the surrounding code with line numbers.
git ls-files | rg '(^|/)(pretrain_gpt\.py|training\.py)$'

echo '--- pretrain_gpt.py (around line 129 and get_batch helpers) ---'
nl -ba pretrain_gpt.py | sed -n '90,220p'

echo '--- training.py (dummy_train_step and skip-iteration handling) ---'
nl -ba megatron/training/training.py | sed -n '2220,2315p'

Repository: NVIDIA/Megatron-LM

Length of output: 306


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show only the relevant sections.
echo '--- pretrain_gpt.py: is_hybrid_cp and CP group selection ---'
rg -n -A6 -B6 'is_hybrid_cp|get_batch_on_this_cp_rank|get_batch_on_this_tp_rank|dynamic_context_parallel|hybrid_context_parallel' pretrain_gpt.py

echo '--- training.py: dummy_train_step and skip-iteration handling ---'
rg -n -A8 -B8 'dummy_train_step|dynamic_context_parallel|sequence_packing_scheduler|skip-iteration|skip iteration|sequence packing' megatron/training/training.py

Repository: NVIDIA/Megatron-LM

Length of output: 10788


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the helper signatures and branches that consume is_hybrid_cp.
echo '--- helper definitions in megatron/core/utils.py ---'
rg -n -A20 -B8 'def get_batch_on_this_tp_rank|def get_batch_on_this_cp_rank|is_hybrid_cp|dynamic_cp_group_func|hybrid_cp_group_func' megatron/core/utils.py

echo '--- any other call sites of get_batch_on_this_cp_rank ---'
rg -n -A3 -B3 'get_batch_on_this_cp_rank\(' .

echo '--- any other uses of dynamic_context_parallel in pretrain_gpt.py ---'
rg -n -A3 -B3 'dynamic_context_parallel' pretrain_gpt.py megatron/training/training.py

Repository: NVIDIA/Megatron-LM

Length of output: 23276


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Print the exact helper definitions with line numbers.
for f in megatron/core/utils.py; do
  echo "--- $f ---"
  grep -n "def get_batch_on_this_tp_rank\|def get_batch_on_this_cp_rank" -n "$f"
  sed -n '1,260p' "$f" | nl -ba | sed -n '1,260p'
done

Repository: NVIDIA/Megatron-LM

Length of output: 299


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Target just the helper bodies.
python3 - <<'PY'
from pathlib import Path
import re

p = Path("megatron/core/utils.py")
text = p.read_text()
for fn in ["get_batch_on_this_tp_rank", "get_batch_on_this_cp_rank"]:
    m = re.search(rf"^def {fn}\b.*?(?=^def |\Z)", text, re.S | re.M)
    if not m:
        print(f"missing {fn}")
        continue
    start = text[:m.start()].count("\n") + 1
    end = text[:m.end()].count("\n") + 1
    print(f"--- {fn} lines {start}-{end} ---")
    for i, line in enumerate(text[m.start():m.end()].splitlines(), start=start):
        print(f"{i:4d}: {line}")
PY

Repository: NVIDIA/Megatron-LM

Length of output: 17496


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
import re

p = Path("megatron/core/utils.py")
text = p.read_text()

for fn in ["get_batch_on_this_tp_rank", "get_batch_on_this_cp_rank"]:
    m = re.search(rf"^def {fn}\b.*?(?=^def |\Z)", text, re.S | re.M)
    if not m:
        print(f"missing {fn}")
        continue
    start = text[:m.start()].count("\n") + 1
    print(f"--- {fn} ---")
    for i, line in enumerate(text[m.start():m.end()].splitlines(), start=start):
        if "is_hybrid_cp" in line or "dynamic_cp_group_func" in line or "hybrid_cp_group_func" in line or "dynamic_cp" in line or "cp_group" in line:
            print(f"{i:4d}: {line}")
PY

Repository: NVIDIA/Megatron-LM

Length of output: 1958


Set is_hybrid_cp from args.dynamic_context_parallel here. get_batch_on_this_tp_rank() and get_batch_on_this_cp_rank() branch on this flag to broadcast local_cp_size and select the hybrid CP group, so hardcoding False disables the dynamic CP path in get_batch() and diverges from dummy_train_step().

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pretrain_gpt.py` at line 129, Initialize is_hybrid_cp from
args.dynamic_context_parallel instead of hardcoding it to False, so get_batch()
uses the dynamic CP behavior consistently with get_batch_on_this_tp_rank(),
get_batch_on_this_cp_rank(), and dummy_train_step().

Comment thread pretrain_hybrid.py
Comment on lines +455 to +460
if args.dynamic_context_parallel:
raise ValueError(
"Dynamic context parallelism is not supported by pretrain_hybrid.py. "
"Hybrid/Mamba layers do not yet implement per-microbatch context groups "
"and padding semantics; use pretrain_gpt.py for DCP training."
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Guard doesn't cover the deprecated hybrid_context_parallel alias.

This check only inspects args.dynamic_context_parallel. But later in this same file the dataset config is built with hybrid_context_parallel=args.hybrid_context_parallel, and GPTDatasetConfig.__post_init__ silently promotes hybrid_context_parallel=True into dynamic_context_parallel=True. A user who still passes the legacy --hybrid-context-parallel flag bypasses this guard entirely and ends up in the exact unsupported state ("Hybrid/Mamba layers do not yet implement per-microbatch context groups and padding semantics") the guard is meant to block.

🐛 Proposed fix
-    if args.dynamic_context_parallel:
+    if args.dynamic_context_parallel or getattr(args, 'hybrid_context_parallel', False):
         raise ValueError(
             "Dynamic context parallelism is not supported by pretrain_hybrid.py. "
             "Hybrid/Mamba layers do not yet implement per-microbatch context groups "
             "and padding semantics; use pretrain_gpt.py for DCP training."
         )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if args.dynamic_context_parallel:
raise ValueError(
"Dynamic context parallelism is not supported by pretrain_hybrid.py. "
"Hybrid/Mamba layers do not yet implement per-microbatch context groups "
"and padding semantics; use pretrain_gpt.py for DCP training."
)
if args.dynamic_context_parallel or getattr(args, 'hybrid_context_parallel', False):
raise ValueError(
"Dynamic context parallelism is not supported by pretrain_hybrid.py. "
"Hybrid/Mamba layers do not yet implement per-microbatch context groups "
"and padding semantics; use pretrain_gpt.py for DCP training."
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pretrain_hybrid.py` around lines 455 - 460, Update the unsupported-feature
guard in pretrain_hybrid.py to reject either args.dynamic_context_parallel or
the deprecated args.hybrid_context_parallel alias before dataset configuration
is built. Preserve the existing ValueError message and behavior for both flags.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you make the equivalent change to hybrid_model.py?

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.

6 participants