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, 12:45 UTC
[MoE Refactor][16/N] Apply Refactor to NVFP4 (#31692)
Signed-off-by: Robert Shaw <robshaw@redhat.com> Signed-off-by: Robert Shaw <114415538+robertgshaw2-redhat@users.noreply.github.com> Co-authored-by: Robert Shaw <robshaw@redhat.com> Co-authored-by: Michael Goin <mgoin64@gmail.com> Co-authored-by: Pavani Majety <pmajety@nvidia.com>
Robert Shaw committed
Jan 7, 2026 at 22:46 UTC
9f6dcb71ae4fd410d144fa3d4bb135ea9ab74185
15 files changed
+780
-684
benchmarks/kernels/benchmark_cutlass_moe_nvfp4.py
+34
-19
@@ -11,14 +11,20 @@ import nvtx
11
import torch
12
import torch.utils.benchmark as benchmark
13
14
+import vllm.model_executor.layers.fused_moe.modular_kernel as mk
15
from vllm import _custom_ops as ops
16
from vllm.config import ParallelConfig, VllmConfig, set_current_vllm_config
17
from vllm.model_executor.layers.fused_moe.config import (
18
fp8_w8a8_moe_quant_config,
19
nvfp4_moe_quant_config,
20
)
20
-from vllm.model_executor.layers.fused_moe.cutlass_moe import cutlass_moe_fp4
21
+from vllm.model_executor.layers.fused_moe.cutlass_moe import (
22
+ CutlassExpertsFp4,
23
+)
24
from vllm.model_executor.layers.fused_moe.fused_moe import fused_experts, fused_topk
25
+from vllm.model_executor.layers.fused_moe.prepare_finalize import (
26
+ MoEPrepareAndFinalizeNoEP,
27
+)
28
from vllm.scalar_type import scalar_types
29
from vllm.utils.argparse_utils import FlexibleArgumentParser
30
from vllm.v1.worker.workspace import init_workspace_manager
@@ -188,19 +194,24 @@ def bench_run(
194
g1_alphas=w1_gs,
195
g2_alphas=w2_gs,
196
)
197
+
198
+ kernel = mk.FusedMoEModularKernel(
199
+ MoEPrepareAndFinalizeNoEP(defer_input_quant=True),
200
+ CutlassExpertsFp4(
201
+ out_dtype=dtype,
202
+ max_experts_per_worker=e,
203
+ quant_config=quant_config,
204
+ ),
205
+ )
206
+
207
for _ in range(num_repeats):
208
with nvtx.annotate("cutlass_moe_fp4", color="green"):
193
- cutlass_moe_fp4(
194
- a=a,
195
- w1_fp4=w1_fp4,
196
- w2_fp4=w2_fp4,
209
+ kernel(
210
+ hidden_states=a,
211
+ w1=w1_fp4,
212
+ w2=w2_fp4,
213
topk_weights=topk_weights,
214
topk_ids=topk_ids,
199
- m=m,
200
- n=n,
201
- k=k,
202
- e=num_experts,
203
- quant_config=quant_config,
215
)
216
217
def run_cutlass_from_graph(
@@ -230,20 +241,24 @@ def bench_run(
241
g2_alphas=w2_gs,
242
)
243
244
+ kernel = mk.FusedMoEModularKernel(
245
+ MoEPrepareAndFinalizeNoEP(defer_input_quant=True),
246
+ CutlassExpertsFp4(
247
+ out_dtype=dtype,
248
+ max_experts_per_worker=e,
249
+ quant_config=quant_config,
250
+ ),
251
+ )
252
+
253
with set_current_vllm_config(
254
VllmConfig(parallel_config=ParallelConfig(pipeline_parallel_size=1))
255
):
236
- return cutlass_moe_fp4(
237
- a=a,
238
- w1_fp4=w1_fp4,
239
- w2_fp4=w2_fp4,
256
+ return kernel(
257
+ hidden_states=a,
258
+ w1=w1_fp4,
259
+ w2=w2_fp4,
260
topk_weights=topk_weights,
261
topk_ids=topk_ids,
242
- m=m,
243
- n=n,
244
- k=k,
245
- e=num_experts,
246
- quant_config=quant_config,
262
)
263
264
def run_triton_from_graph(
docs/design/moe_kernel_features.md
+1
-1
@@ -86,7 +86,7 @@ To be used with a particular `FusedMoEPrepareAndFinalize` subclass, MoE kernels
86
| triton | standard | all<sup>1</sup> | G,A,T | silu, gelu,</br>swigluoai,</br>silu_no_mul,</br>gelu_no_mul | Y | Y | [`fused_experts`][vllm.model_executor.layers.fused_moe.fused_moe.fused_experts],</br>[`TritonExperts`][vllm.model_executor.layers.fused_moe.fused_moe.TritonExperts] |
87
| triton (batched) | batched | all<sup>1</sup> | G,A,T | silu, gelu | <sup>6</sup> | Y | [`BatchedTritonExperts`][vllm.model_executor.layers.fused_moe.fused_batched_moe.BatchedTritonExperts] |
88
| deep gemm | standard,</br>batched | fp8 | G(128),A,T | silu, gelu | <sup>6</sup> | Y | [`deep_gemm_moe_fp8`][vllm.model_executor.layers.fused_moe.deep_gemm_moe.deep_gemm_moe_fp8],</br>[`DeepGemmExperts`][vllm.model_executor.layers.fused_moe.deep_gemm_moe.DeepGemmExperts],</br>[`BatchedDeepGemmExperts`][vllm.model_executor.layers.fused_moe.batched_deep_gemm_moe.BatchedDeepGemmExperts] |
89
-| cutlass_fp4 | standard,</br>batched | nvfp4 | A,T | silu | Y | Y | [`cutlass_moe_fp4`][vllm.model_executor.layers.fused_moe.cutlass_moe.cutlass_moe_fp4],</br>[`CutlassExpertsFp4`][vllm.model_executor.layers.fused_moe.cutlass_moe.CutlassExpertsFp4] |
89
+| cutlass_fp4 | standard,</br>batched | nvfp4 | A,T | silu | Y | Y | [`CutlassExpertsFp4`][vllm.model_executor.layers.fused_moe.cutlass_moe.CutlassExpertsFp4] |
90
| cutlass_fp8 | standard,</br>batched | fp8 | A,T | silu, gelu | Y | Y | [`CutlassExpertsFp8`][vllm.model_executor.layers.fused_moe.cutlass_moe.CutlassExpertsFp8],</br>[`CutlasBatchedExpertsFp8`][vllm.model_executor.layers.fused_moe.cutlass_moe.CutlassBatchedExpertsFp8] |
91
| flashinfer | standard | nvfp4,</br>fp8 | T | <sup>5</sup> | N | Y | [`flashinfer_cutlass_moe_fp4`][vllm.model_executor.layers.fused_moe.flashinfer_cutlass_moe.flashinfer_cutlass_moe_fp4],</br>[`FlashInferExperts`][vllm.model_executor.layers.fused_moe.flashinfer_cutlass_moe.FlashInferExperts] |
92
| gpt oss triton | standard | N/A | N/A | <sup>5</sup> | Y | Y | [`triton_kernel_fused_experts`][vllm.model_executor.layers.fused_moe.gpt_oss_triton_kernels_moe.triton_kernel_fused_experts],</br>[`OAITritonExperts`][vllm.model_executor.layers.fused_moe.gpt_oss_triton_kernels_moe.OAITritonExperts] |
tests/kernels/moe/test_nvfp4_moe.py
+20
-10
@@ -3,6 +3,7 @@
3
import pytest
4
import torch
5
6
+import vllm.model_executor.layers.fused_moe.modular_kernel as mk
7
from tests.kernels.moe.utils import make_test_weights
8
from tests.kernels.quantization.nvfp4_utils import (
9
FLOAT4_E2M1_MAX,
@@ -13,8 +14,13 @@ from tests.kernels.utils import torch_moe
14
from vllm import _custom_ops as ops
15
from vllm.config import ParallelConfig, VllmConfig, set_current_vllm_config
16
from vllm.model_executor.layers.fused_moe.config import nvfp4_moe_quant_config
16
-from vllm.model_executor.layers.fused_moe.cutlass_moe import cutlass_moe_fp4
17
+from vllm.model_executor.layers.fused_moe.cutlass_moe import (
18
+ CutlassExpertsFp4,
19
+)
20
from vllm.model_executor.layers.fused_moe.fused_moe import fused_topk
21
+from vllm.model_executor.layers.fused_moe.prepare_finalize import (
22
+ MoEPrepareAndFinalizeNoEP,
23
+)
24
from vllm.platforms import current_platform
25
from vllm.utils.torch_utils import set_random_seed
26
@@ -83,17 +89,21 @@ def test_cutlass_fp4_moe_no_graph(
89
w2_scale=w2_blockscale,
90
)
91
86
- cutlass_output = cutlass_moe_fp4(
87
- a=a,
88
- w1_fp4=w1_q,
89
- w2_fp4=w2_q,
92
+ kernel = mk.FusedMoEModularKernel(
93
+ MoEPrepareAndFinalizeNoEP(defer_input_quant=True),
94
+ CutlassExpertsFp4(
95
+ out_dtype=dtype,
96
+ max_experts_per_worker=e,
97
+ quant_config=quant_config,
98
+ ),
99
+ )
100
+
101
+ cutlass_output = kernel(
102
+ hidden_states=a,
103
+ w1=w1_q,
104
+ w2=w2_q,
105
topk_weights=topk_weights,
106
topk_ids=topk_ids,
92
- quant_config=quant_config,
93
- m=m,
94
- n=n,
95
- k=k,
96
- e=e,
107
)
108
109
# Reference check:
vllm/model_executor/layers/fused_moe/__init__.py
-2
@@ -72,7 +72,6 @@ if HAS_TRITON:
72
CutlassBatchedExpertsFp8,
73
CutlassExpertsFp8,
74
CutlassExpertsW4A8Fp8,
75
- cutlass_moe_fp4,
75
cutlass_moe_w4a8_fp8,
76
)
77
from vllm.model_executor.layers.fused_moe.deep_gemm_moe import DeepGemmExperts
@@ -95,7 +94,6 @@ if HAS_TRITON:
94
"fused_experts",
95
"get_config_file_name",
96
"GroupedTopk",
98
- "cutlass_moe_fp4",
97
"cutlass_moe_w4a8_fp8",
98
"CutlassExpertsFp8",
99
"CutlassBatchedExpertsFp8",
vllm/model_executor/layers/fused_moe/config.py
+23
@@ -336,6 +336,10 @@ class FusedMoEQuantConfig:
336
def use_int4_w4a16(self) -> bool:
337
return self._a1.dtype is None and self._w1.dtype == "int4"
338
339
+ @property
340
+ def use_nvfp4_w4a16(self) -> bool:
341
+ return self._a1.dtype is None and self._w1.dtype == "nvfp4"
342
+
343
@property
344
def ocp_mx_scheme(self) -> str | None:
345
if not hasattr(self, "_ocp_mx_scheme"):
@@ -690,6 +694,25 @@ def nvfp4_moe_quant_config(
694
)
695
696
697
+def nvfp4_w4a16_moe_quant_config(
698
+ g1_alphas: torch.Tensor,
699
+ g2_alphas: torch.Tensor,
700
+ w1_scale: torch.Tensor,
701
+ w2_scale: torch.Tensor,
702
+) -> FusedMoEQuantConfig:
703
+ """
704
+ Construct a quant config for 16-but activations and nvp4 weights.
705
+ """
706
+ return FusedMoEQuantConfig.make(
707
+ quant_dtype=None,
708
+ w1_scale=w1_scale,
709
+ w2_scale=w2_scale,
710
+ g1_alphas=g1_alphas,
711
+ g2_alphas=g2_alphas,
712
+ weight_dtype="nvfp4",
713
+ )
714
+
715
+
716
def int4_w4a16_moe_quant_config(
717
w1_scale: torch.Tensor,
718
w2_scale: torch.Tensor,
vllm/model_executor/layers/fused_moe/cutlass_moe.py
-62
@@ -706,68 +706,6 @@ class CutlassExpertsFp4(mk.FusedMoEPermuteExpertsUnpermute):
706
)
707
708
709
-def cutlass_moe_fp4(
710
- a: torch.Tensor,
711
- w1_fp4: torch.Tensor,
712
- w2_fp4: torch.Tensor,
713
- topk_weights: torch.Tensor,
714
- topk_ids: torch.Tensor,
715
- quant_config: FusedMoEQuantConfig,
716
- m: int,
717
- n: int,
718
- k: int,
719
- e: int,
720
- expert_map: torch.Tensor | None = None,
721
- apply_router_weight_on_input: bool = False,
722
-) -> torch.Tensor:
723
- assert expert_map is None, (
724
- "Expert Parallelism / expert_map "
725
- "is currently not supported for "
726
- "ModelOptNvFp4FusedMoE's cutlass_moe_fp4."
727
- )
728
-
729
- # TODO(bnell): this feels a bit hacky
730
- # NVFP4 requires two levels of quantization, which involves
731
- # computing some scaling factors dynamically. This makes it
732
- # incompatible with the typical prepare -> MoE -> finalize
733
- # pipeline. Move the quantization logic into the MoE body.
734
- quant_config = FusedMoEQuantConfig.make(
735
- quant_dtype=None, # skip quantization in prepare/finalize
736
- per_act_token_quant=quant_config.per_act_token_quant,
737
- per_out_ch_quant=quant_config.per_out_ch_quant,
738
- block_shape=quant_config.block_shape,
739
- g1_alphas=quant_config.g1_alphas,
740
- g2_alphas=quant_config.g2_alphas,
741
- a1_gscale=quant_config.a1_gscale,
742
- a2_gscale=quant_config.a2_gscale,
743
- w1_scale=quant_config.w1_scale,
744
- w2_scale=quant_config.w2_scale,
745
- )
746
-
747
- fn = mk.FusedMoEModularKernel(
748
- MoEPrepareAndFinalizeNoEP(),
749
- CutlassExpertsFp4(
750
- max_experts_per_worker=e,
751
- out_dtype=a.dtype,
752
- quant_config=quant_config,
753
- use_batched_format=False,
754
- ),
755
- )
756
-
757
- return fn(
758
- hidden_states=a,
759
- w1=w1_fp4,
760
- w2=w2_fp4,
761
- topk_weights=topk_weights,
762
- topk_ids=topk_ids,
763
- inplace=False,
764
- activation="silu",
765
- global_num_experts=e,
766
- expert_map=None,
767
- apply_router_weight_on_input=apply_router_weight_on_input,
768
- )
769
-
770
-
709
# W4A8
710
def run_cutlass_moe_w4a8_fp8(
711
output: torch.Tensor,
vllm/model_executor/layers/fused_moe/flashinfer_cutedsl_moe.py
-39
@@ -335,42 +335,3 @@ def flashinfer_cutedsl_moe_masked(
335
alpha_dtype=get_cute_dtype(w2_alpha),
336
) # in logical [m, k, l]
337
out = out.permute(2, 0, 1)
338
-
339
-
340
-def flashinfer_cutedsl_moe_fp4(
341
- hidden_states: torch.Tensor,
342
- w1: torch.Tensor,
343
- w2: torch.Tensor,
344
- topk_weights: torch.Tensor,
345
- topk_ids: torch.Tensor,
346
- quant_config: FusedMoEQuantConfig,
347
- inplace: bool = False,
348
- activation: str = "silu",
349
- global_num_experts: int = -1,
350
- expert_map: torch.Tensor | None = None,
351
- apply_router_weight_on_input: bool = False,
352
-) -> torch.Tensor:
353
- from vllm.model_executor.layers.fused_moe.flashinfer_cutlass_prepare_finalize import ( # noqa: E501
354
- create_flashinfer_prepare_finalize,
355
- )
356
-
357
- fused_experts = mk.FusedMoEModularKernel(
358
- create_flashinfer_prepare_finalize(use_dp=False), # could be swapped later
359
- FlashInferCuteDSLExperts(
360
- out_dtype=hidden_states.dtype,
361
- quant_config=quant_config,
362
- ),
363
- )
364
-
365
- return fused_experts(
366
- hidden_states=hidden_states,
367
- w1=w1,
368
- w2=w2,
369
- topk_weights=topk_weights,
370
- topk_ids=topk_ids,
371
- inplace=inplace,
372
- activation=activation,
373
- global_num_experts=global_num_experts,
374
- expert_map=expert_map,
375
- apply_router_weight_on_input=apply_router_weight_on_input,
376
- )
vllm/model_executor/layers/fused_moe/flashinfer_cutlass_prepare_finalize.py
+7
-11
@@ -355,21 +355,17 @@ def create_flashinfer_prepare_finalize(
355
use_deepseek_fp8_block_scale: bool = False,
356
) -> FlashInferCutlassMoEPrepareAndFinalize | MoEPrepareAndFinalizeNoEP:
357
"""Factory function to create the appropriate FlashInfer implementation."""
358
- # TODO(rob): migrate non-DP cases to MoEPrepareAndFinalizeNoEP
359
- # once we complete the FP8 refactor.
360
- if use_nvfp4:
361
- if enable_alltoallv:
362
- return FlashInferAllToAllMoEPrepareAndFinalize(use_dp)
363
- else:
364
- return FlashInferAllGatherMoEPrepareAndFinalize(use_dp)
358
366
- # FP8 DP path currently supported via AllGather.
359
if use_dp:
360
+ if enable_alltoallv:
361
+ assert use_nvfp4
362
+ return FlashInferAllToAllMoEPrepareAndFinalize(use_dp)
363
return FlashInferAllGatherMoEPrepareAndFinalize(
364
use_dp=True,
365
use_deepseek_fp8_block_scale=use_deepseek_fp8_block_scale,
366
)
367
else:
373
- # NOTE(rob): CUTLASS FP8 block quant executes the input
374
- # quantzation and grouped gemm in a single kernel.
375
- return MoEPrepareAndFinalizeNoEP(defer_input_quant=use_deepseek_fp8_block_scale)
368
+ # CUTLASS FP8 BLOCK and CUTLASS NVFP4 apply input quantization
369
+ # in a single call with the MoE experts kernel.
370
+ defer_input_quant = use_deepseek_fp8_block_scale or use_nvfp4
371
+ return MoEPrepareAndFinalizeNoEP(defer_input_quant=defer_input_quant)
vllm/model_executor/layers/fused_moe/fused_marlin_moe.py
+5
-2
@@ -540,9 +540,10 @@ class MarlinExpertsBase(mk.FusedMoEPermuteExpertsUnpermute):
540
# TODO (varun) : Enable activation quantization
541
assert (
542
quant_config.use_mxfp4_w4a16
543
+ or quant_config.use_nvfp4_w4a16
544
or quant_config.use_int4_w4a16
545
or quant_config.use_fp8_w8a16
545
- ), "Supports only mxfp4_w4a16, int4_w4a16 or fp8_w8a16"
546
+ ), "Supports only {mxfp,nvfp,int}4_w4a16 or fp8_w8a16"
547
self.w13_g_idx = w13_g_idx
548
self.w2_g_idx = w2_g_idx
549
self.w13_g_idx_sort_indices = w13_g_idx_sort_indices
@@ -555,7 +556,7 @@ class MarlinExpertsBase(mk.FusedMoEPermuteExpertsUnpermute):
556
# uint4b8 will be set for int4 weight and float4_e2m1f will be used for mxfp4
557
if self.quant_config.use_int4_w4a16:
558
return scalar_types.uint4b8.id
558
- elif self.quant_config.use_mxfp4_w4a16:
559
+ elif self.quant_config.use_mxfp4_w4a16 or self.quant_config.use_nvfp4_w4a16:
560
return scalar_types.float4_e2m1f.id
561
elif (
562
self.quant_config.use_fp8_w8a16
@@ -692,6 +693,8 @@ class MarlinExperts(MarlinExpertsBase):
693
gating_output=None,
694
topk_weights=topk_weights,
695
topk_ids=topk_ids,
696
+ global_scale1=self.g1_alphas,
697
+ global_scale2=self.g2_alphas,
698
quant_type_id=self.quant_type_id,
699
apply_router_weight_on_input=apply_router_weight_on_input,
700
global_num_experts=global_num_experts,
vllm/model_executor/layers/fused_moe/layer.py
+1
-9
@@ -38,9 +38,6 @@ from vllm.model_executor.layers.fused_moe.routing_simulator import RoutingSimula
38
from vllm.model_executor.layers.quantization.base_config import (
39
QuantizationConfig,
40
)
41
-from vllm.model_executor.layers.quantization.utils.flashinfer_utils import (
42
- is_flashinfer_supporting_global_sf,
43
-)
41
from vllm.platforms import current_platform
42
from vllm.utils.flashinfer import has_flashinfer_trtllm_fused_moe
43
from vllm.utils.math_utils import cdiv, round_up
@@ -1125,14 +1122,9 @@ class FusedMoE(CustomOp):
1122
global_expert_id = expert_id
1123
expert_id = self._map_global_expert_id_to_local_expert_id(global_expert_id)
1124
1128
- allow_flashinfer = getattr(self.quant_method, "allow_flashinfer", False)
1129
- moe_backend = getattr(self.quant_method, "flashinfer_moe_backend", None)
1130
-
1125
use_global_sf = (
1132
- allow_flashinfer
1133
- and is_flashinfer_supporting_global_sf(moe_backend)
1126
+ getattr(self.quant_method, "use_global_sf", False)
1127
and "input_scale" in weight_name
1135
- and quant_method_name == "ModelOptNvFp4FusedMoE"
1128
)
1129
1130
if expert_id == -1 and not use_global_sf:
vllm/model_executor/layers/fused_moe/oracle/nvfp4.py
new
+280
@@ -0,0 +1,280 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+from enum import Enum
4
+
5
+import torch
6
+
7
+import vllm.envs as envs
8
+import vllm.model_executor.layers.fused_moe.modular_kernel as mk
9
+from vllm.logger import init_logger
10
+from vllm.model_executor.layers.fused_moe.config import (
11
+ FusedMoEConfig,
12
+ FusedMoEQuantConfig,
13
+ nvfp4_moe_quant_config,
14
+ nvfp4_w4a16_moe_quant_config,
15
+)
16
+from vllm.model_executor.layers.fused_moe.cutlass_moe import (
17
+ CutlassExpertsFp4,
18
+)
19
+from vllm.model_executor.layers.fused_moe.flashinfer_cutlass_moe import (
20
+ FlashInferExperts,
21
+)
22
+from vllm.model_executor.layers.fused_moe.fused_marlin_moe import (
23
+ MarlinExperts,
24
+)
25
+from vllm.model_executor.layers.fused_moe.prepare_finalize import (
26
+ MoEPrepareAndFinalizeNoEP,
27
+)
28
+from vllm.model_executor.layers.quantization.utils.flashinfer_fp4_moe import (
29
+ is_flashinfer_fp4_cutedsl_moe_available,
30
+ is_flashinfer_fp4_cutlass_moe_available,
31
+ prepare_nvfp4_moe_layer_for_fi_or_cutlass,
32
+)
33
+from vllm.model_executor.layers.quantization.utils.flashinfer_utils import (
34
+ FlashinferMoeBackend,
35
+ get_flashinfer_moe_backend,
36
+)
37
+from vllm.model_executor.layers.quantization.utils.marlin_utils_fp4 import (
38
+ is_fp4_marlin_supported,
39
+ prepare_nvfp4_moe_layer_for_marlin,
40
+)
41
+from vllm.model_executor.layers.quantization.utils.quant_utils import (
42
+ cutlass_fp4_supported,
43
+)
44
+
45
+logger = init_logger(__name__)
46
+
47
+
48
+class NvFp4MoeBackend(Enum):
49
+ FLASHINFER_CUTLASS = "FlashInfer CUTLASS"
50
+ FLASHINFER_TRTLLM = "FlashInfer TRTLLM"
51
+ FLASHINFER_CUTEDSL = "FlashInfer CUTEDSL"
52
+ VLLM_CUTLASS = "vLLM CUTASS"
53
+ MARLIN = "vLLM MARLIN"
54
+
55
+
56
+FLASHINFER_NVFP4_MOE_BACKENDS = [
57
+ NvFp4MoeBackend.FLASHINFER_CUTLASS,
58
+ NvFp4MoeBackend.FLASHINFER_TRTLLM,
59
+ NvFp4MoeBackend.FLASHINFER_CUTEDSL,
60
+]
61
+
62
+fi_2_vllm_backend_map: dict[FlashinferMoeBackend, NvFp4MoeBackend] = {
63
+ FlashinferMoeBackend.CUTLASS: NvFp4MoeBackend.FLASHINFER_CUTLASS,
64
+ FlashinferMoeBackend.TENSORRT_LLM: NvFp4MoeBackend.FLASHINFER_TRTLLM,
65
+ FlashinferMoeBackend.CUTEDSL: NvFp4MoeBackend.FLASHINFER_CUTEDSL,
66
+}
67
+
68
+
69
+def is_global_sf_supported_for_nvfp4_backend(backend: NvFp4MoeBackend) -> bool:
70
+ # Checks whether `backend` supports quantizing with scaling factors
71
+ # of all experts in Expert Parallel Mode when all experts are not
72
+ # on the same rank.
73
+
74
+ return backend in [
75
+ NvFp4MoeBackend.FLASHINFER_CUTLASS,
76
+ NvFp4MoeBackend.FLASHINFER_TRTLLM,
77
+ ]
78
+
79
+
80
+def select_nvfp4_moe_backend() -> NvFp4MoeBackend:
81
+ def _make_log_backend(backend: NvFp4MoeBackend):
82
+ return f"Using {backend.value} backend for NvFp4 MoE"
83
+
84
+ if cutlass_fp4_supported() and not envs.VLLM_TEST_FORCE_FP8_MARLIN:
85
+ allow_flashinfer = (
86
+ is_flashinfer_fp4_cutlass_moe_available()
87
+ or is_flashinfer_fp4_cutedsl_moe_available()
88
+ )
89
+ if allow_flashinfer and envs.VLLM_USE_FLASHINFER_MOE_FP4:
90
+ backend = fi_2_vllm_backend_map[get_flashinfer_moe_backend()]
91
+ else:
92
+ backend = NvFp4MoeBackend.VLLM_CUTLASS
93
+ elif is_fp4_marlin_supported():
94
+ backend = NvFp4MoeBackend.MARLIN
95
+ else:
96
+ raise ValueError("No NvFp4 kernel backend available for NvFp4 MoE.")
97
+
98
+ # Log warning if FI backend requested but not available.
99
+ if (
100
+ backend not in FLASHINFER_NVFP4_MOE_BACKENDS
101
+ and envs.VLLM_USE_FLASHINFER_MOE_FP4
102
+ ):
103
+ logger.warning_once(
104
+ "Requested FlashInfer backend for NvFp4 MoE, but it's not available. "
105
+ "Falling back to %s for NvFp4 MoE",
106
+ backend.value,
107
+ scope="local",
108
+ )
109
+ else:
110
+ logger.info_once(_make_log_backend(backend), scope="local")
111
+ return backend
112
+
113
+
114
+def convert_to_nvfp4_moe_kernel_format(
115
+ nvfp4_backend: NvFp4MoeBackend,
116
+ layer: torch.nn.Module,
117
+ w13: torch.Tensor,
118
+ w13_scale: torch.Tensor,
119
+ w13_scale_2: torch.Tensor,
120
+ a13_scale: torch.Tensor | None,
121
+ w2: torch.Tensor,
122
+ w2_scale: torch.Tensor,
123
+ w2_scale_2: torch.Tensor,
124
+ a2_scale: torch.Tensor | None,
125
+ is_act_and_mul: bool,
126
+) -> tuple[
127
+ torch.Tensor,
128
+ torch.Tensor,
129
+ torch.Tensor,
130
+ torch.Tensor,
131
+ torch.Tensor,
132
+ torch.Tensor,
133
+ torch.Tensor,
134
+ torch.Tensor,
135
+]:
136
+ if (
137
+ nvfp4_backend in FLASHINFER_NVFP4_MOE_BACKENDS
138
+ or nvfp4_backend == NvFp4MoeBackend.VLLM_CUTLASS
139
+ ):
140
+ (
141
+ w13,
142
+ w13_scale,
143
+ w13_scale_2,
144
+ a13_scale,
145
+ w2,
146
+ w2_scale,
147
+ w2_scale_2,
148
+ a2_scale,
149
+ ) = prepare_nvfp4_moe_layer_for_fi_or_cutlass(
150
+ backend=nvfp4_backend,
151
+ layer=layer,
152
+ w13=w13,
153
+ w13_scale=w13_scale,
154
+ w13_scale_2=w13_scale_2,
155
+ a13_scale=a13_scale,
156
+ w2=w2,
157
+ w2_scale=w2_scale,
158
+ w2_scale_2=w2_scale_2,
159
+ a2_scale=a2_scale,
160
+ is_act_and_mul=is_act_and_mul,
161
+ )
162
+ elif nvfp4_backend == NvFp4MoeBackend.MARLIN:
163
+ a13_scale = None
164
+ a2_scale = None
165
+ (
166
+ w13,
167
+ w13_scale,
168
+ w13_scale_2,
169
+ w2,
170
+ w2_scale,
171
+ w2_scale_2,
172
+ ) = prepare_nvfp4_moe_layer_for_marlin(
173
+ layer=layer,
174
+ w13=w13,
175
+ w13_scale=w13_scale,
176
+ w13_scale_2=w13_scale_2,
177
+ w2=w2,
178
+ w2_scale=w2_scale,
179
+ w2_scale_2=w2_scale_2,
180
+ )
181
+ else:
182
+ raise ValueError(f"Unknown NvFp4 backend for MoE: {nvfp4_backend}")
183
+
184
+ return (
185
+ w13,
186
+ w13_scale,
187
+ w13_scale_2,
188
+ a13_scale,
189
+ w2,
190
+ w2_scale,
191
+ w2_scale_2,
192
+ a2_scale,
193
+ )
194
+
195
+
196
+def make_nvfp4_moe_quant_config(
197
+ backend: NvFp4MoeBackend,
198
+ w13_scale: torch.Tensor,
199
+ w2_scale: torch.Tensor,
200
+ w13_scale_2: torch.Tensor,
201
+ w2_scale_2: torch.Tensor,
202
+ a13_scale: torch.Tensor,
203
+ a2_scale: torch.Tensor,
204
+) -> FusedMoEQuantConfig | None:
205
+ UNSUPPORTED = [NvFp4MoeBackend.FLASHINFER_TRTLLM]
206
+ if backend in UNSUPPORTED:
207
+ return None
208
+
209
+ elif backend == NvFp4MoeBackend.MARLIN:
210
+ return nvfp4_w4a16_moe_quant_config(
211
+ g1_alphas=w13_scale_2,
212
+ g2_alphas=w2_scale_2,
213
+ w1_scale=w13_scale,
214
+ w2_scale=w2_scale,
215
+ )
216
+
217
+ g1_alphas = a13_scale * w13_scale_2
218
+ g2_alphas = a2_scale * w2_scale_2
219
+ return nvfp4_moe_quant_config(
220
+ g1_alphas=g1_alphas,
221
+ g2_alphas=g2_alphas,
222
+ a1_gscale=(1.0 / a13_scale),
223
+ a2_gscale=(1.0 / a2_scale),
224
+ w1_scale=w13_scale,
225
+ w2_scale=w2_scale,
226
+ )
227
+
228
+
229
+def make_nvfp4_moe_kernel(
230
+ backend: NvFp4MoeBackend,
231
+ quant_config: FusedMoEQuantConfig,
232
+ moe_config: FusedMoEConfig,
233
+) -> mk.FusedMoEModularKernel | None:
234
+ assert moe_config.dp_size == 1
235
+
236
+ UNSUPPORTED_BACKENDS = [
237
+ # TRTLLM does not use the modular kernl abstraction.
238
+ NvFp4MoeBackend.FLASHINFER_TRTLLM,
239
+ # CUTEDSL is used with BATCHED (masked) format only.
240
+ # TODO: add here once we support dp/ep via the oracle.
241
+ NvFp4MoeBackend.FLASHINFER_CUTEDSL,
242
+ ]
243
+
244
+ if backend in UNSUPPORTED_BACKENDS:
245
+ return None
246
+
247
+ elif backend == NvFp4MoeBackend.FLASHINFER_CUTLASS:
248
+ return mk.FusedMoEModularKernel(
249
+ MoEPrepareAndFinalizeNoEP(defer_input_quant=True),
250
+ FlashInferExperts(
251
+ out_dtype=moe_config.in_dtype,
252
+ quant_config=quant_config,
253
+ ep_rank=moe_config.ep_rank,
254
+ ep_size=moe_config.ep_size,
255
+ tp_rank=moe_config.tp_rank,
256
+ tp_size=moe_config.tp_size,
257
+ use_dp=False,
258
+ use_deepseek_fp8_block_scale=False,
259
+ ),
260
+ )
261
+
262
+ elif backend == NvFp4MoeBackend.VLLM_CUTLASS:
263
+ return mk.FusedMoEModularKernel(
264
+ MoEPrepareAndFinalizeNoEP(defer_input_quant=True),
265
+ CutlassExpertsFp4(
266
+ out_dtype=moe_config.in_dtype,
267
+ # TODO(rob): see what impact this has on expert map?
268
+ max_experts_per_worker=moe_config.num_experts,
269
+ quant_config=quant_config,
270
+ ),
271
+ )
272
+
273
+ elif backend == NvFp4MoeBackend.MARLIN:
274
+ return mk.FusedMoEModularKernel(
275
+ MoEPrepareAndFinalizeNoEP(),
276
+ MarlinExperts(quant_config=quant_config),
277
+ )
278
+
279
+ else:
280
+ raise ValueError(f"Unknown NvFp4 MoE backend: {backend}")
vllm/model_executor/layers/quantization/compressed_tensors/compressed_tensors_moe.py
+115
-240
@@ -11,7 +11,6 @@ from compressed_tensors.quantization import (
11
QuantizationArgs,
12
QuantizationStrategy,
13
)
14
-from torch.nn.parameter import Parameter
14
15
import vllm.model_executor.layers.fused_moe.modular_kernel as mk
16
from vllm import _custom_ops as ops
@@ -34,12 +33,8 @@ from vllm.model_executor.layers.fused_moe.config import (
33
int4_w4afp8_moe_quant_config,
34
int8_w8a8_moe_quant_config,
35
int8_w8a16_moe_quant_config,
37
- nvfp4_moe_quant_config,
36
)
37
from vllm.model_executor.layers.fused_moe.cpu_fused_moe import select_experts
40
-from vllm.model_executor.layers.fused_moe.flashinfer_cutlass_moe import (
41
- is_valid_flashinfer_cutlass_fused_moe,
42
-)
38
from vllm.model_executor.layers.fused_moe.fused_marlin_moe import (
39
BatchedMarlinExperts,
40
MarlinExperts,
@@ -51,6 +46,15 @@ from vllm.model_executor.layers.fused_moe.oracle.fp8 import (
46
make_fp8_moe_kernel,
47
select_fp8_moe_backend,
48
)
49
+from vllm.model_executor.layers.fused_moe.oracle.nvfp4 import (
50
+ FLASHINFER_NVFP4_MOE_BACKENDS,
51
+ NvFp4MoeBackend,
52
+ convert_to_nvfp4_moe_kernel_format,
53
+ is_global_sf_supported_for_nvfp4_backend,
54
+ make_nvfp4_moe_kernel,
55
+ make_nvfp4_moe_quant_config,
56
+ select_nvfp4_moe_backend,
57
+)
58
from vllm.model_executor.layers.quantization.compressed_tensors.schemes.compressed_tensors_wNa16 import ( # noqa
59
WNA16_SUPPORTED_BITS,
60
WNA16_SUPPORTED_TYPES_MAP,
@@ -58,14 +62,9 @@ from vllm.model_executor.layers.quantization.compressed_tensors.schemes.compress
62
from vllm.model_executor.layers.quantization.utils.flashinfer_fp4_moe import (
63
build_flashinfer_fp4_cutlass_moe_prepare_finalize,
64
flashinfer_trtllm_fp4_moe,
61
- prepare_static_weights_for_trtllm_fp4_moe,
62
- reorder_w1w3_to_w3w1,
65
+ flashinfer_trtllm_fp4_routed_moe,
66
select_nvfp4_gemm_impl,
67
)
65
-from vllm.model_executor.layers.quantization.utils.flashinfer_utils import (
66
- FlashinferMoeBackend,
67
- get_flashinfer_moe_backend,
68
-)
68
from vllm.model_executor.layers.quantization.utils.fp8_utils import (
69
process_fp8_input_tensor_strategy_moe,
70
process_fp8_weight_tensor_strategy_moe,
@@ -77,20 +76,15 @@ from vllm.model_executor.layers.quantization.utils.marlin_utils import (
76
marlin_make_workspace_new,
77
marlin_moe_permute_scales,
78
)
80
-from vllm.model_executor.layers.quantization.utils.marlin_utils_fp4 import (
81
- prepare_moe_fp4_layer_for_marlin,
82
-)
79
from vllm.model_executor.layers.quantization.utils.quant_utils import (
80
convert_bf16_scales_to_fp8,
81
convert_packed_uint4b8_to_signed_int4_inplace,
86
- swizzle_blockscale,
82
)
83
from vllm.model_executor.layers.quantization.utils.w8a8_utils import (
84
normalize_e4m3fn_to_e4m3fnuz,
85
)
86
from vllm.model_executor.utils import replace_parameter, set_weight_attrs
87
from vllm.platforms import CpuArchEnum, current_platform
93
-from vllm.scalar_type import scalar_types
88
89
logger = init_logger(__name__)
90
@@ -218,31 +212,19 @@ class CompressedTensorsMoEMethod(FusedMoEMethodBase):
212
213
class CompressedTensorsW4A4Nvfp4MoEMethod(CompressedTensorsMoEMethod):
214
def __init__(self, moe: FusedMoEConfig, layer_name: str | None = None):
221
- from vllm.model_executor.layers.quantization.utils.nvfp4_moe_support import ( # noqa: E501
222
- detect_nvfp4_moe_support,
223
- )
215
+ if not moe.is_act_and_mul:
216
+ raise ValueError(
217
+ "CompressedTensorsW4A4Nvfp4MoEMethod does not yet "
218
+ "support non gated MoE models."
219
+ )
220
221
super().__init__(moe)
226
- _nvfp4 = detect_nvfp4_moe_support(self.__class__.__name__)
227
- self.cutlass_nvfp4_supported = _nvfp4.cutlass_supported
228
- self.allow_flashinfer = _nvfp4.allow_flashinfer
229
- self.use_marlin = _nvfp4.use_marlin
222
self.group_size = 16
231
- self.layer_name = layer_name
232
- self.marlin_input_dtype = (
233
- get_marlin_input_dtype(layer_name) if self.use_marlin else None
234
- )
235
- self.flashinfer_moe_backend = None
236
- if self.allow_flashinfer:
237
- self.flashinfer_moe_backend = get_flashinfer_moe_backend()
238
- logger.info_once(
239
- f"Using FlashInfer {self.flashinfer_moe_backend.value} kernels"
240
- " for CompressedTensorsW4A4Nvfp4MoEMethod."
241
- )
242
- elif self.use_marlin:
243
- logger.info_once("Using Marlin for CompressedTensorsW4A4Nvfp4MoEMethod.")
244
- else:
245
- logger.info_once("Using Cutlass for CompressedTensorsW4A4Nvfp4MoEMethod.")
223
+ self.nvfp4_backend = select_nvfp4_moe_backend()
224
+ self.use_global_sf = is_global_sf_supported_for_nvfp4_backend(
225
+ self.nvfp4_backend
226
+ )
227
+ self.kernel: mk.FusedMoEModularKernel | None = None
228
229
def create_weights(
230
self,
@@ -355,7 +337,13 @@ class CompressedTensorsW4A4Nvfp4MoEMethod(CompressedTensorsMoEMethod):
337
set_weight_attrs(w2_input_scale, extra_weight_attrs)
338
339
def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
358
- # From packed to weight
340
+ """
341
+ Convert NVFP4 MoE weights into kernel format and setup the kernel.
342
+ """
343
+ # NOTE(rob): wN_weight_packed -> wN_weight is because ModularKernelMethod
344
+ # requires this naming convention. However, the name change breaks
345
+ # reloading because the state dict no longer matches disk. Once we
346
+ # remove MKM, we should revert this change to ensure compatibility.
347
layer.w13_weight = torch.nn.Parameter(
348
layer.w13_weight_packed.data, requires_grad=False
349
)
@@ -366,144 +354,79 @@ class CompressedTensorsW4A4Nvfp4MoEMethod(CompressedTensorsMoEMethod):
354
)
355
delattr(layer, "w2_weight_packed")
356
369
- # reorder GEMM1 weights and block scales for FlashInfer CUTLASS kernel.
370
- if self.allow_flashinfer:
371
- w, s = reorder_w1w3_to_w3w1(
372
- layer.w13_weight.data, layer.w13_weight_scale.data, dim=-2
373
- )
374
- layer.w13_weight = torch.nn.Parameter(w, requires_grad=False)
375
- layer.w13_weight_scale = torch.nn.Parameter(s, requires_grad=False)
376
-
377
- if not torch.allclose(
357
+ # Use a single gscale for w13.
358
+ if self.moe.is_act_and_mul and not torch.allclose(
359
layer.w13_weight_global_scale[:, 0], layer.w13_weight_global_scale[:, 1]
360
):
361
logger.warning_once(
362
"w1_weight_global_scale must match w3_weight_global_scale. "
382
- "Accuracy may be affected."
383
- )
384
-
385
- # Take inverse of global scale saved to disk
386
- layer.w13_weight_scale_2 = torch.nn.Parameter(
387
- 1 / layer.w13_weight_global_scale[:, 0], requires_grad=False
388
- )
389
-
390
- layer.w2_weight_scale_2 = torch.nn.Parameter(
391
- 1 / layer.w2_weight_global_scale.data, requires_grad=False
392
- )
393
-
394
- if self.use_marlin:
395
- prepare_moe_fp4_layer_for_marlin(layer, input_dtype=self.marlin_input_dtype)
396
- return
397
- # w13
398
- if (
399
- self.allow_flashinfer
400
- and self.flashinfer_moe_backend == FlashinferMoeBackend.TENSORRT_LLM
401
- ):
402
- w13_input_global_scale = (
403
- layer.w13_input_global_scale.min()
404
- .to(torch.float32)
405
- .expand(layer.num_experts)
406
- )
407
- else:
408
- w13_input_global_scale = layer.w13_input_global_scale.min(dim=1).values.to(
409
- torch.float32
410
- )
411
- layer.g1_alphas = torch.nn.Parameter(
412
- ((1 / w13_input_global_scale) * layer.w13_weight_scale_2),
413
- requires_grad=False,
414
- )
415
-
416
- layer.w13_input_scale_quant = torch.nn.Parameter(
417
- (w13_input_global_scale), requires_grad=False
418
- )
419
-
420
- # w2
421
- if (
422
- self.allow_flashinfer
423
- and self.flashinfer_moe_backend == FlashinferMoeBackend.TENSORRT_LLM
424
- ):
425
- w2_input_global_scale = (
426
- layer.w2_input_global_scale.min()
427
- .to(torch.float32)
428
- .expand(layer.num_experts)
429
- )
430
- else:
431
- w2_input_global_scale = layer.w2_input_global_scale
432
-
433
- layer.g2_alphas = torch.nn.Parameter(
434
- ((1 / w2_input_global_scale) * layer.w2_weight_scale_2).to(torch.float32),
435
- requires_grad=False,
436
- )
437
-
438
- layer.w2_input_scale_quant = torch.nn.Parameter(
439
- (w2_input_global_scale), requires_grad=False
363
+ "Accuracy may be affected.",
364
+ )
365
+ w13_weight_global_scale = layer.w13_weight_global_scale[:, 0].contiguous()
366
+
367
+ # Shuffle weights into the NvFp4 kernel format.
368
+ (
369
+ w13,
370
+ w13_scale,
371
+ w13_scale_2,
372
+ a13_scale,
373
+ w2,
374
+ w2_scale,
375
+ w2_scale_2,
376
+ a2_scale,
377
+ ) = convert_to_nvfp4_moe_kernel_format(
378
+ nvfp4_backend=self.nvfp4_backend,
379
+ layer=layer,
380
+ w13=layer.w13_weight,
381
+ w13_scale=layer.w13_weight_scale,
382
+ w13_scale_2=(1.0 / w13_weight_global_scale),
383
+ a13_scale=(1.0 / layer.w13_input_global_scale),
384
+ w2=layer.w2_weight,
385
+ w2_scale=layer.w2_weight_scale,
386
+ w2_scale_2=(1.0 / layer.w2_weight_global_scale),
387
+ a2_scale=(1.0 / layer.w2_input_global_scale),
388
+ is_act_and_mul=self.moe.is_act_and_mul,
389
)
390
442
- # TensorRT-LLM specific processing
443
- if (
444
- self.allow_flashinfer
445
- and self.flashinfer_moe_backend == FlashinferMoeBackend.TENSORRT_LLM
446
- ):
447
- # Prepare static weights for TRT-LLM kernel
448
- # alternate: prepare_static_weight_layouts_for_trtllm_moe
449
- (
450
- gemm1_weights_fp4_shuffled,
451
- gemm1_scales_fp4_shuffled,
452
- gemm2_weights_fp4_shuffled,
453
- gemm2_scales_fp4_shuffled,
454
- ) = prepare_static_weights_for_trtllm_fp4_moe(
455
- layer.w13_weight,
456
- layer.w2_weight,
457
- layer.w13_weight_scale,
458
- layer.w2_weight_scale,
459
- layer.w2_weight.size(-2), # hidden_size
460
- layer.w13_weight.size(-2) // 2, # intermediate_size
461
- layer.w13_weight.size(0), # num_experts
462
- )
463
- logger.debug_once("Finished shuffling weights for TRT-LLM MOE")
464
-
465
- layer.w13_weight = Parameter(
466
- gemm1_weights_fp4_shuffled, requires_grad=False
467
- )
468
- layer.w2_weight = Parameter(gemm2_weights_fp4_shuffled, requires_grad=False)
469
- layer.w13_weight_scale = Parameter(
470
- gemm1_scales_fp4_shuffled, requires_grad=False
471
- )
472
- layer.w2_weight_scale = Parameter(
473
- gemm2_scales_fp4_shuffled, requires_grad=False
474
- )
475
-
476
- # Additional parameter needed for TRT-LLM
477
- layer.g1_scale_c = Parameter(
478
- (layer.w2_input_scale_quant * layer.g1_alphas).to(torch.float32),
479
- requires_grad=False,
480
- )
481
- else:
482
- # swizzle weight scales
483
- layer.w13_weight_scale = torch.nn.Parameter(
484
- swizzle_blockscale(layer.w13_weight_scale), requires_grad=False
485
- )
391
+ replace_parameter(layer, "w13_weight", w13)
392
+ replace_parameter(layer, "w13_weight_scale", w13_scale)
393
+ replace_parameter(layer, "w2_weight", w2)
394
+ replace_parameter(layer, "w2_weight_scale", w2_scale)
395
+ layer.w13_weight_scale_2 = w13_scale_2
396
+ layer.w2_weight_scale_2 = w2_scale_2
397
+ layer.w13_input_scale = a13_scale
398
+ layer.w2_input_scale = a2_scale
399
487
- layer.w2_weight_scale = torch.nn.Parameter(
488
- swizzle_blockscale(layer.w2_weight_scale), requires_grad=False
400
+ # Initialize the kernel that will be called in apply().
401
+ self.moe_quant_config = self.get_fused_moe_quant_config(layer)
402
+ use_dp = self.moe.dp_size > 1
403
+ if self.moe_quant_config is not None and not use_dp:
404
+ self.kernel = make_nvfp4_moe_kernel(
405
+ backend=self.nvfp4_backend,
406
+ quant_config=self.moe_quant_config,
407
+ moe_config=self.moe,
408
)
409
410
def maybe_make_prepare_finalize(
411
self,
412
routing_tables: tuple[torch.Tensor, torch.Tensor, torch.Tensor] | None = None,
413
) -> mk.FusedMoEPrepareAndFinalize | None:
495
- if self.use_marlin or (
496
- self.allow_flashinfer
497
- and self.flashinfer_moe_backend == FlashinferMoeBackend.TENSORRT_LLM
498
- ):
414
+ UNSUPPORTED = [NvFp4MoeBackend.MARLIN, NvFp4MoeBackend.FLASHINFER_TRTLLM]
415
+ if self.nvfp4_backend in UNSUPPORTED:
416
return None
500
- elif not self.allow_flashinfer:
417
+ elif self.nvfp4_backend == NvFp4MoeBackend.FLASHINFER_CUTLASS:
418
+ # TP case: avoid convert to ModularKernelMethod - to be refactored.
419
+ if self.moe.dp_size == 1:
420
+ return None
421
+ # For now, fp4 moe only works with the flashinfer dispatcher.
422
+ prepare_finalize = build_flashinfer_fp4_cutlass_moe_prepare_finalize(
423
+ self.moe
424
+ )
425
+ logger.debug_once("%s", prepare_finalize.__class__.__name__)
426
+ return prepare_finalize
427
+ else:
428
return super().maybe_make_prepare_finalize(routing_tables)
429
503
- prepare_finalize = build_flashinfer_fp4_cutlass_moe_prepare_finalize(self.moe)
504
- logger.debug_once("%s", prepare_finalize.__class__.__name__)
505
- return prepare_finalize
506
-
430
def select_gemm_impl(
431
self,
432
prepare_finalize: mk.FusedMoEPrepareAndFinalize,
@@ -514,7 +437,7 @@ class CompressedTensorsW4A4Nvfp4MoEMethod(CompressedTensorsMoEMethod):
437
experts = select_nvfp4_gemm_impl(
438
self.moe,
439
self.moe_quant_config,
517
- allow_flashinfer=self.allow_flashinfer,
440
+ allow_flashinfer=(self.nvfp4_backend in FLASHINFER_NVFP4_MOE_BACKENDS),
441
)
442
logger.debug_once("Using %s", experts.__class__.__name__)
443
return experts
@@ -522,19 +445,14 @@ class CompressedTensorsW4A4Nvfp4MoEMethod(CompressedTensorsMoEMethod):
445
def get_fused_moe_quant_config(
446
self, layer: torch.nn.Module
447
) -> FusedMoEQuantConfig | None:
525
- if (
526
- self.use_marlin
527
- or self.flashinfer_moe_backend == FlashinferMoeBackend.TENSORRT_LLM
528
- ):
529
- return None
530
-
531
- return nvfp4_moe_quant_config(
532
- g1_alphas=layer.g1_alphas,
533
- g2_alphas=layer.g2_alphas,
534
- a1_gscale=layer.w13_input_scale_quant,
535
- a2_gscale=layer.w2_input_scale_quant,
536
- w1_scale=layer.w13_weight_scale,
448
+ return make_nvfp4_moe_quant_config(
449
+ backend=self.nvfp4_backend,
450
+ w13_scale=layer.w13_weight_scale,
451
w2_scale=layer.w2_weight_scale,
452
+ w13_scale_2=layer.w13_weight_scale_2,
453
+ w2_scale_2=layer.w2_weight_scale_2,
454
+ a13_scale=layer.w13_input_scale,
455
+ a2_scale=layer.w2_input_scale,
456
)
457
458
def apply(
@@ -546,14 +464,9 @@ class CompressedTensorsW4A4Nvfp4MoEMethod(CompressedTensorsMoEMethod):
464
assert layer.activation == "silu", "Only SiLU activation is supported."
465
466
if (
549
- self.allow_flashinfer
550
- and self.flashinfer_moe_backend == FlashinferMoeBackend.TENSORRT_LLM
467
+ self.nvfp4_backend == NvFp4MoeBackend.FLASHINFER_TRTLLM
468
+ and not layer.enable_eplb
469
):
552
- if layer.enable_eplb:
553
- raise NotImplementedError(
554
- "EPLB not supported for `CompressedTensorsW4A4MoEMethod` yet."
555
- )
556
-
470
return flashinfer_trtllm_fp4_moe(
471
layer=layer,
472
x=x,
@@ -566,79 +479,41 @@ class CompressedTensorsW4A4Nvfp4MoEMethod(CompressedTensorsMoEMethod):
479
e_score_correction_bias=layer.e_score_correction_bias,
480
)
481
482
+ # Hidden_states in select_experts is only used to extract metadata
483
+ if isinstance(x, tuple):
484
+ x_routing, _ = x
485
+ else:
486
+ x_routing = x
487
topk_weights, topk_ids = layer.select_experts(
570
- hidden_states=x,
488
+ hidden_states=x_routing,
489
router_logits=router_logits,
490
)
491
574
- if self.use_marlin:
575
- return fused_marlin_moe(
492
+ # EPLB path
493
+ if self.nvfp4_backend == NvFp4MoeBackend.FLASHINFER_TRTLLM:
494
+ assert layer.enable_eplb
495
+ return flashinfer_trtllm_fp4_routed_moe(
496
+ layer=layer,
497
+ x=x,
498
+ topk_ids=topk_ids,
499
+ topk_weights=topk_weights,
500
+ top_k=layer.top_k,
501
+ global_num_experts=layer.global_num_experts,
502
+ )
503
+ else:
504
+ assert self.kernel is not None
505
+ return self.kernel(
506
x,
507
layer.w13_weight,
508
layer.w2_weight,
579
- None,
580
- None,
581
- layer.w13_weight_scale,
582
- layer.w2_weight_scale,
583
- router_logits,
509
topk_weights,
510
topk_ids,
586
- global_scale1=layer.w13_weight_scale_2,
587
- global_scale2=layer.w2_weight_scale_2,
588
- quant_type_id=scalar_types.float4_e2m1f.id,
589
- apply_router_weight_on_input=layer.apply_router_weight_on_input,
590
- global_num_experts=layer.global_num_experts,
591
- expert_map=layer.expert_map,
592
- input_dtype=self.marlin_input_dtype,
593
- workspace=layer.workspace,
594
- )
595
-
596
- # FlashInfer fused experts path
597
- elif self.allow_flashinfer:
598
- from vllm.model_executor.layers.fused_moe.flashinfer_cutlass_moe import ( # noqa: E501
599
- flashinfer_cutlass_moe_fp4,
600
- )
601
-
602
- assert is_valid_flashinfer_cutlass_fused_moe(
603
- x, layer.w13_weight, layer.w2_weight
604
- ), "Flashinfer CUTLASS Fused MoE not applicable!"
605
-
606
- assert self.moe_quant_config is not None
607
-
608
- return flashinfer_cutlass_moe_fp4(
609
- hidden_states=x,
610
- w1=layer.w13_weight,
611
- w2=layer.w2_weight,
612
- topk_weights=topk_weights,
613
- topk_ids=topk_ids,
614
- quant_config=self.moe_quant_config,
615
- inplace=False, # TODO(shuw): fix later, now output is high prec
511
+ inplace=False,
512
activation=layer.activation,
513
global_num_experts=layer.global_num_experts,
514
expert_map=layer.expert_map,
515
apply_router_weight_on_input=layer.apply_router_weight_on_input,
516
)
621
- else:
622
- # If no modular kernel is provided, use cutlass_moe_fp4 for TP case
623
- # only (no EP).
624
- from vllm.model_executor.layers.fused_moe.cutlass_moe import cutlass_moe_fp4
625
-
626
- assert self.moe_quant_config is not None
627
- return cutlass_moe_fp4(
628
- a=x,
629
- w1_fp4=layer.w13_weight,
630
- w2_fp4=layer.w2_weight,
631
- topk_weights=topk_weights,
632
- topk_ids=topk_ids,
633
- quant_config=self.moe_quant_config,
634
- expert_map=layer.expert_map,
635
- apply_router_weight_on_input=layer.apply_router_weight_on_input,
636
- # TODO(bnell): derive these from arguments
637
- m=x.shape[0],
638
- n=layer.w2_weight.shape[2] * 2,
639
- k=x.shape[1],
640
- e=layer.w13_weight.shape[0],
641
- ).to(x.dtype)
517
518
519
class CompressedTensorsW8A8Fp8MoEMethod(CompressedTensorsMoEMethod):
vllm/model_executor/layers/quantization/modelopt.py
+87
-285
@@ -15,9 +15,7 @@ from vllm.attention.layer import Attention
15
from vllm.logger import init_logger
16
from vllm.model_executor.layers.fused_moe.config import (
17
FusedMoEQuantConfig,
18
- nvfp4_moe_quant_config,
18
)
20
-from vllm.model_executor.layers.fused_moe.fused_marlin_moe import fused_marlin_moe
19
from vllm.model_executor.layers.fused_moe.layer import (
20
FusedMoE,
21
FusedMoEMethodBase,
@@ -30,6 +28,15 @@ from vllm.model_executor.layers.fused_moe.oracle.fp8 import (
28
make_fp8_moe_quant_config,
29
select_fp8_moe_backend,
30
)
31
+from vllm.model_executor.layers.fused_moe.oracle.nvfp4 import (
32
+ FLASHINFER_NVFP4_MOE_BACKENDS,
33
+ NvFp4MoeBackend,
34
+ convert_to_nvfp4_moe_kernel_format,
35
+ is_global_sf_supported_for_nvfp4_backend,
36
+ make_nvfp4_moe_kernel,
37
+ make_nvfp4_moe_quant_config,
38
+ select_nvfp4_moe_backend,
39
+)
40
from vllm.model_executor.layers.linear import (
41
LinearBase,
42
LinearMethodBase,
@@ -45,16 +52,11 @@ from vllm.model_executor.layers.quantization.utils.flashinfer_fp4_moe import (
52
build_flashinfer_fp4_cutlass_moe_prepare_finalize,
53
flashinfer_trtllm_fp4_moe,
54
flashinfer_trtllm_fp4_routed_moe,
48
- prepare_static_weights_for_trtllm_fp4_moe,
49
- reorder_w1w3_to_w3w1,
55
select_nvfp4_gemm_impl,
56
)
57
from vllm.model_executor.layers.quantization.utils.flashinfer_utils import (
53
- FlashinferMoeBackend,
58
apply_fi_trtllm_fp8_per_tensor_moe,
59
build_flashinfer_fp8_cutlass_moe_prepare_finalize,
56
- get_flashinfer_moe_backend,
57
- is_flashinfer_supporting_global_sf,
60
select_cutlass_fp8_gemm_impl,
61
)
62
from vllm.model_executor.layers.quantization.utils.fp8_utils import (
@@ -69,7 +71,6 @@ from vllm.model_executor.layers.quantization.utils.marlin_utils_fp4 import (
71
apply_fp4_marlin_linear,
72
is_fp4_marlin_supported,
73
prepare_fp4_layer_for_marlin,
72
- prepare_moe_fp4_layer_for_marlin,
74
)
75
from vllm.model_executor.layers.quantization.utils.quant_utils import (
76
GroupShape,
@@ -89,7 +90,6 @@ from vllm.model_executor.parameter import (
90
PerTensorScaleParameter,
91
)
92
from vllm.model_executor.utils import replace_parameter
92
-from vllm.scalar_type import scalar_types
93
from vllm.utils.flashinfer import (
94
flashinfer_scaled_fp4_mm,
95
has_flashinfer,
@@ -1327,43 +1327,32 @@ class ModelOptNvFp4FusedMoE(FusedMoEMethodBase):
1327
quant_config: ModelOptNvFp4Config,
1328
layer: FusedMoE,
1329
) -> None:
1330
- from vllm.model_executor.layers.quantization.utils.nvfp4_moe_support import (
1331
- detect_nvfp4_moe_support, # noqa: E501
1332
- )
1333
-
1330
super().__init__(layer.moe_config)
1331
self.quant_config = quant_config
1336
- self.layer = layer
1337
- _nvfp4 = detect_nvfp4_moe_support(self.__class__.__name__)
1338
- self.cutlass_nvfp4_supported = _nvfp4.cutlass_supported
1339
- self.allow_flashinfer = _nvfp4.allow_flashinfer
1340
- self.use_marlin = _nvfp4.use_marlin
1341
- self.marlin_input_dtype = None
1342
- self.flashinfer_moe_backend = None
1343
- if self.allow_flashinfer:
1344
- self.flashinfer_moe_backend = get_flashinfer_moe_backend()
1345
- logger.info_once(
1346
- f"Using FlashInfer {self.flashinfer_moe_backend.value} kernels"
1347
- " for ModelOptNvFp4FusedMoE."
1332
+ self.nvfp4_backend = select_nvfp4_moe_backend()
1333
+ # TODO: move this type of check into the oracle.
1334
+ if (
1335
+ not self.moe.is_act_and_mul
1336
+ and not self.nvfp4_backend == NvFp4MoeBackend.FLASHINFER_CUTLASS
1337
+ ):
1338
+ raise NotImplementedError(
1339
+ "Non-gated activations are only supported by FlashInfer "
1340
+ "CUTLASS NvFP4 MoE backend."
1341
)
1349
- elif self.use_marlin:
1350
- logger.info_once("Using Marlin for ModelOptNvFp4FusedMoE.")
1351
- else:
1352
- logger.info_once("Using Cutlass for ModelOptNvFp4FusedMoE.")
1342
+
1343
+ self.use_global_sf = is_global_sf_supported_for_nvfp4_backend(
1344
+ self.nvfp4_backend
1345
+ )
1346
+ self.kernel: mk.FusedMoEModularKernel | None = None
1347
1348
def maybe_make_prepare_finalize(
1349
self,
1350
routing_tables: tuple[torch.Tensor, torch.Tensor, torch.Tensor] | None = None,
1351
) -> mk.FusedMoEPrepareAndFinalize | None:
1358
- if self.use_marlin or (
1359
- self.allow_flashinfer
1360
- and self.flashinfer_moe_backend == FlashinferMoeBackend.TENSORRT_LLM
1361
- ):
1352
+ UNSUPPORTED = [NvFp4MoeBackend.MARLIN, NvFp4MoeBackend.FLASHINFER_TRTLLM]
1353
+ if self.nvfp4_backend in UNSUPPORTED:
1354
return None
1363
- elif (
1364
- self.allow_flashinfer
1365
- and self.flashinfer_moe_backend == FlashinferMoeBackend.CUTLASS
1366
- ):
1355
+ elif self.nvfp4_backend == NvFp4MoeBackend.FLASHINFER_CUTLASS:
1356
# TP case: avoid convert to ModularKernelMethod - to be refactored.
1357
if self.moe.dp_size == 1:
1358
return None
@@ -1385,7 +1374,7 @@ class ModelOptNvFp4FusedMoE(FusedMoEMethodBase):
1374
experts = select_nvfp4_gemm_impl(
1375
self.moe,
1376
self.moe_quant_config,
1388
- allow_flashinfer=self.allow_flashinfer,
1377
+ allow_flashinfer=self.nvfp4_backend in FLASHINFER_NVFP4_MOE_BACKENDS,
1378
)
1379
logger.debug_once("Using %s", experts.__class__.__name__)
1380
return experts
@@ -1405,11 +1394,7 @@ class ModelOptNvFp4FusedMoE(FusedMoEMethodBase):
1394
params_dtype: torch.dtype,
1395
**extra_weight_attrs,
1396
):
1408
- if not self.quant_config.is_checkpoint_nvfp4_serialized:
1409
- raise ValueError(
1410
- "NVFP4 quantization was selected, "
1411
- " dynamic quantization is not supported."
1412
- )
1397
+ assert self.quant_config.is_checkpoint_nvfp4_serialized
1398
1399
layer.num_experts = num_experts
1400
layer.params_dtype = params_dtype
@@ -1498,14 +1483,12 @@ class ModelOptNvFp4FusedMoE(FusedMoEMethodBase):
1483
{"quant_method": FusedMoeWeightScaleSupported.TENSOR.value}
1484
)
1485
1501
- use_global_sf = self.allow_flashinfer and is_flashinfer_supporting_global_sf(
1502
- self.flashinfer_moe_backend
1486
+ global_sf_num_experts = (
1487
+ global_num_experts if self.use_global_sf else num_experts
1488
)
1504
- global_scale_num_experts = global_num_experts if use_global_sf else num_experts
1505
-
1489
w13_input_scale = PerTensorScaleParameter(
1490
data=torch.empty(
1508
- global_scale_num_experts,
1491
+ global_sf_num_experts,
1492
2 if self.moe.is_act_and_mul else 1,
1493
dtype=torch.float32,
1494
),
@@ -1514,32 +1497,17 @@ class ModelOptNvFp4FusedMoE(FusedMoEMethodBase):
1497
layer.register_parameter("w13_input_scale", w13_input_scale)
1498
1499
w2_input_scale = PerTensorScaleParameter(
1517
- data=torch.empty(global_scale_num_experts, dtype=torch.float32),
1500
+ data=torch.empty(global_sf_num_experts, dtype=torch.float32),
1501
weight_loader=weight_loader,
1502
)
1503
layer.register_parameter("w2_input_scale", w2_input_scale)
1504
1505
def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
1523
- # GEMM 1 processing
1524
- gemm1_weight = layer.w13_weight.data
1525
- gemm1_weight_scale = layer.w13_weight_scale.data
1526
-
1527
- if (
1528
- self.allow_flashinfer
1529
- and (
1530
- self.flashinfer_moe_backend == FlashinferMoeBackend.CUTLASS
1531
- or self.flashinfer_moe_backend == FlashinferMoeBackend.TENSORRT_LLM
1532
- )
1533
- and self.moe.is_act_and_mul
1534
- ):
1535
- gemm1_weight, gemm1_weight_scale = reorder_w1w3_to_w3w1(
1536
- gemm1_weight, gemm1_weight_scale, dim=-2
1537
- )
1538
-
1539
- layer.w13_weight = Parameter(gemm1_weight, requires_grad=False)
1540
- layer.w13_weight_scale = Parameter(gemm1_weight_scale, requires_grad=False)
1506
+ """
1507
+ Convert NVFP4 MoE weights into kernel format and setup the kernel.
1508
+ """
1509
1542
- # Common processing for w13_weight_scale_2
1510
+ # Use a single gscale for w13.
1511
if self.moe.is_act_and_mul and not torch.allclose(
1512
layer.w13_weight_scale_2[:, 0], layer.w13_weight_scale_2[:, 1]
1513
):
@@ -1547,136 +1515,47 @@ class ModelOptNvFp4FusedMoE(FusedMoEMethodBase):
1515
"w1_weight_scale_2 must match w3_weight_scale_2. "
1516
"Accuracy may be affected."
1517
)
1550
-
1518
w13_weight_scale_2 = layer.w13_weight_scale_2[:, 0].contiguous()
1552
- layer.w13_weight_scale_2 = Parameter(w13_weight_scale_2, requires_grad=False)
1519
1554
- # Common processing for input scales and alphas
1555
- use_global_sf = self.allow_flashinfer and is_flashinfer_supporting_global_sf(
1556
- self.flashinfer_moe_backend
1557
- )
1558
- if use_global_sf:
1559
- # For backends provide by Flashinfer, the input global scales are
1560
- # shared across all experts.
1561
- w13_input_scale = (
1562
- layer.w13_input_scale.max().to(torch.float32).expand(layer.num_experts)
1563
- )
1564
- else:
1565
- w13_input_scale = layer.w13_input_scale.max(dim=1).values.to(torch.float32)
1566
- layer.g1_alphas = Parameter(
1567
- (w13_input_scale * w13_weight_scale_2).to(torch.float32),
1568
- requires_grad=False,
1569
- )
1570
-
1571
- # This is for quantization, so we need to invert it.
1572
- layer.w13_input_scale_quant = Parameter(
1573
- (1 / w13_input_scale).to(torch.float32), requires_grad=False
1574
- )
1575
-
1576
- # GEMM 2 processing
1577
- if use_global_sf:
1578
- # For backends provide by Flashinfer, the input global scales are
1579
- # shared across all experts.
1580
- w2_input_scale = (
1581
- layer.w2_input_scale.max().to(torch.float32).expand(layer.num_experts)
1582
- )
1583
- else:
1584
- w2_input_scale = layer.w2_input_scale
1585
- layer.g2_alphas = Parameter(
1586
- (w2_input_scale * layer.w2_weight_scale_2).to(torch.float32),
1587
- requires_grad=False,
1588
- )
1589
-
1590
- # This is for quantization, so we need to invert it.
1591
- layer.w2_input_scale_quant = Parameter(
1592
- (1 / w2_input_scale).to(torch.float32), requires_grad=False
1520
+ (
1521
+ w13,
1522
+ w13_scale,
1523
+ w13_scale_2,
1524
+ a13_scale,
1525
+ w2,
1526
+ w2_scale,
1527
+ w2_scale_2,
1528
+ a2_scale,
1529
+ ) = convert_to_nvfp4_moe_kernel_format(
1530
+ nvfp4_backend=self.nvfp4_backend,
1531
+ layer=layer,
1532
+ w13=layer.w13_weight,
1533
+ w13_scale=layer.w13_weight_scale,
1534
+ w13_scale_2=w13_weight_scale_2,
1535
+ a13_scale=layer.w13_input_scale,
1536
+ w2=layer.w2_weight,
1537
+ w2_scale=layer.w2_weight_scale,
1538
+ w2_scale_2=layer.w2_weight_scale_2,
1539
+ a2_scale=layer.w2_input_scale,
1540
+ is_act_and_mul=self.moe.is_act_and_mul,
1541
)
1542
1595
- # TensorRT-LLM specific processing
1596
- if (
1597
- self.allow_flashinfer
1598
- and self.flashinfer_moe_backend == FlashinferMoeBackend.TENSORRT_LLM
1599
- ):
1600
- # Prepare static weights for TRT-LLM kernel
1601
- # alternate: prepare_static_weight_layouts_for_trtllm_moe
1602
- (
1603
- gemm1_weights_fp4_shuffled,
1604
- gemm1_scales_fp4_shuffled,
1605
- gemm2_weights_fp4_shuffled,
1606
- gemm2_scales_fp4_shuffled,
1607
- ) = prepare_static_weights_for_trtllm_fp4_moe(
1608
- layer.w13_weight,
1609
- layer.w2_weight,
1610
- layer.w13_weight_scale,
1611
- layer.w2_weight_scale,
1612
- layer.w2_weight.size(-2), # hidden_size
1613
- layer.w13_weight.size(-2) // 2, # intermediate_size
1614
- layer.w13_weight.size(0), # num_experts
1615
- )
1616
- logger.debug_once("Finished shuffling weights for TRT-LLM MOE")
1617
-
1618
- layer.w13_weight = Parameter(
1619
- gemm1_weights_fp4_shuffled, requires_grad=False
1620
- )
1621
- layer.w2_weight = Parameter(gemm2_weights_fp4_shuffled, requires_grad=False)
1622
- layer.w13_weight_scale = Parameter(
1623
- gemm1_scales_fp4_shuffled, requires_grad=False
1624
- )
1625
- layer.w2_weight_scale = Parameter(
1626
- gemm2_scales_fp4_shuffled, requires_grad=False
1627
- )
1628
-
1629
- # Additional parameter needed for TRT-LLM
1630
- layer.g1_scale_c = Parameter(
1631
- (layer.w2_input_scale_quant * layer.g1_alphas).to(torch.float32),
1632
- requires_grad=False,
1633
- )
1634
- elif self.use_marlin:
1635
- # Marlin processing
1636
- prepare_moe_fp4_layer_for_marlin(layer)
1637
- del layer.g1_alphas
1638
- del layer.g2_alphas
1639
- del layer.w13_input_scale_quant
1640
- del layer.w2_input_scale_quant
1641
- else:
1642
- # Non-TRT-LLM processing (Cutlass or non-flashinfer)
1643
- w13_blockscale_swizzled = swizzle_blockscale(layer.w13_weight_scale)
1644
- layer.w13_weight_scale = Parameter(
1645
- w13_blockscale_swizzled, requires_grad=False
1646
- )
1647
-
1648
- w13_weight = layer.w13_weight
1649
- intermediate_size_pad = w13_blockscale_swizzled.size(1) - w13_weight.size(1)
1650
- if intermediate_size_pad:
1651
- # padding gated activations will require to split w1 and w3
1652
- # and pad them individually
1653
- assert not self.moe.is_act_and_mul, (
1654
- "The intermediate size required padding, "
1655
- "but padding is not implemented for gated activations"
1656
- )
1657
-
1658
- layer.w13_weight = Parameter(
1659
- torch.nn.functional.pad(
1660
- w13_weight, (0, 0, 0, intermediate_size_pad)
1661
- ),
1662
- requires_grad=False,
1663
- )
1664
- layer.w2_weight = Parameter(
1665
- torch.nn.functional.pad(
1666
- layer.w2_weight, (0, intermediate_size_pad // 2, 0, 0)
1667
- ),
1668
- requires_grad=False,
1669
- )
1670
- layer.w2_weight_scale = Parameter(
1671
- torch.nn.functional.pad(
1672
- layer.w2_weight_scale, (0, intermediate_size_pad // 16)
1673
- ),
1674
- requires_grad=False,
1675
- )
1543
+ replace_parameter(layer, "w13_weight", w13)
1544
+ replace_parameter(layer, "w13_weight_scale", w13_scale)
1545
+ replace_parameter(layer, "w13_weight_scale_2", w13_scale_2)
1546
+ replace_parameter(layer, "w13_input_scale", a13_scale)
1547
+ replace_parameter(layer, "w2_weight", w2)
1548
+ replace_parameter(layer, "w2_weight_scale", w2_scale)
1549
+ replace_parameter(layer, "w2_weight_scale_2", w2_scale_2)
1550
+ replace_parameter(layer, "w2_input_scale", a2_scale)
1551
1677
- w2_blockscale_swizzled = swizzle_blockscale(layer.w2_weight_scale)
1678
- layer.w2_weight_scale = Parameter(
1679
- w2_blockscale_swizzled, requires_grad=False
1552
+ self.moe_quant_config = self.get_fused_moe_quant_config(layer)
1553
+ use_dp = self.moe.dp_size > 1
1554
+ if self.moe_quant_config is not None and not use_dp:
1555
+ self.kernel = make_nvfp4_moe_kernel(
1556
+ backend=self.nvfp4_backend,
1557
+ quant_config=self.moe_quant_config,
1558
+ moe_config=self.moe,
1559
)
1560
1561
def prepare_dp_allgather_tensor(
@@ -1688,7 +1567,8 @@ class ModelOptNvFp4FusedMoE(FusedMoEMethodBase):
1567
"""Optionally prepare extra tensors to carry through DP allgather/EP."""
1568
import flashinfer
1569
1691
- a1_gscale = layer.w13_input_scale_quant
1570
+ assert self.moe_quant_config is not None
1571
+ a1_gscale = self.moe_quant_config.a1_gscale
1572
hidden_states_fp4, hidden_states_sf = flashinfer.fp4_quantize(
1573
hidden_states,
1574
a1_gscale,
@@ -1700,19 +1580,14 @@ class ModelOptNvFp4FusedMoE(FusedMoEMethodBase):
1580
def get_fused_moe_quant_config(
1581
self, layer: torch.nn.Module
1582
) -> FusedMoEQuantConfig | None:
1703
- if (
1704
- self.use_marlin
1705
- or self.flashinfer_moe_backend == FlashinferMoeBackend.TENSORRT_LLM
1706
- ):
1707
- return None
1708
-
1709
- return nvfp4_moe_quant_config(
1710
- w1_scale=layer.w13_weight_scale,
1583
+ return make_nvfp4_moe_quant_config(
1584
+ backend=self.nvfp4_backend,
1585
+ w13_scale=layer.w13_weight_scale,
1586
w2_scale=layer.w2_weight_scale,
1712
- g1_alphas=layer.g1_alphas,
1713
- g2_alphas=layer.g2_alphas,
1714
- a1_gscale=layer.w13_input_scale_quant,
1715
- a2_gscale=layer.w2_input_scale_quant,
1587
+ w13_scale_2=layer.w13_weight_scale_2,
1588
+ w2_scale_2=layer.w2_weight_scale_2,
1589
+ a13_scale=layer.w13_input_scale,
1590
+ a2_scale=layer.w2_input_scale,
1591
)
1592
1593
@property
@@ -1725,18 +1600,8 @@ class ModelOptNvFp4FusedMoE(FusedMoEMethodBase):
1600
x: torch.Tensor,
1601
router_logits: torch.Tensor,
1602
) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]:
1728
- if not self.moe.is_act_and_mul:
1729
- assert (
1730
- self.allow_flashinfer
1731
- and self.flashinfer_moe_backend == FlashinferMoeBackend.CUTLASS
1732
- ), (
1733
- "Non-gated activations are only supported by the"
1734
- " flashinfer CUTLASS backend for modelopt checkpoints"
1735
- )
1736
-
1603
if (
1738
- self.allow_flashinfer
1739
- and self.flashinfer_moe_backend == FlashinferMoeBackend.TENSORRT_LLM
1604
+ self.nvfp4_backend == NvFp4MoeBackend.FLASHINFER_TRTLLM
1605
and not layer.enable_eplb
1606
):
1607
return flashinfer_trtllm_fp4_moe(
@@ -1762,10 +1627,8 @@ class ModelOptNvFp4FusedMoE(FusedMoEMethodBase):
1627
)
1628
1629
# EPLB path
1765
- if (
1766
- self.allow_flashinfer
1767
- and self.flashinfer_moe_backend == FlashinferMoeBackend.TENSORRT_LLM
1768
- ):
1630
+ if self.nvfp4_backend == NvFp4MoeBackend.FLASHINFER_TRTLLM:
1631
+ assert layer.enable_eplb
1632
return flashinfer_trtllm_fp4_routed_moe(
1633
layer=layer,
1634
x=x,
@@ -1774,81 +1637,20 @@ class ModelOptNvFp4FusedMoE(FusedMoEMethodBase):
1637
top_k=layer.top_k,
1638
global_num_experts=layer.global_num_experts,
1639
)
1777
-
1778
- if self.use_marlin:
1779
- return fused_marlin_moe(
1640
+ else:
1641
+ assert self.kernel is not None
1642
+ return self.kernel(
1643
x,
1644
layer.w13_weight,
1645
layer.w2_weight,
1783
- None,
1784
- None,
1785
- layer.w13_weight_scale,
1786
- layer.w2_weight_scale,
1787
- router_logits,
1646
topk_weights,
1647
topk_ids,
1790
- global_scale1=layer.w13_weight_scale_2,
1791
- global_scale2=layer.w2_weight_scale_2,
1792
- quant_type_id=scalar_types.float4_e2m1f.id,
1793
- apply_router_weight_on_input=layer.apply_router_weight_on_input,
1794
- global_num_experts=layer.global_num_experts,
1795
- expert_map=layer.expert_map,
1796
- input_dtype=self.marlin_input_dtype,
1797
- )
1798
-
1799
- elif self.allow_flashinfer:
1800
- assert self.flashinfer_moe_backend in (
1801
- FlashinferMoeBackend.CUTLASS,
1802
- FlashinferMoeBackend.CUTEDSL,
1803
- )
1804
- if self.flashinfer_moe_backend == FlashinferMoeBackend.CUTLASS:
1805
- from vllm.model_executor.layers.fused_moe.flashinfer_cutlass_moe import ( # noqa: E501
1806
- flashinfer_cutlass_moe_fp4,
1807
- )
1808
-
1809
- flashinfer_fn_moe_fp4 = flashinfer_cutlass_moe_fp4
1810
- else:
1811
- from vllm.model_executor.layers.fused_moe.flashinfer_cutedsl_moe import ( # noqa: E501
1812
- flashinfer_cutedsl_moe_fp4,
1813
- )
1814
-
1815
- flashinfer_fn_moe_fp4 = flashinfer_cutedsl_moe_fp4
1816
-
1817
- assert self.moe_quant_config is not None
1818
- return flashinfer_fn_moe_fp4(
1819
- hidden_states=x,
1820
- w1=layer.w13_weight,
1821
- w2=layer.w2_weight,
1822
- topk_weights=topk_weights,
1823
- topk_ids=topk_ids,
1824
- quant_config=self.moe_quant_config,
1648
inplace=False,
1649
activation=layer.activation,
1650
global_num_experts=layer.global_num_experts,
1651
expert_map=layer.expert_map,
1652
apply_router_weight_on_input=layer.apply_router_weight_on_input,
1653
)
1831
- else:
1832
- # If no modular kernel is provided, use cutlass_moe_fp4 for TP case
1833
- # only (no EP).
1834
- from vllm.model_executor.layers.fused_moe.cutlass_moe import cutlass_moe_fp4
1835
-
1836
- assert self.moe_quant_config is not None
1837
- return cutlass_moe_fp4(
1838
- a=x,
1839
- w1_fp4=layer.w13_weight,
1840
- w2_fp4=layer.w2_weight,
1841
- topk_weights=topk_weights,
1842
- topk_ids=topk_ids,
1843
- quant_config=self.moe_quant_config,
1844
- expert_map=layer.expert_map,
1845
- apply_router_weight_on_input=layer.apply_router_weight_on_input,
1846
- # TODO: derive from arguments
1847
- m=x.shape[0],
1848
- n=layer.w2_weight.shape[2] * 2,
1849
- k=x.shape[1],
1850
- e=layer.w13_weight.shape[0],
1851
- )
1654
1655
1656
ModelOptNvFp4Config.LinearMethodCls = ModelOptNvFp4LinearMethod
vllm/model_executor/layers/quantization/utils/flashinfer_fp4_moe.py
+106
-4
@@ -2,10 +2,13 @@
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
"""Utility helpers for NVFP4 + FlashInfer fused-MoE path"""
4
5
+from typing import TYPE_CHECKING
6
+
7
import torch
8
9
import vllm.envs as envs
10
import vllm.model_executor.layers.fused_moe.modular_kernel as mk
11
+from vllm.logger import init_logger
12
from vllm.model_executor.layers.fused_moe.config import (
13
FusedMoEConfig,
14
FusedMoEQuantConfig,
@@ -20,12 +23,23 @@ from vllm.model_executor.layers.fused_moe.flashinfer_cutlass_moe import (
23
from vllm.model_executor.layers.fused_moe.flashinfer_cutlass_prepare_finalize import ( # noqa: E501
24
create_flashinfer_prepare_finalize,
25
)
26
+from vllm.model_executor.layers.quantization.utils.quant_utils import (
27
+ swizzle_blockscale,
28
+)
29
from vllm.platforms import current_platform
30
from vllm.utils.flashinfer import (
31
has_flashinfer_cutedsl_grouped_gemm_nt_masked,
32
has_flashinfer_cutlass_fused_moe,
33
)
34
35
+if TYPE_CHECKING:
36
+ from vllm.model_executor.layers.fused_moe.oracle.nvfp4 import (
37
+ NvFp4MoeBackend,
38
+ )
39
+
40
+logger = init_logger(__name__)
41
+
42
+
43
__all__ = [
44
"is_flashinfer_fp4_cutlass_moe_available",
45
"is_flashinfer_fp4_cutedsl_moe_available",
@@ -273,10 +287,9 @@ def flashinfer_trtllm_fp4_moe(
287
hidden_states_fp4, hidden_states_scale_linear_fp4 = x
288
else:
289
# hidden_states is the already quantized
276
- a1_gscale = layer.w13_input_scale_quant
290
(hidden_states_fp4, hidden_states_scale_linear_fp4) = flashinfer.fp4_quantize(
291
x,
279
- a1_gscale,
292
+ layer.a1_gscale,
293
is_sf_swizzled_layout=False,
294
)
295
@@ -369,10 +382,9 @@ def flashinfer_trtllm_fp4_routed_moe(
382
hidden_states_fp4, hidden_states_scale_linear_fp4 = x
383
else:
384
# Quantize input to FP4
372
- a1_gscale = layer.w13_input_scale_quant
385
(hidden_states_fp4, hidden_states_scale_linear_fp4) = flashinfer.fp4_quantize(
386
x,
375
- a1_gscale,
387
+ layer.a1_gscale,
388
is_sf_swizzled_layout=False,
389
)
390
@@ -410,3 +422,93 @@ def flashinfer_trtllm_fp4_routed_moe(
422
)[0]
423
424
return out
425
+
426
+
427
+def prepare_nvfp4_moe_layer_for_fi_or_cutlass(
428
+ backend: "NvFp4MoeBackend",
429
+ layer: torch.nn.Module,
430
+ w13: torch.Tensor,
431
+ w13_scale: torch.Tensor,
432
+ w13_scale_2: torch.Tensor,
433
+ a13_scale: torch.Tensor,
434
+ w2: torch.Tensor,
435
+ w2_scale: torch.Tensor,
436
+ w2_scale_2: torch.Tensor,
437
+ a2_scale: torch.Tensor,
438
+ is_act_and_mul: bool,
439
+) -> tuple[
440
+ torch.Tensor,
441
+ torch.Tensor,
442
+ torch.Tensor,
443
+ torch.Tensor,
444
+ torch.Tensor,
445
+ torch.Tensor,
446
+ torch.Tensor,
447
+ torch.Tensor,
448
+]:
449
+ # Delayed import for circular dependency avoidance.
450
+ from vllm.model_executor.layers.fused_moe.oracle.nvfp4 import (
451
+ NvFp4MoeBackend,
452
+ is_global_sf_supported_for_nvfp4_backend,
453
+ )
454
+
455
+ assert backend in [
456
+ NvFp4MoeBackend.VLLM_CUTLASS,
457
+ NvFp4MoeBackend.FLASHINFER_CUTLASS,
458
+ NvFp4MoeBackend.FLASHINFER_TRTLLM,
459
+ NvFp4MoeBackend.FLASHINFER_TRTLLM,
460
+ ]
461
+
462
+ # Reorder [w1, w3] to [w3, w1] for FI NVFP4 MoE kernels.
463
+ if is_act_and_mul and backend in [
464
+ NvFp4MoeBackend.FLASHINFER_CUTLASS,
465
+ NvFp4MoeBackend.FLASHINFER_TRTLLM,
466
+ ]:
467
+ w13, w13_scale = reorder_w1w3_to_w3w1(w13, w13_scale)
468
+
469
+ # For some FI kernels, the input scales are shared by all experts.
470
+ if is_global_sf_supported_for_nvfp4_backend(backend):
471
+ num_experts = w13.shape[0]
472
+ a13_scale = a13_scale.max().to(torch.float32).expand(num_experts)
473
+ a2_scale = a2_scale.max().to(torch.float32).expand(num_experts)
474
+ else:
475
+ a13_scale = a13_scale.max(dim=1).values.to(torch.float32)
476
+
477
+ # Shuffle weights and scales for FI TRTLLM NVFP4 MoE kernels.
478
+ if backend == NvFp4MoeBackend.FLASHINFER_TRTLLM:
479
+ w13, w13_scale, w2, w2_scale = prepare_static_weights_for_trtllm_fp4_moe(
480
+ w13,
481
+ w2,
482
+ w13_scale,
483
+ w2_scale,
484
+ w2.size(-2), # hidden_size
485
+ w13.size(-2) // 2, # intermediate_size
486
+ w13.size(0), # num_experts
487
+ )
488
+
489
+ # We do not need to make this a parameter, because
490
+ # it is not used during the weight (re)-loading process.
491
+ layer.g1_scale_c = a13_scale * w13_scale_2 / a2_scale
492
+ layer.a1_gscale = 1.0 / a13_scale
493
+ layer.g1_alphas = a13_scale * w13_scale_2
494
+ layer.g2_alphas = a2_scale * w2_scale_2
495
+ else:
496
+ # Swizzle the block scales for other FI NVFP4 MoE kernels.
497
+ w13_scale = swizzle_blockscale(w13_scale)
498
+
499
+ # Apply padding if needed.
500
+ pad_size = w13_scale.size(1) - w13.size(1)
501
+ if pad_size > 0:
502
+ if is_act_and_mul:
503
+ raise NotImplementedError(
504
+ "Intermediate size padding for w1 and w3, for %s "
505
+ "NvFp4 backend, but this is not currently supported",
506
+ backend.value,
507
+ )
508
+ w13 = torch.nn.functional.pad(w13, (0, 0, 0, pad_size))
509
+ w2 = torch.nn.functional.pad(w2, (0, pad_size // 2, 0, 0))
510
+ w2_scale = torch.nn.functional.pad(w2_scale, (0, pad_size // 16))
511
+
512
+ w2_scale = swizzle_blockscale(w2_scale)
513
+
514
+ return w13, w13_scale, w13_scale_2, a13_scale, w2, w2_scale, w2_scale_2, a2_scale
vllm/model_executor/layers/quantization/utils/marlin_utils_fp4.py
+101
@@ -8,6 +8,7 @@ import vllm._custom_ops as ops
8
from vllm.logger import init_logger
9
from vllm.model_executor.layers.quantization.utils.marlin_utils import (
10
USE_FP32_REDUCE_DEFAULT,
11
+ get_marlin_input_dtype,
12
marlin_make_workspace_new,
13
marlin_permute_bias,
14
marlin_permute_scales,
@@ -226,6 +227,106 @@ def prepare_fp4_layer_for_marlin(
227
return
228
229
230
+def prepare_nvfp4_moe_layer_for_marlin(
231
+ layer: torch.nn.Module,
232
+ w13: torch.Tensor,
233
+ w13_scale: torch.Tensor,
234
+ w13_scale_2: torch.Tensor,
235
+ w2: torch.Tensor,
236
+ w2_scale: torch.Tensor,
237
+ w2_scale_2: torch.Tensor,
238
+) -> tuple[
239
+ torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor
240
+]:
241
+ logger.warning_once(
242
+ "Your GPU does not have native support for FP4 computation but "
243
+ "FP4 quantization is being used. Weight-only FP4 compression will "
244
+ "be used leveraging the Marlin kernel. This may degrade "
245
+ "performance for compute-heavy workloads."
246
+ )
247
+
248
+ input_dtype = get_marlin_input_dtype(prefix="")
249
+ if input_dtype is not None and input_dtype.itemsize == 1:
250
+ raise RuntimeError("NVFP4 weight + INT8/FP8 activation is not supported.")
251
+
252
+ GROUP_SIZE = 16
253
+ E = layer.num_experts
254
+ K = layer.hidden_size
255
+ N = layer.intermediate_size_per_partition
256
+
257
+ device = w13.device
258
+ param_dtype = layer.params_dtype
259
+ is_a_8bit = input_dtype is not None and input_dtype.itemsize == 1
260
+
261
+ # WORKSPACE
262
+ layer.workspace = marlin_make_workspace_new(device, 4)
263
+ perm = torch.empty(0, dtype=torch.int, device=device)
264
+
265
+ # WEIGHT
266
+ # Repack weights to marlin format
267
+ def repack_weight(weight: torch.Tensor, name: str) -> torch.Tensor:
268
+ tensor_list = []
269
+ if "w13" in name:
270
+ size_n, size_k = N * 2, K
271
+ else:
272
+ size_n, size_k = K, N
273
+
274
+ assert weight.shape == (E, size_n, size_k // 2)
275
+
276
+ for i in range(E):
277
+ qweight = weight[i].view(torch.int32).T.contiguous()
278
+
279
+ marlin_qweight = ops.gptq_marlin_repack(
280
+ b_q_weight=qweight,
281
+ perm=perm,
282
+ size_k=size_k,
283
+ size_n=size_n,
284
+ num_bits=4,
285
+ is_a_8bit=is_a_8bit,
286
+ )
287
+ tensor_list.append(marlin_qweight)
288
+
289
+ return torch.cat([x.unsqueeze(0) for x in tensor_list], 0)
290
+
291
+ w13 = repack_weight(w13, "w13")
292
+ w2 = repack_weight(w2, "w2")
293
+
294
+ # WEIGHT SCALES
295
+ # Permute scales
296
+ def premute_scales(
297
+ scales: torch.Tensor, g_scales: torch.Tensor, name: str
298
+ ) -> tuple[torch.Tensor, torch.Tensor]:
299
+ scales = scales.to(param_dtype)
300
+ g_scales = g_scales.to(param_dtype)
301
+
302
+ tensor_list = []
303
+ if "w13" in name:
304
+ size_n, size_k = N * 2, K
305
+ else:
306
+ size_n, size_k = K, N
307
+
308
+ for i in range(E):
309
+ scale = scales[i].T
310
+ marlin_scales = marlin_permute_scales(
311
+ s=scale,
312
+ size_k=size_k,
313
+ size_n=size_n,
314
+ group_size=GROUP_SIZE,
315
+ is_a_8bit=is_a_8bit,
316
+ )
317
+ marlin_scales = nvfp4_marlin_process_scales(marlin_scales)
318
+ tensor_list.append(marlin_scales)
319
+
320
+ scales = torch.cat([x.unsqueeze(0) for x in tensor_list], 0)
321
+ g_scales = nvfp4_marlin_process_global_scale(g_scales)
322
+ return scales, g_scales
323
+
324
+ w13_scale, w13_scale_2 = premute_scales(w13_scale, w13_scale_2, "w13")
325
+ w2_scale, w2_scale_2 = premute_scales(w2_scale, w2_scale_2, "w2")
326
+
327
+ return w13, w13_scale, w13_scale_2, w2, w2_scale, w2_scale_2
328
+
329
+
330
def prepare_moe_fp4_layer_for_marlin(
331
layer: torch.nn.Module, input_dtype: torch.dtype | None = None
332
) -> None: