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, 19:30 UTC
[MoE Refactor] Migrate ModelOptMxFp8FusedMoE to oracle (#42768)
Signed-off-by: Bill Nell <bnell@redhat.com> Co-authored-by: Robert Shaw <114415538+robertgshaw2-redhat@users.noreply.github.com>
bnellnm committed
May 26, 2026 at 11:14 UTC
b226ddacfda921b8eef7b75794c33d49ae51186a
8 files changed
+114
-130
vllm/model_executor/layers/fused_moe/config.py
+19
-4
@@ -888,20 +888,33 @@ def int4_w4a16_moe_quant_config(
888
def fp8_w8a16_moe_quant_config(
889
w1_scale: torch.Tensor,
890
w2_scale: torch.Tensor,
891
+ w1_bias: torch.Tensor | None = None,
892
+ w2_bias: torch.Tensor | None = None,
893
block_shape: list[int] | None = None,
894
) -> FusedMoEQuantConfig:
895
"""
896
Construct a quant config for 16-bit float activations and fp8 weights.
897
"""
898
group_shape = GroupShape(*block_shape) if block_shape is not None else None
899
+ fp8_dtype = current_platform.fp8_dtype()
900
return FusedMoEQuantConfig(
901
_a1=FusedMoEQuantDesc(),
902
_a2=FusedMoEQuantDesc(),
903
_w1=FusedMoEQuantDesc(
901
- current_platform.fp8_dtype(), group_shape, w1_scale, None, None
904
+ fp8_dtype,
905
+ group_shape,
906
+ w1_scale,
907
+ None,
908
+ None,
909
+ w1_bias,
910
),
911
_w2=FusedMoEQuantDesc(
904
- current_platform.fp8_dtype(), group_shape, w2_scale, None, None
912
+ fp8_dtype,
913
+ group_shape,
914
+ w2_scale,
915
+ None,
916
+ None,
917
+ w2_bias,
918
),
919
)
920
@@ -911,6 +924,8 @@ def int8_w8a16_moe_quant_config(
924
w2_scale: torch.Tensor,
925
w1_zp: torch.Tensor | None,
926
w2_zp: torch.Tensor | None,
927
+ w1_bias: torch.Tensor | None = None,
928
+ w2_bias: torch.Tensor | None = None,
929
block_shape: list[int] | None = None,
930
) -> FusedMoEQuantConfig:
931
"""
@@ -920,8 +935,8 @@ def int8_w8a16_moe_quant_config(
935
return FusedMoEQuantConfig(
936
_a1=FusedMoEQuantDesc(shape=group_shape),
937
_a2=FusedMoEQuantDesc(shape=group_shape),
923
- _w1=FusedMoEQuantDesc(torch.int8, group_shape, w1_scale, None, w1_zp),
924
- _w2=FusedMoEQuantDesc(torch.int8, group_shape, w2_scale, None, w2_zp),
938
+ _w1=FusedMoEQuantDesc(torch.int8, group_shape, w1_scale, None, w1_zp, w1_bias),
939
+ _w2=FusedMoEQuantDesc(torch.int8, group_shape, w2_scale, None, w2_zp, w2_bias),
940
)
941
942
vllm/model_executor/layers/fused_moe/oracle/fp8.py
+13
-10
@@ -421,6 +421,7 @@ def select_fp8_moe_backend(
421
422
def convert_to_fp8_moe_kernel_format(
423
fp8_backend: Fp8MoeBackend,
424
+ # TODO(bnell): replace layer with weight_block_size
425
layer: torch.nn.Module,
426
w13: torch.Tensor,
427
w2: torch.Tensor,
@@ -508,6 +509,8 @@ def make_fp8_moe_quant_config(
509
w2_scale: torch.Tensor,
510
a1_scale: torch.Tensor | None,
511
a2_scale: torch.Tensor | None,
512
+ w1_bias: torch.Tensor | None = None,
513
+ w2_bias: torch.Tensor | None = None,
514
block_shape: list[int] | None = None,
515
per_act_token_quant: bool = False,
516
per_out_ch_quant: bool = False,
@@ -526,19 +529,13 @@ def make_fp8_moe_quant_config(
529
a method of the modular kernel itself.
530
"""
531
529
- # MARLIN is mixed precision W8A16 config.
530
- if fp8_backend == Fp8MoeBackend.MARLIN:
531
- return fp8_w8a16_moe_quant_config(
532
- w1_scale=w1_scale,
533
- w2_scale=w2_scale,
534
- block_shape=block_shape,
535
- )
536
-
537
- # CPU is mixed precision W8A16 config.
538
- if fp8_backend == Fp8MoeBackend.CPU:
532
+ # MARLIN and CPU are mixed precision W8A16 config.
533
+ if fp8_backend == Fp8MoeBackend.MARLIN or fp8_backend == Fp8MoeBackend.CPU:
534
return fp8_w8a16_moe_quant_config(
535
w1_scale=w1_scale,
536
w2_scale=w2_scale,
537
+ w1_bias=w1_bias,
538
+ w2_bias=w2_bias,
539
block_shape=block_shape,
540
)
541
@@ -549,6 +546,8 @@ def make_fp8_moe_quant_config(
546
return fp8_w8a8_moe_quant_config(
547
w1_scale=w1_scale,
548
w2_scale=w2_scale,
549
+ w1_bias=w1_bias,
550
+ w2_bias=w2_bias,
551
a1_scale=a1_scale,
552
a2_scale=a2_scale,
553
a1_gscale=(1.0 / a1_scale),
@@ -566,6 +565,8 @@ def make_fp8_moe_quant_config(
565
"mxfp8",
566
w1_scale=w1_scale,
567
w2_scale=w2_scale,
568
+ w1_bias=w1_bias,
569
+ w2_bias=w2_bias,
570
a1_scale=a1_scale,
571
a2_scale=a2_scale,
572
block_shape=block_shape,
@@ -577,6 +578,8 @@ def make_fp8_moe_quant_config(
578
return fp8_w8a8_moe_quant_config(
579
w1_scale=w1_scale,
580
w2_scale=w2_scale,
581
+ w1_bias=w1_bias,
582
+ w2_bias=w2_bias,
583
a1_scale=a1_scale,
584
a2_scale=a2_scale,
585
block_shape=block_shape,
vllm/model_executor/layers/fused_moe/oracle/int8.py
+6
@@ -147,6 +147,8 @@ def make_int8_moe_quant_config(
147
w2_scale: torch.Tensor,
148
a1_scale: torch.Tensor | None = None,
149
a2_scale: torch.Tensor | None = None,
150
+ w1_bias: torch.Tensor | None = None,
151
+ w2_bias: torch.Tensor | None = None,
152
per_act_token_quant: bool = False,
153
) -> FusedMoEQuantConfig:
154
assert (a1_scale is None and a2_scale is None) or (
@@ -159,6 +161,8 @@ def make_int8_moe_quant_config(
161
w2_scale=w2_scale,
162
w1_zp=None,
163
w2_zp=None,
164
+ w1_bias=w1_bias,
165
+ w2_bias=w2_bias,
166
)
167
168
return int8_w8a8_moe_quant_config(
@@ -166,6 +170,8 @@ def make_int8_moe_quant_config(
170
w2_scale=w2_scale,
171
a1_scale=a1_scale,
172
a2_scale=a2_scale,
173
+ w1_bias=w1_bias,
174
+ w2_bias=w2_bias,
175
per_act_token_quant=per_act_token_quant,
176
)
177
vllm/model_executor/layers/quantization/modelopt.py
+67
-89
@@ -23,13 +23,10 @@ from vllm.model_executor.layers.fused_moe import (
23
FusedMoEMethodBase,
24
FusedMoEQuantConfig,
25
FusedMoeWeightScaleSupported,
26
- MoEActivation,
26
RoutedExperts,
28
- RoutingMethodType,
27
SharedExperts,
28
)
29
from vllm.model_executor.layers.fused_moe.oracle.fp8 import (
32
- Fp8MoeBackend,
30
convert_to_fp8_moe_kernel_format,
31
make_fp8_moe_kernel,
32
make_fp8_moe_quant_config,
@@ -70,7 +67,6 @@ from vllm.model_executor.layers.quantization.utils.mxfp8_utils import (
67
MXFP8_BLOCK_SIZE,
68
MXFP8_SCALE_DTYPE,
69
MXFP8_VALUE_DTYPE,
73
- mxfp8_e4m3_quantize,
70
)
71
from vllm.model_executor.layers.quantization.utils.quant_utils import (
72
GroupShape,
@@ -93,7 +89,6 @@ from vllm.model_executor.parameter import (
89
PerTensorScaleParameter,
90
)
91
from vllm.model_executor.utils import replace_parameter, set_weight_attrs
96
-from vllm.utils.flashinfer import flashinfer_trtllm_fp8_block_scale_moe
92
93
if TYPE_CHECKING:
94
from vllm.model_executor.models.utils import WeightsMapper
@@ -1860,10 +1855,11 @@ class ModelOptMxFp8FusedMoE(FusedMoEMethodBase):
1855
moe_config: FusedMoEConfig,
1856
) -> None:
1857
super().__init__(moe_config)
1858
+ self.weight_block_size = [1, MXFP8_BLOCK_SIZE]
1859
self.quant_config = quant_config
1860
assert self.quant_config.is_checkpoint_mxfp8_serialized
1861
1866
- self.mxfp8_backend, _ = select_mxfp8_moe_backend(self.moe)
1862
+ self.mxfp8_backend, self.experts_cls = select_mxfp8_moe_backend(config=self.moe)
1863
1864
def create_weights(
1865
self,
@@ -2059,12 +2055,41 @@ class ModelOptMxFp8FusedMoE(FusedMoEMethodBase):
2055
)
2056
2057
def process_weights_after_loading(self, layer: RoutedExperts) -> None:
2058
+ # TODO(bnell): why is this required only for mxfp8?
2059
if getattr(layer, "_already_called_process_weights_after_loading", False):
2060
return
2061
+ layer._already_called_process_weights_after_loading = True
2062
2063
self._check_weight_dtypes(layer)
2066
- self._shuffle_weights_for_trtllm(layer)
2067
- layer._already_called_process_weights_after_loading = True
2064
+
2065
+ layer.weight_block_size = self.weight_block_size
2066
+
2067
+ w13, w2, w13_scale, w2_scale = convert_to_fp8_moe_kernel_format(
2068
+ fp8_backend=self.mxfp8_backend,
2069
+ layer=layer,
2070
+ w13=layer.w13_weight,
2071
+ w2=layer.w2_weight,
2072
+ w13_scale=layer.w13_weight_scale,
2073
+ w2_scale=layer.w2_weight_scale,
2074
+ w13_input_scale=None,
2075
+ w2_input_scale=None,
2076
+ )
2077
+
2078
+ replace_parameter(layer, "w13_weight", w13)
2079
+ replace_parameter(layer, "w2_weight", w2)
2080
+ replace_parameter(layer, "w13_weight_scale", w13_scale)
2081
+ replace_parameter(layer, "w2_weight_scale", w2_scale)
2082
+
2083
+ self.moe_quant_config = self.get_fused_moe_quant_config(layer)
2084
+ assert self.moe_quant_config is not None
2085
+ assert self.experts_cls is not None
2086
+ self.moe_kernel = make_fp8_moe_kernel(
2087
+ moe_quant_config=self.moe_quant_config,
2088
+ moe_config=self.moe,
2089
+ fp8_backend=self.mxfp8_backend,
2090
+ experts_cls=self.experts_cls,
2091
+ routing_tables=layer._expert_routing_tables(),
2092
+ )
2093
2094
def maybe_make_prepare_finalize(
2095
self,
@@ -2088,12 +2113,14 @@ class ModelOptMxFp8FusedMoE(FusedMoEMethodBase):
2113
def get_fused_moe_quant_config(
2114
self, layer: RoutedExperts
2115
) -> FusedMoEQuantConfig | None:
2091
- # TRTLLM MXFP8 path is monolithic and does not use modular kernel config.
2092
- return None
2093
-
2094
- @property
2095
- def is_monolithic(self) -> bool:
2096
- return self.mxfp8_backend == Fp8MoeBackend.FLASHINFER_TRTLLM
2116
+ return make_fp8_moe_quant_config(
2117
+ fp8_backend=self.mxfp8_backend,
2118
+ w1_scale=layer.w13_weight_scale,
2119
+ w2_scale=layer.w2_weight_scale,
2120
+ a1_scale=None,
2121
+ a2_scale=None,
2122
+ block_shape=self.weight_block_size,
2123
+ )
2124
2125
def apply_monolithic(
2126
self,
@@ -2102,83 +2129,23 @@ class ModelOptMxFp8FusedMoE(FusedMoEMethodBase):
2129
router_logits: torch.Tensor,
2130
input_ids: torch.Tensor | None = None,
2131
) -> torch.Tensor:
2105
- from flashinfer.fused_moe.core import (
2106
- ActivationType,
2107
- Fp8QuantizationType,
2108
- )
2109
-
2110
- assert self.mxfp8_backend == Fp8MoeBackend.FLASHINFER_TRTLLM
2111
-
2112
- if layer.eplb_state is not None:
2113
- raise NotImplementedError(
2114
- "EPLB is not supported for FlashInfer TRTLLM MXFP8 MoE backend."
2115
- )
2116
-
2117
- supported_activations = [MoEActivation.SILU]
2118
- if layer.activation not in supported_activations:
2119
- raise NotImplementedError(
2120
- "FlashInfer TRTLLM MXFP8 MoE supports only "
2121
- f"{supported_activations}, got {layer.activation}."
2122
- )
2123
-
2124
- # Map vLLM MoEActivation to FlashInfer ActivationType.
2125
- activation_map = {
2126
- MoEActivation.SILU: ActivationType.Swiglu,
2127
- MoEActivation.RELU2_NO_MUL: ActivationType.Relu2,
2128
- }
2129
- fi_activation_type: ActivationType = activation_map[layer.activation]
2130
-
2131
- # DeepSeekV3 routing requires float32 logits; others expect bfloat16.
2132
- if layer.routing_method_type == RoutingMethodType.DeepSeekV3:
2133
- assert router_logits.dtype == torch.float32, (
2134
- "DeepSeekV3 routing requires float32 router_logits, "
2135
- f"got {router_logits.dtype}."
2136
- )
2137
- else:
2138
- router_logits = router_logits.to(torch.bfloat16)
2139
-
2140
- # Treat 0 as "unset" for compatibility with ungrouped routing configs.
2141
- n_group = layer.num_expert_group or None
2142
- topk_group = layer.topk_group or None
2143
-
2144
- hidden_states_mxfp8, hidden_states_scale = mxfp8_e4m3_quantize(
2132
+ assert self.is_monolithic
2133
+ assert self.moe_kernel is not None
2134
+ return self.moe_kernel.apply_monolithic(
2135
x,
2146
- is_sf_swizzled_layout=False,
2147
- )
2148
-
2149
- kwargs: dict = dict(
2150
- routing_logits=router_logits,
2151
- routing_bias=layer.e_score_correction_bias,
2152
- hidden_states=hidden_states_mxfp8,
2153
- hidden_states_scale=hidden_states_scale,
2154
- gemm1_weights=layer.w13_weight,
2155
- gemm1_weights_scale=layer.w13_weight_scale,
2156
- gemm2_weights=layer.w2_weight,
2157
- gemm2_weights_scale=layer.w2_weight_scale,
2158
- num_experts=layer.global_num_experts,
2159
- top_k=layer.top_k,
2160
- # Keep Optional semantics: FlashInfer expects None for non-grouped
2161
- # routing (e.g. Qwen3 Renormalize), not 0.
2162
- n_group=n_group,
2163
- topk_group=topk_group,
2164
- intermediate_size=layer.intermediate_size_per_partition,
2165
- local_expert_offset=layer.ep_rank * layer.local_num_experts,
2166
- local_num_experts=layer.local_num_experts,
2136
+ layer.w13_weight,
2137
+ layer.w2_weight,
2138
+ router_logits,
2139
+ activation=layer.activation,
2140
+ global_num_experts=layer.global_num_experts,
2141
+ expert_map=layer.expert_map,
2142
+ apply_router_weight_on_input=layer.apply_router_weight_on_input,
2143
+ num_expert_group=layer.num_expert_group,
2144
+ topk_group=layer.topk_group,
2145
+ e_score_correction_bias=layer.e_score_correction_bias,
2146
routed_scaling_factor=layer.routed_scaling_factor,
2168
- routing_method_type=layer.routing_method_type,
2169
- use_shuffled_weight=True,
2170
- weight_layout=0,
2171
- fp8_quantization_type=Fp8QuantizationType.MxFp8,
2147
)
2148
2174
- if fi_activation_type != ActivationType.Swiglu:
2175
- raise NotImplementedError(
2176
- "FlashInfer TRTLLM MXFP8 MoE supports only Swiglu activation, "
2177
- f"got {fi_activation_type}."
2178
- )
2179
-
2180
- return flashinfer_trtllm_fp8_block_scale_moe(**kwargs)
2181
-
2149
def apply(
2150
self,
2151
layer: RoutedExperts,
@@ -2189,8 +2156,19 @@ class ModelOptMxFp8FusedMoE(FusedMoEMethodBase):
2156
shared_experts_input: torch.Tensor | None,
2157
) -> torch.Tensor:
2158
assert not self.is_monolithic
2192
- raise NotImplementedError(
2193
- "Non-monolithic MXFP8 MoE path is not yet implemented."
2159
+ assert self.moe_kernel is not None
2160
+ return self.moe_kernel.apply(
2161
+ x,
2162
+ layer.w13_weight,
2163
+ layer.w2_weight,
2164
+ topk_weights,
2165
+ topk_ids,
2166
+ activation=layer.activation,
2167
+ global_num_experts=layer.global_num_experts,
2168
+ expert_map=layer.expert_map,
2169
+ apply_router_weight_on_input=layer.apply_router_weight_on_input,
2170
+ shared_experts=shared_experts,
2171
+ shared_experts_input=shared_experts_input,
2172
)
2173
2174
vllm/model_executor/layers/quantization/online/fp8.py
+3
-4
@@ -371,19 +371,18 @@ class _Fp8OnlineMoEBase(OnlineMoEMethodBase):
371
a1_scale = layer.w13_input_scale
372
a2_scale = layer.w2_input_scale
373
374
- quant_config = make_fp8_moe_quant_config(
374
+ return make_fp8_moe_quant_config(
375
fp8_backend=self.fp8_backend,
376
w1_scale=w1_scale,
377
w2_scale=w2_scale,
378
a1_scale=a1_scale,
379
a2_scale=a2_scale,
380
+ w1_bias=getattr(layer, "w13_bias", None),
381
+ w2_bias=getattr(layer, "w2_bias", None),
382
block_shape=self.weight_block_size,
383
swiglu_limit=getattr(layer, "swiglu_limit", None),
384
)
385
384
- self._maybe_inject_biases(quant_config, layer)
385
- return quant_config
386
-
386
387
class Fp8PerTensorOnlineMoEMethod(_Fp8OnlineMoEBase):
388
"""Online tensorwise FP8 MoE quantization.
vllm/model_executor/layers/quantization/online/int8.py
+3
-3
@@ -105,9 +105,9 @@ class Int8OnlineMoEMethod(OnlineMoEMethodBase):
105
def get_fused_moe_quant_config(
106
self, layer: torch.nn.Module
107
) -> "FusedMoEQuantConfig | None":
108
- quant_config = make_int8_moe_quant_config(
108
+ return make_int8_moe_quant_config(
109
w1_scale=layer.w13_scale,
110
w2_scale=layer.w2_scale,
111
+ w1_bias=getattr(layer, "w13_bias", None),
112
+ w2_bias=getattr(layer, "w2_bias", None),
113
)
112
- self._maybe_inject_biases(quant_config, layer)
113
- return quant_config
vllm/model_executor/layers/quantization/online/moe_base.py
-16
@@ -8,7 +8,6 @@ import torch
8
import vllm.model_executor.layers.fused_moe.modular_kernel as mk
9
from vllm.model_executor.layers.fused_moe import (
10
FusedMoEMethodBase,
11
- FusedMoEQuantConfig,
11
RoutedExperts,
12
SharedExperts,
13
)
@@ -101,21 +100,6 @@ class OnlineMoEMethodBase(FusedMoEMethodBase):
100
def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
101
pass
102
104
- def _maybe_inject_biases(
105
- self,
106
- quant_config: FusedMoEQuantConfig,
107
- layer: torch.nn.Module,
108
- ) -> None:
109
- """Inject biases into the quant config if the model has them
110
- (e.g. GPT-OSS biased MoE)."""
111
- if self.moe.has_bias:
112
- w13_bias = getattr(layer, "w13_bias", None)
113
- w2_bias = getattr(layer, "w2_bias", None)
114
- if w13_bias is not None:
115
- quant_config._w1.bias = w13_bias
116
- if w2_bias is not None:
117
- quant_config._w2.bias = w2_bias
118
-
103
def maybe_make_prepare_finalize(
104
self,
105
routing_tables: tuple[torch.Tensor, torch.Tensor, torch.Tensor] | None = None,
vllm/model_executor/layers/quantization/online/mxfp8.py
+3
-4
@@ -214,19 +214,18 @@ class Mxfp8OnlineMoEMethod(OnlineMoEMethodBase):
214
a1_scale = layer.w13_input_scale
215
a2_scale = layer.w2_input_scale
216
217
- quant_config = make_fp8_moe_quant_config(
217
+ return make_fp8_moe_quant_config(
218
fp8_backend=self.fp8_backend,
219
w1_scale=w1_scale,
220
w2_scale=w2_scale,
221
a1_scale=a1_scale,
222
a2_scale=a2_scale,
223
+ w1_bias=getattr(layer, "w13_bias", None),
224
+ w2_bias=getattr(layer, "w2_bias", None),
225
block_shape=self.weight_block_size,
226
swiglu_limit=getattr(layer, "swiglu_limit", None),
227
)
228
227
- self._maybe_inject_biases(quant_config, layer)
228
- return quant_config
229
-
229
def process_weights_after_loading(self, layer: Module) -> None:
230
if getattr(layer, "_already_called_process_weights_after_loading", False):
231
return