@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / 268b1c55ad
Fork 0

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 10, 19:00 UTC

[MoE Refactor][13/N] Convert FI to Use PFNoEP (#31533)

Signed-off-by: Robert Shaw <robshaw@redhat.com> Signed-off-by: Robert Shaw <robertgshaw2@gmail.com> Signed-off-by: Robert Shaw <114415538+robertgshaw2-redhat@users.noreply.github.com> Co-authored-by: Robert Shaw <robshaw@redhat.com> Co-authored-by: Wentao Ye <44945378+yewentao256@users.noreply.github.com>

Robert Shaw committed Jan 3, 2026 at 15:26 UTC 268b1c55ade396f9b4b7c811d66725a4c9b74590
7 files changed +78 -168
vllm/model_executor/layers/fused_moe/all2all_utils.py
+13 -3
@@ -15,6 +15,9 @@ from vllm.model_executor.layers.fused_moe.config import (
15 from vllm.model_executor.layers.fused_moe.modular_kernel import (
16 FusedMoEPrepareAndFinalize,
17 )
18 +from vllm.model_executor.layers.quantization.utils.flashinfer_utils import (
19 + build_flashinfer_fp8_cutlass_moe_prepare_finalize,
20 +)
21 from vllm.platforms import current_platform
22 from vllm.utils.import_utils import has_deep_ep, has_pplx
23
@@ -77,10 +80,17 @@ def maybe_make_prepare_finalize(
80
81 prepare_finalize: FusedMoEPrepareAndFinalize | None = None
82
80 - # TODO: could allow this now
81 - assert not moe.use_flashinfer_cutlass_kernels, "Must be created in modelopt.py"
83 + if moe.use_flashinfer_cutlass_kernels:
84 + assert quant_config is not None
85 + use_deepseek_fp8_block_scale = (
86 + quant_config is not None and quant_config.is_block_quantized
87 + )
88 + prepare_finalize = build_flashinfer_fp8_cutlass_moe_prepare_finalize(
89 + moe=moe,
90 + use_deepseek_fp8_block_scale=use_deepseek_fp8_block_scale,
91 + )
92
83 - if moe.use_pplx_kernels:
93 + elif moe.use_pplx_kernels:
94 assert quant_config is not None
95
96 hidden_dim_bytes, hidden_scale_bytes = pplx_hidden_dim_scale_bytes(
vllm/model_executor/layers/fused_moe/flashinfer_cutlass_prepare_finalize.py
+17 -5
@@ -10,6 +10,9 @@ from vllm.distributed.device_communicators.base_device_communicator import (
10 )
11 from vllm.forward_context import get_forward_context
12 from vllm.model_executor.layers.fused_moe.config import FusedMoEQuantConfig
13 +from vllm.model_executor.layers.fused_moe.prepare_finalize import (
14 + MoEPrepareAndFinalizeNoEP,
15 +)
16 from vllm.model_executor.layers.fused_moe.topk_weight_and_reduce import (
17 TopKWeightAndReduceNoOP,
18 )
@@ -349,14 +352,23 @@ def create_flashinfer_prepare_finalize(
352 use_nvfp4: bool = False,
353 enable_alltoallv: bool = False,
354 use_deepseek_fp8_block_scale: bool = False,
352 -) -> FlashInferCutlassMoEPrepareAndFinalize:
355 +) -> FlashInferCutlassMoEPrepareAndFinalize | MoEPrepareAndFinalizeNoEP:
356 """Factory function to create the appropriate FlashInfer implementation."""
357 + # TODO(rob): migrate non-DP cases to MoEPrepareAndFinalizeNoEP
358 + # once we complete the FP8 refactor.
359 if use_nvfp4:
360 if enable_alltoallv:
361 return FlashInferAllToAllMoEPrepareAndFinalize(use_dp)
362 else:
363 return FlashInferAllGatherMoEPrepareAndFinalize(use_dp)
359 - # FP8 path currently supported via AllGather; optionally enable block-scale
360 - return FlashInferAllGatherMoEPrepareAndFinalize(
361 - use_dp=use_dp, use_deepseek_fp8_block_scale=use_deepseek_fp8_block_scale
362 - )
364 +
365 + # FP8 DP path currently supported via AllGather.
366 + if use_dp:
367 + return FlashInferAllGatherMoEPrepareAndFinalize(
368 + use_dp=True,
369 + use_deepseek_fp8_block_scale=use_deepseek_fp8_block_scale,
370 + )
371 + else:
372 + # NOTE(rob): CUTLASS FP8 block quant executes the input
373 + # quantzation and grouped gemm in a single kernel.
374 + return MoEPrepareAndFinalizeNoEP(defer_input_quant=use_deepseek_fp8_block_scale)
vllm/model_executor/layers/fused_moe/fused_moe_modular_method.py
-1
@@ -49,7 +49,6 @@ class FusedMoEModularMethod(FusedMoEMethodBase, CustomOp):
49 prepare_finalize,
50 old_quant_method.select_gemm_impl(prepare_finalize, moe_layer),
51 shared_experts,
52 - getattr(moe_layer, "shared_experts_stream", None),
52 moe_parallel_config=moe_layer.moe_parallel_config,
53 ),
54 )
vllm/model_executor/layers/fused_moe/layer.py
+2 -2
@@ -356,14 +356,14 @@ class FusedMoE(CustomOp):
356 # TODO: Remove this after more extensive testings with TP/DP
357 # and other execution modes
358 if envs.VLLM_DISABLE_SHARED_EXPERTS_STREAM:
359 - logger.info_once("Disabling MoE shared_experts cuda stream")
359 + logger.debug_once("Disabling MoE shared_experts cuda stream", scope="local")
360 self.shared_experts_stream = None
361 else:
362 # TODO(rob): enable shared expert overlap with non-cuda-alike.
363 # aux_stream() returns None on non-cuda-alike platforms.
364 self.shared_experts_stream = aux_stream()
365 if self.shared_experts_stream is not None:
366 - logger.info_once(
366 + logger.debug_once(
367 "Enabled separate cuda stream for MoE shared_experts", scope="local"
368 )
369
vllm/model_executor/layers/fused_moe/modular_kernel.py
+5 -75
@@ -21,7 +21,6 @@ from vllm.model_executor.layers.fused_moe.utils import (
21 count_expert_num_tokens,
22 disable_inplace,
23 )
24 -from vllm.platforms import current_platform
24 from vllm.utils.math_utils import cdiv
25 from vllm.v1.worker.ubatching import (
26 dbo_enabled,
@@ -682,14 +681,12 @@ class FusedMoEModularKernel(torch.nn.Module):
681 prepare_finalize: FusedMoEPrepareAndFinalize,
682 fused_experts: FusedMoEPermuteExpertsUnpermute,
683 shared_experts: torch.nn.Module | None = None,
685 - shared_experts_stream: torch.cuda.Stream | None = None,
684 moe_parallel_config: FusedMoEParallelConfig | None = None,
685 ):
686 super().__init__()
687 self.prepare_finalize = prepare_finalize
688 self.fused_experts = fused_experts
689 self.shared_experts = shared_experts
692 - self.shared_experts_stream = shared_experts_stream
690
691 # prefer an explicit FusedMoEParallelConfig when available (from
692 # FusedMoE layers / tests).
@@ -904,34 +901,6 @@ class FusedMoEModularKernel(torch.nn.Module):
901 expert_num_tokens_cpu=c_expert_num_tokens_cpu,
902 )
903
907 - def _maybe_setup_shared_experts_stream(
908 - self, hidden_states: torch.Tensor
909 - ) -> tuple[bool, torch.Tensor | None]:
910 - # decide whether to run shared experts on a separate CUDA stream to
911 - # overlap with the main fused MoE kernel.
912 - use_shared_experts_stream = (
913 - self.shared_experts is not None
914 - and self.shared_experts_stream is not None
915 - and hidden_states.is_cuda
916 - and (
917 - hidden_states.shape[0]
918 - <= envs.VLLM_SHARED_EXPERTS_STREAM_TOKEN_THRESHOLD
919 - )
920 - )
921 -
922 - hidden_states_clone: torch.Tensor | None = None
923 - if use_shared_experts_stream and self.shared_experts_stream is not None:
924 - # TODO: Optimize this (complicated)
925 - # Note: this clone adds overhead but is required
926 - # for correctness with multiple CUDA streams and CUDA graph capture.
927 - hidden_states_clone = hidden_states.clone()
928 - # record that the clone will be used by the separate stream so its
929 - # lifetime is correctly tracked.
930 - hidden_states_clone.record_stream(self.shared_experts_stream)
931 - self.shared_experts_stream.wait_stream(torch.cuda.current_stream())
932 -
933 - return use_shared_experts_stream, hidden_states_clone
934 -
904 def _prepare(
905 self,
906 hidden_states: torch.Tensor,
@@ -1119,30 +1088,12 @@ class FusedMoEModularKernel(torch.nn.Module):
1088 topk_weights: torch.Tensor,
1089 topk_ids: torch.Tensor,
1090 apply_router_weight_on_input: bool,
1122 - hidden_states_clone: torch.Tensor | None = None,
1123 - use_shared_experts_stream: bool = False,
1091 ) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]:
1092 """
1093 The _finalize method is a wrapper around self.prepare_finalize.finalize
1094 that handles DBO, async and shared expert overlap.
1095 """
1129 -
1130 - def maybe_run_shared_experts() -> torch.Tensor | None:
1131 - if self.shared_experts is None:
1132 - return None
1133 -
1134 - if (
1135 - not use_shared_experts_stream
1136 - or self.shared_experts_stream is not None
1137 - and (not hidden_states.is_cuda or not torch.cuda.is_available())
1138 - ):
1139 - # fall back to running on the current stream
1140 - return self.shared_experts(hidden_states)
1141 -
1142 - assert hidden_states_clone is not None
1143 - # launch shared experts on the dedicated stream.
1144 - with torch.cuda.stream(self.shared_experts_stream):
1145 - return self.shared_experts(hidden_states_clone)
1096 + shared_output: torch.Tensor | None = None
1097
1098 if not self.prepare_finalize.supports_async():
1099 assert not dbo_enabled()
@@ -1155,7 +1106,8 @@ class FusedMoEModularKernel(torch.nn.Module):
1106 apply_router_weight_on_input,
1107 self.fused_experts.finalize_weight_and_reduce_impl(),
1108 )
1158 - shared_output = maybe_run_shared_experts()
1109 + if self.shared_experts is not None:
1110 + shared_output = self.shared_experts(hidden_states)
1111 else:
1112 finalize_ret = self.prepare_finalize.finalize_async(
1113 output,
@@ -1165,8 +1117,8 @@ class FusedMoEModularKernel(torch.nn.Module):
1117 apply_router_weight_on_input,
1118 self.fused_experts.finalize_weight_and_reduce_impl(),
1119 )
1168 -
1169 - shared_output = maybe_run_shared_experts()
1120 + if self.shared_experts is not None:
1121 + shared_output = self.shared_experts(hidden_states)
1122
1123 # TODO(lucas): refactor this in the alternative schedules followup
1124 # currently unpack if we have hook + receiver pair or just
@@ -1189,28 +1141,12 @@ class FusedMoEModularKernel(torch.nn.Module):
1141
1142 receiver()
1143
1192 - self._wait_for_shared_experts_stream(hidden_states, use_shared_experts_stream)
1193 -
1144 if self.shared_experts is None:
1145 return output
1146 else:
1147 assert shared_output is not None
1148 return shared_output, output
1149
1200 - def _wait_for_shared_experts_stream(
1201 - self, hidden_states: torch.Tensor, use_shared_experts_stream: bool
1202 - ) -> None:
1203 - # ensure that any work enqueued on the shared_experts_stream is
1204 - # completed before the shared_output tensor is consumed
1205 - if (
1206 - self.shared_experts is not None
1207 - and use_shared_experts_stream
1208 - and self.shared_experts_stream is not None
1209 - and hidden_states.is_cuda
1210 - and current_platform.is_cuda()
1211 - ):
1212 - torch.cuda.current_stream().wait_stream(self.shared_experts_stream)
1213 -
1150 def forward(
1151 self,
1152 hidden_states: torch.Tensor,
@@ -1257,10 +1193,6 @@ class FusedMoEModularKernel(torch.nn.Module):
1193 else:
1194 output = torch.zeros_like(hidden_states)
1195
1260 - use_shared_experts_stream, hidden_states_clone = (
1261 - self._maybe_setup_shared_experts_stream(hidden_states)
1262 - )
1263 -
1196 local_num_experts = w1.size(0)
1197 if global_num_experts == -1:
1198 global_num_experts = local_num_experts
@@ -1297,6 +1229,4 @@ class FusedMoEModularKernel(torch.nn.Module):
1229 topk_weights,
1230 topk_ids,
1231 apply_router_weight_on_input,
1300 - hidden_states_clone=hidden_states_clone,
1301 - use_shared_experts_stream=use_shared_experts_stream,
1232 )
vllm/model_executor/layers/quantization/fp8.py
+40 -73
@@ -48,7 +48,6 @@ from vllm.model_executor.layers.quantization.kv_cache import BaseKVCacheMethod
48 from vllm.model_executor.layers.quantization.utils.flashinfer_utils import (
49 FlashinferMoeBackend,
50 apply_flashinfer_per_tensor_scale_fp8,
51 - build_flashinfer_fp8_cutlass_moe_prepare_finalize,
51 get_flashinfer_moe_backend,
52 register_moe_scaling_factors,
53 rotate_flashinfer_fp8_moe_weights,
@@ -973,27 +972,33 @@ class Fp8MoEMethod(FusedMoEMethodBase):
972 # done, then we will initialzie the TP case and DP/EP case
973 # via the same code path (i.e. via maybe_init_modular_kernel).
974 # NOTE(rob): in progress migrating all into this format.
976 - if self.fp8_backend == Fp8MoeBackend.FLASHINFER_CUTLASS:
977 - from vllm.model_executor.layers.fused_moe.flashinfer_cutlass_moe import (
978 - FlashInferExperts,
979 - )
980 - from vllm.model_executor.layers.fused_moe.flashinfer_cutlass_prepare_finalize import ( # noqa: E501
981 - FlashInferAllGatherMoEPrepareAndFinalize,
982 - )
975
984 - config = self.get_fused_moe_quant_config(layer)
985 - assert config is not None
986 - self.moe_quant_config = config
976 + from vllm.model_executor.layers.fused_moe import (
977 + TritonOrDeepGemmExperts,
978 + )
979 + from vllm.model_executor.layers.fused_moe.flashinfer_cutlass_moe import (
980 + FlashInferExperts,
981 + )
982 + from vllm.model_executor.layers.fused_moe.fused_marlin_moe import (
983 + MarlinExperts,
984 + )
985 + from vllm.model_executor.layers.fused_moe.prepare_finalize import (
986 + MoEPrepareAndFinalizeNoEP,
987 + )
988 + from vllm.model_executor.layers.fused_moe.rocm_aiter_fused_moe import (
989 + AiterExperts,
990 + )
991 +
992 + self.moe_quant_config = self.get_fused_moe_quant_config(layer)
993 + assert self.moe_quant_config is not None
994 + self.use_inplace = True
995
996 + if self.fp8_backend == Fp8MoeBackend.FLASHINFER_CUTLASS:
997 self.kernel = mk.FusedMoEModularKernel(
989 - # TODO(rob): we can use the generic MoEPrepareAndFinalizeNoEP
990 - # with the changes to defer input quantization
991 - FlashInferAllGatherMoEPrepareAndFinalize(
992 - use_dp=(self.moe.dp_size > 1),
993 - use_deepseek_fp8_block_scale=self.block_quant,
994 - ),
998 + # TODO: make defer_input_quant an attr of the FlashInferExperts
999 + MoEPrepareAndFinalizeNoEP(defer_input_quant=self.block_quant),
1000 FlashInferExperts(
996 - out_dtype=torch.get_default_dtype(),
1001 + out_dtype=layer.orig_dtype,
1002 quant_config=self.moe_quant_config,
1003 ep_rank=self.moe.ep_rank,
1004 ep_size=self.moe.ep_size,
@@ -1005,50 +1010,26 @@ class Fp8MoEMethod(FusedMoEMethodBase):
1010 )
1011 self.use_inplace = False
1012
1008 - elif self.fp8_backend in [
1009 - Fp8MoeBackend.DEEPGEMM,
1010 - Fp8MoeBackend.TRITON,
1011 - Fp8MoeBackend.MARLIN,
1012 - Fp8MoeBackend.AITER,
1013 - ]:
1014 - from vllm.model_executor.layers.fused_moe import (
1015 - TritonOrDeepGemmExperts,
1016 - )
1017 - from vllm.model_executor.layers.fused_moe.fused_marlin_moe import (
1018 - MarlinExperts,
1013 + elif self.fp8_backend == Fp8MoeBackend.AITER:
1014 + self.kernel = mk.FusedMoEModularKernel(
1015 + # TODO: make defer_input_quant an attr of the AiterExperts
1016 + MoEPrepareAndFinalizeNoEP(defer_input_quant=True),
1017 + AiterExperts(quant_config=self.moe_quant_config),
1018 )
1020 - from vllm.model_executor.layers.fused_moe.prepare_finalize import (
1021 - MoEPrepareAndFinalizeNoEP,
1019 + elif self.fp8_backend == Fp8MoeBackend.MARLIN:
1020 + self.kernel = mk.FusedMoEModularKernel(
1021 + MoEPrepareAndFinalizeNoEP(),
1022 + MarlinExperts(quant_config=self.moe_quant_config),
1023 )
1023 - from vllm.model_executor.layers.fused_moe.rocm_aiter_fused_moe import (
1024 - AiterExperts,
1024 + else:
1025 + self.kernel = mk.FusedMoEModularKernel(
1026 + MoEPrepareAndFinalizeNoEP(),
1027 + TritonOrDeepGemmExperts(
1028 + quant_config=self.moe_quant_config,
1029 + allow_deep_gemm=(self.fp8_backend == Fp8MoeBackend.DEEPGEMM),
1030 + ),
1031 )
1032
1027 - config = self.get_fused_moe_quant_config(layer)
1028 - assert config is not None
1029 - self.moe_quant_config = config
1030 -
1031 - if self.fp8_backend == Fp8MoeBackend.AITER:
1032 - self.kernel = mk.FusedMoEModularKernel(
1033 - # TODO: make defer_input_quant an attr of the AiterExperts
1034 - MoEPrepareAndFinalizeNoEP(defer_input_quant=True),
1035 - AiterExperts(quant_config=self.moe_quant_config),
1036 - )
1037 - elif self.fp8_backend == Fp8MoeBackend.MARLIN:
1038 - self.kernel = mk.FusedMoEModularKernel(
1039 - MoEPrepareAndFinalizeNoEP(),
1040 - MarlinExperts(quant_config=self.moe_quant_config),
1041 - )
1042 - else:
1043 - self.kernel = mk.FusedMoEModularKernel(
1044 - MoEPrepareAndFinalizeNoEP(),
1045 - TritonOrDeepGemmExperts(
1046 - quant_config=self.moe_quant_config,
1047 - allow_deep_gemm=(self.fp8_backend == Fp8MoeBackend.DEEPGEMM),
1048 - ),
1049 - )
1050 - self.use_inplace = True
1051 -
1033 def process_weights_after_loading(self, layer: Module) -> None:
1034 if getattr(layer, "_already_called_process_weights_after_loading", False):
1035 return
@@ -1121,21 +1102,7 @@ class Fp8MoEMethod(FusedMoEMethodBase):
1102 or self.flashinfer_moe_backend == FlashinferMoeBackend.TENSORRT_LLM
1103 ):
1104 return None
1124 - elif self.flashinfer_moe_backend == FlashinferMoeBackend.CUTLASS:
1125 - if self.block_quant:
1126 - assert self.weight_block_size == [128, 128], (
1127 - f"Only support weight_block_size == [128, 128], "
1128 - f"got {self.weight_block_size}"
1129 - )
1130 - # Wire block-scale flag through prepare/finalize when using CUTLASS
1131 - prepare_finalize = build_flashinfer_fp8_cutlass_moe_prepare_finalize(
1132 - self.moe,
1133 - use_deepseek_fp8_block_scale=self.block_quant,
1134 - )
1135 - logger.debug_once("%s", prepare_finalize.__class__.__name__)
1136 - return prepare_finalize
1137 - else:
1138 - return super().maybe_make_prepare_finalize(routing_tables)
1105 + return super().maybe_make_prepare_finalize(routing_tables)
1106
1107 def select_gemm_impl(
1108 self,
vllm/model_executor/layers/quantization/modelopt.py
+1 -9
@@ -46,7 +46,6 @@ from vllm.model_executor.layers.quantization.utils.flashinfer_fp4_moe import (
46 from vllm.model_executor.layers.quantization.utils.flashinfer_utils import (
47 FlashinferMoeBackend,
48 apply_flashinfer_per_tensor_scale_fp8,
49 - build_flashinfer_fp8_cutlass_moe_prepare_finalize,
49 flashinfer_cutlass_moe_fp8,
50 get_flashinfer_moe_backend,
51 is_flashinfer_supporting_global_sf,
@@ -751,14 +750,7 @@ class ModelOptFp8MoEMethod(FusedMoEMethodBase):
750 # TRT LLM not supported with all2all yet.
751 if self.flashinfer_moe_backend == FlashinferMoeBackend.TENSORRT_LLM:
752 return None
754 - elif self.flashinfer_moe_backend == FlashinferMoeBackend.CUTLASS:
755 - prepare_finalize = build_flashinfer_fp8_cutlass_moe_prepare_finalize(
756 - self.moe
757 - )
758 - logger.debug_once("%s", prepare_finalize.__class__.__name__)
759 - return prepare_finalize
760 - else:
761 - return super().maybe_make_prepare_finalize(routing_tables)
753 + return super().maybe_make_prepare_finalize(routing_tables)
754
755 def select_gemm_impl(
756 self,