Skip to content

Commit 4fd32fb

Browse files
committed
fix(deepseek): use HybridEP for text inference
Signed-off-by: Chen Cui <chcui@nvidia.com>
1 parent 09bd5c9 commit 4fd32fb

5 files changed

Lines changed: 130 additions & 0 deletions

File tree

‎examples/conversion/hf_to_megatron_generate_text.py‎

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,29 @@ def _decode_completion(tokenizer, generated_ids: torch.Tensor, prompt_length: in
161161
)
162162

163163

164+
def _moe_dispatcher_overrides(args: argparse.Namespace) -> dict[str, str | int | bool]:
165+
"""Build explicit flex-dispatcher overrides for providers and loaded checkpoints."""
166+
if args.moe_flex_dispatcher_backend is None:
167+
if args.moe_flex_dispatcher_num_sms is not None or args.hybridep_permute_fusion is not None:
168+
raise ValueError(
169+
"--moe-flex-dispatcher-num-sms and --[no-]hybridep-permute-fusion require "
170+
"--moe-flex-dispatcher-backend"
171+
)
172+
return {}
173+
174+
overrides: dict[str, str | int | bool] = {
175+
"moe_token_dispatcher_type": "flex",
176+
"moe_flex_dispatcher_backend": args.moe_flex_dispatcher_backend,
177+
}
178+
if args.moe_flex_dispatcher_num_sms is not None:
179+
overrides["moe_flex_dispatcher_num_sms"] = args.moe_flex_dispatcher_num_sms
180+
if args.hybridep_permute_fusion is not None:
181+
if args.moe_flex_dispatcher_backend != "hybridep":
182+
raise ValueError("--[no-]hybridep-permute-fusion requires --moe-flex-dispatcher-backend=hybridep")
183+
overrides["moe_permute_fusion_into_hybridep"] = args.hybridep_permute_fusion
184+
return overrides
185+
186+
164187
def main(args) -> None:
165188
"""Main function for text generation from HuggingFace or Megatron models.
166189
@@ -177,6 +200,7 @@ def main(args) -> None:
177200
pp = args.pp
178201
ep = args.ep
179202
etp = args.etp
203+
dispatcher_overrides = _moe_dispatcher_overrides(args)
180204

181205
# Choose loading method based on arguments
182206
if args.megatron_model_path:
@@ -201,6 +225,8 @@ def main(args) -> None:
201225
model_provider.expert_model_parallel_size = ep
202226
model_provider.expert_tensor_parallel_size = etp
203227
model_provider.pipeline_dtype = torch.bfloat16
228+
for name, value in dispatcher_overrides.items():
229+
setattr(model_provider, name, value)
204230

205231
# Read pipeline layout from checkpoint for PP > 1
206232
if args.pipeline_model_parallel_layout is not None:
@@ -232,6 +258,7 @@ def main(args) -> None:
232258
"expert_model_parallel_size": ep,
233259
"expert_tensor_parallel_size": etp,
234260
"pipeline_dtype": torch.bfloat16,
261+
**dispatcher_overrides,
235262
}
236263
if args.pipeline_model_parallel_layout is not None:
237264
mp_overrides["pipeline_model_parallel_layout"] = args.pipeline_model_parallel_layout
@@ -259,6 +286,8 @@ def main(args) -> None:
259286
model_provider.expert_model_parallel_size = ep
260287
model_provider.expert_tensor_parallel_size = etp
261288
model_provider.pipeline_dtype = torch.bfloat16
289+
for name, value in dispatcher_overrides.items():
290+
setattr(model_provider, name, value)
262291
if args.pipeline_model_parallel_layout is not None:
263292
model_provider.pipeline_model_parallel_layout = args.pipeline_model_parallel_layout
264293

@@ -425,6 +454,22 @@ def build_parser() -> argparse.ArgumentParser:
425454
)
426455
parser.add_argument("--ep", type=int, default=1, help="Expert parallelism size")
427456
parser.add_argument("--etp", type=int, default=1, help="Expert tensor parallelism size")
457+
parser.add_argument(
458+
"--moe-flex-dispatcher-backend",
459+
choices=("deepep", "hybridep"),
460+
help="Override the MoE token dispatcher with the selected flex backend.",
461+
)
462+
parser.add_argument(
463+
"--moe-flex-dispatcher-num-sms",
464+
type=int,
465+
help="Number of SMs assigned to the selected flex dispatcher backend.",
466+
)
467+
parser.add_argument(
468+
"--hybridep-permute-fusion",
469+
action=argparse.BooleanOptionalAction,
470+
default=None,
471+
help="Enable or disable HybridEP permutation fusion when HybridEP is selected.",
472+
)
428473
parser.add_argument("--megatron_model_path", type=str, default=None, help="Path to the Megatron model checkpoint")
429474
parser.add_argument("--trust-remote-code", action="store_true", help="if trust_remote_code")
430475
return parser

‎examples/models/deepseek_v4/README.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,18 @@ performance features, so it is not a supported runtime for that recipe.
6161
- `slurm_pretrain.sh` runs the DeepSeek-V4-Flash pretraining recipes.
6262
- `slurm_sft.sh` runs DeepSeek-V4-Flash full SFT end to end (import, then fine-tune) on Hopper or Blackwell, with MTP on or off.
6363

64+
The inference script selects the `flex`/HybridEP MoE dispatcher with 16
65+
dispatcher SMs and unfused HybridEP permutation for both direct-HF and
66+
Megatron-checkpoint loading. The explicit checkpoint override prevents an older
67+
serialized `alltoall` setting from restoring the standard dispatcher. Custom
68+
launchers need a runtime containing the HybridEP package. HybridEP auto-detects
69+
accessible ranks and fabric support when its topology environment variables are
70+
unset. To override detection, set
71+
`NUM_OF_HYBRID_EP_RANKS_PER_NVLINK_DOMAIN`, `NVLINK_DOMAIN_SIZE`, and
72+
`USE_MNNVL` to values that describe the actual deployment. This selection is
73+
inference-specific; training recipes retain their independently qualified
74+
dispatcher defaults.
75+
6476
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.
6577

6678
## Pretraining Recipes

‎examples/models/deepseek_v4/inference.sh‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,13 @@
2222
# EP: expert-parallel size (default: 4 for Flash, 8 for Pro)
2323
# PP: pipeline-parallel size (default: 1 for Flash, 4 for Pro)
2424
# PROMPT: prompt string (default: "Explain hyper-connections in transformer models.")
25+
# NUM_OF_HYBRID_EP_RANKS_PER_NVLINK_DOMAIN: optional HybridEP topology override
26+
# NVLINK_DOMAIN_SIZE: optional physical NVLink-domain size override
27+
# USE_MNNVL: optional HybridEP fabric override (0 or 1)
2528
#
2629
# Defaults below are for GB200 (192 GB). For H100 (80 GB) configs, see README.md.
30+
# HybridEP auto-detects accessible ranks and fabric support when the topology
31+
# overrides are unset. If set, the values must describe the actual deployment.
2732

2833
set -xeuo pipefail
2934

@@ -54,6 +59,9 @@ uv run python -m torch.distributed.run --nproc_per_node=$((PP * EP)) \
5459
--prompt "${PROMPT}" \
5560
--max_new_tokens 100 \
5661
--tp ${TP} --pp ${PP} --ep ${EP} \
62+
--moe-flex-dispatcher-backend hybridep \
63+
--moe-flex-dispatcher-num-sms 16 \
64+
--no-hybridep-permute-fusion \
5765
--legacy-full-prefix \
5866
--trust-remote-code
5967

@@ -67,6 +75,9 @@ if [[ -d "${MEGATRON_DIR}/iter_0000000" ]]; then
6775
--prompt "${PROMPT}" \
6876
--max_new_tokens 100 \
6977
--tp ${TP} --pp ${PP} --ep ${EP} \
78+
--moe-flex-dispatcher-backend hybridep \
79+
--moe-flex-dispatcher-num-sms 16 \
80+
--no-hybridep-permute-fusion \
7081
--legacy-full-prefix \
7182
--trust-remote-code
7283
fi

‎src/megatron/bridge/models/model_provider.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,10 @@ class ModelParallelKwargs(TypedDict, total=False):
557557
hierarchical_context_parallel_sizes: list[int] | None
558558
pipeline_model_parallel_layout: list[list[str]] | None
559559
pipeline_dtype: torch.dtype
560+
moe_token_dispatcher_type: str
561+
moe_flex_dispatcher_backend: str | None
562+
moe_flex_dispatcher_num_sms: int | None
563+
moe_permute_fusion_into_hybridep: bool
560564

561565

562566
def get_model(

‎tests/unit_tests/examples/test_hf_to_megatron_generate_text.py‎

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
_decode_completion = _SCRIPT_GLOBALS["_decode_completion"]
2828
_hf_revision_kwargs = _SCRIPT_GLOBALS["_hf_revision_kwargs"]
2929
_maybe_gather_tensor_parallel_logits = _SCRIPT_GLOBALS["_maybe_gather_tensor_parallel_logits"]
30+
_moe_dispatcher_overrides = _SCRIPT_GLOBALS["_moe_dispatcher_overrides"]
3031
_run_megatron_forward = _SCRIPT_GLOBALS["_run_megatron_forward"]
3132
_text_forward_step = _SCRIPT_GLOBALS["text_forward_step"]
3233
_tokenize_prompt = _SCRIPT_GLOBALS["_tokenize_prompt"]
@@ -107,6 +108,63 @@ def test_legacy_full_prefix_disables_inference_context() -> None:
107108
assert _build_inference_context(input_ids, legacy_full_prefix=True) is None
108109

109110

111+
@pytest.mark.unit
112+
def test_hybridep_cli_builds_checkpoint_safe_dispatcher_overrides() -> None:
113+
args = _build_parser().parse_args(
114+
[
115+
"--hf_model_path",
116+
"org/model",
117+
"--moe-flex-dispatcher-backend",
118+
"hybridep",
119+
"--moe-flex-dispatcher-num-sms",
120+
"16",
121+
"--no-hybridep-permute-fusion",
122+
]
123+
)
124+
125+
assert _moe_dispatcher_overrides(args) == {
126+
"moe_token_dispatcher_type": "flex",
127+
"moe_flex_dispatcher_backend": "hybridep",
128+
"moe_flex_dispatcher_num_sms": 16,
129+
"moe_permute_fusion_into_hybridep": False,
130+
}
131+
132+
133+
@pytest.mark.unit
134+
def test_flex_dispatcher_cli_is_opt_in() -> None:
135+
args = _build_parser().parse_args(["--hf_model_path", "org/model"])
136+
137+
assert _moe_dispatcher_overrides(args) == {}
138+
139+
140+
@pytest.mark.unit
141+
@pytest.mark.parametrize("option", ["--moe-flex-dispatcher-num-sms", "--no-hybridep-permute-fusion"])
142+
def test_flex_dispatcher_dependent_options_require_backend(option: str) -> None:
143+
command = ["--hf_model_path", "org/model", option]
144+
if option == "--moe-flex-dispatcher-num-sms":
145+
command.append("16")
146+
args = _build_parser().parse_args(command)
147+
148+
with pytest.raises(ValueError, match="require --moe-flex-dispatcher-backend"):
149+
_moe_dispatcher_overrides(args)
150+
151+
152+
@pytest.mark.unit
153+
def test_hybridep_permute_flag_rejects_other_backends() -> None:
154+
args = _build_parser().parse_args(
155+
[
156+
"--hf_model_path",
157+
"org/model",
158+
"--moe-flex-dispatcher-backend",
159+
"deepep",
160+
"--no-hybridep-permute-fusion",
161+
]
162+
)
163+
164+
with pytest.raises(ValueError, match="requires --moe-flex-dispatcher-backend=hybridep"):
165+
_moe_dispatcher_overrides(args)
166+
167+
110168
@pytest.mark.unit
111169
def test_kv_cache_builds_static_inference_context() -> None:
112170
input_ids = torch.ones((2, 3), dtype=torch.long)

0 commit comments

Comments
 (0)