@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / 6d0734c562
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 15, 01:00 UTC

[NVIDIA] Add SM100 Flashinfer MoE blockscale fp8 backend for low latency (#20645)

Signed-off-by: kaixih <kaixih@nvidia.com> Signed-off-by: mgoin <mgoin64@gmail.com> Co-authored-by: mgoin <mgoin64@gmail.com>

Kaixi Hou committed Jul 19, 2025 at 02:33 UTC 6d0734c562e759fdb7076d762222b3881e62ab1f
6 files changed +187 -31
vllm/envs.py
+8 -3
@@ -119,7 +119,8 @@ if TYPE_CHECKING:
119 VLLM_TPU_BUCKET_PADDING_GAP: int = 0
120 VLLM_TPU_MOST_MODEL_LEN: Optional[int] = None
121 VLLM_USE_DEEP_GEMM: bool = False
122 - VLLM_USE_FLASHINFER_MOE: bool = False
122 + VLLM_USE_FLASHINFER_MOE_FP8: bool = False
123 + VLLM_USE_FLASHINFER_MOE_FP4: bool = False
124 VLLM_XGRAMMAR_CACHE_MB: int = 0
125 VLLM_MSGPACK_ZERO_COPY_THRESHOLD: int = 256
126 VLLM_ALLOW_INSECURE_SERIALIZATION: bool = False
@@ -854,9 +855,13 @@ environment_variables: dict[str, Callable[[], Any]] = {
855 "VLLM_USE_DEEP_GEMM":
856 lambda: bool(int(os.getenv("VLLM_USE_DEEP_GEMM", "0"))),
857
858 + # Allow use of FlashInfer MoE kernels for fused moe ops.
859 + "VLLM_USE_FLASHINFER_MOE_FP8":
860 + lambda: bool(int(os.getenv("VLLM_USE_FLASHINFER_MOE_FP8", "0"))),
861 +
862 # Allow use of FlashInfer CUTLASS kernels for fused moe ops.
858 - "VLLM_USE_FLASHINFER_MOE":
859 - lambda: bool(int(os.getenv("VLLM_USE_FLASHINFER_MOE", "0"))),
863 + "VLLM_USE_FLASHINFER_MOE_FP4":
864 + lambda: bool(int(os.getenv("VLLM_USE_FLASHINFER_MOE_FP4", "0"))),
865
866 # Control the cache sized used by the xgrammar compiler. The default
867 # of 512 MB should be enough for roughly 1000 JSON schemas.
vllm/model_executor/layers/fused_moe/config.py
+1 -1
@@ -191,7 +191,7 @@ class FusedMoEParallelConfig:
191
192 @property
193 def use_flashinfer_cutlass_kernels(self):
194 - return (envs.VLLM_USE_FLASHINFER_MOE
194 + return (envs.VLLM_USE_FLASHINFER_MOE_FP4
195 and has_flashinfer_cutlass_fused_moe())
196
197 @staticmethod
vllm/model_executor/layers/fused_moe/fused_moe.py
+99 -1
@@ -28,7 +28,7 @@ from vllm.model_executor.layers.fused_moe.prepare_finalize import (
28 from vllm.model_executor.layers.fused_moe.topk_weight_and_reduce import (
29 TopKWeightAndReduceNoOP)
30 from vllm.model_executor.layers.fused_moe.utils import (
31 - _resize_cache, moe_kernel_quantize_input)
31 + _resize_cache, moe_kernel_quantize_input, per_token_group_quant_fp8)
32 from vllm.model_executor.layers.quantization.utils.mxfp4_utils import (
33 dequant_mxfp4)
34 from vllm.platforms import current_platform
@@ -1061,6 +1061,104 @@ direct_register_custom_op(
1061 )
1062
1063
1064 +def next_positive_power_of_2(x: int) -> int:
1065 + if x < 1:
1066 + return 1
1067 + return 1 << (x - 1).bit_length()
1068 +
1069 +
1070 +def _get_tile_tokens_dim(num_tokens, top_k, num_experts):
1071 + # Guess tokens per expert assuming perfect expert distribution first.
1072 + num_tokens_per_expert = (num_tokens * top_k) // num_experts
1073 + # And pad the number to the next power of 2.
1074 + tile_tokens_dim = next_positive_power_of_2(num_tokens_per_expert)
1075 + # Cap to 8-64 tokens per CTA tile as it's the range supported by the kernel.
1076 + tile_tokens_dim = min(max(tile_tokens_dim, 8), 64)
1077 + return tile_tokens_dim
1078 +
1079 +
1080 +def flashinfer_fused_moe_blockscale_fp8(
1081 + routing_logits: torch.Tensor,
1082 + routing_bias: torch.Tensor,
1083 + x: torch.Tensor,
1084 + w13_weight: torch.Tensor,
1085 + w13_weight_scale_inv: torch.Tensor,
1086 + w2_weight: torch.Tensor,
1087 + w2_weight_scale_inv: torch.Tensor,
1088 + global_num_experts: int,
1089 + top_k: int,
1090 + num_expert_group: int,
1091 + topk_group: int,
1092 + intermediate_size: int,
1093 + expert_offset: int,
1094 + local_num_experts: int,
1095 + block_shape: list[int],
1096 + routed_scaling: float = 1.0) -> torch.Tensor:
1097 + from vllm.utils.flashinfer import flashinfer_trtllm_fp8_block_scale_moe
1098 + assert top_k <= global_num_experts
1099 + assert top_k <= 8
1100 + assert topk_group <= 4
1101 + assert global_num_experts > num_expert_group
1102 + assert global_num_experts % num_expert_group == 0
1103 + assert global_num_experts % 4 == 0
1104 + assert top_k < (topk_group * global_num_experts / num_expert_group)
1105 + assert block_shape == [128, 128]
1106 +
1107 + a_q, a_sf = per_token_group_quant_fp8(x, block_shape[1])
1108 + # NOTE: scales of hidden states have to be transposed!
1109 + a_sf_t = a_sf.t().contiguous()
1110 + return flashinfer_trtllm_fp8_block_scale_moe(
1111 + routing_logits=routing_logits,
1112 + routing_bias=routing_bias,
1113 + hidden_states=a_q,
1114 + hidden_states_scale=a_sf_t,
1115 + gemm1_weights=w13_weight,
1116 + gemm1_weights_scale=w13_weight_scale_inv,
1117 + gemm2_weights=w2_weight,
1118 + gemm2_weights_scale=w2_weight_scale_inv,
1119 + num_experts=global_num_experts,
1120 + top_k=top_k,
1121 + n_group=num_expert_group,
1122 + topk_group=topk_group,
1123 + intermediate_size=intermediate_size,
1124 + local_expert_offset=expert_offset,
1125 + local_num_experts=local_num_experts,
1126 + routed_scaling_factor=routed_scaling,
1127 + tile_tokens_dim=_get_tile_tokens_dim(x.shape[0], top_k,
1128 + global_num_experts),
1129 + routing_method_type=2, # DeepSeek-styled routing method
1130 + )
1131 +
1132 +
1133 +def flashinfer_fused_moe_blockscale_fp8_fake(
1134 + routing_logits: torch.Tensor,
1135 + routing_bias: torch.Tensor,
1136 + x: torch.Tensor,
1137 + w13_weight: torch.Tensor,
1138 + w13_weight_scale_inv: torch.Tensor,
1139 + w2_weight: torch.Tensor,
1140 + w2_weight_scale_inv: torch.Tensor,
1141 + global_num_experts: int,
1142 + top_k: int,
1143 + num_expert_group: int,
1144 + topk_group: int,
1145 + intermediate_size: int,
1146 + expert_offset: int,
1147 + local_num_experts: int,
1148 + block_shape: list[int],
1149 + routed_scaling: float = 1.0) -> torch.Tensor:
1150 + return torch.empty_like(x)
1151 +
1152 +
1153 +direct_register_custom_op(
1154 + op_name="flashinfer_fused_moe_blockscale_fp8",
1155 + op_func=flashinfer_fused_moe_blockscale_fp8,
1156 + mutates_args=[],
1157 + fake_impl=flashinfer_fused_moe_blockscale_fp8_fake,
1158 + tags=(torch.Tag.needs_fixed_stride_order, ),
1159 +)
1160 +
1161 +
1162 def outplace_fused_experts(
1163 hidden_states: torch.Tensor,
1164 w1: torch.Tensor,
vllm/model_executor/layers/quantization/fp8.py
+63 -19
@@ -43,6 +43,7 @@ from vllm.platforms import current_platform
43 from vllm.scalar_type import scalar_types
44 from vllm.utils import has_deep_gemm
45 from vllm.utils.deep_gemm import is_blackwell_deep_gemm_used
46 +from vllm.utils.flashinfer import has_flashinfer_moe
47
48 if TYPE_CHECKING:
49 from vllm.model_executor.models.utils import WeightsMapper
@@ -52,6 +53,11 @@ ACTIVATION_SCHEMES = ["static", "dynamic"]
53 logger = init_logger(__name__)
54
55
56 +def _swap_w13_to_w31(x: torch.Tensor) -> torch.Tensor:
57 + return x.reshape(-1, 2, x.shape[-2] // 2,
58 + x.shape[-1]).flip(dims=[1]).reshape(x.shape)
59 +
60 +
61 def _is_col_major(x: torch.Tensor) -> bool:
62 assert x.dim() == 3
63 b, m, n = x.shape
@@ -473,6 +479,11 @@ class Fp8MoEMethod(FusedMoEMethodBase):
479 self.quant_config = quant_config
480 self.block_quant = self.quant_config.weight_block_size is not None
481
482 + self.flashinfer_moe_enabled = False
483 + if envs.VLLM_USE_FLASHINFER_MOE_FP8 and has_flashinfer_moe():
484 + logger.info_once(
485 + "Using FlashInfer MoE FP8 kernels for Fp8MoEMethod.")
486 + self.flashinfer_moe_enabled = True
487 # For GPUs that lack FP8 hardware support, we can leverage the Marlin
488 # kernel for fast weight-only FP8 quantization
489 self.use_marlin = (not current_platform.has_device_capability(89)
@@ -674,6 +685,14 @@ class Fp8MoEMethod(FusedMoEMethodBase):
685 normalize_e4m3fn_to_e4m3fnuz(
686 layer.w2_weight, layer.w2_weight_scale_inv,
687 layer.w2_input_scale)
688 + elif self.flashinfer_moe_enabled:
689 + # NOTE: weights have to be swapped since the activation is
690 + # applied on different half for flashinfer vs vllm
691 + w13_weight = _swap_w13_to_w31(layer.w13_weight.data)
692 + w13_weight_scale_inv = _swap_w13_to_w31(
693 + layer.w13_weight_scale_inv.data)
694 + w2_weight = layer.w2_weight.data
695 + w2_weight_scale_inv = layer.w2_weight_scale_inv.data
696 else:
697 w13_weight = layer.w13_weight.data
698 w13_weight_scale_inv = layer.w13_weight_scale_inv.data
@@ -915,25 +934,25 @@ class Fp8MoEMethod(FusedMoEMethodBase):
934 assert logical_to_physical_map is not None
935 assert logical_replica_count is not None
936 assert isinstance(layer, FusedMoE)
918 -
919 - topk_weights, topk_ids = FusedMoE.select_experts(
920 - hidden_states=x,
921 - router_logits=router_logits,
922 - use_grouped_topk=use_grouped_topk,
923 - top_k=top_k,
924 - renormalize=renormalize,
925 - topk_group=topk_group,
926 - num_expert_group=num_expert_group,
927 - custom_routing_function=custom_routing_function,
928 - scoring_func=scoring_func,
929 - e_score_correction_bias=e_score_correction_bias,
930 - indices_type=self.topk_indices_dtype,
931 - enable_eplb=enable_eplb,
932 - expert_map=expert_map,
933 - expert_load_view=expert_load_view,
934 - logical_to_physical_map=logical_to_physical_map,
935 - logical_replica_count=logical_replica_count,
936 - )
937 + if not self.flashinfer_moe_enabled:
938 + topk_weights, topk_ids = FusedMoE.select_experts(
939 + hidden_states=x,
940 + router_logits=router_logits,
941 + use_grouped_topk=use_grouped_topk,
942 + top_k=top_k,
943 + renormalize=renormalize,
944 + topk_group=topk_group,
945 + num_expert_group=num_expert_group,
946 + custom_routing_function=custom_routing_function,
947 + scoring_func=scoring_func,
948 + e_score_correction_bias=e_score_correction_bias,
949 + indices_type=self.topk_indices_dtype,
950 + enable_eplb=enable_eplb,
951 + expert_map=expert_map,
952 + expert_load_view=expert_load_view,
953 + logical_to_physical_map=logical_to_physical_map,
954 + logical_replica_count=logical_replica_count,
955 + )
956
957 if self.rocm_aiter_moe_enabled:
958 from vllm.model_executor.layers.fused_moe.rocm_aiter_fused_moe import ( # noqa: E501
@@ -971,6 +990,31 @@ class Fp8MoEMethod(FusedMoEMethodBase):
990 apply_router_weight_on_input=apply_router_weight_on_input,
991 global_num_experts=global_num_experts,
992 expert_map=expert_map)
993 + elif self.flashinfer_moe_enabled:
994 + # Currently only work with DS models
995 + assert self.block_quant
996 + assert (renormalize and use_grouped_topk
997 + and scoring_func == 'sigmoid'
998 + and custom_routing_function is None)
999 + assert activation == "silu"
1000 + return torch.ops.vllm.flashinfer_fused_moe_blockscale_fp8(
1001 + routing_logits=router_logits.to(torch.float32),
1002 + routing_bias=e_score_correction_bias,
1003 + x=x,
1004 + w13_weight=layer.w13_weight,
1005 + w13_weight_scale_inv=layer.w13_weight_scale_inv,
1006 + w2_weight=layer.w2_weight,
1007 + w2_weight_scale_inv=layer.w2_weight_scale_inv,
1008 + global_num_experts=global_num_experts,
1009 + top_k=top_k,
1010 + num_expert_group=num_expert_group,
1011 + topk_group=topk_group,
1012 + intermediate_size=layer.intermediate_size_per_partition,
1013 + expert_offset=layer.ep_rank * layer.local_num_experts,
1014 + local_num_experts=layer.local_num_experts,
1015 + block_shape=self.quant_config.weight_block_size,
1016 + routed_scaling=1.0,
1017 + )
1018 else:
1019 return self.fused_experts(
1020 hidden_states=x,
vllm/model_executor/layers/quantization/modelopt.py
+4 -5
@@ -721,7 +721,7 @@ class ModelOptNvFp4FusedMoE(FusedMoEMethodBase):
721 self.use_marlin = False
722 self.allow_flashinfer_cutlass = False
723
724 - if envs.VLLM_USE_FLASHINFER_MOE:
724 + if envs.VLLM_USE_FLASHINFER_MOE_FP4:
725 if self.cutlass_nvfp4_supported and current_platform.is_cuda() \
726 and current_platform.is_device_capability(100):
727 logger.info_once(
@@ -800,10 +800,9 @@ class ModelOptNvFp4FusedMoE(FusedMoEMethodBase):
800 assert moe.dp_size > 1
801 logger.debug_once("Using CutlassExpertsFp4")
802 # Currently CutlassExpertsFp4 doesn't support DP
803 - raise ValueError(
804 - "CutlassExpertsFp4 doesn't support DP. "
805 - "Use flashinfer CUTLASS FusedMoE(VLLM_USE_FLASHINFER_MOE)"
806 - " backend instead.")
803 + raise ValueError("CutlassExpertsFp4 doesn't support DP. "
804 + "Use flashinfer CUTLASS FusedMoE backend instead "
805 + "(set VLLM_USE_FLASHINFER_MOE_FP4=1)")
806
807 return experts
808
vllm/utils/flashinfer.py
+12 -2
@@ -64,6 +64,8 @@ def _lazy_import_wrapper(module_name: str,
64
65
66 # Create lazy wrappers for each function
67 +flashinfer_trtllm_fp8_block_scale_moe = _lazy_import_wrapper(
68 + "flashinfer.fused_moe", "trtllm_fp8_block_scale_moe")
69 flashinfer_cutlass_fused_moe = _lazy_import_wrapper("flashinfer.fused_moe",
70 "cutlass_fused_moe")
71 fp4_quantize = _lazy_import_wrapper("flashinfer", "fp4_quantize")
@@ -77,10 +79,16 @@ autotune = _lazy_import_wrapper(
79 fallback_fn=lambda *args, **kwargs: contextlib.nullcontext())
80
81
82 +@functools.cache
83 +def has_flashinfer_moe() -> bool:
84 + """Return ``True`` if FlashInfer MoE module is available."""
85 + return importlib.util.find_spec("flashinfer.fused_moe") is not None
86 +
87 +
88 @functools.cache
89 def has_flashinfer_cutlass_fused_moe() -> bool:
90 """Return ``True`` if FlashInfer CUTLASS fused MoE is available."""
83 - if not has_flashinfer():
91 + if not has_flashinfer_moe():
92 return False
93
94 # Check if all required functions are available
@@ -99,9 +107,11 @@ def has_flashinfer_cutlass_fused_moe() -> bool:
107
108 __all__ = [
109 "has_flashinfer",
102 - "has_flashinfer_cutlass_fused_moe",
110 + "flashinfer_trtllm_fp8_block_scale_moe",
111 "flashinfer_cutlass_fused_moe",
112 "fp4_quantize",
113 "fp4_swizzle_blockscale",
114 "autotune",
115 + "has_flashinfer_moe",
116 + "has_flashinfer_cutlass_fused_moe",
117 ]