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 6, 09:30 UTC
[MoE Refactor] DefaultMoERunner simplifcation (#33049)
Signed-off-by: Bill Nell <bnell@redhat.com>
bnellnm committed
Mar 19, 2026 at 15:07 UTC
9279c59a0e81cdc846416ab310b1f9f9c00edfbb
2 files changed
+379
-295
vllm/model_executor/layers/fused_moe/layer.py
+2
@@ -504,6 +504,8 @@ class FusedMoE(CustomOp):
504
self.apply_router_weight_on_input = apply_router_weight_on_input
505
self.activation = MoEActivation.from_str(activation)
506
507
+ # TODO(bnell): we should not have to create a router if the kernel is
508
+ # monolithic.
509
self.router = create_fused_moe_router(
510
top_k=top_k,
511
global_num_experts=self.global_num_experts,
vllm/model_executor/layers/fused_moe/runner/default_moe_runner.py
+377
-295
@@ -1,5 +1,6 @@
1
# SPDX-License-Identifier: Apache-2.0
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+from collections.abc import Callable
4
from contextlib import nullcontext
5
from typing import TYPE_CHECKING
6
@@ -82,9 +83,22 @@ def _moe_forward(
83
layer = get_layer_from_name(_resolve_layer_name(layer_name))
84
# TODO(bnell): this can be removed after MK migration is complete.
85
layer.ensure_moe_quant_config_init()
85
- return layer.runner.forward_impl(
86
- layer, hidden_states, router_logits, shared_experts_input
87
- )
86
+ runner = layer.runner
87
+ with runner._sequence_parallel_context():
88
+ if runner.use_dp_chunking:
89
+ return runner.forward_impl_chunked(
90
+ layer,
91
+ hidden_states,
92
+ router_logits,
93
+ shared_experts_input,
94
+ )
95
+ else:
96
+ return runner.forward_impl(
97
+ layer,
98
+ hidden_states,
99
+ router_logits,
100
+ shared_experts_input,
101
+ )
102
103
104
def _moe_forward_fake(
@@ -105,9 +119,22 @@ def _moe_forward_shared(
119
layer = get_layer_from_name(_resolve_layer_name(layer_name))
120
# TODO(bnell): this can be removed after MK migration is complete.
121
layer.ensure_moe_quant_config_init()
108
- return layer.runner.forward_impl(
109
- layer, hidden_states, router_logits, shared_experts_input
110
- )
122
+ runner = layer.runner
123
+ with runner._sequence_parallel_context():
124
+ if runner.use_dp_chunking:
125
+ return runner.forward_impl_chunked(
126
+ layer,
127
+ hidden_states,
128
+ router_logits,
129
+ shared_experts_input,
130
+ )
131
+ else:
132
+ return runner.forward_impl(
133
+ layer,
134
+ hidden_states,
135
+ router_logits,
136
+ shared_experts_input,
137
+ )
138
139
140
def _moe_forward_shared_fake(
@@ -191,10 +218,17 @@ class DefaultMoERunner(MoERunner):
218
self.reduce_results = reduce_results
219
self.enable_dbo = enable_dbo
220
221
+ # Chunked all2all staging tensor
222
+ # TODO(bnell) rename these?
223
+ self.batched_hidden_states: torch.Tensor | None = None
224
+ self.batched_router_logits: torch.Tensor | None = None
225
+ self._maybe_init_dp_chunking()
226
+
227
# Allow disabling of the separate shared experts stream for
228
# debug purposes.
229
# TODO: Remove this after more extensive testings with TP/DP
230
# and other execution modes
231
+ self.use_shared_experts_stream = False
232
if envs.VLLM_DISABLE_SHARED_EXPERTS_STREAM:
233
logger.debug_once("Disabling MoE shared_experts cuda stream", scope="local")
234
self.shared_experts_stream = None
@@ -210,23 +244,20 @@ class DefaultMoERunner(MoERunner):
244
# Needed for string -> FusedMoE layer lookup in custom ops.
245
self.layer_name = layer.layer_name
246
247
+ self.moe_forward = self._select_forward(layer)
248
+
249
+ def _select_forward(self, layer: torch.nn.Module) -> Callable:
250
if current_platform.is_tpu() or current_platform.is_cpu():
251
# TODO: Once the OOM issue for the TPU backend is resolved, we
252
# will switch to using the moe_forward custom op.
253
# Note: CPU doesn't require wrapped forward_impl.
217
- if self.shared_experts is None:
218
- self.moe_forward = _moe_forward
219
- else:
220
- self.moe_forward = _moe_forward_shared
221
- else:
222
- if self.shared_experts is None:
223
- self.moe_forward = torch.ops.vllm.moe_forward
224
- else:
225
- self.moe_forward = torch.ops.vllm.moe_forward_shared
254
+ return _moe_forward if self.shared_experts is None else _moe_forward_shared
255
227
- # Chunked all2all staging tensor
228
- self.batched_hidden_states: torch.Tensor | None = None
229
- self.batched_router_logits: torch.Tensor | None = None
256
+ return (
257
+ torch.ops.vllm.moe_forward
258
+ if self.shared_experts is None
259
+ else torch.ops.vllm.moe_forward_shared
260
+ )
261
262
@property
263
def use_dp_chunking(self) -> bool:
@@ -241,22 +272,8 @@ class DefaultMoERunner(MoERunner):
272
self,
273
hidden_states: torch.Tensor,
274
shared_input: torch.Tensor | None,
244
- has_separate_shared_experts: bool,
245
- use_chunked_impl: bool,
246
- ) -> tuple[bool, torch.Tensor | None]:
247
- use_shared_experts_stream = (
248
- current_platform.is_cuda()
249
- and has_separate_shared_experts
250
- and not use_chunked_impl
251
- and self.shared_experts_stream is not None
252
- and (
253
- hidden_states.shape[0]
254
- <= envs.VLLM_SHARED_EXPERTS_STREAM_TOKEN_THRESHOLD
255
- )
256
- )
257
-
258
- shared_experts_input: torch.Tensor | None = None
259
- if use_shared_experts_stream:
275
+ ):
276
+ if self.use_shared_experts_stream:
277
assert self.shared_experts_stream is not None
278
assert self.moe_config.disable_inplace
279
@@ -278,12 +295,11 @@ class DefaultMoERunner(MoERunner):
295
assert self.shared_experts_stream is not None
296
self.shared_experts_stream.wait_stream(current_stream())
297
281
- return use_shared_experts_stream, shared_experts_input
282
-
283
- def ensure_dp_chunking_init(self):
284
- if not self.use_dp_chunking or self.batched_hidden_states is not None:
298
+ def _maybe_init_dp_chunking(self):
299
+ if not self.use_dp_chunking:
300
return
301
302
+ assert self.batched_hidden_states is None
303
states_shape: tuple[int, ...]
304
logits_shape: tuple[int, ...]
305
@@ -309,6 +325,38 @@ class DefaultMoERunner(MoERunner):
325
device=device,
326
)
327
328
+ @property
329
+ def has_separate_shared_experts(self) -> bool:
330
+ return (
331
+ not self.quant_method.mk_owns_shared_expert
332
+ and self.shared_experts is not None
333
+ )
334
+
335
+ def _apply_shared_experts(
336
+ self,
337
+ hidden_states: torch.Tensor,
338
+ allow_streaming: bool = False,
339
+ ) -> torch.Tensor | None:
340
+ shared_output: torch.Tensor | None = None
341
+ if self.has_separate_shared_experts:
342
+ assert self.shared_experts is not None
343
+
344
+ if self.use_shared_experts_stream and allow_streaming:
345
+ # Run shared experts in parallel on a separate stream
346
+ # NOTE: We start the separate stream here and mark the
347
+ # sync end point immediately after it is done. This is
348
+ # important to avoid excessive stream allocations by the cuda
349
+ # graph replay later.
350
+ with torch.cuda.stream(self.shared_experts_stream):
351
+ # Note that hidden_states clone() is necessary here to avoid
352
+ # conflict with the main stream
353
+ shared_output = self.shared_experts(hidden_states)
354
+ current_stream().wait_stream(self.shared_experts_stream)
355
+ else:
356
+ shared_output = self.shared_experts(hidden_states)
357
+
358
+ return shared_output
359
+
360
def must_reduce_shared_expert_outputs(self) -> bool:
361
"""
362
The shared_experts are typically computed using the RowParallelLinear
@@ -322,7 +370,6 @@ class DefaultMoERunner(MoERunner):
370
Therefore it is required that we reduce the shared_experts output
371
early.
372
"""
325
- assert self.quant_method is not None
373
return (
374
self.quant_method.moe_kernel is not None
375
and self.quant_method.moe_kernel.output_is_reduced()
@@ -357,7 +404,7 @@ class DefaultMoERunner(MoERunner):
404
return result
405
return hidden_states
406
360
- def _reduce_output(
407
+ def _maybe_reduce_output(
408
self,
409
states: torch.Tensor | tuple[torch.Tensor, torch.Tensor],
410
trunc_sizes: list[int],
@@ -397,23 +444,16 @@ class DefaultMoERunner(MoERunner):
444
return "from_forward_context"
445
return self.layer_name
446
400
- def forward(
447
+ def _maybe_pad_hidden_states(
448
self,
449
+ original_hidden_states: torch.Tensor | None,
450
hidden_states: torch.Tensor,
403
- router_logits: torch.Tensor,
404
- ) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]:
405
- # For latent MoE: save ORIGINAL hidden_states before transform
406
- # (shared_experts need original dimension, routed experts use transformed)
407
- if self.shared_experts is not None:
408
- original_hidden_states = hidden_states
409
- original_hidden_dim = hidden_states.shape[-1]
410
- else:
411
- original_hidden_states = None
412
-
413
- # Apply transform for routed experts (e.g., latent projection for latent MoE)
414
- hidden_states = self.apply_routed_input_transform(hidden_states)
415
-
416
- # This is the dimension after transform (for routed expert output slicing)
451
+ ) -> tuple[torch.Tensor, list[int]]:
452
+ original_hidden_dim = (
453
+ original_hidden_states.shape[-1]
454
+ if original_hidden_states is not None
455
+ else 0
456
+ )
457
transformed_hidden_dim = hidden_states.shape[-1]
458
if (
459
not self.quant_method.skip_forward_padding
@@ -426,134 +466,235 @@ class DefaultMoERunner(MoERunner):
466
value=0.0,
467
)
468
429
- fused_output = self.moe_forward(
430
- hidden_states,
431
- router_logits,
432
- original_hidden_states,
433
- self._encode_layer_name(),
434
- )
435
-
469
if self.shared_experts is not None:
470
orig_hidden_dims = [original_hidden_dim, transformed_hidden_dim]
471
else:
472
orig_hidden_dims = [transformed_hidden_dim]
473
441
- return self._reduce_output(fused_output, orig_hidden_dims)
474
+ return hidden_states, orig_hidden_dims
475
443
- def forward_impl_chunked(
476
+ def _apply_quant_method(
477
self,
478
layer: torch.nn.Module,
446
- full_hidden_states: torch.Tensor,
447
- full_router_logits: torch.Tensor,
448
- full_shared_input: torch.Tensor | None,
449
- has_separate_shared_experts: bool,
450
- ) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]:
479
+ hidden_states: torch.Tensor,
480
+ router_logits: torch.Tensor,
481
+ shared_input: torch.Tensor | None,
482
+ run_shared_experts_before: bool = True,
483
+ ) -> tuple[torch.Tensor | None, torch.Tensor]:
484
+ shared_input = shared_input if shared_input is not None else hidden_states
485
+ shared_output: torch.Tensor | None = None
486
+
487
+ # Run this before quant_method to avoid inplace issues.
488
+ if run_shared_experts_before:
489
+ shared_output = self._apply_shared_experts(shared_input, False)
490
+
491
+ if self.quant_method.is_monolithic:
492
+ result = self.quant_method.apply_monolithic(
493
+ layer=layer,
494
+ x=hidden_states,
495
+ router_logits=router_logits,
496
+ )
497
+ else:
498
+ topk_weights, topk_ids = self.router.select_experts(
499
+ hidden_states=hidden_states,
500
+ router_logits=router_logits,
501
+ )
502
+
503
+ result = self.quant_method.apply(
504
+ layer=layer,
505
+ x=hidden_states,
506
+ topk_weights=topk_weights,
507
+ topk_ids=topk_ids,
508
+ shared_experts_input=shared_input,
509
+ )
510
+
511
+ if isinstance(result, tuple):
512
+ assert shared_output is None
513
+ shared_output, hidden_states = result
514
+ else:
515
+ hidden_states = result
516
+
517
+ if not run_shared_experts_before and self.has_separate_shared_experts:
518
+ assert shared_output is None
519
+ shared_output = self._apply_shared_experts(shared_input, True)
520
+
521
+ return shared_output, hidden_states
522
+
523
+ def _sequence_parallel_context(self):
524
+ ctx = get_forward_context()
525
+ return (
526
+ ctx.dp_metadata.sp_local_sizes(self.moe_config.sp_size)
527
+ if ctx.dp_metadata
528
+ else nullcontext()
529
+ )
530
+
531
+ def _allocate_dp_chunking_outputs(
532
+ self,
533
+ hidden_states: torch.Tensor,
534
+ router_logits: torch.Tensor,
535
+ ) -> tuple[torch.Tensor | None, torch.Tensor]:
536
+ assert self.use_dp_chunking
537
+
538
+ # Assert the inputs are of the proper type and shape.
539
assert self.batched_hidden_states is not None
540
assert self.batched_router_logits is not None
453
- assert self.batched_hidden_states.dtype == full_hidden_states.dtype, (
454
- f"{self.batched_hidden_states.dtype} == {full_hidden_states.dtype}"
541
+
542
+ assert self.batched_hidden_states.dtype == hidden_states.dtype, (
543
+ f"{self.batched_hidden_states.dtype} == {hidden_states.dtype}"
544
)
456
- assert self.batched_router_logits.dtype == full_router_logits.dtype, (
457
- f"{self.batched_router_logits.dtype} == {full_router_logits.dtype}"
545
+ assert self.batched_router_logits.dtype == router_logits.dtype, (
546
+ f"{self.batched_router_logits.dtype} == {router_logits.dtype}"
547
)
459
- # Check size compatibility.
460
- assert self.batched_hidden_states.size(-1) == full_hidden_states.size(-1)
461
- assert self.batched_router_logits.size(-1) == full_router_logits.size(-1)
548
463
- # TODO(bnell): Fix shared_expert_inputs w/chunking.
464
- # assert shared_input is None, (
465
- # "Routed input transform is not currently supported with DP chunking."
466
- # )
549
+ # Check size compatibility.
550
+ assert self.batched_hidden_states.size(-1) == hidden_states.size(-1)
551
+ assert self.batched_router_logits.size(-1) == router_logits.size(-1)
552
468
- full_fused_final_hidden_states = torch.empty_like(full_hidden_states)
553
+ final_fused_hidden_states = torch.empty_like(hidden_states)
554
if self.shared_experts is not None:
470
- full_shared_final_hidden_states = torch.empty_like(full_hidden_states)
471
-
472
- def process_chunk(chunk_start, chunk_end, skip_result_store=False):
473
- chunk_size = chunk_end - chunk_start
474
- hidden_states = full_hidden_states[chunk_start:chunk_end, :]
475
- router_logits = full_router_logits[chunk_start:chunk_end, :]
476
- shared_input = (
477
- full_shared_input[chunk_start:chunk_end, :]
478
- if full_shared_input is not None
479
- else None
480
- )
555
+ final_shared_hidden_states = torch.empty_like(hidden_states)
556
+ else:
557
+ final_shared_hidden_states = None
558
482
- assert self.batched_hidden_states is not None
483
- assert self.batched_router_logits is not None
484
- # This is only true when DBO has been enabled in the config.
485
- # Both tensors will have an outer dimension for the ubatch id
486
- if self.batched_hidden_states.dim() == 3:
487
- assert self.batched_router_logits.dim() == 3
488
- batch_buffer_idx = dbo_current_ubatch_id()
489
- batched_hidden_states = self.batched_hidden_states[batch_buffer_idx, :]
490
- batched_router_logits = self.batched_router_logits[batch_buffer_idx, :]
491
- else:
492
- batched_hidden_states = self.batched_hidden_states
493
- batched_router_logits = self.batched_router_logits
559
+ return final_shared_hidden_states, final_fused_hidden_states
560
+
561
+ def _maybe_gate(
562
+ self,
563
+ hidden_states: torch.Tensor,
564
+ router_logits: torch.Tensor,
565
+ ) -> torch.Tensor:
566
+ # If router/gate provided, then apply it here.
567
+ # (Note: This code runs only when "overlapped mode" is on to allow
568
+ # parallel execution of shared experts with the FusedMoE via
569
+ # separate cuda stream)
570
+ if self.gate is not None:
571
+ router_logits, _ = self.gate(hidden_states)
572
+ return router_logits
573
+
574
+ @property
575
+ def do_naive_dispatch_combine(self) -> bool:
576
+ return (
577
+ self.moe_config.dp_size > 1 and not self.quant_method.supports_internal_mk
578
+ )
579
495
- assert (
496
- batched_hidden_states.size(0) # type: ignore
497
- >= chunk_size
580
+ def _maybe_dispatch(
581
+ self,
582
+ layer: torch.nn.Module,
583
+ hidden_states: torch.Tensor,
584
+ router_logits: torch.Tensor,
585
+ ) -> tuple[torch.Tensor, torch.Tensor]:
586
+ # For naive dispatch/combine Dp/Ep, dispatch the hidden states and
587
+ # router logits to all experts.
588
+ # NOTE: this will be removed once all kernels are migrated into the
589
+ # MoEKernel framework.
590
+ if self.do_naive_dispatch_combine:
591
+ hidden_states, router_logits = get_ep_group().dispatch_router_logits(
592
+ hidden_states,
593
+ router_logits,
594
+ self.moe_config.is_sequence_parallel,
595
)
499
- assert (
500
- batched_router_logits.size(0) # type: ignore
501
- >= chunk_size
596
+
597
+ # NOTE: Similar with DP, PCP also needs dispatch and combine. For
598
+ # simplicity, AgRsAll2All was added separately for PCP here. Maybe
599
+ # we should modify All2AllManager abstraction to better support PCP.
600
+ if self.moe_config.pcp_size > 1:
601
+ hidden_states = get_pcp_group().all_gather(
602
+ hidden_states,
603
+ dim=0,
604
)
503
- staged_hidden_states = batched_hidden_states[:chunk_size, :] # type: ignore
504
- staged_router_logits = batched_router_logits[:chunk_size, :] # type: ignore
505
- staged_hidden_states.copy_(hidden_states, non_blocking=True)
506
- staged_router_logits.copy_(router_logits, non_blocking=True)
605
+ router_logits = get_pcp_group().all_gather(
606
+ router_logits,
607
+ dim=0,
608
+ )
609
+
610
+ return hidden_states, router_logits
611
508
- shared_input = (
509
- shared_input if shared_input is not None else staged_hidden_states
612
+ def _maybe_combine(
613
+ self,
614
+ shared_output: torch.Tensor | None,
615
+ hidden_states: torch.Tensor,
616
+ ) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor | None]:
617
+ if self.do_naive_dispatch_combine:
618
+ hidden_states = get_ep_group().combine(
619
+ hidden_states, self.moe_config.is_sequence_parallel
620
)
621
512
- # Matrix multiply.
513
- if self.quant_method.is_monolithic:
514
- assert has_separate_shared_experts or self.shared_experts is None
515
- final_hidden_states = self.quant_method.apply_monolithic(
516
- layer=layer,
517
- x=staged_hidden_states,
518
- router_logits=staged_router_logits,
519
- )
520
- else:
521
- topk_weights, topk_ids = self.router.select_experts(
522
- hidden_states=staged_hidden_states,
523
- router_logits=staged_router_logits,
524
- )
622
+ if self.moe_config.pcp_size > 1:
623
+ hidden_states = get_pcp_group().reduce_scatter(
624
+ hidden_states,
625
+ dim=0,
626
+ )
627
+ # need RS for shared_output?
628
526
- final_hidden_states = self.quant_method.apply(
527
- layer=layer,
528
- x=staged_hidden_states,
529
- topk_weights=topk_weights,
530
- topk_ids=topk_ids,
531
- shared_experts_input=shared_input,
532
- )
629
+ if self.shared_experts is not None:
630
+ assert shared_output is not None
631
+ return shared_output, hidden_states
632
+ else:
633
+ return hidden_states
634
534
- if has_separate_shared_experts:
535
- assert not isinstance(final_hidden_states, tuple)
536
- assert self.shared_experts is not None
635
+ def forward(
636
+ self,
637
+ hidden_states: torch.Tensor,
638
+ router_logits: torch.Tensor,
639
+ ) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]:
640
+ # For latent MoE: save ORIGINAL hidden_states before transform
641
+ # (shared_experts need original dimension, routed experts use transformed)
642
+ if self.shared_experts is not None:
643
+ original_hidden_states = hidden_states
644
+ else:
645
+ original_hidden_states = None
646
538
- shared_output = self.shared_experts(shared_input)
647
+ # Apply transform for routed experts (e.g., latent projection for latent MoE)
648
+ hidden_states = self.apply_routed_input_transform(hidden_states)
649
540
- final_hidden_states = (
541
- shared_output,
542
- final_hidden_states,
543
- )
650
+ hidden_states, og_hidden_dims = self._maybe_pad_hidden_states(
651
+ original_hidden_states,
652
+ hidden_states,
653
+ )
654
545
- if not skip_result_store:
546
- if self.shared_experts is None:
547
- full_fused_final_hidden_states[chunk_start:chunk_end, :].copy_(
548
- final_hidden_states, non_blocking=True
549
- )
550
- else:
551
- full_shared_final_hidden_states[chunk_start:chunk_end, :].copy_(
552
- final_hidden_states[0], non_blocking=True
553
- )
554
- full_fused_final_hidden_states[chunk_start:chunk_end, :].copy_(
555
- final_hidden_states[1], non_blocking=True
556
- )
655
+ fused_output = self.moe_forward(
656
+ hidden_states,
657
+ router_logits,
658
+ original_hidden_states,
659
+ self._encode_layer_name(),
660
+ )
661
+
662
+ return self._maybe_reduce_output(fused_output, og_hidden_dims)
663
+
664
+ def _slice_and_copy_input(
665
+ self,
666
+ out_slice: torch.Tensor,
667
+ orig: torch.Tensor | None,
668
+ start: int,
669
+ end: int,
670
+ ) -> torch.Tensor:
671
+ assert orig is not None
672
+ slice_size = end - start
673
+ orig_slice = orig[start:end, :]
674
+ if self.enable_dbo:
675
+ assert out_slice.dim() == 3
676
+ batch_buffer_idx = dbo_current_ubatch_id()
677
+ out_slice = out_slice[batch_buffer_idx, :]
678
+
679
+ assert out_slice.size(0) >= slice_size
680
+ out_slice = out_slice[:slice_size, :]
681
+ out_slice.copy_(orig_slice, non_blocking=True)
682
+ return out_slice
683
+
684
+ def forward_impl_chunked(
685
+ self,
686
+ layer: torch.nn.Module,
687
+ hidden_states: torch.Tensor,
688
+ router_logits: torch.Tensor,
689
+ shared_input: torch.Tensor | None,
690
+ ) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]:
691
+ # Gate overlap not supported when chunking is enabled. Run the
692
+ # gate first.
693
+ router_logits = self._maybe_gate(hidden_states, router_logits)
694
+
695
+ final_shared_hidden_states, final_fused_hidden_states = (
696
+ self._allocate_dp_chunking_outputs(hidden_states, router_logits)
697
+ )
698
699
ctx = get_forward_context()
700
# flashinfer_cutlass_kernels can handle: optional DP + TP/EP
@@ -567,7 +708,7 @@ class DefaultMoERunner(MoERunner):
708
max_tokens_across_dispatchers, self.moe_config.sp_size
709
)
710
570
- num_tokens = full_hidden_states.size(0)
711
+ num_tokens = hidden_states.size(0)
712
for chunk_idx, chunk_start_ in enumerate(
713
range(0, max_tokens_across_dispatchers, moe_dp_chunk_size_per_rank)
714
):
@@ -578,17 +719,55 @@ class DefaultMoERunner(MoERunner):
719
# clamp start and end
720
chunk_start = min(chunk_start, num_tokens - 1)
721
chunk_end = min(chunk_end, num_tokens)
581
- with ctx.dp_metadata.chunked_sizes(
722
+ chunk_sizes = ctx.dp_metadata.chunked_sizes(
723
self.moe_config.sp_size, moe_dp_chunk_size_per_rank, chunk_idx
583
- ):
584
- process_chunk(
585
- chunk_start, chunk_end, skip_result_store=chunk_start_ >= num_tokens
724
+ )
725
+ with chunk_sizes:
726
+ hidden_states_chunk = self._slice_and_copy_input(
727
+ self.batched_hidden_states,
728
+ hidden_states,
729
+ chunk_start,
730
+ chunk_end,
731
+ )
732
+
733
+ router_logits_chunk = self._slice_and_copy_input(
734
+ self.batched_router_logits,
735
+ router_logits,
736
+ chunk_start,
737
+ chunk_end,
738
+ )
739
+
740
+ shared_input_chunk = (
741
+ shared_input[chunk_start:chunk_end, :]
742
+ if shared_input is not None
743
+ else None
744
+ )
745
+
746
+ shared_output_chunk, hidden_states_chunk = self._apply_quant_method(
747
+ layer=layer,
748
+ hidden_states=hidden_states_chunk,
749
+ router_logits=router_logits_chunk,
750
+ shared_input=shared_input_chunk,
751
)
752
753
+ # Store outputs
754
+ # TODO(bnell): document when chunk_start >= num_tokens
755
+ if chunk_start < num_tokens:
756
+ final_fused_hidden_states[chunk_start:chunk_end, :].copy_(
757
+ hidden_states_chunk, non_blocking=True
758
+ )
759
+ if self.shared_experts is not None:
760
+ assert shared_output_chunk is not None
761
+ assert final_shared_hidden_states is not None
762
+ final_shared_hidden_states[chunk_start:chunk_end, :].copy_(
763
+ shared_output_chunk, non_blocking=True
764
+ )
765
+
766
if self.shared_experts is None:
589
- return full_fused_final_hidden_states
767
+ return final_fused_hidden_states
768
else:
591
- return (full_shared_final_hidden_states, full_fused_final_hidden_states)
769
+ assert final_shared_hidden_states is not None
770
+ return (final_shared_hidden_states, final_fused_hidden_states)
771
772
def forward_impl(
773
self,
@@ -597,148 +776,51 @@ class DefaultMoERunner(MoERunner):
776
router_logits: torch.Tensor,
777
shared_input: torch.Tensor | None,
778
) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]:
600
- assert self.quant_method is not None
601
-
602
- self.ensure_dp_chunking_init()
603
-
604
- has_separate_shared_experts = (
605
- not self.quant_method.mk_owns_shared_expert
606
- and self.shared_experts is not None
779
+ self.use_shared_experts_stream = (
780
+ current_platform.is_cuda()
781
+ and self.has_separate_shared_experts
782
+ and not self.use_dp_chunking
783
+ and self.shared_experts_stream is not None
784
+ and (
785
+ hidden_states.shape[0]
786
+ <= envs.VLLM_SHARED_EXPERTS_STREAM_TOKEN_THRESHOLD
787
+ )
788
)
789
609
- use_chunked_impl = self.use_dp_chunking
790
+ # Check if we need to run shared experts before matrix multiply because
791
+ # matrix multiply may modify the hidden_states.
792
+ run_shared_experts_before = (
793
+ self.has_separate_shared_experts and not self.use_shared_experts_stream
794
+ )
795
611
- use_shared_experts_stream, shared_experts_input = (
796
+ # The shared experts stream must be set up before calling the gate so they
797
+ # can be overlapped.
798
+ if not run_shared_experts_before:
799
self._maybe_setup_shared_experts_stream(
800
hidden_states,
801
shared_input,
615
- has_separate_shared_experts,
616
- use_chunked_impl,
802
)
618
- )
803
620
- # If router/gate provided, then apply it here.
621
- # (Note: This code runs only when "overlapped mode" is on to allow
622
- # parallel execution of shared experts with the FusedMoE via
623
- # separate cuda stream)
624
- if self.gate is not None:
625
- router_logits, _ = self.gate(hidden_states)
626
-
627
- if use_chunked_impl:
628
- return self.forward_impl_chunked(
629
- layer,
630
- hidden_states,
631
- router_logits,
632
- shared_input,
633
- has_separate_shared_experts,
634
- )
804
+ router_logits = self._maybe_gate(hidden_states, router_logits)
805
636
- # NOTE(rob): once we finish migrating all the quant methods to use
637
- # MKs, we can remove the naive dispatch/combine path from here.
638
- do_naive_dispatch_combine = (
639
- self.moe_config.dp_size > 1 and not self.quant_method.supports_internal_mk
806
+ # TODO(bnell): parts of the dispatch/combine steps will go away once
807
+ # #32567 lands and the remaining kernels are made MKs. The PCP
808
+ # code will probably remain
809
+ hidden_states, router_logits = self._maybe_dispatch(
810
+ layer,
811
+ hidden_states,
812
+ router_logits,
813
)
814
642
- ctx = get_forward_context()
643
- sp_ctx = (
644
- ctx.dp_metadata.sp_local_sizes(self.moe_config.sp_size)
645
- if ctx.dp_metadata
646
- else nullcontext()
815
+ shared_output, hidden_states = self._apply_quant_method(
816
+ layer=layer,
817
+ hidden_states=hidden_states,
818
+ router_logits=router_logits,
819
+ shared_input=shared_input,
820
+ run_shared_experts_before=run_shared_experts_before,
821
)
822
649
- with sp_ctx:
650
- # Run shared experts before matrix multiply.
651
- # because matrix multiply maybe modify the hidden_states.
652
- if has_separate_shared_experts and not use_shared_experts_stream:
653
- assert self.shared_experts is not None
654
- shared_input = (
655
- shared_input if shared_input is not None else hidden_states
656
- )
657
- shared_output = self.shared_experts(shared_input)
658
-
659
- # For naive dispatch/combine Dp/Ep, dispatch the hidden states and
660
- # router logits to all experts.
661
- # NOTE: this will be removed once all kernels are migrated into the
662
- # MoEKernel framework.
663
- if do_naive_dispatch_combine:
664
- hidden_states, router_logits = get_ep_group().dispatch_router_logits(
665
- hidden_states,
666
- router_logits,
667
- self.moe_config.is_sequence_parallel,
668
- )
669
-
670
- # NOTE: Similar with DP, PCP also needs dispatch and combine. For
671
- # simplicity, AgRsAll2All was added separately for PCP here. Maybe
672
- # we should modify All2AllManager abstract to better support PCP.
673
- if self.moe_config.pcp_size > 1:
674
- hidden_states = get_pcp_group().all_gather(
675
- hidden_states,
676
- dim=0,
677
- )
678
- router_logits = get_pcp_group().all_gather(
679
- router_logits,
680
- dim=0,
681
- )
682
-
683
- # Matrix multiply.
684
- if self.quant_method.is_monolithic:
685
- final_hidden_states = self.quant_method.apply_monolithic(
686
- layer=layer,
687
- x=hidden_states,
688
- router_logits=router_logits,
689
- )
690
- else:
691
- topk_weights, topk_ids = self.router.select_experts(
692
- hidden_states=hidden_states,
693
- router_logits=router_logits,
694
- )
695
-
696
- final_hidden_states = self.quant_method.apply(
697
- layer=layer,
698
- x=hidden_states,
699
- topk_weights=topk_weights,
700
- topk_ids=topk_ids,
701
- shared_experts_input=shared_input,
702
- )
703
-
704
- if has_separate_shared_experts:
705
- assert self.shared_experts is not None
706
-
707
- if use_shared_experts_stream:
708
- # Run shared experts in parallel on a separate stream
709
- # NOTE: We start the separate stream here and mark the
710
- # sync end point immediately after it is done. This is
711
- # important to avoid excessive stream allocations by the cuda
712
- # graph replay later.
713
- with torch.cuda.stream(self.shared_experts_stream):
714
- # Note that hidden_states clone() is necessary here to avoid
715
- # conflict with the main stream
716
- shared_output = self.shared_experts(shared_experts_input)
717
- current_stream().wait_stream(self.shared_experts_stream)
718
-
719
- final_hidden_states = (
720
- shared_output,
721
- final_hidden_states,
722
- )
723
-
724
- def combine_output(states: torch.Tensor) -> torch.Tensor:
725
- if do_naive_dispatch_combine:
726
- states = get_ep_group().combine(
727
- states, self.moe_config.is_sequence_parallel
728
- )
729
-
730
- if self.moe_config.pcp_size > 1:
731
- states = get_pcp_group().reduce_scatter(
732
- states,
733
- dim=0,
734
- )
735
-
736
- return states
737
-
738
- if self.shared_experts is not None:
739
- return (
740
- final_hidden_states[0],
741
- combine_output(final_hidden_states[1]),
742
- )
743
- else:
744
- return combine_output(final_hidden_states)
823
+ return self._maybe_combine(
824
+ shared_output,
825
+ hidden_states,
826
+ )