Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
25 changes: 25 additions & 0 deletions docs/user-guide/features/multi_token_prediction.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,31 @@ We can train GPTModel like models with Multi-Token Prediction (MTP) by setting m
| mtp_num_layers | Number of Multi-Token Prediction (MTP) Layers. MTP extends the prediction scope to multiple future tokens at each position. This MTP implementation sequentially predict additional tokens by using D sequential modules to predict D additional tokens. Default is None. |
| mtp_loss_scaling_factor | Scaling factor of Multi-Token Prediction (MTP) loss. We compute the average of the MTP losses across all depths, and multiply it the scaling factor to obtain the overall MTP loss, which serves as an additional training objective. Default is 0.1. |

## Pipeline Parallel Layout for MTP

MTP supports flexible placement of MTP layers across pipeline stages using a custom `pipeline_model_parallel_layout`. By default, all MTP layers are placed on the last pipeline stage, but you can customize their placement.

### MTP Standalone Mode

When MTP layers are placed in a separate virtual pipeline (vpp) stage that is not on the last pipeline rank, the `mtp_standalone` flag is automatically set to `True`. This mode enables MTP to run independently in its own pipeline stage.

### Layout Format

Use `m` to represent MTP layers in the pipeline layout string. For example:
- `"E|t*3|(t|)*5mL"` - MTP in the last stage
- `"E|t*3|(t|)*4tm|L"` - MTP in the second-to-last stage with a decoder layer
- `"E|t*3|(t|)*3tt|m|L"` - MTP in a standalone stage (second-to-last) with no other layers

### Constraints

- All MTP layers must be placed in the same one virtual pipeline stage.
- MTP layers cannot be placed on the first pipeline rank.

## Implementation Notes

- For models with MTP layers, the final layernorm is placed in the stage that contains the last decoder layer, rather than in the post-process stage. This may cause small numerical differences in gradient norm reduction when final layernorm is placed in different pipeline stages in deterministic mode. Bitwise alignment can be achieved by disabling gradient norm clipping.
- MTP loss is computed in the post-processing stage.

## Precautions

Please do not use Context Parallel (CP), or arbitrary AttnMaskType, or learned absolute position embedding type with MTP. These use cases are not yet supported.
12 changes: 9 additions & 3 deletions gpt_builders.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
get_gpt_layer_with_transformer_engine_spec,
get_gpt_layer_with_inference_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 @@ -69,7 +70,12 @@ def gpt_builder(args, pre_process, post_process, vp_stage=None, config=None, pg_
# Only happens with block spec (TransformerBlockSubmodules) when using MoE.
transformer_layer_spec_for_mtp = _get_transformer_layer_spec(use_te, config)
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
)
transformer_layer_spec_for_mtp = 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,
Expand Down Expand Up @@ -101,12 +107,12 @@ def gpt_builder(args, pre_process, post_process, vp_stage=None, config=None, pg_

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
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 @@ -329,6 +329,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
Expand Up @@ -68,6 +68,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 @@ -207,7 +209,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 @@ -352,37 +351,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
33 changes: 15 additions & 18 deletions megatron/core/models/gpt/fine_grained_callables.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,22 +157,20 @@ 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.

empty_decoder = len(self.gpt_model.decoder.layers) == 0
layer_norm = self.gpt_model.decoder.final_layernorm
if not self.gpt_model.config.mtp_num_layers and empty_decoder and layer_norm:
hidden_states = layer_norm(hidden_states)
hidden_states = make_viewless_tensor(
inp=hidden_states, requires_grad=True, keep_graph=True
)
Expand Down Expand Up @@ -251,6 +249,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 @@ -470,6 +469,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 @@ -509,15 +514,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
23 changes: 21 additions & 2 deletions megatron/core/models/gpt/gpt_layer_specs.py
Original file line number Diff line number Diff line change
Expand Up @@ -515,7 +515,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 @@ -607,6 +607,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 @@ -624,6 +639,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 @@ -691,7 +710,7 @@ 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:
Expand Down
Loading
Loading