Skip to content

Commit 41dddbd

Browse files
author
Huy Vu2
committed
merge main
2 parents 11b6e34 + 8f43506 commit 41dddbd

53 files changed

Lines changed: 3445 additions & 322 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎docs/skills-index.md‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ skills/perf-techniques/packed-sequences-long-context/SKILL
1717
skills/perf-techniques/sequence-packing/SKILL
1818
skills/perf-techniques/hybrid-context-parallel/SKILL
1919
skills/perf-techniques/expert-parallel-overlap/SKILL
20-
skills/perf-techniques/moe-comm-overlap/SKILL
2120
```
2221

2322
```{toctree}

‎docs/training/communication-overlap.md‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,5 +107,3 @@ For most teams, the right order is:
107107
- [docs/training/hybrid-context-parallel.md](hybrid-context-parallel.md)
108108
- [skills/perf-techniques/tp-dp-comm-overlap/SKILL.md](../skills/perf-techniques/tp-dp-comm-overlap/SKILL.md)
109109
- [skills/perf-techniques/expert-parallel-overlap/SKILL.md](../skills/perf-techniques/expert-parallel-overlap/SKILL.md)
110-
- [skills/perf-techniques/moe-comm-overlap/SKILL.md](../skills/perf-techniques/moe-comm-overlap/SKILL.md)
111-
- [skills/perf-techniques/moe-comm-overlap/card.yaml](../skills/perf-techniques/moe-comm-overlap/card.yaml)

‎examples/conversion/compare_hf_and_megatron/compare.py‎

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@
121121

122122
from megatron.bridge import AutoBridge
123123
from megatron.bridge.models.hf_pretrained.utils import is_safe_repo
124-
from megatron.bridge.utils.common_utils import get_last_rank, print_rank_0
124+
from megatron.bridge.utils.common_utils import disable_mtp_for_inference, get_last_rank, print_rank_0
125125

126126

127127
# Cosine similarity threshold: require at least 98% similarity (2% tolerance)
@@ -618,8 +618,7 @@ def _load_megatron_model(args):
618618

619619
# Workaround: disable MTP for inference (causes hangs on NCCL collectives)
620620
for m in megatron_model:
621-
m.config.mtp_num_layers = None
622-
m.config.grad_scale_func = None
621+
disable_mtp_for_inference(m)
623622

624623
model_components = [m.eval() for m in megatron_model]
625624

@@ -732,6 +731,11 @@ def compare_models_one_step(args) -> None:
732731

733732
# Broadcast HF results to all ranks
734733
if torch.distributed.is_initialized():
734+
# Ensure consistent dtype across ranks: rank 0 has bfloat16 logits from the HF model,
735+
# so all ranks must use the same dtype for NCCL broadcast to work correctly.
736+
if hf_logits is not None:
737+
hf_logits = hf_logits.float()
738+
735739
# Create tensors for broadcasting if they don't exist on non-rank-0
736740
if hf_next_token is None:
737741
hf_next_token = torch.zeros(1, device=input_ids.device, dtype=torch.long)
@@ -748,6 +752,8 @@ def compare_models_one_step(args) -> None:
748752
# Broadcast from rank 0 to all ranks
749753
torch.distributed.broadcast(hf_next_token, 0)
750754
torch.distributed.broadcast(hf_logits, 0)
755+
torch.distributed.barrier()
756+
print_rank_0("HF results broadcast complete.")
751757

752758
# Run Megatron model forward pass
753759
print_rank_0("=== RUNNING MEGATRON MODEL (1-STEP) ===")
@@ -812,27 +818,26 @@ def compare_models_one_step(args) -> None:
812818
top5_tokens = [tokenizer.decode([idx]) for idx in top5_ids]
813819
print(f"Megatron Top 5: {list(zip(top5_tokens, top5_vals.tolist()))}")
814820

821+
# Megatron may pad vocab_size for GPU kernel efficiency — truncate
822+
# to the HF vocab size so logits are directly comparable.
823+
hf_vocab_size = hf_logits.shape[0]
824+
megatron_logits_cmp = megatron_logits[:hf_vocab_size]
825+
megatron_next_token_cmp = torch.argmax(megatron_logits_cmp, dim=-1)
826+
815827
# Compare outputs (only where we have valid Megatron results)
816828
print("=== COMPARISON ===")
817-
token_match = hf_next_token.item() == megatron_next_token.item()
829+
token_match = hf_next_token.item() == megatron_next_token_cmp.item()
818830
token_status_emoji = "✅" if token_match else "❌"
819831
print(f"Token match: {token_match} {token_status_emoji}")
820832

821-
# Compare logits if shapes match
822-
if hf_logits.shape == megatron_logits.shape:
823-
diff = (hf_logits - megatron_logits).abs()
824-
print(f"Logits diff - max: {diff.max():.6f}, mean: {diff.mean():.6f}")
825-
cosine_sim = torch.cosine_similarity(hf_logits.unsqueeze(0), megatron_logits.unsqueeze(0))
826-
cos_val = cosine_sim.item()
827-
percent = cos_val * 100.0
828-
status_emoji = "✅" if cos_val >= SIMILARITY_THRESHOLD else "❌"
829-
tolerance_text = "within ±2%" if cos_val >= SIMILARITY_THRESHOLD else "outside ±2%"
830-
print(
831-
f"Cosine similarity: {cos_val:.6f} ({percent:.2f}%) {status_emoji} ({tolerance_text} tolerance)"
832-
)
833-
else:
834-
print(f"Shape mismatch: HF {hf_logits.shape} vs Megatron {megatron_logits.shape}")
835-
print("Cannot compare logits directly due to shape mismatch")
833+
diff = (hf_logits - megatron_logits_cmp).abs()
834+
print(f"Logits diff - max: {diff.max():.6f}, mean: {diff.mean():.6f}")
835+
cosine_sim = torch.cosine_similarity(hf_logits.unsqueeze(0), megatron_logits_cmp.unsqueeze(0))
836+
cos_val = cosine_sim.item()
837+
percent = cos_val * 100.0
838+
status_emoji = "✅" if cos_val >= SIMILARITY_THRESHOLD else "❌"
839+
tolerance_text = "within ±2%" if cos_val >= SIMILARITY_THRESHOLD else "outside ±2%"
840+
print(f"Cosine similarity: {cos_val:.6f} ({percent:.2f}%) {status_emoji} ({tolerance_text} tolerance)")
836841

837842
print("=== COMPARISON COMPLETE ===")
838843
else:

‎examples/conversion/hf_megatron_roundtrip_multi_gpu.py‎

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -166,14 +166,12 @@ def main(
166166
original_param = bridge.hf_pretrained.state[name]
167167
compare_param = param
168168
compare_original = original_param
169-
# Cast to float32 for params with known dtype mismatches between Megatron and HF
170-
# (e.g. Megatron keeps expert_bias in float32 while HF may use bfloat16)
171-
if any(p in name for p in IGNORE_PRECISION_PARAMS) or compare_param.dtype != compare_original.dtype:
169+
# Cast to float32 for params with known precision mismatches.
170+
# Any new known mismatch should be recorded in IGNORE_PRECISION_PARAMS above.
171+
if any(p in name for p in IGNORE_PRECISION_PARAMS):
172172
compare_param = param.float()
173173
compare_original = original_param.float()
174-
match = torch.allclose(
175-
compare_param, compare_original.to(compare_param.device), atol=1e-1
176-
) # Increased tolerance for bfloat16
174+
match = torch.allclose(compare_param, compare_original.to(compare_param.device), atol=1e-1)
177175
all_match = all_match and match
178176
table.add_row(
179177
name,

‎examples/conversion/hf_to_megatron_generate_text.py‎

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131

3232
from megatron.bridge import AutoBridge
3333
from megatron.bridge.models.hf_pretrained.utils import is_safe_repo
34-
from megatron.bridge.utils.common_utils import get_last_rank, print_rank_0
34+
from megatron.bridge.utils.common_utils import disable_mtp_for_inference, get_last_rank, print_rank_0
3535

3636

3737
class SingleBatchIterator:
@@ -167,24 +167,10 @@ def main(args) -> None:
167167
model_provider.initialize_model_parallel(seed=0)
168168
model = model_provider.provide_distributed_model(wrap_with_ddp=False)
169169

170-
# Disable MTP for inference (MTP is only used during training)
171-
def _disable_mtp(m):
172-
"""Disable MTP on a model by clearing mtp_process on the language model."""
173-
m.config.mtp_num_layers = None
174-
inner = m.module if hasattr(m, "module") else m
175-
lang = getattr(inner, "language_model", inner)
176-
if hasattr(lang, "mtp_process"):
177-
lang.mtp_process = False
178-
179170
model = [m.cuda() for m in model]
180171
for m in model:
181172
m.eval()
182-
_disable_mtp(m)
183-
184-
# Set grad_scale_func to None on the model's config for inference
185-
for m in model:
186-
if hasattr(m, "config"):
187-
m.config.grad_scale_func = None
173+
disable_mtp_for_inference(m)
188174

189175
# Initialize tokenizer
190176
tokenizer = AutoTokenizer.from_pretrained(
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# MiniMax-M2 Examples
2+
3+
This directory contains example scripts for [MiniMax-M2](https://huggingface.co/MiniMaxAI/MiniMax-M2), a large sparse MoE model with 456B total parameters (45.9B active), 256 experts, and FP8 quantization.
4+
5+
## Hardware Requirements
6+
7+
MiniMax-M2 requires **at least 2 nodes (16 GPUs)** for inference and conversion. The model cannot fit on a single 8-GPU node because:
8+
9+
- TEGroupedMLP workspace is proportional to `num_experts / EP`; with EP=8 on 1 node, workspace alone OOMs.
10+
- TP does **not** reduce expert memory — use EP instead.
11+
- Minimum recommended config: `TP=1, EP=16, PP=1` (2 nodes × 8 GPUs).
12+
13+
## Checkpoint Conversion
14+
15+
[slurm_conversion.sh](slurm_conversion.sh) sweeps multiple TP/PP/EP configs to verify HF ↔ Megatron round-trip conversion.
16+
17+
### Setup
18+
19+
Edit the variables at the top of `slurm_conversion.sh`:
20+
21+
```bash
22+
CONTAINER_IMAGE="/path/to/container.sqsh"
23+
# Optional:
24+
export HF_TOKEN="hf_your_token_here"
25+
export HF_HOME="/path/to/shared/HF_HOME"
26+
```
27+
28+
### Submit
29+
30+
```bash
31+
sbatch examples/models/minimax_m2/slurm_conversion.sh
32+
```
33+
34+
### Expected output (per config)
35+
36+
```
37+
MiniMax-M2 Round-Trip Conversion Sweep
38+
Job: <JOB_ID> | Nodes: 2
39+
Parallelism configs: 2,1,8 1,2,8 2,2,4
40+
======================================
41+
Config 1/3: TP=2, PP=1, EP=8
42+
...
43+
All parameters match: True ✅
44+
[OK] Config 1: TP=2, PP=1, EP=8 passed
45+
...
46+
All 3 configs passed
47+
======================================
48+
```
49+
50+
## Inference
51+
52+
[slurm_inference.sh](slurm_inference.sh) runs text generation on the full FP8 checkpoint with `TP=1, EP=16`.
53+
54+
### Setup
55+
56+
Edit the variables at the top of `slurm_inference.sh`:
57+
58+
```bash
59+
CONTAINER_IMAGE="/path/to/container.sqsh"
60+
export HF_TOKEN="hf_your_token_here"
61+
```
62+
63+
### Submit
64+
65+
```bash
66+
sbatch examples/models/minimax_m2/slurm_inference.sh
67+
```
68+
69+
### Expected output
70+
71+
```
72+
======== GENERATED TEXT OUTPUT ========
73+
Prompt: What is 2+2?
74+
Generated: What is 2+2? The answer is 4.
75+
=======================================
76+
```
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
#!/bin/bash
2+
# Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
# ==============================================================================
17+
# MiniMax-M2 Conversion Round-Trip Verification (Multi-Node via Slurm)
18+
#
19+
# MiniMax-M2 (MoE: 256 experts, top-8, ~230GB fp8)
20+
# The full model OOMs on a single 8-GPU node — minimum 2 nodes (16 GPUs)
21+
# with EP >= 8.
22+
#
23+
# Sweeps multiple parallelism configs (TP,PP,EP) to verify HF <-> Megatron
24+
# round-trip conversion. Each config runs sequentially.
25+
#
26+
# Usage:
27+
# 1. Fill in CONTAINER_IMAGE, CONTAINER_MOUNTS, and token exports
28+
# 2. Adjust PARALLELISM_CONFIGS if needed
29+
# 3. Submit: sbatch slurm_conversion.sh
30+
# ==============================================================================
31+
32+
#SBATCH --job-name=minimax-m2-roundtrip
33+
#SBATCH --nodes=2
34+
#SBATCH --ntasks-per-node=8
35+
#SBATCH --gpus-per-node=8
36+
#SBATCH --time=4:00:00
37+
#SBATCH --account=<your-account>
38+
#SBATCH --partition=batch
39+
#SBATCH --output=logs/minimax_m2_roundtrip_%j.log
40+
#SBATCH --exclusive
41+
42+
# ── Container ────────────────────────────────────────────────────────────
43+
CONTAINER_IMAGE=""
44+
# CONTAINER_IMAGE="/path/to/container.sqsh"
45+
CONTAINER_MOUNTS=""
46+
# CONTAINER_MOUNTS="/path/to/shared/storage:/mnt/storage,/path/to/project:/opt/Megatron-Bridge"
47+
WORKDIR="/opt/Megatron-Bridge"
48+
49+
# ── Tokens / Caches ──────────────────────────────────────────────────────
50+
# export HF_TOKEN="hf_your_token_here"
51+
# export HF_HOME="/path/to/shared/HF_HOME"
52+
# export UV_CACHE_DIR="/path/to/shared/uv_cache"
53+
54+
# ── Parallelism configs: "TP,PP,EP" per entry ────────────────────────────
55+
# TP*PP*EP must equal total GPUs (NODES * GPUS_PER_NODE).
56+
# EP must divide 256 (number of experts).
57+
# Note: TP=2,EP=4 on 8 GPUs (1 node) OOMs — use EP >= 8 with 2+ nodes.
58+
PARALLELISM_CONFIGS=("2,1,8" "1,2,8" "2,2,4")
59+
60+
# ── Model ─────────────────────────────────────────────────────────────────
61+
MODEL_NAME=MiniMax-M2
62+
HF_MODEL_ID=MiniMaxAI/$MODEL_NAME
63+
64+
# ── Environment ───────────────────────────────────────────────────────────
65+
export TORCH_NCCL_AVOID_RECORD_STREAMS=1
66+
export NCCL_NVLS_ENABLE=0
67+
68+
# ==============================================================================
69+
# Job Execution
70+
# ==============================================================================
71+
72+
echo "======================================"
73+
echo "MiniMax-M2 Round-Trip Conversion Sweep"
74+
echo "Job: $SLURM_JOB_ID | Nodes: $SLURM_JOB_NUM_NODES"
75+
echo "Parallelism configs: ${PARALLELISM_CONFIGS[*]}"
76+
echo "======================================"
77+
78+
mkdir -p logs
79+
80+
if [ -z "$CONTAINER_IMAGE" ]; then
81+
echo "ERROR: CONTAINER_IMAGE must be set."
82+
exit 1
83+
fi
84+
85+
SRUN_CMD="srun --mpi=pmix --container-image=$CONTAINER_IMAGE"
86+
if [ -n "$CONTAINER_MOUNTS" ]; then
87+
SRUN_CMD="$SRUN_CMD --container-mounts=$CONTAINER_MOUNTS"
88+
fi
89+
90+
CONFIG_INDEX=0
91+
for CONFIG in "${PARALLELISM_CONFIGS[@]}"; do
92+
IFS=',' read -r TP PP EP <<< "$CONFIG"
93+
CONFIG_INDEX=$((CONFIG_INDEX + 1))
94+
95+
echo ""
96+
echo "======================================"
97+
echo "Config $CONFIG_INDEX/${#PARALLELISM_CONFIGS[@]}: TP=$TP, PP=$PP, EP=$EP"
98+
echo "======================================"
99+
100+
# Sync dependencies once per node, then run the roundtrip
101+
CMD="if [ \"\$SLURM_LOCALID\" -eq 0 ]; then uv sync; else sleep 10; fi && "
102+
CMD="${CMD}uv run --no-sync python examples/conversion/hf_megatron_roundtrip_multi_gpu.py"
103+
CMD="$CMD --hf-model-id $HF_MODEL_ID"
104+
CMD="$CMD --tp $TP --pp $PP --ep $EP"
105+
CMD="$CMD --trust-remote-code"
106+
107+
echo "Executing: $CMD"
108+
109+
$SRUN_CMD bash -c "cd $WORKDIR && $CMD"
110+
RUN_EXIT=$?
111+
if [ $RUN_EXIT -ne 0 ]; then
112+
echo "ERROR: Config TP=$TP, PP=$PP, EP=$EP failed (exit $RUN_EXIT)"
113+
exit $RUN_EXIT
114+
fi
115+
echo "[OK] Config $CONFIG_INDEX: TP=$TP, PP=$PP, EP=$EP passed"
116+
done
117+
118+
echo ""
119+
echo "======================================"
120+
echo "All ${#PARALLELISM_CONFIGS[@]} configs passed"
121+
echo "======================================"

0 commit comments

Comments
 (0)