Skip to content

Add TV (total-variation) loss option for the MTP draft head - #56

Open
ElliotXinqiWang wants to merge 23 commits into
radixark:miles-mainfrom
ElliotXinqiWang:feat/mtp-tv-loss
Open

Add TV (total-variation) loss option for the MTP draft head#56
ElliotXinqiWang wants to merge 23 commits into
radixark:miles-mainfrom
ElliotXinqiWang:feat/mtp-tv-loss

Conversation

@ElliotXinqiWang

Copy link
Copy Markdown

PR: Add TV (total-variation) loss option for the MTP draft head

Repo: radixark/Megatron-LM Base: miles-main @ 36ceb4fc0 Branch: feat/mtp-tv-loss
Files: megatron/core/models/gpt/gpt_model.py (+106), megatron/core/transformer/transformer_config.py (+13)

Motivation

The MTP draft head is trained with cross-entropy. But for speculative decoding the
rejection-sampling acceptance rate is accept_rate = 1 - d_TV(p_main, q_draft), so
total variation is the direct objective; CE only bounds TV via Pinsker. Optimizing TV
directly heals the draft head faster (esp. when the main model has drifted from the head's
training distribution — e.g. an official MTP head grafted onto an SFT'd / RL'd main model).

Ref: "Breaking Entropy Bounds: Accelerating RL Training via MTP with Rejection Sampling" (Bebop).

What it adds

  • TransformerConfig.mtp_loss_type ('ce' default | 'tv') and mtp_tv_chunk: int = 0.
    Auto-exposed as --mtp-loss-type / --mtp-tv-chunk (ArgumentGroupFactory) and mapped back
    via core_transformer_config_from_args — no manual arg wiring.
  • GPTModel: when mtp_loss_type == 'tv', the MTP-loss branch computes per token
    1 - Σ_v min(softmax(p_main).detach(), softmax(q_draft)). p_main is the main model's
    next-token distribution (fully detached; main hidden states are rolled to align with the
    MTP target, mirroring the existing label shift). Produces mtp_loss in the same [b, s]
    layout as the CE branch, so the existing MTPLossAutoScaler / scaling / logging path is
    unchanged — it's a drop-in alternative to the CE branch.
  • mtp_tv_chunk > 0 chunks the vocab-sum over the sequence dim and gradient-checkpoints
    each chunk
    (recomputes softmax in backward), so backward memory is ~one chunk instead of
    O(T·V) — this is what lets TV fit alongside full-backbone (joint) training.

Limitation

TV needs the full vocabulary per rank for Σ_v min(p,q), so it requires
tensor-model-parallel size 1 (raises NotImplementedError for TP>1). Vocab-parallel TV
(all-reduce of the min-overlap) is possible future work.

Validation

Trained the Qwen3.5-35B-A3B grafted MTP head with --mtp-loss-type tv --mtp-tv-chunk 512
(freeze + joint), 300 steps each, on the miles RL stack; mtp_loss (= mean TV) ~0.13–0.15,
no OOM with chunking, spec accept-rate tracked as expected. CE path unchanged (default).

Notes for reviewers

  • Default behavior is identical (mtp_loss_type='ce').
  • --mtp-tv-chunk only affects the TV path; ignored for CE.
  • Companion (separate) change in miles: none required — it forwards Megatron args; an optional
    reset_arg could surface --mtp-loss-type explicitly.

yueming-yuan and others added 23 commits February 25, 2026 19:22
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…rk#4)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

Co-authored-by: Yueming Yuan <yym022502@gmail.com>
…adixark#5)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
- Detach output layer params to prevent MTP gradient flowing to output layer
- Add mtp_kwargs interface for flexible MTP label/loss_mask passing
- Roll mtp_labels and loss_mask for RL training compatibility

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Yueming Yuan <yym022502@gmail.com>
…yers (radixark#10)

- Add is_mtp flag to MoE layers and multi_token_prediction module
- Bypass routing replay for MTP layers (MTP uses fresh routing)
- Replace rdxa/dev's built-in RouterReplay with miles.utils.routing_replay:
  - moe_utils.py: use get_routing_replay_compute_topk() wrapper
  - router.py: use register_routing_replay() for initialization

Co-authored-by: Yueming Yuan <yym022502@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Yueming Yuan <yym022502@gmail.com>
After bumping Megatron (rdxa/dev), colocated IPC weight update fails with
torch.AcceleratorError: CUDA error: invalid argument during
torch.multiprocessing serialization of CUDA tensors.

Root cause: Megatron's new TMS hook (PR NVIDIA#3048) alters allocator behavior
in training flow, causing allocations via cuMemCreate/cuMemMap which are
incompatible with CUDA IPC (_share_cuda_() fails).

Fix: resolve mapping.py and dynamic_context.py conflicts to isolate
hook side effects so TMS/allocator state remains IPC-compatible during
the weight update phase.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- merge(): truncate dp_reshardable padding on optimizer/param_state path
- load_parameter_state_from_dp_reshardable: tolerate missing 'padding' key
- ShardedTensor: relax flattened_range to deprecation warning

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This PR rebases from radixark Megatron fork [miles-20260218](https://github.com/radixark/Megatron-LM/tree/miles-20260218) and resolve conflicts.

Upgrade Megatron from Dec 17 (3714d81) to Feb 13 (1dcf0da)

PR link: radixark#13

Co-authored-by: Yueming Yuan <yym022502@gmail.com>
Made-with: Cursor
…se `--disable-weight-backuper` in miles (radixark#18)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: fzyzcjy <5236035+fzyzcjy@users.noreply.github.com>
…adixark#20)

Co-authored-by: fzyzcjy <5236035+fzyzcjy@users.noreply.github.com>
Squash merge of the dense true-on-policy Megatron branch.

Co-authored-by: zju-stu-lizheng <lizheng.cs@zju.edu.cn>
Co-authored-by: zyxiyy02 <282300612+zyxiyy02@users.noreply.github.com>
Co-authored-by: Yi Zhang <1109276519@qq.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: fzyzcjy <5236035+fzyzcjy@users.noreply.github.com>
MTP draft heads are trained with cross-entropy, but for speculative decoding the
rejection-sampling acceptance rate is accept_rate = 1 - d_TV(p_main, q_draft), so
total variation is the *direct* training target (CE only bounds it via Pinsker).
This adds an opt-in TV loss for the MTP head:

- TransformerConfig.mtp_loss_type ('ce' default | 'tv') + mtp_tv_chunk (int),
  auto-exposed as --mtp-loss-type / --mtp-tv-chunk via ArgumentGroupFactory.
- GPTModel MTP loss: when mtp_loss_type=='tv', compute per-token
  1 - sum_v min(softmax(p_main).detach(), softmax(q_draft)); p_main is the main
  model's next-token distribution (detached; hidden states rolled to align with
  the MTP target). Drop-in for the CE branch -> reuses MTPLossAutoScaler path.
- mtp_tv_chunk>0 chunks the vocab-sum over the sequence dim and gradient-
  checkpoints each chunk, so backward memory is ~1 chunk instead of O(T*V),
  letting TV fit alongside full-backbone joint training.

Limitation: TV needs the full vocab per rank for sum_v min(p,q), so it requires
tensor-model-parallel size 1 (raises NotImplementedError for TP>1).

Ref: "Breaking Entropy Bounds: Accelerating RL Training via MTP with Rejection
Sampling" (Bebop).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants