Skip to content

Commit 5519e38

Browse files
Name the per-stage layer writer and warn when freezing the MTP head
Follow-up to #2623's Claude review: is_stage_layer_writer spells out that, unlike is_writer_rank, it has no PP term; the QAD MTP freeze now warns and scans only the student. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: Keval Morabia <28916987+kevalmorabia97@users.noreply.github.com>
1 parent f1661bf commit 5519e38

2 files changed

Lines changed: 14 additions & 9 deletions

File tree

‎modelopt/torch/distill/plugins/megatron.py‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import modelopt.torch.distill as mtd
4141
from modelopt.torch.distill.config import Criterion
4242
from modelopt.torch.quantization.nn import TensorQuantizer
43+
from modelopt.torch.utils import warn_rank_0
4344

4445
if TYPE_CHECKING:
4546
from megatron.core.dist_checkpointing.mapping import ShardedStateDict
@@ -592,9 +593,11 @@ def _sharded_state_dict(self, *args, **kwargs) -> "ShardedStateDict":
592593
skip_mtp_loss = _mtp_excluded_from_quantization(model)
593594
if distill_cfg.skip_lm_loss and skip_mtp_loss:
594595
# Freeze the untrained MTP head: DDP's overlapped grad reduce asserts on params with no grad.
595-
for name, param in model.named_parameters():
596-
if "mtp" in name.split("."):
597-
param.requires_grad_(False)
596+
warn_rank_0("MTP head is outside quantization and its loss is skipped: freezing it.")
597+
with model.hide_teacher_model():
598+
for name, param in model.named_parameters():
599+
if "mtp" in name.split("."):
600+
param.requires_grad_(False)
598601

599602
def _compute_student_lm_loss(self, labels, logits) -> Tensor:
600603
self._lm_loss_call_count += 1

‎modelopt/torch/export/unified_export_megatron.py‎

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -358,9 +358,11 @@ def save_pretrained(
358358
"Export at expert parallel size > 1 needs grouped-GEMM experts; "
359359
"export SequentialMLP (--no_moe_grouped_gemm) checkpoints at EP=1."
360360
)
361-
# One writer per pipeline stage: other TP / DP / EP ranks hold the same layers (EP>1 ranks
362-
# hold no gathered experts at all), and writing them too would race on the same files.
363-
writes_layers = (
361+
# One layer-shard writer per pipeline stage: other TP / DP / EP ranks hold the same layers
362+
# (EP>1 ranks hold no gathered experts at all), and writing them too would race on the same
363+
# files. Unlike is_writer_rank (one global writer of save_directory metadata), this has no
364+
# PP term on purpose: each stage owns a disjoint slice of the layer shards.
365+
is_stage_layer_writer = (
364366
tp_rank == 0 and get_data_parallel_rank() == 0 and get_expert_model_parallel_rank() == 0
365367
)
366368

@@ -428,7 +430,7 @@ def save_pretrained(
428430

429431
# The live MTP export runs EP collectives, so every last-stage main rank joins it; the
430432
# collective-free copy from the source checkpoint only runs on the writer.
431-
mtp_state_dict = self._get_mtp_state_dict(copy_from_pretrained=writes_layers)
433+
mtp_state_dict = self._get_mtp_state_dict(copy_from_pretrained=is_stage_layer_writer)
432434
if len(mtp_state_dict) > 0:
433435
layer_state_dicts[self.model.config.num_layers].update(mtp_state_dict)
434436
print(f"Successfully loaded {len(mtp_state_dict)} MTP tensors")
@@ -466,7 +468,7 @@ def save_pretrained(
466468
# Add multimodal components to state_dict. Since only support decoder model quantization,
467469
# no changes will be made to the multimodal components. We copy the multimodal components
468470
# from the pretrained model directly to the state_dict to avoid implementing the export logic.
469-
if is_first_stage_main_rank and writes_layers:
471+
if is_first_stage_main_rank and is_stage_layer_writer:
470472
# layer_state_dicts is keyed by layer_number (1-indexed), so the first
471473
# decoder layer on this (first) PP stage is the smallest key, not 0.
472474
# Merge the multimodal components into that shard so they land in a file
@@ -495,7 +497,7 @@ def save_pretrained(
495497
torch.distributed.barrier()
496498

497499
save_safetensors_by_layer_index(
498-
layer_state_dicts=layer_state_dicts if writes_layers else {},
500+
layer_state_dicts=layer_state_dicts if is_stage_layer_writer else {},
499501
total_layers=self.model.config.num_layers,
500502
save_directory=save_directory,
501503
name_template="model-{:05d}-of-{:05d}",

0 commit comments

Comments
 (0)