Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
48792b7
Squash following code changes (commits):
shifangx Aug 13, 2025
259cd21
Fix error when saving checkpoints
BestJuly Aug 14, 2025
d515cb0
fix hang issue with torch.distributed.batch_isend_irecv
shifangx Sep 1, 2025
ebee833
add conditions to communicate p2p shapes, will switch to calculate sh…
BestJuly Sep 2, 2025
813985d
make it compatible with 1f1b
BestJuly Sep 6, 2025
cebb164
support mtp standalone in 1f1b
Wohox Sep 16, 2025
9b61229
fix duplicated final layer norm in mtp combine
Wohox Sep 16, 2025
e650d5a
formatting
BestJuly Sep 22, 2025
e0dae54
update mtp offset in 1f1b callcables
Wohox Sep 24, 2025
db1435f
fix 1f1b overlap ut & remove final layernorm in postprocess
Wohox Sep 25, 2025
29e7ea0
fix ut failure: no mtp_standalone attribute
BestJuly Oct 8, 2025
8947471
add UT, fix pipeline failure and add document
BestJuly Oct 10, 2025
41e0bee
Update copyright
BestJuly Oct 28, 2025
c1022a7
format
BestJuly Oct 28, 2025
319390b
remove abundant argument in BlendedMegatronDatasetBuilder
BestJuly Nov 14, 2025
95031fc
Merge branch 'main' into lit/mtp_layer_standalone_main
BestJuly Nov 14, 2025
e3116bb
Merge branch 'main' into lit/mtp_layer_standalone_main
BestJuly Nov 18, 2025
5d3f862
Merge branch 'main' into lit/mtp_layer_standalone_main
BestJuly Nov 25, 2025
a3fa990
fix missing layernorm
Wohox Dec 19, 2025
ae43919
Merge branch 'main' into lit/mtp_layer_standalone_main
BestJuly Jan 6, 2026
50d34a3
Address review comments
BestJuly Jan 6, 2026
69bf9bf
Merge branch 'main' into lit/mtp_layer_standalone_main
BestJuly Jan 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 14 additions & 12 deletions gpt_builders.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
# Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.

from megatron.core.models.gpt import GPTModel
from megatron.core.models.gpt.gpt_layer_specs import (
get_gpt_decoder_block_spec,
get_gpt_layer_local_spec,
get_gpt_layer_with_transformer_engine_spec,
get_gpt_mtp_block_spec,
get_gpt_decoder_layer_specs,
)
from megatron.core.models.gpt.heterogeneous.heterogeneous_layer_specs import (
get_gpt_heterogeneous_layer_spec,
Expand Down Expand Up @@ -57,18 +58,19 @@ def gpt_builder(args, pre_process, post_process, vp_stage=None, config=None):
transformer_layer_spec = _get_transformer_layer_spec(use_te, config)
mtp_block_spec = None
if args.mtp_num_layers is not None:
if (
hasattr(transformer_layer_spec, 'layer_specs')
and len(transformer_layer_spec.layer_specs) == 0
):
# Get the decoder layer spec explicitly if no decoder layer in the last stage,
# Only happens with block spec (TransformerBlockSubmodules) when using MoE.
transformer_layer_spec_for_mtp = _get_transformer_layer_spec(use_te, config)
# Get GPT decoder layer specs for the model.
if args.spec is not None:
mtp_transformer_layer_spec = import_module(args.spec)
else:
transformer_layer_spec_for_mtp = transformer_layer_spec
# Define the decoder block spec
decoder_layer_specs = get_gpt_decoder_layer_specs(
config, use_transformer_engine=use_te, normalization=args.normalization, qk_l2_norm=args.qk_l2_norm, vp_stage=vp_stage
)
mtp_transformer_layer_spec = decoder_layer_specs[-1]
# Use spec of the last layer in decoder block as spec of the transformer layer in MTP
mtp_block_spec = get_gpt_mtp_block_spec(
config,
transformer_layer_spec_for_mtp,
mtp_transformer_layer_spec,
use_transformer_engine=use_te,
vp_stage=vp_stage,
)
Expand Down Expand Up @@ -96,12 +98,12 @@ def gpt_builder(args, pre_process, post_process, vp_stage=None, config=None):

def _get_transformer_layer_spec(use_te, config):
"""Get transformer layer specification based on configuration.

Args:
use_te (bool): Whether to use Transformer Engine
args: Training arguments
config: Model configuration

Returns:
transformer_layer_spec: The transformer layer specification
"""
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
# Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.

import logging
import math
Expand Down Expand Up @@ -47,11 +47,13 @@ def __init__(
sizes: List[int],
is_built_on_rank: Callable,
config: BlendedMegatronDatasetConfig,
vp_stage: Optional[int] = None,
):
self.cls = cls
self.sizes = sizes
self.is_built_on_rank = is_built_on_rank
self.config = config
self.vp_stage = vp_stage

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.

Why is this needed here? We are not even passing the vp_stage when calling the BlendedMegatronDatasetBuilder

https://github.com/BestJuly/Megatron-LM/blob/c1022a7696393759729dbb8da3ff7144f27df634/pretrain_gpt.py#L230-L233

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.

I am also confused. Is it because we want to make sure we don't build the datasets on more than one vp stage per rank?

@BestJuly BestJuly Nov 6, 2025 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This should be wrongly added when resolving conflicts, as I find vp_stage has been included in partial(is_dataset_built_on_rank, vp_stage=vp_stage). Will correct this.


log_single_rank(
logger,
Expand Down
12 changes: 10 additions & 2 deletions megatron/core/distributed/finalize_model_grads.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.
# Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.

from functools import partial
from typing import Callable, List, Optional, Union
Expand Down Expand Up @@ -193,7 +193,11 @@ def _allreduce_word_embedding_grads(
pp_group = parallel_state.get_pipeline_model_parallel_group()

_allreduce_embedding_grad(
model, embd_group, pp_group, partial(_get_shared_word_embedding_weight, config=config)
model,
embd_group,
pp_group,
partial(_get_shared_word_embedding_weight, config=config),
config=config,
)


Expand All @@ -203,6 +207,7 @@ def _allreduce_embedding_grad(
pp_group: torch.distributed.ProcessGroup,
weight_getter: Callable[[torch.nn.Module], Optional[torch.nn.Parameter]],
skip_if_none: bool = True,
config: TransformerConfig = None,
):
"""Unified helper to all-reduce embedding parameters across pipeline stages.

Expand All @@ -229,6 +234,9 @@ def _allreduce_embedding_grad(
model_module = model[0]
elif is_pp_last_stage(pp_group):
model_module = model[-1]
elif getattr(config, 'mtp_num_layers', None) is not None and config.mtp_num_layers > 0:
# Embedding for MTP layers is in the last virtual pipeline model parallel stage.
model_module = model[-1]
else: # We do not support an interleaved schedule for models with encoders yet.
model_module = model[0]

Expand Down
6 changes: 5 additions & 1 deletion megatron/core/model_parallel_config.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
# Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.

import warnings
from dataclasses import dataclass
Expand Down Expand Up @@ -316,6 +316,10 @@ class ModelParallelConfig:
rank 1 | 0 1 2 0 1 2 3 4 3 4
"""

mtp_standalone: bool = False
"""This will be set automatically according to the pipeline layout,
and will be set to True if MTP is in a separate vpp stage."""

###################
# CPU Offloading
###################
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.
# Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
import logging
import os
from typing import Optional, Tuple
Expand Down Expand Up @@ -62,6 +62,8 @@ def _is_in_embd_group(self):
if torch.distributed.get_rank() in torch.distributed.get_process_group_ranks(
self.embd_group
):
if getattr(self, 'mtp_process', False):
return True
if (
torch.distributed.get_rank()
== torch.distributed.get_process_group_ranks(self.embd_group)[0]
Expand Down Expand Up @@ -201,7 +203,10 @@ def setup_embeddings_and_output_layer(self) -> None:
):
self.shared_embedding_or_output_weight().shared_embedding = True

if (self.post_process or getattr(self, 'mtp_process', False)) and not self.pre_process:
if (
(self.post_process and self.share_embeddings_and_output_weights)
or getattr(self, 'mtp_process', False)
) and not self.pre_process:
assert not (
is_vp_first_stage(self.vp_stage, self.vp_size) and is_pp_first_stage(self.pp_group)
)
Expand Down
44 changes: 23 additions & 21 deletions megatron/core/models/common/model_chunk_schedule_plan.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
get_comm_stream,
get_comp_stream,
)
from megatron.core.transformer.multi_token_prediction import get_mtp_num_layers_to_build


class ModelChunkState:
Expand Down Expand Up @@ -312,37 +311,40 @@ def __init__(
self._model_chunk_state.context_mask = None
self._model_chunk_state.attention_bias = None

transformer_num_layers = model.decoder.num_layers_per_pipeline_rank
mtp_num_layers = get_mtp_num_layers_to_build(model.config, vp_stage=self.vp_stage)

# build preprocess
self.pre_process = PreProcessNode(model, self._model_chunk_state, self._event, comp_stream)
# build layer schedule plan for each layer
for layer_idx in range(transformer_num_layers):
layer = model.decoder._get_layer(layer_idx)
layer_plan = TransformerLayerSchedulePlan(
layer, self._event, self._model_chunk_state, comp_stream, comm_stream

# build layer schedule plan for each layer.
# The methods to obtain layers are different for MTP so we need the other build plan for
# MTP. Also, this can help annotate MTP layer so that it can know where MTP is.
self._build_layer_schedule_plan(model.decoder, comp_stream, comm_stream)
self._build_layer_schedule_plan(getattr(model, "mtp", None), comp_stream, comm_stream)

# build post process
if model.post_process:
self.post_process = PostProcessNode(
model, self._model_chunk_state, self._event, comp_stream
)
self._transformer_layers.append(layer_plan)

# build mtp layers
for layer_idx in range(mtp_num_layers):
def _build_layer_schedule_plan(self, module, comp_stream, comm_stream):
if module is None:
return
num_layers = len(module.layers)
for layer_idx in range(num_layers):
extra_args = {
"is_first_layer": layer_idx == 0,
"is_last_layer": layer_idx == mtp_num_layers - 1,
"is_last_layer": layer_idx == num_layers - 1,
}
layer = model.mtp.layers[layer_idx]
layer_plan = TransformerLayerSchedulePlan(
layer, self.event, self.state, comp_stream, comm_stream, extra_args
module.layers[layer_idx],
self.event,
self.state,
comp_stream,
comm_stream,
extra_args,
)
self._transformer_layers.append(layer_plan)

# build post process
if model.post_process:
self.post_process = PostProcessNode(
model, self._model_chunk_state, self._event, comp_stream
)

@property
def event(self):
"""Gets the CUDA event for synchronization."""
Expand Down
36 changes: 14 additions & 22 deletions megatron/core/models/gpt/fine_grained_callables.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,26 +153,19 @@ def forward_impl(self, hidden_states):
"""Implements the forward pass for postprocessing.

This method handles:
1. Final layer normalization
2. Output layer computation
3. Loss computation if labels are provided
1. Output layer computation
2. Loss computation if labels are provided

Args:
hidden_states: The hidden states from the transformer layers.

Returns:
The logits or loss depending on whether labels are provided.
"""
# Final layer norm from Decoder
if self.gpt_model.decoder.final_layernorm and not self.gpt_model.mtp_process:
hidden_states = self.gpt_model.decoder.final_layernorm(hidden_states)
# TENorm produces a "viewed" tensor. This will result in schedule.py's
# deallocate_output_tensor() throwing an error, so a viewless tensor is
# created to prevent this.
hidden_states = make_viewless_tensor(
inp=hidden_states, requires_grad=True, keep_graph=True
)

Note:
Final layernorm now has been moved from the post-process stage to the
last decoder layer, so we don't need to run the final layer norm here.
"""
# Run GPTModel._postprocess
loss = self.gpt_model._postprocess(
hidden_states=hidden_states,
Expand Down Expand Up @@ -246,6 +239,7 @@ def __init__(
self.submodule = submodule
self.detached = tuple()
self.before_detached = tuple()
self.is_mtp = extra_args.get("is_mtp", False)

# Create flags to indicate first and last layer
self.is_first_layer = extra_args.get("is_first_layer", False)
Expand Down Expand Up @@ -443,6 +437,12 @@ def submodule_combine_forward(

# release tensor reference after use
node.layer_state.residual = None

# final layer norm from decoder
final_layernorm = node.chunk_state.model.decoder.final_layernorm
if not node.is_mtp and final_layernorm and node.is_last_layer:
output = final_layernorm(output)
output = make_viewless_tensor(inp=output, requires_grad=True, keep_graph=True)
return output

def mlp_wrapper(node: ScheduleNode, *args, **kwargs):
Expand Down Expand Up @@ -482,15 +482,7 @@ def build_mtp_layer_callables(layer):
def submodule_mtp_attn_forward(node, hidden_states):
# MTP Block Preprocess
if node.is_first_layer:
# Final layer norm from Decoder
final_layernorm = node.chunk_state.model.decoder.final_layernorm
if final_layernorm:
hidden_states = final_layernorm(hidden_states)
hidden_states = make_viewless_tensor(
inp=hidden_states, requires_grad=True, keep_graph=True
)
hidden_states = node.detach(hidden_states)
offset = get_mtp_layer_offset(layer.config)
offset = get_mtp_layer_offset(layer.config, node.chunk_state.model.vp_stage)
node.chunk_state.mtp_hidden_states = list(torch.chunk(hidden_states, 1 + offset, dim=0))
hidden_states = node.chunk_state.mtp_hidden_states[offset]

Expand Down
28 changes: 22 additions & 6 deletions megatron/core/models/gpt/gpt_layer_specs.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
# Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.

import warnings
from typing import Optional, Union
Expand Down Expand Up @@ -401,7 +401,7 @@ def get_mlp_module_spec_for_backend(
)


def get_gpt_decoder_block_spec(
def get_gpt_decoder_layer_specs(
config: TransformerConfig,
use_transformer_engine: bool,
normalization: Optional[str] = None,
Expand Down Expand Up @@ -485,6 +485,21 @@ def get_gpt_decoder_block_spec(
else:
raise ValueError(f"Invalid layer pattern: {moe_layer_pattern}")

return layer_specs


def get_gpt_decoder_block_spec(
config: TransformerConfig,
use_transformer_engine: bool,
normalization: Optional[str] = None,
qk_l2_norm: Optional[bool] = False,
vp_stage: Optional[int] = None,
pp_rank: Optional[int] = None,
) -> TransformerBlockSubmodules:
"""GPT block spec."""
layer_specs = get_gpt_decoder_layer_specs(
config, use_transformer_engine, normalization, qk_l2_norm
)
# Slice the layer specs to only include the layers that are built in this pipeline stage.
# Note: MCore layer_number starts at 1
num_layers_to_build = get_num_layers_to_build(config, vp_stage=vp_stage, pp_rank=pp_rank)
Expand All @@ -500,6 +515,10 @@ def get_gpt_decoder_block_spec(
offset = get_transformer_layer_offset(config, vp_stage=vp_stage, pp_rank=pp_rank)
local_layer_specs = layer_specs[offset : offset + num_layers_to_build]

if use_transformer_engine:
layer_norm_impl = TENorm
else:
layer_norm_impl = LNImpl
# Block spec.
block_spec = TransformerBlockSubmodules(
layer_specs=local_layer_specs, layer_norm=layer_norm_impl
Expand Down Expand Up @@ -559,13 +578,10 @@ def get_gpt_mtp_block_spec_for_backend(
mtp_num_layers = config.mtp_num_layers if config.mtp_num_layers else 0
mtp_layer_specs = [mtp_layer_spec] * mtp_num_layers

offset = get_mtp_layer_offset(config)
offset = get_mtp_layer_offset(config, vp_stage=vp_stage)
# split the mtp layer specs to only include the layers that are built in this pipeline stage.
mtp_layer_specs = mtp_layer_specs[offset : offset + num_layers_to_build]
if len(mtp_layer_specs) > 0:
assert (
len(mtp_layer_specs) == config.mtp_num_layers
), +f"currently all of the mtp layers must stage in the same pipeline stage."
mtp_block_spec = MultiTokenPredictionBlockSubmodules(layer_specs=mtp_layer_specs)
else:
mtp_block_spec = None
Expand Down
Loading