Skip to content

Commit 0528670

Browse files
tomlifuclaude
authored andcommitted
perf(recipe): update VRNVL72 Qwen3.5-VL 35B/397B MXFP8 recipes (#6450)
Signed-off-by: tomlifu <lifuz@nvidia.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: NeMo Bot <nemo-bot@nvidia.com>
1 parent 08bdb37 commit 0528670

3 files changed

Lines changed: 338 additions & 30 deletions

File tree

‎src/megatron/bridge/perf_recipes/qwen_vl/vrnvl72/qwen35_vl.py‎

Lines changed: 114 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@
3131
def qwen35_vl_35b_a3b_pretrain_8gpu_vrnvl72_bf16_config() -> ConfigContainer:
3232
"""Qwen3.5-VL 35B-A3B pretrain: 8× VRNVL72, BF16 (alias of GB300)."""
3333
cfg = _build_qwen35_vl_35b_a3b_gb300_bf16()
34-
# The GB300 base selects the cuDNN GDN kernel, which has not been measured on VRNVL72; keep FLA until it is.
34+
# The GB300 base selects the cuDNN GDN kernel. On VRNVL72 it was measured only with MXFP8 (against FLA
35+
# at 35B), so this precision keeps FLA until it is measured too.
3536
_select_gdn_kernel_backend(cfg, "fla")
3637
# Keep process settings next to the recipe so users can see the exact benchmark environment.
3738
cfg.env_vars = {
@@ -59,7 +60,8 @@ def qwen35_vl_35b_a3b_pretrain_8gpu_vrnvl72_bf16_config() -> ConfigContainer:
5960
def qwen35_vl_35b_a3b_pretrain_8gpu_vrnvl72_fp8cs_config() -> ConfigContainer:
6061
"""Qwen3.5-VL 35B-A3B pretrain: 8× VRNVL72, FP8-CS (alias of GB300)."""
6162
cfg = qwen35_vl_35b_a3b_pretrain_8gpu_gb300_fp8cs_config()
62-
# The GB300 base selects the cuDNN GDN kernel, which has not been measured on VRNVL72; keep FLA until it is.
63+
# The GB300 base selects the cuDNN GDN kernel. On VRNVL72 it was measured only with MXFP8 (against FLA
64+
# at 35B), so this precision keeps FLA until it is measured too.
6365
_select_gdn_kernel_backend(cfg, "fla")
6466
# Keep process settings next to the recipe so users can see the exact benchmark environment.
6567
cfg.env_vars = {
@@ -85,29 +87,79 @@ def qwen35_vl_35b_a3b_pretrain_8gpu_vrnvl72_fp8cs_config() -> ConfigContainer:
8587

8688

8789
def qwen35_vl_35b_a3b_pretrain_8gpu_vrnvl72_fp8mx_config() -> ConfigContainer:
88-
"""Qwen3.5-VL 35B-A3B pretrain: 8× VRNVL72, FP8-MX (alias of GB300)."""
90+
"""Qwen3.5-VL 35B-A3B pretrain: 8× VRNVL72, MXFP8, EP=4, HybridEP.
91+
92+
Starts from the GB300 MXFP8 recipe (CuTe DSL fused grouped MLP, cuDNN GDN, per-layer CUDA graphs, forced load
93+
balancing) and applies EP 4, MBS 4 and the kernel settings of the measured VRNVL72 runs, at GBS 1024.
94+
"""
8995
cfg = qwen35_vl_35b_a3b_pretrain_8gpu_gb300_fp8mx_config()
90-
# The GB300 base selects the cuDNN GDN kernel, which has not been measured on VRNVL72; keep FLA until it is.
91-
_select_gdn_kernel_backend(cfg, "fla")
96+
97+
# EP 4 and MBS 4 as in the measured VRNVL72 runs: each expert-parallel group is one 4-GPU node
98+
# (NUM_OF_HYBRID_EP_RANKS_PER_NVLINK_DOMAIN below must equal EP). GBS 1024 gives 32 microbatches
99+
# per step on 8 GPUs.
100+
cfg.model.expert_model_parallel_size = 4
101+
cfg.train.micro_batch_size = 4
102+
cfg.train.global_batch_size = 1024
103+
104+
# The mock VLM loader runs the HF image processor on the CPU. One worker per rank supplies fewer
105+
# samples than this batch size consumes on VRNVL72, which would make the step wait for data.
106+
cfg.dataset.num_workers = 8
107+
108+
# Measured on VRNVL72 with MXFP8: keep the cuDNN GDN kernel that the GB300 base selects.
109+
_select_gdn_kernel_backend(cfg, "transformer_engine")
110+
111+
# Run the shared expert through Transformer Engine's grouped GEMM with the same 32-wide GLU
112+
# interleaving as the routed experts.
113+
cfg.model.use_grouped_gemm_for_shared_expert = True
114+
cfg.model.moe_shared_expert_glu_interleave_size = 32
115+
116+
# HybridEP: permute outside the dispatcher (already the default; stated explicitly because fusing
117+
# the permute into HybridEP measured slower on VRNVL72), 16 SMs for preprocessing as in the measured
118+
# runs, and overlap the dispatch backward with the expert weight-gradient GEMMs. Megatron-Core does
119+
# not allow this overlap together with EP A2A overlap or delay_wgrad_compute.
120+
cfg.model.moe_permute_fusion_into_hybridep = False
121+
cfg.model.moe_hybridep_num_sms_preprocessing = 16
122+
cfg.model.overlap_dispatch_backward_with_experts_wgrad = True
123+
124+
# Vision-encoder CUDA graph. The cap is the number of vision patches per microbatch: a longer
125+
# vision sequence stops the run, and a shorter one is padded to the cap on every replay, so set it
126+
# to MBS x images per sample x patches per image whenever one of them changes. The Qwen-VL data
127+
# path resizes each 256x256 mock image twice: qwen_vl_utils snaps it to 252x252, then the HF
128+
# processor scales it up to at least QWEN_VL_MIN_PIXELS (448 * 448) pixels in area. Each image
129+
# therefore reaches the encoder as 448x448 = 28x28 patches of 16x16 pixels, 784 patches, and MBS 4
130+
# gives 3136. With the graph, training runs the encoder as one dense attention over all images of a
131+
# microbatch (images attend to each other, and padding is not masked) and without the 2D rotary
132+
# embedding, which the capture does not record; evaluation runs eagerly, per image and with the
133+
# rotary embedding. vision_cuda_graph_impl = "none" restores that behavior in training as well.
134+
cfg.model.vision_cuda_graph_impl = "transformer_engine"
135+
cfg.model.vision_cuda_graph_scope = ["attn", "mlp"]
136+
cfg.model.max_vision_cuda_graph_seq_length = 3136
137+
92138
# Keep process settings next to the recipe so users can see the exact benchmark environment.
93139
cfg.env_vars = {
94140
**COMMON_PERF_ENV_VARS,
95141
# CUDA stream scheduling for this model and parallel layout.
96142
"CUDA_DEVICE_MAX_CONNECTIONS": 32,
97143
# CUDA graph and allocator behavior for this recipe.
98144
"NCCL_GRAPH_REGISTER": 0,
99-
"PYTORCH_CUDA_ALLOC_CONF": "expandable_segments:True",
100-
"TORCH_NCCL_AVOID_RECORD_STREAMS": 1,
145+
"PYTORCH_CUDA_ALLOC_CONF": "expandable_segments:True,graph_capture_record_stream_reuse:True",
146+
"TORCH_NCCL_AVOID_RECORD_STREAMS": 0,
101147
# NCCL user-buffer and launch settings.
102148
"NCCL_NVLS_ENABLE": 0,
103-
# HybridEP topology for the target system.
104-
"NUM_OF_HYBRID_EP_RANKS_PER_NVLINK_DOMAIN": 8,
149+
# HybridEP topology for the target system: one rank per GPU of the EP 4 group.
150+
"NUM_OF_HYBRID_EP_RANKS_PER_NVLINK_DOMAIN": 4,
105151
"NUM_OF_TOKENS_PER_CHUNK_COMBINE_API": 128,
106152
"NVLINK_DOMAIN_SIZE": 72,
107153
"USE_MNNVL": 1,
108154
# Transformer Engine overlap settings for this model.
109-
"NVTE_BWD_LAYERNORM_SM_MARGIN": 20,
110-
"NVTE_FWD_LAYERNORM_SM_MARGIN": 20,
155+
"NVTE_BWD_LAYERNORM_SM_MARGIN": 0,
156+
"NVTE_FWD_LAYERNORM_SM_MARGIN": 0,
157+
# cuDNN normalization kernels.
158+
"NVTE_NORM_BWD_USE_CUDNN": 1,
159+
"NVTE_NORM_FWD_USE_CUDNN": 1,
160+
# Gate for the CuTe DSL fused grouped MLP that the GB300 base enables. Transformer Engine
161+
# checks it when it registers the fusion; without it the op fuser runs the unfused path.
162+
"NVTE_CUTEDSL_FUSED_GROUPED_MLP": 1,
111163
}
112164
return cfg
113165

@@ -193,7 +245,8 @@ def qwen35_vl_122b_a10b_pretrain_32gpu_vrnvl72_fp8mx_config() -> ConfigContainer
193245
def qwen35_vl_397b_a17b_pretrain_64gpu_vrnvl72_bf16_config() -> ConfigContainer:
194246
"""Qwen3.5-VL 397B-A17B pretrain: 64× VRNVL72, BF16 (alias of GB300)."""
195247
cfg = qwen35_vl_397b_a17b_pretrain_64gpu_gb300_bf16_config()
196-
# The GB300 base selects the cuDNN GDN kernel, which has not been measured on VRNVL72; keep FLA until it is.
248+
# The GB300 base selects the cuDNN GDN kernel. On VRNVL72 it was measured only with MXFP8 (against FLA
249+
# at 35B), so this precision keeps FLA until it is measured too.
197250
_select_gdn_kernel_backend(cfg, "fla")
198251
# Keep process settings next to the recipe so users can see the exact benchmark environment.
199252
cfg.env_vars = {
@@ -221,7 +274,8 @@ def qwen35_vl_397b_a17b_pretrain_64gpu_vrnvl72_bf16_config() -> ConfigContainer:
221274
def qwen35_vl_397b_a17b_pretrain_64gpu_vrnvl72_fp8cs_config() -> ConfigContainer:
222275
"""Qwen3.5-VL 397B-A17B pretrain: 64× VRNVL72, FP8-CS (alias of GB300)."""
223276
cfg = qwen35_vl_397b_a17b_pretrain_64gpu_gb300_fp8cs_config()
224-
# The GB300 base selects the cuDNN GDN kernel, which has not been measured on VRNVL72; keep FLA until it is.
277+
# The GB300 base selects the cuDNN GDN kernel. On VRNVL72 it was measured only with MXFP8 (against FLA
278+
# at 35B), so this precision keeps FLA until it is measured too.
225279
_select_gdn_kernel_backend(cfg, "fla")
226280
# Keep process settings next to the recipe so users can see the exact benchmark environment.
227281
cfg.env_vars = {
@@ -247,10 +301,46 @@ def qwen35_vl_397b_a17b_pretrain_64gpu_vrnvl72_fp8cs_config() -> ConfigContainer
247301

248302

249303
def qwen35_vl_397b_a17b_pretrain_64gpu_vrnvl72_fp8mx_config() -> ConfigContainer:
250-
"""Qwen3.5-VL 397B-A17B pretrain: 64× VRNVL72, FP8-MX (alias of GB300)."""
304+
"""Qwen3.5-VL 397B-A17B pretrain: 64× VRNVL72, MXFP8, EP=32, HybridEP.
305+
306+
Starts from the GB300 MXFP8 recipe (cuDNN GDN, per-layer CUDA graphs, forced load balancing) and adds the
307+
CuTe DSL fused grouped MLP and the settings of the measured VRNVL72 run.
308+
"""
251309
cfg = qwen35_vl_397b_a17b_pretrain_64gpu_gb300_fp8mx_config()
252-
# The GB300 base selects the cuDNN GDN kernel, which has not been measured on VRNVL72; keep FLA until it is.
253-
_select_gdn_kernel_backend(cfg, "fla")
310+
311+
# MBS 1, as in the GB300 base; the vision cap below is set for it.
312+
cfg.train.micro_batch_size = 1
313+
314+
# Keep the cuDNN GDN kernel that the GB300 base selects: it measured faster than FLA on VRNVL72 at
315+
# 35B, and the measured 397B run used it.
316+
_select_gdn_kernel_backend(cfg, "transformer_engine")
317+
318+
# Route the experts through Transformer Engine's CuTe DSL fused grouped MLP (FC1 + SwiGLU + FC2 for
319+
# MXFP8). The GB300 397B base does not enable the op fuser (it is off by default), so it is enabled
320+
# here; with it, Megatron-Core also runs the experts on grouped tensors. The op fuser, the 32-wide
321+
# GLU interleaving and NVTE_CUTEDSL_FUSED_GROUPED_MLP below are required together.
322+
cfg.model.use_transformer_engine_op_fuser = True
323+
cfg.model.moe_mlp_glu_interleave_size = 32
324+
325+
# Recompute the GDN output norm, as the measured run did in effect. That run also listed core_attn, but
326+
# all of its attention layers ran inside CUDA graphs, where Megatron-Core does not recompute. The vision
327+
# encoder inherits selective recompute of its attention (vision_recompute_granularity="inherit"); inside
328+
# the vision CUDA graph below that has no effect either, but it applies once the vision graph is off.
329+
cfg.model.recompute_granularity = "selective"
330+
cfg.model.recompute_modules = ["gdn_norm_out"]
331+
332+
# HybridEP: permute outside the dispatcher. This is already the default; it is stated explicitly
333+
# because fusing the permute into HybridEP measured slower on VRNVL72.
334+
cfg.model.moe_permute_fusion_into_hybridep = False
335+
336+
# Vision-encoder CUDA graph with the cap for MBS 1: each 256x256 mock image reaches the encoder as
337+
# 784 patches (see the 35B recipe above). Set the cap to MBS x images per sample x patches per image
338+
# whenever one of them changes. As in the 35B recipe, the graphed encoder runs without the 2D rotary
339+
# embedding, and images that share a microbatch attend to each other.
340+
cfg.model.vision_cuda_graph_impl = "transformer_engine"
341+
cfg.model.vision_cuda_graph_scope = ["attn", "mlp"]
342+
cfg.model.max_vision_cuda_graph_seq_length = 784
343+
254344
# Keep process settings next to the recipe so users can see the exact benchmark environment.
255345
cfg.env_vars = {
256346
**COMMON_PERF_ENV_VARS,
@@ -267,8 +357,14 @@ def qwen35_vl_397b_a17b_pretrain_64gpu_vrnvl72_fp8mx_config() -> ConfigContainer
267357
"NUM_OF_TOKENS_PER_CHUNK_COMBINE_API": 128,
268358
"NVLINK_DOMAIN_SIZE": 72,
269359
"USE_MNNVL": 1,
360+
# HybridEP dispatch and preprocessing chunks of the measured run (HybridEP's default is 64).
361+
"NUM_OF_TOKENS_PER_CHUNK_DISPATCH_API": 128,
362+
"NUM_OF_TOKENS_PER_CHUNK_PREPROCESSING_API": 128,
270363
# Transformer Engine overlap settings for this model.
271-
"NVTE_BWD_LAYERNORM_SM_MARGIN": 20,
272-
"NVTE_FWD_LAYERNORM_SM_MARGIN": 20,
364+
"NVTE_BWD_LAYERNORM_SM_MARGIN": 16,
365+
"NVTE_FWD_LAYERNORM_SM_MARGIN": 16,
366+
# Gate for the CuTe DSL fused grouped MLP enabled above. Transformer Engine checks it when it
367+
# registers the fusion; without it the op fuser runs the unfused path.
368+
"NVTE_CUTEDSL_FUSED_GROUPED_MLP": 1,
273369
}
274370
return cfg

‎tests/unit_tests/recipes/test_perf_recipe_environment.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
"nemotron_3_5_lightning_pretrain_8gpu_vrnvl72_fp8mx_config",
6464
"nemotron_3_ultra_pretrain_256gpu_vrnvl72_fp8mx_config",
6565
"nemotron_3_ultra_pretrain_256gpu_vrnvl72_nvfp4_config",
66+
"qwen35_vl_35b_a3b_pretrain_8gpu_vrnvl72_fp8mx_config",
6667
}
6768

6869

0 commit comments

Comments
 (0)