Skip to content

Commit 9a58ada

Browse files
Merge branch 'main' into rob-luke/training/mlflow-description
2 parents f027234 + 2bb60cc commit 9a58ada

117 files changed

Lines changed: 10575 additions & 823 deletions

File tree

Some content is hidden

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

‎.main.commit‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
b2a8ec7458e90216e3a8d147d8ddf81cfb895f7c
1+
38986a98aae6a0cc4c8ae7b435db3288a890b0cb

‎docs/models/llm/deepseek-v4.md‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# DeepSeek V4
2+
3+
[DeepSeek-V4](https://github.com/deepseek-ai/DeepSeek-V4) is the next-generation Mixture-of-Experts language model from DeepSeek-AI. It extends the V3 design with **Hyper-Connections (mHC)** for multi-stream residual mixing, **Compressed Sparse Attention (CSA)** with a learned token-importance indexer (DSA), **hash-routed MoE layers** for the first few decoder blocks, and a refined **Multi-Token Prediction (MTP)** head with separate `e_proj` / `h_proj` projections.
4+
5+
DeepSeek V4 models are supported via the Bridge system with auto-detected configuration and weight mapping.
6+
7+
## Model Architecture Features
8+
9+
- **Hybrid Attention (DSv4HybridSelfAttention)**: Per-layer mix of dense MLA and Compressed Sparse Attention selected by `compress_ratios`
10+
- **Compressed Sparse Attention (CSA)** with **DSA Indexer**: Top-k token selection over windowed keys; `index_n_heads`, `index_head_dim`, `index_topk` control the indexer
11+
- **Hyper-Connections (mHC)**: 4-stream residual mixing per layer (`hc_mult = 4`) with sinkhorn-iterated attention; per-MTP-layer `hc_head_*` learns output contraction
12+
- **Hash-Routed MoE**: First few decoder layers use a deterministic vocab → expert mapping (`tid2eid`) instead of softmax routing
13+
- **Multi-Token Prediction (MTP)**: One MTP layer with separate `e_proj` and `h_proj` projections (post-MCore #4518)
14+
- **YaRN RoPE**: `rotary_scaling_factor=16`, `original_max_position_embeddings=65536`; `mscale=mscale_all_dim=1.0` for V4
15+
- **Sigmoid Gating with Expert Bias**: `noaux_tc` load balancing, `sqrtsoftplus` scoring, expert bias enabled
16+
- **`o_groups` Output Projection**: `o_lora_rank` low-rank output projection split into `o_groups` parallel groups
17+
18+
## Examples, Parallelism, and Limitations
19+
20+
For checkpoint conversion and inference scripts, recommended parallelism settings, and current known limitations, see the [DeepSeek V4 examples README](https://github.com/NVIDIA-NeMo/Megatron-Bridge/tree/main/examples/models/deepseek_v4).
21+
22+
## Hugging Face Model Cards & References
23+
24+
### Hugging Face Model Cards
25+
- DeepSeek-V4-Flash: https://huggingface.co/deepseek-ai/DeepSeek-V4-Flash
26+
- DeepSeek-V4-Flash-Base: https://huggingface.co/deepseek-ai/DeepSeek-V4-Flash-Base
27+
- DeepSeek-V4-Pro: https://huggingface.co/deepseek-ai/DeepSeek-V4-Pro
28+
- DeepSeek-V4-Pro-Base: https://huggingface.co/deepseek-ai/DeepSeek-V4-Pro-Base
29+
30+
### Additional Resources
31+
- GitHub Repository: https://github.com/deepseek-ai/DeepSeek-V4
32+
33+
## Related Docs
34+
- DeepSeek V4 examples: [examples/models/deepseek_v4/README.md](https://github.com/NVIDIA-NeMo/Megatron-Bridge/tree/main/examples/models/deepseek_v4)

‎docs/models/llm/index.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ This section documents Large Language Models supported by Megatron Bridge, with
77
88
deepseek-v2.md
99
deepseek-v3.md
10+
deepseek-v4.md
1011
gemma2.md
1112
gemma3.md
1213
glm45.md

‎docs/models/llm/nemotron3-nano.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,5 +91,5 @@ A LoRA checkpoint only contains the learnable adapter weights. In order to conve
9191
uv run python examples/peft/merge_lora.py \
9292
--hf-model-path $HF_MODEL_ID \
9393
--lora-checkpoint /path/to/lora/ckpt/iter_xxxxxxx
94-
--output /path/to/merged/ckpt
94+
--output /path/to/merged/hf/ckpt
9595
```

‎docs/models/vlm/nemotron-nano-v2-vl.md‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,10 +149,9 @@ format for downstream evaluation, it is necessary to merge the LoRA adapters bac
149149
uv run python examples/peft/merge_lora.py \
150150
--hf-model-path $HF_MODEL_PATH \
151151
--lora-checkpoint <trained LoRA checkpoint>/iter_N \
152-
--output <LoRA checkpoint merged>
152+
--output <merged HF checkpoint>
153153
```
154-
You can now run in-framework inference with `hf_to_megatron_generate_vlm.py` by supplying the merged LoRA checkpoint.
155-
You can also export the merged LoRA checkpoint to Hugging Face format.
154+
The output is a merged Hugging Face checkpoint for downstream evaluation.
156155

157156

158157
## Example Datasets

‎examples/conversion/convert_checkpoints_multi_gpu.py‎

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
"""
5454

5555
import argparse
56+
import datetime
5657
import os
5758
import sys
5859

@@ -84,6 +85,20 @@ def _check_distributed():
8485
sys.exit(1)
8586

8687

88+
def _ensure_distributed_initialized(timeout_minutes: int | None):
89+
_check_distributed()
90+
if timeout_minutes is None:
91+
return
92+
if torch.distributed.is_initialized():
93+
return
94+
95+
torch.cuda.set_device(int(os.environ.get("LOCAL_RANK", "0")))
96+
torch.distributed.init_process_group(
97+
"nccl",
98+
timeout=datetime.timedelta(minutes=timeout_minutes),
99+
)
100+
101+
87102
@torchrun_main
88103
def import_hf_to_megatron(
89104
hf_model: str,
@@ -94,9 +109,10 @@ def import_hf_to_megatron(
94109
etp: int = 1,
95110
torch_dtype: str = "bfloat16",
96111
trust_remote_code: bool = False,
112+
distributed_timeout_minutes: int | None = None,
97113
) -> None:
98114
"""Import a HuggingFace model and save it as a distributed Megatron checkpoint."""
99-
_check_distributed()
115+
_ensure_distributed_initialized(distributed_timeout_minutes)
100116
dtype = _parse_dtype(torch_dtype)
101117

102118
print_rank_0(f"Importing: {hf_model} -> {megatron_path}")
@@ -115,6 +131,15 @@ def import_hf_to_megatron(
115131
model_provider.expert_tensor_parallel_size = etp
116132
model_provider.pipeline_dtype = dtype
117133
model_provider.params_dtype = dtype
134+
# Auto-generate pipeline layout for models that need it (e.g. DSv4 hash MoE)
135+
if pp > 1 and hasattr(bridge._model_bridge, "generate_pipeline_layout"):
136+
hf_config = bridge.hf_pretrained.config
137+
num_layers = hf_config.num_hidden_layers
138+
mtp = getattr(hf_config, "num_nextn_predict_layers", 0) or 0
139+
model_provider.pipeline_model_parallel_layout = bridge._model_bridge.generate_pipeline_layout(
140+
num_layers, pp, mtp
141+
)
142+
print_rank_0(f" Auto-generated pipeline layout for PP={pp} ({num_layers} layers, {mtp} MTP)")
118143
model_provider.finalize()
119144
model_provider.initialize_model_parallel(seed=0)
120145

@@ -151,9 +176,10 @@ def export_megatron_to_hf(
151176
show_progress: bool = True,
152177
distributed_save: bool = False,
153178
save_every_n_ranks: int = 1,
179+
distributed_timeout_minutes: int | None = None,
154180
) -> None:
155181
"""Export a distributed Megatron checkpoint to HuggingFace format."""
156-
_check_distributed()
182+
_ensure_distributed_initialized(distributed_timeout_minutes)
157183
dtype = _parse_dtype(torch_dtype)
158184

159185
print_rank_0(f"Exporting: {megatron_path} -> {hf_path}")
@@ -173,6 +199,23 @@ def export_megatron_to_hf(
173199
model_provider.expert_tensor_parallel_size = etp
174200
model_provider.pipeline_dtype = dtype
175201
model_provider.params_dtype = dtype
202+
# For PP > 1 export, read pipeline layout from checkpoint
203+
if pp > 1:
204+
from pathlib import Path
205+
206+
import yaml
207+
208+
ckpt_path = Path(megatron_path)
209+
for candidate in [ckpt_path, *ckpt_path.glob("iter_*")]:
210+
rc = candidate / "run_config.yaml"
211+
if rc.exists():
212+
with open(rc) as f:
213+
cfg = yaml.safe_load(f)
214+
saved_layout = cfg.get("model", {}).get("pipeline_model_parallel_layout")
215+
if isinstance(saved_layout, list):
216+
model_provider.pipeline_model_parallel_layout = saved_layout
217+
print_rank_0(f" Read pipeline layout from checkpoint ({len(saved_layout)} stages)")
218+
break
176219
model_provider.finalize()
177220
model_provider.initialize_model_parallel(seed=0)
178221

@@ -218,6 +261,12 @@ def _add_common_args(parser: argparse.ArgumentParser) -> None:
218261
help="Model precision (default: bfloat16)",
219262
)
220263
parser.add_argument("--trust-remote-code", action="store_true", help="Allow custom model code execution")
264+
parser.add_argument(
265+
"--distributed-timeout-minutes",
266+
type=int,
267+
default=None,
268+
help="Initialize the distributed process group with this timeout before model setup",
269+
)
221270

222271

223272
def main():
@@ -255,7 +304,6 @@ def main():
255304
default=1,
256305
help="Only every N-th rank writes files (reduces I/O, only with --distributed-save)",
257306
)
258-
259307
args = parser.parse_args()
260308

261309
if not args.command:
@@ -272,6 +320,7 @@ def main():
272320
etp=args.etp,
273321
torch_dtype=args.torch_dtype,
274322
trust_remote_code=args.trust_remote_code,
323+
distributed_timeout_minutes=args.distributed_timeout_minutes,
275324
)
276325
elif args.command == "export":
277326
export_megatron_to_hf(
@@ -288,6 +337,7 @@ def main():
288337
show_progress=not args.no_progress,
289338
distributed_save=args.distributed_save,
290339
save_every_n_ranks=args.save_every_n_ranks,
340+
distributed_timeout_minutes=args.distributed_timeout_minutes,
291341
)
292342

293343

‎examples/conversion/hf_to_megatron_generate_text.py‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,23 @@ def main(args) -> None:
128128
model_provider.expert_tensor_parallel_size = etp
129129
model_provider.pipeline_dtype = torch.bfloat16
130130

131+
# Read pipeline layout from checkpoint for PP > 1
132+
if pp > 1:
133+
from pathlib import Path
134+
135+
import yaml
136+
137+
ckpt_path = Path(args.megatron_model_path)
138+
for candidate in [ckpt_path, *ckpt_path.glob("iter_*")]:
139+
rc = candidate / "run_config.yaml"
140+
if rc.exists():
141+
with open(rc) as f:
142+
cfg = yaml.safe_load(f)
143+
saved_layout = cfg.get("model", {}).get("pipeline_model_parallel_layout")
144+
if isinstance(saved_layout, list):
145+
model_provider.pipeline_model_parallel_layout = saved_layout
146+
break
147+
131148
# Once all overrides are set, finalize the model provider to ensure the post initialization logic is run
132149
model_provider.finalize()
133150
model_provider.initialize_model_parallel(seed=0)
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# DeepSeek V4
2+
3+
End-to-end conversion and inference scripts for the DeepSeek V4 family on Megatron Bridge.
4+
5+
The bridge supports four published variants out of the same code path. The on-disk quantisation differs between post-trained (Flash, Pro) and pretrained-only (Flash-Base, Pro-Base) models — see [`docs/models/llm/deepseek-v4.md`](../../../docs/models/llm/deepseek-v4.md) for the per-variant scheme.
6+
7+
## MCore Dev Branch Requirement
8+
9+
DSv4 imports require MCore changes that are not yet on a tagged release: PR [#3430](https://github.com/NVIDIA/Megatron-LM/pull/3430), PR [#4458](https://github.com/NVIDIA/Megatron-LM/pull/4458), PR [#4481](https://github.com/NVIDIA/Megatron-LM/pull/4481), and PR [#4518](https://github.com/NVIDIA/Megatron-LM/pull/4518), and PR [#4839](https://github.com/NVIDIA/Megatron-LM/pull/4839). Until these merge to Megatron-LM `main` and the bridge submodule pin advances, point `3rdparty/Megatron-LM` at the Megatron-LM `dev` branch:
10+
11+
```bash
12+
./scripts/switch_mcore.sh dev
13+
uv sync
14+
```
15+
16+
Use `./scripts/switch_mcore.sh main` and `uv sync --locked` to return to the pinned main-branch submodule.
17+
18+
| Variant | HF path | Quant scheme | Validation |
19+
|---------|---------|--------------|------------|
20+
| DeepSeek-V4-Flash | `deepseek-ai/DeepSeek-V4-Flash` | FP8 attn + MXFP4 experts | Verified on GB200, last-token logit cosine 0.96-0.99 (short prompts ~0.98, long prompts >1024 tokens ~0.96-0.99) vs official inference |
21+
| DeepSeek-V4-Flash-Base | `deepseek-ai/DeepSeek-V4-Flash-Base` | uniform FP8 (F32 scales) | Verified on GB200, last-real-token logit cosine 0.9866-0.9930, mean 0.9907 vs official inference |
22+
| DeepSeek-V4-Pro | `deepseek-ai/DeepSeek-V4-Pro` | FP8 attn + MXFP4 experts | Import, export, inference verified on GB200 (PP=4 EP=8) and H100 (PP=16 EP=8) |
23+
| DeepSeek-V4-Pro-Base | `deepseek-ai/DeepSeek-V4-Pro-Base` | uniform FP8 (F32 scales) | Same bridge code as Pro; end-to-end untested |
24+
25+
## Examples
26+
27+
- `conversion.sh` imports HF weights into Megatron Bridge and exports Megatron checkpoints back to HF format.
28+
- `inference.sh` runs text generation against an HF or Megatron checkpoint.
29+
30+
Run `bash conversion.sh` after setting `WORKSPACE` and `MODEL_VARIANT`. See each script's header comments for the expected environment variables and `#SBATCH` directives to edit before submitting.
31+
32+
The bridge's `maybe_modify_loaded_hf_weight` hook dispatches dequantisation by tensor dtype:
33+
34+
- `int8` -> MXFP4 packed nibbles -> `bfloat16` via the E2M1 lookup table and per-row 16-K-tile E8M0 scales
35+
- `float8_e4m3fn` with companion `.scale` -> `bfloat16` via 128x128 block-scale expansion, handling both E8M0 and F32 scale dtypes
36+
37+
No external dequantisation script is required.
38+
39+
## Parallelism Configurations
40+
41+
DSv4 currently requires **TP=1** because MLA tensor parallelism is not supported alongside the DSv4 hybrid attention path. Scale via expert and pipeline parallelism instead.
42+
43+
| Model | TP | PP | EP | GPUs | GPU | Verified |
44+
|-------|---:|---:|---:|-----:|-----|----------|
45+
| DeepSeek-V4-Flash | 1 | 1 | 4 | 4 | GB200 192GB | Import, export, inference |
46+
| DeepSeek-V4-Flash | 1 | 1 | 8 | 8 | H100 80GB | Import, export, inference |
47+
| DeepSeek-V4-Flash-Base | 1 | 1 | 4 | 4 | GB200 192GB | Import, export, inference |
48+
| DeepSeek-V4-Pro | 1 | 4 | 8 | 32 | GB200 192GB | Import, export, inference |
49+
| DeepSeek-V4-Pro | 1 | 16 | 8 | 128 | H100 80GB | Import, export, inference |
50+
51+
## Known Limitations
52+
53+
- **MTP is disabled for inference** via `disable_mtp_for_inference()`. MTP weights are mapped end-to-end and loaded into the Megatron model.
54+
55+
- **Fused mHC is not supported on H100.** Set `use_fused_mhc=False` in the bridge config when running on Hopper GPUs. Fused mHC is enabled by default and works on GB200.
56+
57+
- **`fast_hadamard_transform` is optional.** When unavailable, DSA falls back to a PyTorch hadamard implementation. Throughput is lower but numerical behavior is unchanged.
58+
59+
- **Logit parity is verified for Flash and Flash-Base** against the official inference stack at last-real-token logits. The remaining gap is structural, from different attention/HC kernel decompositions and accumulation precisions between MCore and official inference.
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
#!/usr/bin/env bash
2+
# Copyright (c) 2026, 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+
# DeepSeek-V4 import + export with the Bridge.
17+
#
18+
# DSv4 currently requires TP=1; scale via expert and pipeline parallelism (EP, PP).
19+
# The Bridge dispatches FP8 / MXFP4 dequantisation by tensor dtype, so the
20+
# same script works for Flash, Flash-Base, Pro, and Pro-Base.
21+
#
22+
# Override defaults by exporting environment variables before running:
23+
# WORKSPACE: directory for converted Megatron checkpoints (default: /workspace)
24+
# MODEL_VARIANT: one of DeepSeek-V4-Flash, DeepSeek-V4-Flash-Base,
25+
# DeepSeek-V4-Pro, DeepSeek-V4-Pro-Base
26+
# (default: DeepSeek-V4-Flash-Base)
27+
# EP: expert-parallel size (default: 4 for Flash, 8 for Pro)
28+
# PP: pipeline-parallel size (default: 1 for Flash, 4 for Pro)
29+
#
30+
# Defaults below are for GB200 (192 GB). For H100 (80 GB) configs, see README.md.
31+
32+
set -xeuo pipefail
33+
34+
WORKSPACE=${WORKSPACE:-/workspace}
35+
MODEL_VARIANT=${MODEL_VARIANT:-DeepSeek-V4-Flash-Base}
36+
HF_MODEL_ID="deepseek-ai/${MODEL_VARIANT}"
37+
38+
if [[ -z "${EP:-}" ]]; then
39+
case "${MODEL_VARIANT}" in
40+
DeepSeek-V4-Pro*) EP=8 ;;
41+
*) EP=4 ;;
42+
esac
43+
fi
44+
if [[ -z "${PP:-}" ]]; then
45+
case "${MODEL_VARIANT}" in
46+
DeepSeek-V4-Pro*) PP=4 ;;
47+
*) PP=1 ;;
48+
esac
49+
fi
50+
TP=1
51+
52+
MEGATRON_DIR="${WORKSPACE}/models/${MODEL_VARIANT}"
53+
EXPORT_DIR="${WORKSPACE}/models/${MODEL_VARIANT}-hf-export"
54+
ITER=iter_0000000
55+
56+
# 1) Import HF -> Megatron (FP8 / MXFP4 dequantised to bfloat16 in-flight)
57+
uv run python -m torch.distributed.run --nproc_per_node=$((PP * EP)) \
58+
examples/conversion/convert_checkpoints_multi_gpu.py import \
59+
--hf-model "${HF_MODEL_ID}" \
60+
--megatron-path "${MEGATRON_DIR}" \
61+
--tp ${TP} --pp ${PP} --ep ${EP} \
62+
--torch-dtype bfloat16 \
63+
--trust-remote-code
64+
65+
# 2) Compare HF and Megatron logits on a short prompt
66+
uv run python -m torch.distributed.run --nproc_per_node=$((PP * EP)) \
67+
examples/conversion/compare_hf_and_megatron/compare.py \
68+
--hf_model_path "${HF_MODEL_ID}" \
69+
--megatron_model_path "${MEGATRON_DIR}" \
70+
--prompt "Hello, how are you?" \
71+
--tp ${TP} --pp ${PP} --ep ${EP} \
72+
--trust-remote-code
73+
74+
# 3) Export Megatron -> HF (round-trip)
75+
uv run python -m torch.distributed.run --nproc_per_node=$((PP * EP)) \
76+
examples/conversion/convert_checkpoints_multi_gpu.py export \
77+
--hf-model "${HF_MODEL_ID}" \
78+
--megatron-path "${MEGATRON_DIR}/${ITER}" \
79+
--tp ${TP} --pp ${PP} --ep ${EP} \
80+
--torch-dtype bfloat16 \
81+
--hf-path "${EXPORT_DIR}" \
82+
--distributed-save \
83+
--trust-remote-code
84+
85+
# 4) Round-trip validation (bf16 -> Megatron -> bf16)
86+
# DSv4 HF weights are quantized (FP8/MXFP4), so the first import dequantises
87+
# to bfloat16. A true lossless roundtrip re-imports the exported bf16 checkpoint
88+
# and compares against the first export.
89+
ROUNDTRIP_DIR="${WORKSPACE}/models/${MODEL_VARIANT}-roundtrip"
90+
uv run python -m torch.distributed.run --nproc_per_node=$((PP * EP)) \
91+
examples/conversion/convert_checkpoints_multi_gpu.py import \
92+
--hf-model "${EXPORT_DIR}" \
93+
--megatron-path "${ROUNDTRIP_DIR}" \
94+
--tp ${TP} --pp ${PP} --ep ${EP} \
95+
--torch-dtype bfloat16 \
96+
--trust-remote-code
97+
98+
ROUNDTRIP_EXPORT_DIR="${WORKSPACE}/models/${MODEL_VARIANT}-roundtrip-export"
99+
uv run python -m torch.distributed.run --nproc_per_node=$((PP * EP)) \
100+
examples/conversion/convert_checkpoints_multi_gpu.py export \
101+
--hf-model "${EXPORT_DIR}" \
102+
--megatron-path "${ROUNDTRIP_DIR}" \
103+
--hf-path "${ROUNDTRIP_EXPORT_DIR}" \
104+
--tp ${TP} --pp ${PP} --ep ${EP} \
105+
--torch-dtype bfloat16 \
106+
--distributed-save \
107+
--trust-remote-code

0 commit comments

Comments
 (0)