[DSv4.1] Integrate Mega-Gate from DeepGEMM (#56266)

Signed-off-by: zjy0516 <riverclouds.zhu@qq.com> Signed-off-by: Thien Tran <gau.nernst@yahoo.com.sg> Co-authored-by: OpenAI Codex <codex@openai.com> Co-authored-by: zjy0516 <riverclouds.zhu@qq.com>

Thien Tran committed Sep 18, 2026 at 03:06 UTC 41f9104fa649aed2c26af8a459210619ad9514fe
9 files changed +369 -35
.buildkite/test_areas/kernels.yaml
+1
@@ -120,6 +120,7 @@ steps:
120 - csrc/fused_deepseek_v4_qnorm_rope_kv_insert_kernel.cu
121 - vllm/models/deepseek_v4/common/ops/
122 - vllm/models/deepseek_v4/nvidia/
123 + - vllm/utils/deep_gemm.py
124 - tests/kernels/test_fused_deepseek_v4_qnorm_rope_kv_insert.py
125 - tests/models/test_deepseek_v4_mega_moe.py
126 commands:
tests/kernels/test_mhc_kernels.py
+3 -2
@@ -132,6 +132,7 @@ def test_v41_dspark_head_collapses_with_last_ffn_mix(num_tokens, monkeypatch):
132
133 monkeypatch.setattr(dspark, "mhc_post_tilelang", lambda *args: streams)
134 draft = SimpleNamespace(
135 + use_mega_moe=False,
136 use_sequence_parallel=False,
137 hc_mult=hc_mult,
138 layers=[make_layer(mix) for mix in mixes],
@@ -452,7 +453,7 @@ def test_deepseek_v41_decoder_mixes_match_torch(
453 device=DEVICE, dtype=torch.bfloat16
454 )
455 decoder.attn = lambda positions, x, _: x * 0.5
455 - decoder.ffn = lambda x, input_ids: x * 0.25
456 + decoder.ffn = lambda x, input_ids, mega_gate_metadata=None: x * 0.25
457 with torch.device(DEVICE):
458 decoder.hc_attn_fn = torch.randn(24, 20480) * 0.02
459 decoder.hc_ffn_fn = torch.randn(24, 20480) * 0.02
@@ -536,7 +537,7 @@ def test_deepseek_v41_capture_previous_aux(entry, monkeypatch, default_vllm_conf
537 device=DEVICE, dtype=torch.bfloat16
538 )
539 decoder.attn = lambda positions, x, _: x * 0.5
539 - decoder.ffn = lambda x, input_ids: x * 0.25
540 + decoder.ffn = lambda x, input_ids, mega_gate_metadata=None: x * 0.25
541 with torch.device(DEVICE):
542 decoder.hc_attn_fn = torch.randn(24, 20480) * 0.02
543 decoder.hc_ffn_fn = torch.randn(24, 20480) * 0.02
tests/models/test_deepseek_v4_mega_moe.py
+138 -12
@@ -15,6 +15,7 @@ from vllm.models.deepseek_v4.nvidia.model import (
15 DeepseekV4MegaMoEExperts,
16 DeepseekV4MoE,
17 make_deepseek_v4_expert_params_mapping,
18 + prepare_mega_gate_routing_metadata,
19 )
20 from vllm.models.deepseek_v4.nvidia.mtp import DeepSeekV4MTP
21 from vllm.models.deepseek_v4.nvidia.ops.prepare_megamoe import prepare_megamoe_inputs
@@ -22,6 +23,7 @@ from vllm.models.deepseek_v41.common.mm_preprocess import IMAGE_SENTINEL_BASE_ID
23 from vllm.models.deepseek_v41.nvidia.model import DeepseekV4MoE as DeepseekV41MoE
24 from vllm.platforms import current_platform
25 from vllm.transformers_utils.configs.deepseek_v41 import DeepseekV41Config
26 +from vllm.utils.torch_utils import set_default_torch_dtype
27
28 pytestmark = pytest.mark.skipif(
29 not current_platform.is_cuda(),
@@ -33,6 +35,7 @@ pytestmark = pytest.mark.skipif(
35 def v41_moe_config(dist_init):
36 return SimpleNamespace(
37 model_config=SimpleNamespace(
38 + dtype=torch.bfloat16,
39 hf_config=DeepseekV41Config(
40 text_config=dict(
41 hidden_size=128,
@@ -63,28 +66,52 @@ def v41_moe_config(dist_init):
66 )
67
68
69 +@pytest.mark.parametrize("use_cudagraph", [False, True])
70 +@pytest.mark.parametrize("above_threshold", [False, True])
71 @pytest.mark.parametrize("vision", [False, True])
67 -@pytest.mark.parametrize("layer_id,num_experts,top_k", [(0, 8, 2), (2, 4, 3)])
72 +@pytest.mark.parametrize("layer_id,num_experts,top_k", [(0, 384, 6), (2, 128, 3)])
73 def test_deepseek_v41_moe_routes_without_hash_table(
69 - v41_moe_config, monkeypatch, vision, layer_id, num_experts, top_k
74 + v41_moe_config,
75 + monkeypatch,
76 + vision,
77 + layer_id,
78 + num_experts,
79 + top_k,
80 + above_threshold,
81 + use_cudagraph,
82 ):
83 """Main and draft layers select experts by score and preserve image routing."""
84 + if not current_platform.is_device_capability_family(100):
85 + pytest.skip("DeepGEMM Mega Gate requires SM100")
86 +
87 config = v41_moe_config.model_config.hf_config
88 + config.hidden_size = 256
89 + config.n_routed_experts = 384
90 + config.num_experts_per_tok = 6
91 + config.dspark_n_routed_experts = 128
92 + num_tokens = (1 if num_experts == 128 else 16) + int(above_threshold)
93 config.vision_n_layers = int(vision)
74 - with torch.device("cuda"):
94 + with (
95 + set_default_torch_dtype(v41_moe_config.model_config.dtype),
96 + torch.device("cuda"),
97 + ):
98 moe = DeepseekV41MoE(v41_moe_config, prefix=f"model.layers.{layer_id}.ffn")
76 - hidden_states = torch.randn(4, config.hidden_size)
99 + hidden_states = torch.randn(
100 + num_tokens, config.hidden_size, dtype=torch.bfloat16
101 + )
102 input_ids = (
78 - torch.tensor([42, IMAGE_SENTINEL_BASE_ID, IMAGE_SENTINEL_BASE_ID, 129257])
103 + torch.tensor(
104 + [42, IMAGE_SENTINEL_BASE_ID, IMAGE_SENTINEL_BASE_ID, 129257]
105 + ).repeat((num_tokens + 3) // 4)[:num_tokens]
106 if vision
107 else None
108 )
109 assert moe.gate.tid2eid is None
110 assert moe.gate.weight.shape == (num_experts, config.hidden_size)
111 assert isinstance(moe.experts, DeepseekV4MegaMoEExperts)
85 - assert moe.experts.w13_weight.shape == (num_experts, 256, 64)
112 + assert moe.experts.w13_weight.shape == (num_experts, 256, 128)
113 assert moe.experts.top_k == top_k
87 - assert (config.n_routed_experts, config.num_experts_per_tok) == (8, 2)
114 + assert (config.n_routed_experts, config.num_experts_per_tok) == (384, 6)
115
116 with torch.no_grad():
117 moe.gate.weight.normal_(std=0.01)
@@ -93,7 +120,7 @@ def test_deepseek_v41_moe_routes_without_hash_table(
120 moe.gate.bias_vl.copy_(-moe.gate.e_score_correction_bias)
121
122 scores = torch.nn.functional.softplus(
96 - torch.nn.functional.linear(hidden_states, moe.gate.weight)
123 + torch.mm(hidden_states, moe.gate.weight.t(), out_dtype=torch.float32)
124 ).sqrt()
125 bias = moe.gate.e_score_correction_bias
126 if vision:
@@ -105,15 +132,49 @@ def test_deepseek_v41_moe_routes_without_hash_table(
132 dim=-1, keepdim=True
133 )
134
135 + routed = {}
136 +
137 def check_routing(x, weights, ids, *, activation_clamp):
109 - torch.testing.assert_close(ids, expected_ids)
110 - torch.testing.assert_close(weights, expected_weights)
138 + routed["ids"], routed["weights"] = ids, weights
139 assert activation_clamp == config.swiglu_limit
140 return x.clone()
141
142 monkeypatch.setattr(moe.experts, "forward", check_routing)
143 monkeypatch.setattr(moe.shared_experts, "forward", lambda x: 2 * x)
116 - torch.testing.assert_close(moe(hidden_states, input_ids), 3 * hidden_states)
144 + routing_input_ids = (
145 + input_ids
146 + if input_ids is not None
147 + else torch.zeros(num_tokens, dtype=torch.int64, device="cuda")
148 + )
149 + metadata = prepare_mega_gate_routing_metadata(
150 + routing_input_ids,
151 + has_hash_routing=False,
152 + image_sentinel_base_id=IMAGE_SENTINEL_BASE_ID if vision else None,
153 + )
154 + if use_cudagraph:
155 + stream = torch.cuda.Stream()
156 + stream.wait_stream(torch.cuda.current_stream())
157 + with torch.cuda.stream(stream):
158 + for _ in range(3):
159 + moe(hidden_states, input_ids, metadata)
160 + stream.synchronize()
161 + graph = torch.cuda.CUDAGraph()
162 + with torch.cuda.graph(graph, stream=stream):
163 + output = moe(hidden_states, input_ids, metadata)
164 + graph.replay()
165 + torch.accelerator.synchronize()
166 + else:
167 + output = moe(hidden_states, input_ids, metadata)
168 +
169 + ids, weights = routed["ids"], routed["weights"]
170 + torch.testing.assert_close(ids, expected_ids)
171 + torch.testing.assert_close(
172 + weights,
173 + expected_weights,
174 + rtol=1e-3,
175 + atol=1e-4,
176 + )
177 + torch.testing.assert_close(output, 3 * hidden_states)
178
179
180 @pytest.mark.parametrize(
@@ -156,6 +217,67 @@ def test_deepseek_v4_moe_preserves_configured_hash_layers(v41_moe_config):
217 moe(torch.zeros(1, 128))
218
219
220 +@pytest.mark.parametrize("num_tokens", [16, 17])
221 +def test_deepseek_v4_mega_gate_hash_routing_correctness(
222 + v41_moe_config, monkeypatch, num_tokens
223 +):
224 + if not current_platform.is_device_capability_family(100):
225 + pytest.skip("DeepGEMM Mega Gate requires SM100")
226 +
227 + config = v41_moe_config.model_config.hf_config
228 + config.hidden_size = 256
229 + config.num_hash_layers = 1
230 + config.vocab_size = 32
231 + with (
232 + set_default_torch_dtype(v41_moe_config.model_config.dtype),
233 + torch.device("cuda"),
234 + ):
235 + moe = DeepseekV4MoE(
236 + v41_moe_config,
237 + prefix="model.layers.0.ffn",
238 + num_hash_layers=config.num_hash_layers,
239 + )
240 + hidden_states = torch.randn(
241 + num_tokens, config.hidden_size, dtype=torch.bfloat16
242 + )
243 + input_ids = torch.arange(num_tokens)
244 +
245 + token_ids = torch.arange(config.vocab_size, device="cuda")
246 + fixed_ids = torch.stack(
247 + (
248 + token_ids % config.n_routed_experts,
249 + (token_ids + 3) % config.n_routed_experts,
250 + ),
251 + dim=1,
252 + )
253 + with torch.no_grad():
254 + moe.gate.weight.normal_(std=0.01)
255 + moe.gate.tid2eid.copy_(fixed_ids)
256 +
257 + expected_ids = fixed_ids[input_ids]
258 + scores = torch.nn.functional.softplus(
259 + torch.mm(hidden_states, moe.gate.weight.t(), out_dtype=torch.float32)
260 + ).sqrt()
261 + expected_weights = scores.gather(1, expected_ids)
262 + expected_weights *= config.routed_scaling_factor / expected_weights.sum(
263 + dim=-1, keepdim=True
264 + )
265 +
266 + def check_routing(x, weights, ids, *, activation_clamp):
267 + torch.testing.assert_close(ids, expected_ids)
268 + torch.testing.assert_close(weights, expected_weights, rtol=1e-3, atol=1e-4)
269 + return x.clone()
270 +
271 + monkeypatch.setattr(moe.experts, "forward", check_routing)
272 + monkeypatch.setattr(moe.shared_experts, "forward", torch.zeros_like)
273 + metadata = prepare_mega_gate_routing_metadata(
274 + input_ids,
275 + has_hash_routing=True,
276 + image_sentinel_base_id=None,
277 + )
278 + torch.testing.assert_close(moe(hidden_states, input_ids, metadata), hidden_states)
279 +
280 +
281 def test_deepseek_v4_mega_moe_expert_mapping():
282 mapping = make_deepseek_v4_expert_params_mapping(2)
283
@@ -513,6 +635,7 @@ def test_deepseek_v4_mega_moe_does_not_double_add_fused_shared_expert(
635 monkeypatch, fused
636 ):
637 class FakeGate(torch.nn.Module):
638 + weight = torch.empty(2, 128)
639 tid2eid = None
640 e_score_correction_bias = None
641
@@ -760,7 +883,8 @@ def test_deepseek_v4_drafter_pwal_hooks_finalize_mega_moe():
883 not torch.cuda.is_available(),
884 reason="DeepSeek V4 MegaMoE fused input staging requires CUDA.",
885 )
763 -def test_deepseek_v4_mega_moe_fused_input_staging_masks_padding():
886 +@pytest.mark.parametrize("nonfinite_padding", [False, True])
887 +def test_deepseek_v4_mega_moe_fused_input_staging_masks_padding(nonfinite_padding):
888 from vllm.third_party.deep_gemm.utils import per_token_cast_to_fp8
889
890 device = torch.device("cuda")
@@ -796,6 +920,8 @@ def test_deepseek_v4_mega_moe_fused_input_staging_masks_padding():
920 [False, True, False, False, True, False, True],
921 device=device,
922 )
923 + if nonfinite_padding:
924 + topk_weights[is_padding] = float("nan")
925
926 ref_x, ref_x_sf = per_token_cast_to_fp8(
927 hidden_states,
vllm/models/deepseek_v4/nvidia/dspark.py
+14
@@ -17,6 +17,7 @@ import torch.nn as nn
17
18 import vllm.envs as envs
19 from vllm.config import VllmConfig, get_current_vllm_config
20 +from vllm.config.kernel import MEGA_MOE_BACKENDS
21 from vllm.distributed import (
22 get_tensor_model_parallel_rank,
23 get_tensor_model_parallel_world_size,
@@ -51,12 +52,14 @@ from vllm.models.common.ops.sequence_parallel import (
52 sp_padding_mask,
53 sp_shard,
54 )
55 +from vllm.models.deepseek_v4.common.mm_preprocess import IMAGE_SENTINEL_BASE_ID
56
57 from .model import (
58 DeepseekV4DecoderLayer,
59 DeepseekV4Model,
60 _use_sequence_parallel,
61 make_deepseek_v4_expert_params_mapping,
62 + prepare_mega_gate_routing_metadata,
63 )
64
65 logger = init_logger(__name__)
@@ -96,6 +99,7 @@ class DSparkDeepseekV4Model(nn.Module):
99 self.rms_norm_eps = config.rms_norm_eps
100 self.num_hidden_layers = config.num_hidden_layers
101 self.target_layer_ids = tuple(config.dspark_target_layer_ids)
102 + self.use_mega_moe = vllm_config.kernel_config.moe_backend in MEGA_MOE_BACKENDS
103 self.use_sequence_parallel = _use_sequence_parallel(vllm_config)
104
105 self.num_dspark_layers = getattr(config, "n_mtp_layers", None) or 3
@@ -235,6 +239,15 @@ class DSparkDeepseekV4Model(nn.Module):
239 )
240 inputs_embeds = sp_shard(inputs_embeds)
241 input_ids = sp_shard(input_ids)
242 + mega_gate_metadata = None
243 + if self.use_mega_moe:
244 + mega_gate_metadata = prepare_mega_gate_routing_metadata(
245 + input_ids,
246 + has_hash_routing=False,
247 + image_sentinel_base_id=IMAGE_SENTINEL_BASE_ID
248 + if getattr(self.config, "vision_n_layers", 0) > 0
249 + else None,
250 + )
251 # Expand to hc_mult copies for hyper-connections ([T, H] -> [T, hc, H]).
252 hidden_states = inputs_embeds.unsqueeze(-2).repeat(1, self.hc_mult, 1)
253
@@ -247,6 +260,7 @@ class DSparkDeepseekV4Model(nn.Module):
260 post_mix,
261 res_mix,
262 residual,
263 + mega_gate_metadata=mega_gate_metadata,
264 )
265 hidden_states = mhc_post_tilelang(hidden_states, residual, post_mix, res_mix)
266 if self.use_sequence_parallel:
vllm/models/deepseek_v4/nvidia/model.py
+110 -19
@@ -98,6 +98,37 @@ from ..common.mm_preprocess import IMAGE_SENTINEL_BASE_ID
98 logger = init_logger(__name__)
99
100
101 +class MegaGateRoutingMetadata(typing.NamedTuple):
102 + safe_hash_input_ids: torch.Tensor | None
103 + image_token_mask: torch.Tensor | None
104 + hash_token_mask: torch.Tensor | None
105 +
106 +
107 +def prepare_mega_gate_routing_metadata(
108 + input_ids: torch.Tensor,
109 + *,
110 + has_hash_routing: bool,
111 + image_sentinel_base_id: int | None,
112 +) -> MegaGateRoutingMetadata:
113 + if image_sentinel_base_id is None:
114 + return MegaGateRoutingMetadata(
115 + input_ids if has_hash_routing else None,
116 + None,
117 + torch.ones_like(input_ids, dtype=torch.bool) if has_hash_routing else None,
118 + )
119 +
120 + image_token_mask = (input_ids >= image_sentinel_base_id) & (
121 + input_ids < image_sentinel_base_id + 5
122 + )
123 + if not has_hash_routing:
124 + return MegaGateRoutingMetadata(None, image_token_mask, None)
125 + return MegaGateRoutingMetadata(
126 + torch.where(image_token_mask, 0, input_ids),
127 + image_token_mask,
128 + ~image_token_mask,
129 + )
130 +
131 +
132 class DeepseekV4MLP(nn.Module):
133 def __init__(
134 self,
@@ -803,6 +834,10 @@ class DeepseekV4MoE(nn.Module):
834 raise NotImplementedError(
835 "DeepSeek V4 MegaMoE currently supports sqrtsoftplus routing only."
836 )
837 + if self.use_mega_moe and not self.renormalize:
838 + raise NotImplementedError(
839 + "DeepSeek V4 MegaMoE requires normalized top-k probabilities."
840 + )
841 if self.use_mega_moe and getattr(config, "expert_dtype", "fp4") != "fp4":
842 raise NotImplementedError(
843 "DeepSeek V4 MegaMoE only supports fp4 experts; got expert_dtype="
@@ -1000,7 +1035,10 @@ class DeepseekV4MoE(nn.Module):
1035 )
1036
1037 def forward(
1003 - self, hidden_states: torch.Tensor, input_ids: torch.Tensor | None = None
1038 + self,
1039 + hidden_states: torch.Tensor,
1040 + input_ids: torch.Tensor | None = None,
1041 + mega_gate_metadata: MegaGateRoutingMetadata | None = None,
1042 ) -> torch.Tensor:
1043 if self.gate.tid2eid is not None and input_ids is None:
1044 raise ValueError("DeepSeek V4 hash MoE routing requires input_ids.")
@@ -1013,23 +1051,61 @@ class DeepseekV4MoE(nn.Module):
1051 return self._forward_fused_moe(hidden_states, input_ids)
1052
1053 org_shape = hidden_states.shape
1016 - router_logits, _ = self.gate(hidden_states)
1017 - topk_weights, topk_ids = fused_topk_bias(
1018 - hidden_states=hidden_states,
1019 - gating_output=router_logits,
1020 - scoring_func=self.scoring_func,
1021 - e_score_correction_bias=self.gate.e_score_correction_bias.data
1022 - if self.gate.e_score_correction_bias is not None
1023 - else None,
1024 - topk=self.n_activated_experts,
1025 - renormalize=self.renormalize,
1026 - indices_type=self.hash_indices_dtype,
1027 - input_tokens=input_ids,
1028 - hash_indices_table=self.gate.tid2eid,
1029 - routed_scaling_factor=self.routed_scaling_factor,
1030 - bias_vl=bias_vl.data if bias_vl is not None else None,
1031 - image_sentinel_lo=self.image_sentinel_lo if bias_vl is not None else 0,
1032 - )
1054 + # Small local padded batches favor GateLinear; 128-expert gates cross earlier.
1055 + gate_threshold = 1 if self.gate.weight.shape[0] == 128 else 16
1056 + if hidden_states.shape[0] <= gate_threshold:
1057 + router_logits, _ = self.gate(hidden_states)
1058 + topk_weights, topk_ids = fused_topk_bias(
1059 + hidden_states=hidden_states,
1060 + gating_output=router_logits,
1061 + scoring_func=self.scoring_func,
1062 + e_score_correction_bias=self.gate.e_score_correction_bias.data
1063 + if self.gate.e_score_correction_bias is not None
1064 + else None,
1065 + topk=self.n_activated_experts,
1066 + renormalize=self.renormalize,
1067 + indices_type=self.hash_indices_dtype,
1068 + input_tokens=input_ids,
1069 + hash_indices_table=self.gate.tid2eid,
1070 + routed_scaling_factor=self.routed_scaling_factor,
1071 + bias_vl=bias_vl.data if bias_vl is not None else None,
1072 + image_sentinel_lo=self.image_sentinel_lo if bias_vl is not None else 0,
1073 + )
1074 + else:
1075 + from vllm.utils.deep_gemm import bf16_mega_gate
1076 +
1077 + if mega_gate_metadata is None:
1078 + raise RuntimeError(
1079 + "MegaMoE routing metadata must be prepared once by the model."
1080 + )
1081 +
1082 + unmapped_topk_idx = None
1083 + fix_routing_mask = None
1084 + if self.gate.tid2eid is not None:
1085 + if mega_gate_metadata.safe_hash_input_ids is None:
1086 + raise RuntimeError("Hash routing requires prepared input IDs.")
1087 + if mega_gate_metadata.hash_token_mask is None:
1088 + raise RuntimeError("Hash routing requires a prepared routing mask.")
1089 + unmapped_topk_idx = self.gate.tid2eid[
1090 + mega_gate_metadata.safe_hash_input_ids
1091 + ]
1092 + fix_routing_mask = mega_gate_metadata.hash_token_mask
1093 +
1094 + topk_weights, topk_ids = bf16_mega_gate(
1095 + hidden_states,
1096 + self.gate.weight,
1097 + self.n_activated_experts,
1098 + scoring_func=self.scoring_func,
1099 + routed_scaling_factor=self.routed_scaling_factor,
1100 + ep_rank=self.ep_rank,
1101 + bias=self.gate.e_score_correction_bias.data
1102 + if self.gate.e_score_correction_bias is not None
1103 + else None,
1104 + image_bias=bias_vl.data if bias_vl is not None else None,
1105 + image_token_mask=mega_gate_metadata.image_token_mask,
1106 + fix_routing_mask=fix_routing_mask,
1107 + unmapped_topk_idx=unmapped_topk_idx,
1108 + )
1109 activation_clamp = (
1110 float(self.swiglu_limit) if self.swiglu_limit is not None else None
1111 )
@@ -1248,6 +1324,7 @@ class DeepseekV4DecoderLayer(nn.Module):
1324 post_mix: torch.Tensor | None = None,
1325 res_mix: torch.Tensor | None = None,
1326 residual: torch.Tensor | None = None,
1327 + mega_gate_metadata: MegaGateRoutingMetadata | None = None,
1328 ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
1329 attn_norm_weight = self.attn_norm.weight.data
1330 attn_norm_eps = self.attn_norm.variance_epsilon
@@ -1332,7 +1409,7 @@ class DeepseekV4DecoderLayer(nn.Module):
1409 norm_eps=ffn_norm_eps,
1410 )
1411
1335 - x = self.ffn(x, input_ids)
1412 + x = self.ffn(x, input_ids, mega_gate_metadata)
1413 return x, residual, post_mix, res_mix
1414
1415
@@ -1394,6 +1471,9 @@ class DeepseekV4Model(nn.Module, EagleModelMixin):
1471 ),
1472 prefix=f"{prefix}.layers",
1473 )
1474 + self.has_local_hash_moe = self.start_layer < min(
1475 + self.end_layer, config.num_hash_layers
1476 + )
1477
1478 if get_pp_group().is_last_rank:
1479 self.norm = RMSNorm(config.hidden_size, self.rms_norm_eps)
@@ -1496,6 +1576,16 @@ class DeepseekV4Model(nn.Module, EagleModelMixin):
1576 hidden_states = sp_shard(hidden_states)
1577 input_ids = sp_shard(input_ids)
1578
1579 + mega_gate_metadata = None
1580 + if self.use_mega_moe:
1581 + mega_gate_metadata = prepare_mega_gate_routing_metadata(
1582 + input_ids,
1583 + has_hash_routing=self.has_local_hash_moe,
1584 + image_sentinel_base_id=IMAGE_SENTINEL_BASE_ID
1585 + if getattr(self.config, "vision_n_layers", 0) > 0
1586 + else None,
1587 + )
1588 +
1589 residual, post_mix, res_mix = None, None, None
1590 remote_aux = self.collect_remote_aux_hidden_states(intermediate_tensors)
1591 aux_hidden_states: list[torch.Tensor] = []
@@ -1511,6 +1601,7 @@ class DeepseekV4Model(nn.Module, EagleModelMixin):
1601 post_mix,
1602 res_mix,
1603 residual,
1604 + mega_gate_metadata=mega_gate_metadata,
1605 )
1606 if idx + 1 in self.aux_hidden_state_layers:
1607 # Reconstruct the aux hidden state for draft models
vllm/models/deepseek_v4/nvidia/mtp.py
+20 -1
@@ -20,6 +20,7 @@ import torch.nn as nn
20
21 import vllm.envs as envs
22 from vllm.config import VllmConfig
23 +from vllm.config.kernel import MEGA_MOE_BACKENDS
24 from vllm.distributed import (
25 get_tensor_model_parallel_rank,
26 get_tensor_model_parallel_world_size,
@@ -51,6 +52,7 @@ from vllm.models.common.ops.sequence_parallel import (
52 sp_padding_mask,
53 sp_shard,
54 )
55 +from vllm.models.deepseek_v4.common.mm_preprocess import IMAGE_SENTINEL_BASE_ID
56 from vllm.models.deepseek_v4.common.ops.fused_mtp_input_rmsnorm import (
57 _FUSED_MTP_INPUT_RMSNORM_KERNEL,
58 _MTP_SHARED_HEAD_RMSNORM_KERNEL,
@@ -62,6 +64,7 @@ from .model import (
64 DeepseekV4Model,
65 _use_sequence_parallel,
66 make_deepseek_v4_expert_params_mapping,
67 + prepare_mega_gate_routing_metadata,
68 )
69
70 logger = init_logger(__name__)
@@ -92,6 +95,7 @@ class DeepSeekV4MultiTokenPredictorLayer(nn.Module):
95 self.config = config
96 quant_config = vllm_config.quant_config
97 self.rms_norm_eps = config.rms_norm_eps
98 + self.use_mega_moe = vllm_config.kernel_config.moe_backend in MEGA_MOE_BACKENDS
99
100 self.enorm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
101 self.hnorm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
@@ -181,8 +185,23 @@ class DeepSeekV4MultiTokenPredictorLayer(nn.Module):
185 hidden_states = self.h_proj(previous_hidden_states) + self.e_proj(
186 inputs_embeds
187 ).unsqueeze(-2)
188 + mega_gate_metadata = None
189 + if self.use_mega_moe:
190 + routing_input_ids = input_ids
191 + if self.mtp_block.use_sequence_parallel:
192 + routing_input_ids = sp_shard(routing_input_ids)
193 + mega_gate_metadata = prepare_mega_gate_routing_metadata(
194 + routing_input_ids,
195 + has_hash_routing=False,
196 + image_sentinel_base_id=IMAGE_SENTINEL_BASE_ID
197 + if getattr(self.config, "vision_n_layers", 0) > 0
198 + else None,
199 + )
200 hidden_states, residual, post_mix, res_mix = self.mtp_block(
185 - positions=positions, x=hidden_states, input_ids=input_ids
201 + positions=positions,
202 + x=hidden_states,
203 + input_ids=input_ids,
204 + mega_gate_metadata=mega_gate_metadata,
205 )
206 hidden_states = mhc_post_tilelang(hidden_states, residual, post_mix, res_mix)
207 if self.mtp_block.use_sequence_parallel:
vllm/models/deepseek_v41/nvidia/dspark.py
+14
@@ -18,6 +18,7 @@ import torch.nn as nn
18
19 import vllm.envs as envs
20 from vllm.config import VllmConfig, get_current_vllm_config
21 +from vllm.config.kernel import MEGA_MOE_BACKENDS
22 from vllm.distributed import (
23 get_tensor_model_parallel_rank,
24 get_tensor_model_parallel_world_size,
@@ -50,12 +51,14 @@ from vllm.models.common.ops.sequence_parallel import (
51 sp_shard,
52 )
53
54 +from ..common.mm_preprocess import IMAGE_SENTINEL_BASE_ID
55 from .model import (
56 DeepseekV4DecoderLayer,
57 DeepseekV4Model,
58 _linear_scale_param_name,
59 _use_sequence_parallel,
60 make_deepseek_v4_expert_params_mapping,
61 + prepare_mega_gate_routing_metadata,
62 )
63
64 logger = init_logger(__name__)
@@ -77,6 +80,7 @@ class DSparkDeepseekV4Model(nn.Module):
80 self.rms_norm_eps = config.rms_norm_eps
81 self.num_hidden_layers = config.num_hidden_layers
82 self.target_layer_ids = tuple(config.dspark_target_layer_ids)
83 + self.use_mega_moe = vllm_config.kernel_config.moe_backend in MEGA_MOE_BACKENDS
84 self.use_sequence_parallel = _use_sequence_parallel(vllm_config)
85
86 self.num_dspark_layers = (
@@ -202,6 +206,15 @@ class DSparkDeepseekV4Model(nn.Module):
206 )
207 inputs_embeds = sp_shard(inputs_embeds)
208 input_ids = sp_shard(input_ids)
209 + mega_gate_metadata = None
210 + if self.use_mega_moe:
211 + mega_gate_metadata = prepare_mega_gate_routing_metadata(
212 + input_ids,
213 + has_hash_routing=False,
214 + image_sentinel_base_id=IMAGE_SENTINEL_BASE_ID
215 + if getattr(self.config, "vision_n_layers", 0) > 0
216 + else None,
217 + )
218 # Expand to hc_mult copies for hyper-connections ([T, H] -> [T, hc, H]).
219 hidden_states = inputs_embeds.unsqueeze(-2).repeat(1, self.hc_mult, 1)
220
@@ -215,6 +228,7 @@ class DSparkDeepseekV4Model(nn.Module):
228 post_mix,
229 res_mix,
230 residual,
231 + mega_gate_metadata=mega_gate_metadata,
232 )
233 hidden_states = mhc_post_tilelang(hidden_states, residual, post_mix, res_mix)
234 # Collapse the hc copies with the pre-mix from the last layer's FFN
vllm/models/deepseek_v41/nvidia/model.py
+15 -1
@@ -61,7 +61,9 @@ from vllm.models.deepseek_v4.nvidia.model import (
61 DeepseekV4MoE as DeepseekV4MoEBase,
62 )
63 from vllm.models.deepseek_v4.nvidia.model import (
64 + MegaGateRoutingMetadata,
65 make_deepseek_v4_expert_params_mapping,
66 + prepare_mega_gate_routing_metadata,
67 )
68 from vllm.models.deepseek_v41.attention import DeepseekV4Attention
69 from vllm.models.deepseek_v41.nvidia.flash_mla_mega_attn import (
@@ -339,6 +341,7 @@ class DeepseekV4DecoderLayer(nn.Module):
341 engram_mask: torch.Tensor | None = None,
342 *,
343 capture_previous_aux: bool = False,
344 + mega_gate_metadata: MegaGateRoutingMetadata | None = None,
345 ) -> tuple[
346 torch.Tensor,
347 torch.Tensor,
@@ -462,7 +465,7 @@ class DeepseekV4DecoderLayer(nn.Module):
465 norm_weight=self.ffn_norm.weight,
466 norm_eps=self.ffn_norm.variance_epsilon,
467 )
465 - x = self.ffn(x, input_ids)
468 + x = self.ffn(x, input_ids, mega_gate_metadata)
469 return x, residual, post_mix, res_mix, ffn_pre, previous_aux
470
471
@@ -705,6 +708,16 @@ class DeepseekV4Model(nn.Module, EagleModelMixin):
708 hidden_states = sp_shard(hidden_states)
709 input_ids = sp_shard(input_ids)
710
711 + mega_gate_metadata = None
712 + if self.use_mega_moe:
713 + mega_gate_metadata = prepare_mega_gate_routing_metadata(
714 + input_ids,
715 + has_hash_routing=False,
716 + image_sentinel_base_id=IMAGE_SENTINEL_BASE_ID
717 + if getattr(self.config, "vision_n_layers", 0) > 0
718 + else None,
719 + )
720 +
721 residual, post_mix, res_mix = None, None, None
722 pre_mix: torch.Tensor | None = None
723 if not get_pp_group().is_first_rank:
@@ -728,6 +741,7 @@ class DeepseekV4Model(nn.Module, EagleModelMixin):
741 engram_hashes,
742 engram_mask,
743 capture_previous_aux=idx in self.aux_hidden_state_layers,
744 + mega_gate_metadata=mega_gate_metadata,
745 )
746 if previous_aux is not None:
747 # idx is the one-based id of the layer whose post this is.
vllm/utils/deep_gemm.py
+54
@@ -164,6 +164,7 @@ _get_sparse_mqa_logits_metadata_impl: Callable[..., Any] | None = None
164 _get_paged_sparse_mqa_logits_metadata_impl: Callable[..., Any] | None = None
165 _tf32_hc_prenorm_gemm_impl: Callable[..., Any] | None = None
166 _mega_mhc_impl: Callable[..., Any] | None = None
167 +_bf16_mega_gate_impl: Callable[..., Any] | None = None
168 _get_mn_major_tma_aligned_tensor_impl: Callable[..., Any] | None = None
169 _get_mk_alignment_for_contiguous_layout_impl: Callable[..., Any] | None = None
170 _get_theoretical_mk_alignment_for_contiguous_layout_impl: Callable[..., Any] | None = (
@@ -240,6 +241,7 @@ def _lazy_init() -> None:
241 global _get_sparse_mqa_logits_metadata_impl
242 global _get_paged_sparse_mqa_logits_metadata_impl
243 global _tf32_hc_prenorm_gemm_impl, _mega_mhc_impl
244 + global _bf16_mega_gate_impl
245 global _get_mn_major_tma_aligned_tensor_impl
246 global _get_mk_alignment_for_contiguous_layout_impl
247 global _get_theoretical_mk_alignment_for_contiguous_layout_impl
@@ -260,6 +262,7 @@ def _lazy_init() -> None:
262 or _get_paged_mqa_logits_metadata_impl is not None
263 or _tf32_hc_prenorm_gemm_impl is not None
264 or _mega_mhc_impl is not None
265 + or _bf16_mega_gate_impl is not None
266 or _get_mk_alignment_for_contiguous_layout_impl is not None
267 or _transform_sf_into_required_layout_impl is not None
268 or _pack_ue8m0_to_int_impl is not None
@@ -315,6 +318,7 @@ def _lazy_init() -> None:
318 )
319 _tf32_hc_prenorm_gemm_impl = getattr(_dg, "tf32_hc_prenorm_gemm", None)
320 _mega_mhc_impl = getattr(_dg, "mega_mhc", None)
321 + _bf16_mega_gate_impl = getattr(_dg, "bf16_mega_gate", None)
322 _get_mn_major_tma_aligned_tensor_impl = getattr(
323 _dg, "get_mn_major_tma_aligned_tensor", None
324 )
@@ -482,6 +486,55 @@ def cublaslt_gemm_nt(*args, **kwargs):
486 return _cublaslt_gemm_nt_impl(*args, **kwargs)
487
488
489 +def bf16_mega_gate(
490 + x: torch.Tensor,
491 + weight: torch.Tensor,
492 + num_topk: int,
493 + *,
494 + scoring_func: str,
495 + routed_scaling_factor: float,
496 + ep_rank: int,
497 + bias: torch.Tensor | None = None,
498 + image_bias: torch.Tensor | None = None,
499 + image_token_mask: torch.Tensor | None = None,
500 + fix_routing_mask: torch.Tensor | None = None,
501 + unmapped_topk_idx: torch.Tensor | None = None,
502 +) -> tuple[torch.Tensor, torch.Tensor]:
503 + """Run DeepGEMM Mega Gate.
504 +
505 + The output order follows vLLM's router contract: weights, then indices.
506 + Passing PyTorch-owned outputs to DeepGEMM makes their storage part of CUDA
507 + graph capture.
508 + """
509 + _lazy_init()
510 + if _bf16_mega_gate_impl is None:
511 + raise RuntimeError(
512 + "DeepGEMM MegaMoE requires a DeepGEMM build with bf16_mega_gate."
513 + )
514 +
515 + topk_idx = torch.empty((x.shape[0], num_topk), dtype=torch.int64, device=x.device)
516 + topk_weights = torch.empty(
517 + (x.shape[0], num_topk), dtype=torch.float32, device=x.device
518 + )
519 + _bf16_mega_gate_impl(
520 + x,
521 + weight,
522 + num_topk,
523 + use_shared_as_routed=False,
524 + num_shared_experts=0,
525 + routed_scaling_factor=routed_scaling_factor,
526 + ep_rank=ep_rank,
527 + scoring_func=scoring_func,
528 + bias=bias,
529 + image_bias=image_bias,
530 + image_token_mask=image_token_mask,
531 + fix_routing_mask=fix_routing_mask,
532 + unmapped_topk_idx=unmapped_topk_idx,
533 + out=(topk_idx, topk_weights),
534 + )
535 + return topk_weights, topk_idx
536 +
537 +
538 def fp8_gemm_nt(*args, **kwargs):
539 _lazy_init()
540 if _fp8_gemm_nt_impl is None:
@@ -988,6 +1041,7 @@ def should_use_deepgemm_for_fp8_linear(
1041
1042 __all__ = [
1043 "calc_diff",
1044 + "bf16_mega_gate",
1045 "DeepGemmQuantScaleFMT",
1046 "fp8_gemm_nt",
1047 "fp8_einsum",