A high-throughput and memory-efficient inference and serving engine for LLMs
Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git
Synced Nov 2, 04:45 UTC
[Feature] Support EPLB for DeepSeek v4 Mega Moe (#43339)
Signed-off-by: wzhao18 <wzhao18.sz@gmail.com> Co-authored-by: Wei Zhao (Engrg-Hardware 1) <weizha@login-lyris01.lyris.clusters.nvidia.com>
Wei Zhao committed
Jun 2, 2026 at 13:56 UTC
242709415287e2f7ebe6e3fcddea349c498307d9
4 files changed
+232
-46
vllm/distributed/eplb/eplb_utils.py
+16
-7
@@ -61,25 +61,31 @@ class CpuGpuEvent:
61
self._recorded.set()
62
63
64
-def override_envs_for_eplb(parallel_config: ParallelConfig) -> None:
64
+def override_envs_for_eplb(
65
+ parallel_config: ParallelConfig,
66
+ moe_backend: str | None = None,
67
+) -> None:
68
"""
69
Override environment variables for EPLB when specific conditions are met.
70
71
Args:
72
parallel_config: The parallel configuration object.
73
+ moe_backend: The configured MoE backend (e.g. ``deep_gemm_mega_moe``).
74
"""
75
is_data_parallel = parallel_config.data_parallel_size > 1
76
is_eplb_enabled = parallel_config.enable_eplb
77
async_eplb = parallel_config.eplb_config.use_async
78
is_deepep_ll = parallel_config.all2all_backend == "deepep_low_latency"
79
+ is_mega_moe = moe_backend == "deep_gemm_mega_moe"
80
is_nccl_based_eplb_communicator = parallel_config.eplb_config.communicator in (
81
"torch_nccl",
82
"pynccl",
83
)
84
80
- # Override NCCL_MAX_CTAS to avoid hangs when using async EPLB with the
81
- # DeepEP low-latency backend.
85
+ # Override NCCL_MAX_CTAS to avoid hangs when EPLB's NCCL weight exchange
86
+ # contends with MoE backend's cooperative-launch on GPU SMs.
87
#
88
+ # DeepEP low-latency:
89
# The hang happens when two ranks interleave kernel launches differently
90
# between NCCL collectives (used by async EPLB weight exchange) and DeepEP
91
# low-latency (LL) kernels. DeepEP LL uses a cooperative launch and tries
@@ -94,12 +100,14 @@ def override_envs_for_eplb(parallel_config: ParallelConfig) -> None:
100
# Limiting NCCL occupancy via NCCL_MAX_CTAS leaves space for the DeepEP
101
# cooperative kernel to launch and complete, breaking the deadlock.
102
# See: https://github.com/deepseek-ai/DeepEP/issues/496
103
+ #
104
+ # DeepGEMM Mega MoE also uses cooperative launch and will cause hang even
105
+ # with sync EPLB.
106
if (
107
is_data_parallel
108
and is_eplb_enabled
100
- and is_deepep_ll
101
- and async_eplb
109
and is_nccl_based_eplb_communicator
110
+ and ((is_deepep_ll and async_eplb) or is_mega_moe)
111
):
112
current_value_str = os.getenv("NCCL_MAX_CTAS")
113
@@ -108,9 +116,10 @@ def override_envs_for_eplb(parallel_config: ParallelConfig) -> None:
116
117
override_value = 8
118
os.environ["NCCL_MAX_CTAS"] = str(override_value)
119
+ backend = "deepep_low_latency" if is_deepep_ll else "deep_gemm_mega_moe"
120
logger.info_once(
121
f"EPLB: Setting NCCL_MAX_CTAS={override_value} "
113
- "for expert parallel with NCCL-based EPLB communicator and "
114
- "deepep_low_latency backend",
122
+ f"for expert parallel with NCCL-based EPLB communicator and "
123
+ f"cooperative MoE backend ({backend})",
124
scope="global",
125
)
vllm/models/deepseek_v4/nvidia/model.py
+211
-38
@@ -1,7 +1,7 @@
1
# SPDX-License-Identifier: Apache-2.0
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
import typing
4
-from collections.abc import Callable, Iterable
4
+from collections.abc import Callable, Iterable, MutableSequence, Sequence
5
from itertools import islice
6
7
import regex as re
@@ -15,6 +15,7 @@ from vllm.distributed import (
15
get_tensor_model_parallel_rank,
16
get_tensor_model_parallel_world_size,
17
)
18
+from vllm.distributed.eplb.eplb_state import EplbLayerState
19
from vllm.model_executor.kernels.mhc.tilelang import (
20
hc_head_fused_kernel_tilelang,
21
mhc_fused_post_pre_tilelang,
@@ -23,6 +24,9 @@ from vllm.model_executor.kernels.mhc.tilelang import (
24
)
25
from vllm.model_executor.layers.activation import SiluAndMul, SiluAndMulWithClamp
26
from vllm.model_executor.layers.fused_moe import FusedMoE
27
+from vllm.model_executor.layers.fused_moe.router.base_router import (
28
+ eplb_map_to_physical_and_record,
29
+)
30
from vllm.model_executor.layers.fused_moe.router.fused_topk_bias_router import (
31
fused_topk_bias,
32
)
@@ -40,7 +44,7 @@ from vllm.model_executor.layers.vocab_parallel_embedding import (
44
VocabParallelEmbedding,
45
)
46
from vllm.model_executor.model_loader.weight_utils import default_weight_loader
43
-from vllm.model_executor.models.interfaces import SupportsPP
47
+from vllm.model_executor.models.interfaces import MixtureOfExperts, SupportsPP
48
from vllm.model_executor.models.utils import (
49
AutoWeightsLoader,
50
PPMissingLayer,
@@ -144,6 +148,7 @@ class DeepseekV4MegaMoEExperts(nn.Module):
148
hidden_size: int,
149
intermediate_size: int,
150
prefix: str = "",
151
+ num_logical_experts: int | None = None,
152
):
153
super().__init__()
154
self.prefix = prefix
@@ -156,6 +161,12 @@ class DeepseekV4MegaMoEExperts(nn.Module):
161
self.intermediate_size = intermediate_size
162
self.max_num_tokens = vllm_config.scheduler_config.max_num_batched_tokens
163
164
+ self.num_logical_experts = (
165
+ num_logical_experts if num_logical_experts is not None else num_experts
166
+ )
167
+
168
+ self.eplb_state = EplbLayerState()
169
+
170
weight_attrs = {"weight_loader": self.weight_loader}
171
self.w13_weight = nn.Parameter(
172
torch.zeros(
@@ -206,10 +217,22 @@ class DeepseekV4MegaMoEExperts(nn.Module):
217
self._transformed_l1_weights: tuple[torch.Tensor, torch.Tensor] | None = None
218
self._transformed_l2_weights: tuple[torch.Tensor, torch.Tensor] | None = None
219
209
- def _map_global_expert_id(self, expert_id: int) -> int:
210
- if expert_id < self.experts_start_idx or expert_id >= self.experts_end_idx:
211
- return -1
212
- return expert_id - self.experts_start_idx
220
+ # Register in the static forward context so the custom-op wrapper
221
+ # can look up this module by name from within a torch.compile graph.
222
+ compilation_config = vllm_config.compilation_config
223
+ if prefix in compilation_config.static_forward_context:
224
+ raise ValueError(f"Duplicate layer name: {prefix}")
225
+ compilation_config.static_forward_context[prefix] = self
226
+
227
+ def _map_global_expert_id(self, expert_id: int) -> list[int]:
228
+ """Return local (per-rank) slot offsets where logical expert
229
+ `expert_id` should land on this rank.
230
+ """
231
+ physical_ids: list[int] = []
232
+ for p in range(self.experts_start_idx, self.experts_end_idx):
233
+ if p % self.num_logical_experts == expert_id:
234
+ physical_ids.append(p - self.experts_start_idx)
235
+ return physical_ids
236
237
def weight_loader(
238
self,
@@ -220,30 +243,38 @@ class DeepseekV4MegaMoEExperts(nn.Module):
243
expert_id: int,
244
return_success: bool = False,
245
) -> bool | None:
223
- local_expert_id = self._map_global_expert_id(expert_id)
224
- if local_expert_id == -1:
246
+ local_expert_ids = self._map_global_expert_id(expert_id)
247
+ if not local_expert_ids:
248
return False if return_success else None
249
227
- expert_data = param.data[local_expert_id]
228
- if shard_id in ("w1", "w3"):
229
- if "w13_" not in weight_name:
230
- return False if return_success else None
231
- shard_offset = 0 if shard_id == "w1" else self.intermediate_size
232
- expert_data = expert_data.narrow(0, shard_offset, self.intermediate_size)
233
- elif shard_id == "w2":
234
- if "w2_" not in weight_name:
235
- return False if return_success else None
236
- else:
237
- raise ValueError(f"Unsupported expert shard id: {shard_id}")
250
+ loaded_any = False
251
+ for local_expert_id in local_expert_ids:
252
+ expert_data = param.data[local_expert_id]
253
+ if shard_id in ("w1", "w3"):
254
+ if "w13_" not in weight_name:
255
+ continue
256
+ shard_offset = 0 if shard_id == "w1" else self.intermediate_size
257
+ expert_data = expert_data.narrow(
258
+ 0, shard_offset, self.intermediate_size
259
+ )
260
+ elif shard_id == "w2":
261
+ if "w2_" not in weight_name:
262
+ continue
263
+ else:
264
+ raise ValueError(f"Unsupported expert shard id: {shard_id}")
265
239
- if expert_data.shape != loaded_weight.shape:
240
- raise ValueError(
241
- f"DeepSeek V4 MegaMoE expert weight shape mismatch for "
242
- f"{weight_name}: parameter shard {tuple(expert_data.shape)} "
243
- f"vs checkpoint {tuple(loaded_weight.shape)}"
244
- )
245
- expert_data.copy_(loaded_weight)
246
- return True if return_success else None
266
+ if expert_data.shape != loaded_weight.shape:
267
+ raise ValueError(
268
+ f"DeepSeek V4 MegaMoE expert weight shape mismatch for "
269
+ f"{weight_name}: parameter shard {tuple(expert_data.shape)} "
270
+ f"vs checkpoint {tuple(loaded_weight.shape)}"
271
+ )
272
+ expert_data.copy_(loaded_weight)
273
+ loaded_any = True
274
+
275
+ if return_success:
276
+ return loaded_any
277
+ return None
278
279
@staticmethod
280
def _ue8m0_uint8_to_float(sf: torch.Tensor) -> torch.Tensor:
@@ -264,7 +295,9 @@ class DeepseekV4MegaMoEExperts(nn.Module):
295
return
296
297
self._check_runtime_supported()
267
- import vllm.third_party.deep_gemm as deep_gemm
298
+ from vllm.utils.deep_gemm import _import_deep_gemm
299
+
300
+ deep_gemm = _import_deep_gemm()
301
302
w13_scale = deep_gemm.transform_sf_into_required_layout(
303
self._ue8m0_uint8_to_float(self.w13_weight_scale.data).contiguous(),
@@ -298,7 +331,9 @@ class DeepseekV4MegaMoEExperts(nn.Module):
331
self.w2_weight_scale = None
332
333
def get_symm_buffer(self):
301
- import vllm.third_party.deep_gemm as deep_gemm
334
+ from vllm.utils.deep_gemm import _import_deep_gemm
335
+
336
+ deep_gemm = _import_deep_gemm()
337
338
group = get_ep_group().device_group
339
device = torch.accelerator.current_device_index()
@@ -324,6 +359,52 @@ class DeepseekV4MegaMoEExperts(nn.Module):
359
self._symm_buffer_cache[key] = symm_buffer
360
return symm_buffer
361
362
+ def set_eplb_state(
363
+ self,
364
+ moe_layer_idx: int,
365
+ expert_load_view: torch.Tensor,
366
+ logical_to_physical_map: torch.Tensor,
367
+ logical_replica_count: torch.Tensor,
368
+ ) -> None:
369
+ self.eplb_state.set_layer_state(
370
+ moe_layer_idx,
371
+ expert_load_view,
372
+ logical_to_physical_map,
373
+ logical_replica_count,
374
+ )
375
+
376
+ def get_expert_weights(self) -> list[torch.Tensor]:
377
+ self.finalize_weights()
378
+ assert self._transformed_l1_weights is not None
379
+ assert self._transformed_l2_weights is not None
380
+
381
+ def _to_eplb_view(name: str, t: torch.Tensor) -> torch.Tensor:
382
+ """Return a (num_local_experts, -1) view with contiguous memory layout."""
383
+ assert t.shape[0] == self.num_local_experts
384
+ if t.is_contiguous():
385
+ return t.view(self.num_local_experts, -1)
386
+ elif t.dim() == 3 and t.stride(1) == 1 and t.stride(2) == t.shape[1]:
387
+ # scales have shape (E, M, N) with memory layout (E, N, M)
388
+ back = torch.transpose(t, 1, 2)
389
+ assert back.is_contiguous()
390
+ return back.view(self.num_local_experts, -1)
391
+
392
+ raise AssertionError(
393
+ f"DSv4 EPLB {name}: non-contiguous expert tensor with "
394
+ f"unexpected layout shape={tuple(t.shape)} "
395
+ f"stride={tuple(t.stride())} dtype={t.dtype}"
396
+ )
397
+
398
+ return [
399
+ _to_eplb_view("l1_packed", self._transformed_l1_weights[0]),
400
+ _to_eplb_view("l1_scale", self._transformed_l1_weights[1]),
401
+ _to_eplb_view("l2_weight", self._transformed_l2_weights[0]),
402
+ _to_eplb_view("l2_scale", self._transformed_l2_weights[1]),
403
+ ]
404
+
405
+ def update_expert_map(self) -> None:
406
+ pass
407
+
408
def forward(
409
self,
410
hidden_states: torch.Tensor,
@@ -358,10 +439,27 @@ class DeepseekV4MegaMoEExperts(nn.Module):
439
activation_clamp: float | None,
440
fast_math: bool,
441
) -> None:
361
- import vllm.third_party.deep_gemm as deep_gemm
442
+ from vllm.utils.deep_gemm import _import_deep_gemm
443
+
444
+ deep_gemm = _import_deep_gemm()
445
446
symm_buffer = self.get_symm_buffer()
447
num_tokens = hidden_states.shape[0]
448
+
449
+ # EPLB: map logical expert IDs to physical replicas and record load.
450
+ eplb_state = self.eplb_state
451
+ if eplb_state.logical_to_physical_map is not None:
452
+ assert eplb_state.expert_load_view is not None
453
+ assert eplb_state.logical_replica_count is not None
454
+ assert eplb_state.should_record_tensor is not None
455
+ topk_ids = eplb_map_to_physical_and_record(
456
+ topk_ids=topk_ids,
457
+ expert_load_view=eplb_state.expert_load_view,
458
+ logical_to_physical_map=eplb_state.logical_to_physical_map,
459
+ logical_replica_count=eplb_state.logical_replica_count,
460
+ record_enabled=eplb_state.should_record_tensor,
461
+ )
462
+
463
prepare_megamoe_inputs(
464
hidden_states,
465
topk_weights,
@@ -493,17 +591,33 @@ class DeepseekV4MoE(nn.Module):
591
self.ep_group = get_ep_group()
592
self.ep_size = self.ep_group.world_size
593
self.ep_rank = self.ep_group.rank_in_group
496
- assert config.n_routed_experts % self.ep_size == 0
594
498
- self.n_local_experts = config.n_routed_experts // self.ep_size
499
- self.experts_start_idx = self.ep_rank * self.n_local_experts
500
- self.experts_end_idx = self.experts_start_idx + self.n_local_experts
595
+ eplb_config = vllm_config.parallel_config.eplb_config
596
+ self.n_redundant_experts = eplb_config.num_redundant_experts
597
+ self.n_routed_experts = config.n_routed_experts
598
+ self.n_shared_experts = config.n_shared_experts or 0
599
+ self.n_logical_experts = self.n_routed_experts
600
+ self.n_physical_experts = self.n_logical_experts + self.n_redundant_experts
601
+ assert self.n_physical_experts % self.ep_size == 0, (
602
+ f"n_physical_experts={self.n_physical_experts} must be divisible by "
603
+ f"ep_size={self.ep_size}. Adjust num_redundant_experts."
604
+ )
605
+ self.n_local_physical_experts = self.n_physical_experts // self.ep_size
606
+ self.physical_expert_start = self.ep_rank * self.n_local_physical_experts
607
+ self.physical_expert_end = (
608
+ self.physical_expert_start + self.n_local_physical_experts
609
+ )
610
+
611
+ self.n_local_experts = self.n_local_physical_experts
612
+ self.experts_start_idx = self.physical_expert_start
613
+ self.experts_end_idx = self.physical_expert_end
614
615
self.experts = DeepseekV4MegaMoEExperts(
616
vllm_config,
504
- num_experts=config.n_routed_experts,
505
- num_local_experts=self.n_local_experts,
506
- experts_start_idx=self.experts_start_idx,
617
+ num_experts=self.n_physical_experts,
618
+ num_local_experts=self.n_local_physical_experts,
619
+ experts_start_idx=self.physical_expert_start,
620
+ num_logical_experts=self.n_logical_experts,
621
top_k=config.num_experts_per_tok,
622
hidden_size=config.hidden_size,
623
intermediate_size=config.moe_intermediate_size,
@@ -1242,7 +1356,44 @@ def _make_deepseek_v4_weights_mapper(expert_dtype: str) -> WeightsMapper:
1356
)
1357
1358
1245
-class DeepseekV4ForCausalLM(nn.Module, SupportsPP):
1359
+class DeepseekV4MixtureOfExperts(MixtureOfExperts):
1360
+ moe_mlp_layers: list["DeepseekV4MoE"]
1361
+
1362
+ def extract_moe_parameters(self, example_moe: "DeepseekV4MoE | None") -> None:
1363
+ if example_moe is None:
1364
+ self.num_moe_layers = 0
1365
+ self.num_expert_groups = 0
1366
+ self.num_logical_experts = 0
1367
+ self.num_physical_experts = 0
1368
+ self.num_local_physical_experts = 0
1369
+ self.num_routed_experts = 0
1370
+ self.num_shared_experts = 0
1371
+ self.num_redundant_experts = 0
1372
+ return
1373
+ self.num_logical_experts = example_moe.n_logical_experts
1374
+ self.num_physical_experts = example_moe.n_physical_experts
1375
+ self.num_local_physical_experts = example_moe.n_local_physical_experts
1376
+ self.num_routed_experts = example_moe.n_routed_experts
1377
+ self.num_shared_experts = example_moe.n_shared_experts
1378
+ self.num_redundant_experts = example_moe.n_redundant_experts
1379
+
1380
+ def update_physical_experts_metadata(
1381
+ self,
1382
+ num_physical_experts: int,
1383
+ num_local_physical_experts: int,
1384
+ ) -> None:
1385
+ assert self.num_local_physical_experts == num_local_physical_experts
1386
+ self.num_physical_experts = num_physical_experts
1387
+ self.num_local_physical_experts = num_local_physical_experts
1388
+ self.num_redundant_experts = num_physical_experts - self.num_logical_experts
1389
+ for moe in self.moe_mlp_layers:
1390
+ moe.n_local_physical_experts = num_local_physical_experts
1391
+ moe.n_physical_experts = num_physical_experts
1392
+ moe.n_redundant_experts = self.num_redundant_experts
1393
+ moe.experts.update_expert_map()
1394
+
1395
+
1396
+class DeepseekV4ForCausalLM(nn.Module, SupportsPP, DeepseekV4MixtureOfExperts):
1397
model_cls = DeepseekV4Model
1398
1399
# Default mapper assumes the original FP4-expert checkpoint layout.
@@ -1274,6 +1425,28 @@ class DeepseekV4ForCausalLM(nn.Module, SupportsPP):
1425
self.model.make_empty_intermediate_tensors
1426
)
1427
1428
+ self.set_moe_parameters()
1429
+
1430
+ def set_moe_parameters(self) -> None:
1431
+ self.expert_weights: MutableSequence[Sequence[torch.Tensor]] = []
1432
+ self.num_expert_groups = getattr(self.config, "n_group", 1)
1433
+ self.num_moe_layers = self.config.num_hidden_layers
1434
+ self.moe_layers: list[nn.Module] = []
1435
+ self.moe_mlp_layers: list[DeepseekV4MoE] = []
1436
+ example_moe: DeepseekV4MoE | None = None
1437
+ for layer in self.model.layers:
1438
+ if isinstance(layer, PPMissingLayer):
1439
+ continue
1440
+ if not isinstance(layer, DeepseekV4DecoderLayer):
1441
+ continue
1442
+ if isinstance(layer.ffn, DeepseekV4MoE):
1443
+ example_moe = layer.ffn
1444
+ self.moe_mlp_layers.append(layer.ffn)
1445
+ self.moe_layers.append(layer.ffn.experts)
1446
+
1447
+ self.num_moe_layers = len(self.moe_layers)
1448
+ self.extract_moe_parameters(example_moe)
1449
+
1450
def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
1451
return self.model.embed_input_ids(input_ids)
1452
vllm/utils/deep_gemm.py
+1
@@ -140,6 +140,7 @@ _get_mk_alignment_for_contiguous_layout_impl: Callable[..., Any] | None = None
140
_transform_sf_into_required_layout_impl: Callable[..., Any] | None = None
141
142
143
+@functools.cache
144
def _import_deep_gemm():
145
"""Import the deep_gemm module.
146
vllm/v1/worker/gpu_worker.py
+4
-1
@@ -1147,7 +1147,10 @@ def init_worker_distributed_environment(
1147
from vllm.model_executor.layers.batch_invariant import init_batch_invariance
1148
1149
init_batch_invariance()
1150
- override_envs_for_eplb(parallel_config)
1150
+ override_envs_for_eplb(
1151
+ parallel_config,
1152
+ moe_backend=getattr(vllm_config.kernel_config, "moe_backend", None),
1153
+ )
1154
set_custom_all_reduce(not parallel_config.disable_custom_all_reduce)
1155
1156
init_method = distributed_init_method or "env://"