@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / 97995f6376
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 7, 09:00 UTC

[MoE Refactor] Create MK for TRTLLM Kernels (#32564)

Signed-off-by: Robert Shaw <robshaw@redhat.com> Signed-off-by: Robert Shaw <rshaw@neuralmagic.com> Signed-off-by: Robert Shaw <robertgshaw2@gmail.com> Co-authored-by: Robert Shaw <robshaw@redhat.com> Co-authored-by: Robert Shaw <rshaw@neuralmagic.com>

Robert Shaw committed Mar 3, 2026 at 13:39 UTC 97995f6376fd3dae7a67624055ddf038233e181e
77 files changed +2574 -2086
.buildkite/test_areas/kernels.yaml
+2 -1
@@ -44,7 +44,8 @@ steps:
44 - vllm/envs.py
45 - vllm/config
46 commands:
47 - - pytest -v -s kernels/moe --shard-id=$$BUILDKITE_PARALLEL_JOB --num-shards=$$BUILDKITE_PARALLEL_JOB_COUNT
47 + - pytest -v -s kernels/moe --ignore=kernels/moe/test_modular_oai_triton_moe.py --shard-id=$$BUILDKITE_PARALLEL_JOB --num-shards=$$BUILDKITE_PARALLEL_JOB_COUNT
48 + - pytest -v -s kernels/moe/test_modular_oai_triton_moe.py --shard-id=$$BUILDKITE_PARALLEL_JOB --num-shards=$$BUILDKITE_PARALLEL_JOB_COUNT
49 parallelism: 2
50
51 - label: Kernels Mamba Test
benchmarks/kernels/benchmark_cutlass_moe_fp8.py
+17 -11
@@ -12,12 +12,12 @@ import vllm.model_executor.layers.fused_moe.modular_kernel as mk
12 from tests.kernels.moe.utils import make_dummy_moe_config
13 from vllm import _custom_ops as ops
14 from vllm.model_executor.layers.fused_moe.activation import MoEActivation
15 +from vllm.model_executor.layers.fused_moe.all2all_utils import (
16 + maybe_make_prepare_finalize,
17 +)
18 from vllm.model_executor.layers.fused_moe.config import fp8_w8a8_moe_quant_config
19 from vllm.model_executor.layers.fused_moe.cutlass_moe import CutlassExpertsFp8
20 from vllm.model_executor.layers.fused_moe.fused_moe import fused_experts, fused_topk
18 -from vllm.model_executor.layers.fused_moe.prepare_finalize import (
19 - MoEPrepareAndFinalizeNoEP,
20 -)
21 from vllm.platforms import current_platform
22 from vllm.utils.argparse_utils import FlexibleArgumentParser
23 from vllm.v1.worker.workspace import init_workspace_manager
@@ -137,15 +137,21 @@ def bench_run(
137 per_out_ch_quant=per_out_ch,
138 )
139
140 - fn = mk.FusedMoEModularKernel(
141 - MoEPrepareAndFinalizeNoEP(),
140 + moe_config = make_dummy_moe_config(
141 + num_experts=num_experts,
142 + hidden_dim=k,
143 + intermediate_size_per_partition=n,
144 + in_dtype=a.dtype,
145 + )
146 + fn = mk.FusedMoEKernel(
147 + maybe_make_prepare_finalize(
148 + moe=moe_config,
149 + quant_config=quant_config,
150 + allow_new_interface=True,
151 + use_monolithic=False,
152 + ),
153 CutlassExpertsFp8(
143 - moe_config=make_dummy_moe_config(
144 - num_experts=num_experts,
145 - hidden_dim=k,
146 - intermediate_size_per_partition=n,
147 - in_dtype=a.dtype,
148 - ),
154 + moe_config=moe_config,
155 quant_config=quant_config,
156 ),
157 )
benchmarks/kernels/benchmark_cutlass_moe_nvfp4.py
+26 -9
@@ -15,6 +15,9 @@ import vllm.model_executor.layers.fused_moe.modular_kernel as mk
15 from tests.kernels.moe.utils import make_dummy_moe_config
16 from vllm import _custom_ops as ops
17 from vllm.config import ParallelConfig, VllmConfig, set_current_vllm_config
18 +from vllm.model_executor.layers.fused_moe.all2all_utils import (
19 + maybe_make_prepare_finalize,
20 +)
21 from vllm.model_executor.layers.fused_moe.config import (
22 fp8_w8a8_moe_quant_config,
23 nvfp4_moe_quant_config,
@@ -23,9 +26,6 @@ from vllm.model_executor.layers.fused_moe.cutlass_moe import (
26 CutlassExpertsFp4,
27 )
28 from vllm.model_executor.layers.fused_moe.fused_moe import fused_experts, fused_topk
26 -from vllm.model_executor.layers.fused_moe.prepare_finalize import (
27 - MoEPrepareAndFinalizeNoEP,
28 -)
29 from vllm.scalar_type import scalar_types
30 from vllm.utils.argparse_utils import FlexibleArgumentParser
31 from vllm.v1.worker.workspace import init_workspace_manager
@@ -196,10 +196,21 @@ def bench_run(
196 g2_alphas=w2_gs,
197 )
198
199 - kernel = mk.FusedMoEModularKernel(
200 - MoEPrepareAndFinalizeNoEP(),
199 + moe_config = make_dummy_moe_config(
200 + num_experts=num_experts,
201 + hidden_dim=k,
202 + intermediate_size_per_partition=n,
203 + in_dtype=a.dtype,
204 + )
205 + kernel = mk.FusedMoEKernel(
206 + maybe_make_prepare_finalize(
207 + moe=moe_config,
208 + quant_config=quant_config,
209 + allow_new_interface=True,
210 + use_monolithic=False,
211 + ),
212 CutlassExpertsFp4(
202 - make_dummy_moe_config(),
213 + moe_config=moe_config,
214 quant_config=quant_config,
215 ),
216 )
@@ -240,11 +251,17 @@ def bench_run(
251 g1_alphas=w1_gs,
252 g2_alphas=w2_gs,
253 )
254 + moe_config = make_dummy_moe_config()
255
244 - kernel = mk.FusedMoEModularKernel(
245 - MoEPrepareAndFinalizeNoEP(),
256 + kernel = mk.FusedMoEKernel(
257 + maybe_make_prepare_finalize(
258 + moe=moe_config,
259 + quant_config=quant_config,
260 + allow_new_interface=True,
261 + use_monolithic=False,
262 + ),
263 CutlassExpertsFp4(
247 - make_dummy_moe_config(),
264 + moe_config=moe_config,
265 quant_config=quant_config,
266 ),
267 )
benchmarks/kernels/benchmark_grouped_gemm_cutlass.py
+31 -19
@@ -9,15 +9,15 @@ import vllm.model_executor.layers.fused_moe.modular_kernel as mk
9 from tests.kernels.moe.utils import make_dummy_moe_config
10 from vllm import _custom_ops as ops
11 from vllm.config import ParallelConfig, VllmConfig, set_current_vllm_config
12 +from vllm.model_executor.layers.fused_moe.all2all_utils import (
13 + maybe_make_prepare_finalize,
14 +)
15 from vllm.model_executor.layers.fused_moe.config import fp8_w8a8_moe_quant_config
16 from vllm.model_executor.layers.fused_moe.cutlass_moe import CutlassExpertsFp8
17 from vllm.model_executor.layers.fused_moe.fused_moe import (
18 fused_experts,
19 fused_topk,
20 )
18 -from vllm.model_executor.layers.fused_moe.prepare_finalize import (
19 - MoEPrepareAndFinalizeNoEP,
20 -)
21 from vllm.utils.argparse_utils import FlexibleArgumentParser
22 from vllm.v1.worker.workspace import init_workspace_manager
23
@@ -131,16 +131,22 @@ def bench_run(
131 w2_scale=w2_scale,
132 per_act_token_quant=per_act_token,
133 )
134 + moe_config = make_dummy_moe_config(
135 + num_experts=w2.shape[0],
136 + hidden_dim=w2.shape[1],
137 + intermediate_size_per_partition=w2.shape[2],
138 + in_dtype=a.dtype,
139 + )
140
135 - fn = mk.FusedMoEModularKernel(
136 - MoEPrepareAndFinalizeNoEP(),
141 + fn = mk.FusedMoEKernel(
142 + maybe_make_prepare_finalize(
143 + moe=moe_config,
144 + quant_config=quant_config,
145 + allow_new_interface=True,
146 + use_monolithic=False,
147 + ),
148 CutlassExpertsFp8(
138 - moe_config=make_dummy_moe_config(
139 - num_experts=w2.shape[0],
140 - hidden_dim=w2.shape[1],
141 - intermediate_size_per_partition=w2.shape[2],
142 - in_dtype=a.dtype,
143 - ),
149 + moe_config=moe_config,
150 quant_config=quant_config,
151 ),
152 )
@@ -163,16 +169,22 @@ def bench_run(
169 w2_scale=w2_scale,
170 per_act_token_quant=per_act_token,
171 )
172 + moe_config = make_dummy_moe_config(
173 + num_experts=w2.shape[0],
174 + hidden_dim=w2.shape[1],
175 + intermediate_size_per_partition=w2.shape[2],
176 + in_dtype=a.dtype,
177 + )
178
167 - fn = mk.FusedMoEModularKernel(
168 - MoEPrepareAndFinalizeNoEP(),
179 + fn = mk.FusedMoEKernel(
180 + maybe_make_prepare_finalize(
181 + moe=moe_config,
182 + quant_config=quant_config,
183 + allow_new_interface=True,
184 + use_monolithic=False,
185 + ),
186 CutlassExpertsFp8(
170 - moe_config=make_dummy_moe_config(
171 - num_experts=w2.shape[0],
172 - hidden_dim=w2.shape[1],
173 - intermediate_size_per_partition=w2.shape[2],
174 - in_dtype=a.dtype,
175 - ),
187 + moe_config=moe_config,
188 quant_config=quant_config,
189 ),
190 )
benchmarks/kernels/benchmark_moe.py
+37 -17
@@ -17,6 +17,9 @@ from ray.experimental.tqdm_ray import tqdm
17
18 from vllm.model_executor.layers.fused_moe import fused_topk
19 from vllm.model_executor.layers.fused_moe.activation import MoEActivation
20 +from vllm.model_executor.layers.fused_moe.all2all_utils import (
21 + maybe_make_prepare_finalize,
22 +)
23 from vllm.model_executor.layers.fused_moe.config import (
24 FusedMoEConfig,
25 FusedMoEParallelConfig,
@@ -242,24 +245,33 @@ def benchmark_config(
245
246 deep_gemm_experts = None
247 if use_deep_gemm:
245 - deep_gemm_experts = mk.FusedMoEModularKernel(
246 - prepare_finalize=MoEPrepareAndFinalizeNoEP(),
248 + moe_config = (
249 + FusedMoEConfig(
250 + num_experts=num_experts,
251 + experts_per_token=topk,
252 + hidden_dim=hidden_size,
253 + intermediate_size_per_partition=shard_intermediate_size,
254 + num_local_experts=num_experts,
255 + num_logical_experts=num_experts,
256 + activation=MoEActivation.SILU,
257 + moe_parallel_config=FusedMoEParallelConfig.make_no_parallel(),
258 + in_dtype=init_dtype,
259 + routing_method=RoutingMethodType.TopK,
260 + device="cuda",
261 + ),
262 + )
263 + deep_gemm_experts = mk.FusedMoEKernel(
264 + prepare_finalize=maybe_make_prepare_finalize(
265 + moe=moe_config,
266 + quant_config=quant_config,
267 + allow_new_interface=True,
268 + use_monolithic=False,
269 + ),
270 fused_experts=TritonOrDeepGemmExperts(
248 - moe_config=FusedMoEConfig(
249 - num_experts=num_experts,
250 - experts_per_token=topk,
251 - hidden_dim=hidden_size,
252 - intermediate_size_per_partition=shard_intermediate_size,
253 - num_local_experts=num_experts,
254 - num_logical_experts=num_experts,
255 - activation=MoEActivation.SILU,
256 - moe_parallel_config=FusedMoEParallelConfig.make_no_parallel(),
257 - in_dtype=init_dtype,
258 - routing_method=RoutingMethodType.TopK,
259 - device="cuda",
260 - ),
271 + moe_config=moe_config,
272 quant_config=quant_config,
273 ),
274 + inplace=not disable_inplace(),
275 )
276
277 with override_config(config):
@@ -269,8 +281,16 @@ def benchmark_config(
281
282 inplace = not disable_inplace()
283 if use_deep_gemm:
272 - return deep_gemm_experts(
273 - x, w1, w2, topk_weights, topk_ids, inplace=inplace
284 + return deep_gemm_experts.apply(
285 + x,
286 + w1,
287 + w2,
288 + topk_weights,
289 + topk_ids,
290 + activation=MoEActivation.SILU,
291 + global_num_experts=num_experts,
292 + apply_router_weight_on_input=False,
293 + expert_map=False,
294 )
295 return fused_experts(
296 x,
docs/design/dbo.md
+1 -1
@@ -81,7 +81,7 @@ The current implementation has all `dbo_yield` and `dbo_maybe_run_recv_hook` cal
81
82 The `make_ubatch_context` function initializes two `UBatchContexts`, one for each UBatch thread. It takes two CUDA streams, the preexisting `ForwardContexts` and a CPU thread barrier. This function should be used exclusively to instantiate `UBatchContexts`. It will handle all of the event initialization.
83
84 -The `dbo_register_recv_hook` method registers a callback that can be returned by the `FusedMoEPrepareAndFinalize` class in the other UBatch thread’s `UBatchContext`. The callback will be run when the other thread calls `dbo_maybe_run_recv_hook`. This is typically used to wait on an all-to-all kernel.
84 +The `dbo_register_recv_hook` method registers a callback that can be returned by the `FusedMoEPrepareAndFinalizeModular` class in the other UBatch thread’s `UBatchContext`. The callback will be run when the other thread calls `dbo_maybe_run_recv_hook`. This is typically used to wait on an all-to-all kernel.
85
86 The `dbo_maybe_run_recv_hook` method runs a callback that’s set by the `dbo_register_recv_hook` function if that callback exists.
87
docs/design/fused_moe_modular_kernel.md
+52 -52
@@ -37,31 +37,31 @@ The rest of the document will focus on the Contiguous / Non-Batched case. Extrap
37 FusedMoEModularKernel splits the FusedMoE operation into 3 parts,
38
39 1. TopKWeightAndReduce
40 -2. FusedMoEPrepareAndFinalize
41 -3. FusedMoEPermuteExpertsUnpermute
40 +2. FusedMoEPrepareAndFinalizeModular
41 +3. FusedMoEExpertsModular
42
43 ### TopKWeightAndReduce
44
45 -The TopK Weight Application and Reduction components happen right after the Unpermute operation and before the All2All Combine. Note that the `FusedMoEPermuteExpertsUnpermute` is responsible for the Unpermute and `FusedMoEPrepareAndFinalize` is responsible for the All2All Combine. There is value in doing the TopK Weight Application and Reduction in the `FusedMoEPermuteExpertsUnpermute`. But some implementations choose to do it `FusedMoEPrepareAndFinalize`. In order to enable this flexibility, we have a TopKWeightAndReduce abstract class.
45 +The TopK Weight Application and Reduction components happen right after the Unpermute operation and before the All2All Combine. Note that the `FusedMoEExpertsModular` is responsible for the Unpermute and `FusedMoEPrepareAndFinalizeModular` is responsible for the All2All Combine. There is value in doing the TopK Weight Application and Reduction in the `FusedMoEExpertsModular`. But some implementations choose to do it `FusedMoEPrepareAndFinalizeModular`. In order to enable this flexibility, we have a TopKWeightAndReduce abstract class.
46
47 Please find the implementations of TopKWeightAndReduce [here](../../vllm/model_executor/layers/fused_moe/topk_weight_and_reduce.py).
48
49 -`FusedMoEPrepareAndFinalize::finalize()` method accepts a `TopKWeightAndReduce` argument that is invoked inside the method.
50 -The `FusedMoEModularKernel` acts as a bridge between the `FusedMoEPermuteExpertsUnpermute` and `FusedMoEPerpareAndFinalize` implementations to determine where the TopK Weight Application and Reduction happens.
49 +`FusedMoEPrepareAndFinalizeModular::finalize()` method accepts a `TopKWeightAndReduce` argument that is invoked inside the method.
50 +The `FusedMoEModularKernel` acts as a bridge between the `FusedMoEExpertsModular` and `FusedMoEPerpareAndFinalize` implementations to determine where the TopK Weight Application and Reduction happens.
51
52 -* `FusedMoEPermuteExpertsUnpermute::finalize_weight_and_reduce_impl` method returns `TopKWeightAndReduceNoOp` if the `FusedMoEPermuteExpertsUnpermute` implementation does the weight application and reduction itself.
53 -* `FusedMoEPermuteExpertsUnpermute::finalize_weight_and_reduce_impl` method returns `TopKWeightAndReduceContiguous` / `TopKWeightAndReduceNaiveBatched` / `TopKWeightAndReduceDelegate` if the `FusedMoEPermuteExpertsUnpermute` implementation needs the `FusedMoEPrepareAndFinalize::finalize()` to do the weight application and reduction.
52 +* `FusedMoEExpertsModular::finalize_weight_and_reduce_impl` method returns `TopKWeightAndReduceNoOp` if the `FusedMoEExpertsModular` implementation does the weight application and reduction itself.
53 +* `FusedMoEExpertsModular::finalize_weight_and_reduce_impl` method returns `TopKWeightAndReduceContiguous` / `TopKWeightAndReduceNaiveBatched` / `TopKWeightAndReduceDelegate` if the `FusedMoEExpertsModular` implementation needs the `FusedMoEPrepareAndFinalizeModular::finalize()` to do the weight application and reduction.
54
55 -### FusedMoEPrepareAndFinalize
55 +### FusedMoEPrepareAndFinalizeModular
56
57 -The `FusedMoEPrepareAndFinalize` abstract class exposes `prepare`, `prepare_no_receive` and `finalize` functions.
58 -The `prepare` function is responsible for input activation Quantization and All2All Dispatch. If implemented, The `prepare_no_receive` is like `prepare` except it does not wait to receive results from other workers. Instead it returns a "receiver" callback that must be invoked to wait for the final results of worker. It is not required that this method is supported by all `FusedMoEPrepareAndFinalize` classes, but if it is available, it can be used to interleave work with the initial all to all communication, e.g. interleaving shared experts with fused experts. The `finalize` function is responsible for invoking the All2All Combine. Additionally the `finalize` function may or may not do the TopK weight application and reduction (Please refer to the TopKWeightAndReduce section)
57 +The `FusedMoEPrepareAndFinalizeModular` abstract class exposes `prepare`, `prepare_no_receive` and `finalize` functions.
58 +The `prepare` function is responsible for input activation Quantization and All2All Dispatch. If implemented, The `prepare_no_receive` is like `prepare` except it does not wait to receive results from other workers. Instead it returns a "receiver" callback that must be invoked to wait for the final results of worker. It is not required that this method is supported by all `FusedMoEPrepareAndFinalizeModular` classes, but if it is available, it can be used to interleave work with the initial all to all communication, e.g. interleaving shared experts with fused experts. The `finalize` function is responsible for invoking the All2All Combine. Additionally the `finalize` function may or may not do the TopK weight application and reduction (Please refer to the TopKWeightAndReduce section)
59
60 -![FusedMoEPrepareAndFinalize Blocks](../assets/design/fused_moe_modular_kernel/prepare_and_finalize_blocks.png)
60 +![FusedMoEPrepareAndFinalizeModular Blocks](../assets/design/fused_moe_modular_kernel/prepare_and_finalize_blocks.png)
61
62 -### FusedMoEPermuteExpertsUnpermute
62 +### FusedMoEExpertsModular
63
64 -The `FusedMoEPermuteExpertsUnpermute` class is where the crux of the MoE operations happen. The `FusedMoEPermuteExpertsUnpermute` abstract class exposes a few important functions,
64 +The `FusedMoEExpertsModular` class is where the crux of the MoE operations happen. The `FusedMoEExpertsModular` abstract class exposes a few important functions,
65
66 * apply()
67 * workspace_shapes()
@@ -81,25 +81,25 @@ The `apply` method is where the implementations perform
81
82 #### workspace_shapes()
83
84 -The core FusedMoE implementation performs a series of operations. It would be inefficient to create output memory for each of these operations separately. To that effect, implementations are required to declare 2 workspace shapes, the workspace datatype and the FusedMoE output shape as outputs of the workspace_shapes() method. This information is used to allocate the workspace tensors and the output tensor in `FusedMoEModularKernel::forward()` and passed on to the `FusedMoEPermuteExpertsUnpermute::apply()` method. The workspaces could then be used as intermediate buffers in the FusedMoE implementation.
84 +The core FusedMoE implementation performs a series of operations. It would be inefficient to create output memory for each of these operations separately. To that effect, implementations are required to declare 2 workspace shapes, the workspace datatype and the FusedMoE output shape as outputs of the workspace_shapes() method. This information is used to allocate the workspace tensors and the output tensor in `FusedMoEModularKernel::forward()` and passed on to the `FusedMoEExpertsModular::apply()` method. The workspaces could then be used as intermediate buffers in the FusedMoE implementation.
85
86 #### finalize_weight_and_reduce_impl()
87
88 -It is sometimes efficient to perform TopK weight application and Reduction inside the `FusedMoEPermuteExpertsUnpermute::apply()`. Find an example [here](https://github.com/vllm-project/vllm/pull/20228). We have a `TopKWeightAndReduce` abstract class to facilitate such implementations. Please refer to the TopKWeightAndReduce section.
89 -`FusedMoEPermuteExpertsUnpermute::finalize_weight_and_reduce_impl()` returns the `TopKWeightAndReduce` object that the implementation wants the `FusedMoEPrepareAndFinalize::finalize()` to use.
88 +It is sometimes efficient to perform TopK weight application and Reduction inside the `FusedMoEExpertsModular::apply()`. Find an example [here](https://github.com/vllm-project/vllm/pull/20228). We have a `TopKWeightAndReduce` abstract class to facilitate such implementations. Please refer to the TopKWeightAndReduce section.
89 +`FusedMoEExpertsModular::finalize_weight_and_reduce_impl()` returns the `TopKWeightAndReduce` object that the implementation wants the `FusedMoEPrepareAndFinalizeModular::finalize()` to use.
90
91 -![FusedMoEPermuteExpertsUnpermute Blocks](../assets/design/fused_moe_modular_kernel/fused_experts_blocks.png)
91 +![FusedMoEExpertsModular Blocks](../assets/design/fused_moe_modular_kernel/fused_experts_blocks.png)
92
93 ### FusedMoEModularKernel
94
95 -`FusedMoEModularKernel` is composed of the `FusedMoEPrepareAndFinalize` and `FusedMoEPermuteExpertsUnpermute` objects.
95 +`FusedMoEModularKernel` is composed of the `FusedMoEPrepareAndFinalizeModular` and `FusedMoEExpertsModular` objects.
96 `FusedMoEModularKernel` pseudocode/sketch,
97
98 ```py
99 class FusedMoEModularKernel:
100 def __init__(self,
101 - prepare_finalize: FusedMoEPrepareAndFinalize,
102 - fused_experts: FusedMoEPermuteExpertsUnpermute):
101 + prepare_finalize: FusedMoEPrepareAndFinalizeModular,
102 + fused_experts: FusedMoEExpertsModular):
103
104 self.prepare_finalize = prepare_finalize
105 self.fused_experts = fused_experts
@@ -128,53 +128,53 @@ class FusedMoEModularKernel:
128
129 ## How-To
130
131 -### How To Add a FusedMoEPrepareAndFinalize Type
131 +### How To Add a FusedMoEPrepareAndFinalizeModular Type
132
133 -Typically a FusedMoEPrepareAndFinalize type is backed by an All2All Dispatch & Combine implementation / kernel. For example,
133 +Typically a FusedMoEPrepareAndFinalizeModular type is backed by an All2All Dispatch & Combine implementation / kernel. For example,
134
135 * DeepEPHTPrepareAndFinalize type is backed by DeepEP High-Throughput All2All kernels, and
136 * DeepEPLLPrepareAndFinalize type is backed by DeepEP Low-Latency All2All kernels.
137
138 #### Step 1: Add an All2All manager
139
140 -The purpose of the All2All Manager is to set up the All2All kernel implementations. The `FusedMoEPrepareAndFinalize` implementations typically fetch a kernel-implementation "handle" from the All2All Manager to invoke the Dispatch and Combine functions. Please look at the All2All Manager implementations [here](../../vllm/distributed/device_communicators/all2all.py).
140 +The purpose of the All2All Manager is to set up the All2All kernel implementations. The `FusedMoEPrepareAndFinalizeModular` implementations typically fetch a kernel-implementation "handle" from the All2All Manager to invoke the Dispatch and Combine functions. Please look at the All2All Manager implementations [here](../../vllm/distributed/device_communicators/all2all.py).
141
142 -#### Step 2: Add a FusedMoEPrepareAndFinalize Type
142 +#### Step 2: Add a FusedMoEPrepareAndFinalizeModular Type
143
144 -This section describes the significance of the various functions exposed by the `FusedMoEPrepareAndFinalize` abstract class.
144 +This section describes the significance of the various functions exposed by the `FusedMoEPrepareAndFinalizeModular` abstract class.
145
146 -`FusedMoEPrepareAndFinalize::prepare()`: The prepare method implements the Quantization and All2All Dispatch. Typically the Dispatch function from the relevant All2All Manager is invoked.
146 +`FusedMoEPrepareAndFinalizeModular::prepare()`: The prepare method implements the Quantization and All2All Dispatch. Typically the Dispatch function from the relevant All2All Manager is invoked.
147
148 -`FusedMoEPrepareAndFinalize::has_prepare_no_receive()`: Indicates whether or not this subclass implements `prepare_no_receive`. Defaults to False.
148 +`FusedMoEPrepareAndFinalizeModular::has_prepare_no_receive()`: Indicates whether or not this subclass implements `prepare_no_receive`. Defaults to False.
149
150 -`FusedMoEPrepareAndFinalize::prepare_no_receive()`: The prepare_no_receive method implements the Quantization and All2All Dispatch. It does not wait for the result of the dispatch operation but instead returns a thunk that can be invoked to wait for the final results. Typically the Dispatch function from the relevant All2All Manager is invoked.
150 +`FusedMoEPrepareAndFinalizeModular::prepare_no_receive()`: The prepare_no_receive method implements the Quantization and All2All Dispatch. It does not wait for the result of the dispatch operation but instead returns a thunk that can be invoked to wait for the final results. Typically the Dispatch function from the relevant All2All Manager is invoked.
151
152 -`FusedMoEPrepareAndFinalize::finalize()`: Maybe perform TopK Weight Application and Reduction and All2All Combine. Typically the Combine function from the relevant All2AllManager is invoked.
152 +`FusedMoEPrepareAndFinalizeModular::finalize()`: Maybe perform TopK Weight Application and Reduction and All2All Combine. Typically the Combine function from the relevant All2AllManager is invoked.
153
154 -`FusedMoEPrepareAndFinalize::activation_format()`: Return `FusedMoEActivationFormat.BatchedExperts` if the output of the prepare method (i.e. the All2All dispatch) is Batched. Return `FusedMoEActivationFormat.Standard` otherwise.
154 +`FusedMoEPrepareAndFinalizeModular::activation_format()`: Return `FusedMoEActivationFormat.BatchedExperts` if the output of the prepare method (i.e. the All2All dispatch) is Batched. Return `FusedMoEActivationFormat.Standard` otherwise.
155
156 -`FusedMoEPrepareAndFinalize::topk_indices_dtype()`: Data type of the TopK ids. Some All2All kernels have strict requirements pertaining to the data type of the TopK ids. This requirement is passed on to the `FusedMoe::select_experts` function so it could be respected. If there are no strict requirements return None.
156 +`FusedMoEPrepareAndFinalizeModular::topk_indices_dtype()`: Data type of the TopK ids. Some All2All kernels have strict requirements pertaining to the data type of the TopK ids. This requirement is passed on to the `FusedMoe::select_experts` function so it could be respected. If there are no strict requirements return None.
157
158 -`FusedMoEPrepareAndFinalize::max_num_tokens_per_rank()`: This is the maximum number of tokens that would be submitted to the All2All Dispatch at once.
158 +`FusedMoEPrepareAndFinalizeModular::max_num_tokens_per_rank()`: This is the maximum number of tokens that would be submitted to the All2All Dispatch at once.
159
160 -`FusedMoEPrepareAndFinalize::num_dispatchers()`: Total number of dispatching units. This value determines the size of the Dispatch output. The Dispatch output is of shape (num_local_experts, max_num_tokens, K). Here max_num_tokens = num_dispatchers() * max_num_tokens_per_rank().
160 +`FusedMoEPrepareAndFinalizeModular::num_dispatchers()`: Total number of dispatching units. This value determines the size of the Dispatch output. The Dispatch output is of shape (num_local_experts, max_num_tokens, K). Here max_num_tokens = num_dispatchers() * max_num_tokens_per_rank().
161
162 -We suggest picking an already existing `FusedMoEPrepareAndFinalize` implementation that matches your All2All implementation closely and using it as a reference.
162 +We suggest picking an already existing `FusedMoEPrepareAndFinalizeModular` implementation that matches your All2All implementation closely and using it as a reference.
163
164 -### How To Add a FusedMoEPermuteExpertsUnpermute Type
164 +### How To Add a FusedMoEExpertsModular Type
165
166 -FusedMoEPermuteExpertsUnpermute performs the core of the FusedMoE operations. The various functions exposed by the abstract class and their significance is as follows,
166 +FusedMoEExpertsModular performs the core of the FusedMoE operations. The various functions exposed by the abstract class and their significance is as follows,
167
168 -`FusedMoEPermuteExpertsUnpermute::activation_formats()`: Return the supported Input and Output activation formats. i.e. Contiguous / Batched format.
168 +`FusedMoEExpertsModular::activation_formats()`: Return the supported Input and Output activation formats. i.e. Contiguous / Batched format.
169
170 -`FusedMoEPermuteExpertsUnpermute::supports_chunking()`: Return True if the implementation supports chunking. Typically
170 +`FusedMoEExpertsModular::supports_chunking()`: Return True if the implementation supports chunking. Typically
171 implementations that input `FusedMoEActivationFormat.Standard` support chunking and `FusedMoEActivationFormat.BatchedExperts` do not.
172
173 -`FusedMoEPermuteExpertsUnpermute::supports_expert_map()`: Return True if the implementation supports expert map.
173 +`FusedMoEExpertsModular::supports_expert_map()`: Return True if the implementation supports expert map.
174
175 -`FusedMoEPermuteExpertsUnpermute::workspace_shapes()` /
176 -`FusedMoEPermuteExpertsUnpermute::finalize_weight_and_reduce_impl` /
177 -`FusedMoEPermuteExpertsUnpermute::apply`: Refer to `FusedMoEPermuteExpertsUnpermute` section above.
175 +`FusedMoEExpertsModular::workspace_shapes()` /
176 +`FusedMoEExpertsModular::finalize_weight_and_reduce_impl` /
177 +`FusedMoEExpertsModular::apply`: Refer to `FusedMoEExpertsModular` section above.
178
179 ### FusedMoEModularKernel Initialization
180
@@ -186,14 +186,14 @@ implementations that input `FusedMoEActivationFormat.Standard` support chunking
186
187 #### maybe_make_prepare_finalize
188
189 -The `maybe_make_prepare_finalize` method is responsible for constructing an instance of `FusedMoEPrepareAndFinalize` when appropriate based on the current all2all backend, e.g. when EP + DP is enabled. The base class method currently constructs all the `FusedMoEPrepareAndFinalize` objects for the EP+DP case. Derived classes can override this method to construct prepare/finalize objects for different scenarios, e.g. `ModelOptNvFp4FusedMoE` can construct a `FlashInferCutlassMoEPrepareAndFinalize` for the EP+TP case.
189 +The `maybe_make_prepare_finalize` method is responsible for constructing an instance of `FusedMoEPrepareAndFinalizeModular` when appropriate based on the current all2all backend, e.g. when EP + DP is enabled. The base class method currently constructs all the `FusedMoEPrepareAndFinalizeModular` objects for the EP+DP case. Derived classes can override this method to construct prepare/finalize objects for different scenarios, e.g. `ModelOptNvFp4FusedMoE` can construct a `FlashInferCutlassMoEPrepareAndFinalize` for the EP+TP case.
190 Please refer to the implementations in,
191
192 * `ModelOptNvFp4FusedMoE`
193
194 #### select_gemm_impl
195
196 -The `select_gemm_impl` method is undefined in the base class. It is the responsibility of the derived class to implement a method that constructs a valid/appropriate `FusedMoEPermuteExpertsUnpermute` object.
196 +The `select_gemm_impl` method is undefined in the base class. It is the responsibility of the derived class to implement a method that constructs a valid/appropriate `FusedMoEExpertsModular` object.
197 Please refer to the implementations in,
198
199 * `UnquantizedFusedMoEMethod`
@@ -205,7 +205,7 @@ derived classes.
205
206 #### init_prepare_finalize
207
208 -Based on the input and env settings, the `init_prepare_finalize` method creates the appropriate `FusedMoEPrepareAndFinalize` object. The method then queries `select_gemm_impl` for the appropriate `FusedMoEPermuteExpertsUnpermute` object and builds the `FusedMoEModularKernel` object
208 +Based on the input and env settings, the `init_prepare_finalize` method creates the appropriate `FusedMoEPrepareAndFinalizeModular` object. The method then queries `select_gemm_impl` for the appropriate `FusedMoEExpertsModular` object and builds the `FusedMoEModularKernel` object
209
210 Please take a look at [init_prepare_finalize](https://github.com/vllm-project/vllm/blob/1cbf951ba272c230823b947631065b826409fa62/vllm/model_executor/layers/fused_moe/layer.py#L188).
211 **Important**: The `FusedMoEMethodBase` derived classes use the `FusedMoEMethodBase::fused_experts` object in their `apply` methods. When settings permit the construction of a valid `FusedMoEModularKernel` object, we override `FusedMoEMethodBase::fused_experts` with it. This essentially makes the derived classes agnostic to what FusedMoE implementation is used.
@@ -214,9 +214,9 @@ Please take a look at [init_prepare_finalize](https://github.com/vllm-project/vl
214
215 We have `FusedMoEModularKernel` unit tests at [test_modular_kernel_combinations.py](../../tests/kernels/moe/test_modular_kernel_combinations.py).
216
217 -The unit test iterates through all combinations of `FusedMoEPrepareAndFinalize` and `FusedMoEPremuteExpertsUnpermute` types and if they are
217 +The unit test iterates through all combinations of `FusedMoEPrepareAndFinalizeModular` and `FusedMoEPremuteExpertsUnpermute` types and if they are
218 compatible, runs some correctness tests.
219 -If you are adding some `FusedMoEPrepareAndFinalize` / `FusedMoEPermuteExpertsUnpermute` implementations,
219 +If you are adding some `FusedMoEPrepareAndFinalizeModular` / `FusedMoEExpertsModular` implementations,
220
221 1. Add the implementation type to `MK_ALL_PREPARE_FINALIZE_TYPES` and `MK_FUSED_EXPERT_TYPES` in [mk_objects.py](../../tests/kernels/moe/modular_kernel_tools/mk_objects.py) respectively.
222 2. Update `Config::is_batched_prepare_finalize()`, `Config::is_batched_fused_experts()`, `Config::is_standard_fused_experts()`,
@@ -225,24 +225,24 @@ If you are adding some `FusedMoEPrepareAndFinalize` / `FusedMoEPermuteExpertsUnp
225
226 Doing this will add the new implementation to the test suite.
227
228 -### How To Check `FusedMoEPrepareAndFinalize` & `FusedMoEPermuteExpertsUnpermute` Compatibility
228 +### How To Check `FusedMoEPrepareAndFinalizeModular` & `FusedMoEExpertsModular` Compatibility
229
230 The unit test file [test_modular_kernel_combinations.py](../../tests/kernels/moe/test_modular_kernel_combinations.py) can also be executed as a standalone script.
231 Example: `python3 -m tests.kernels.moe.test_modular_kernel_combinations --pf-type DeepEPLLPrepareAndFinalize --experts-type BatchedTritonExperts`
232 -As a side effect, this script can be used to test `FusedMoEPrepareAndFinalize` & `FusedMoEPermuteExpertsUnpermute` compatibility. When invoked
232 +As a side effect, this script can be used to test `FusedMoEPrepareAndFinalizeModular` & `FusedMoEExpertsModular` compatibility. When invoked
233 with incompatible types, the script will error.
234
235 ### How To Profile
236
237 Please take a look at [profile_modular_kernel.py](../../tests/kernels/moe/modular_kernel_tools/profile_modular_kernel.py)
238 The script can be used to generate Torch traces for a single `FusedMoEModularKernel::forward()` call for any compatible
239 -`FusedMoEPrepareAndFinalize` and `FusedMoEPermuteExpertsUnpermute` types.
239 +`FusedMoEPrepareAndFinalizeModular` and `FusedMoEExpertsModular` types.
240 Example: `python3 -m tests.kernels.moe.modular_kernel_tools.profile_modular_kernel --pf-type DeepEPLLPrepareAndFinalize --experts-type BatchedTritonExperts`
241
242 -## FusedMoEPrepareAndFinalize Implementations
242 +## FusedMoEPrepareAndFinalizeModular Implementations
243
244 See [Fused MoE Kernel features](./moe_kernel_features.md#fused-moe-modular-all2all-backends) for a list of all the available modular prepare and finalize subclasses.
245
246 -## FusedMoEPermuteExpertsUnpermute
246 +## FusedMoEExpertsModular
247
248 See [Fused MoE Kernel features](./moe_kernel_features.md#fused-moe-experts-kernels) for a list of all the available modular experts.
docs/design/moe_kernel_features.md
+7 -9
@@ -4,17 +4,17 @@ The purpose of this document is to provide an overview of the various MoE kernel
4
5 ## Fused MoE Modular All2All backends
6
7 -There are a number of all2all communication backends that are used to implement expert parallelism (EP) for the `FusedMoE` layer. The different `FusedMoEPrepareAndFinalize` subclasses provide an interface for each all2all backend.
7 +There are a number of all2all communication backends that are used to implement expert parallelism (EP) for the `FusedMoE` layer. The different `FusedMoEPrepareAndFinalizeModular` subclasses provide an interface for each all2all backend.
8
9 The following table describes the relevant features of each backend, i.e. activation format, supported quantization schemes and async support.
10
11 -The output activation format (standard or batched) corresponds to the output of the prepare step of the `FusedMoEPrepareAndFinalize` subclass, and the finalize step requires the same format. All the backend `prepare` methods expect activations in the standard format and all the `finalize` methods return activations in standard format. More details on the formats can be found in the [Fused MoE Modular Kernel](./fused_moe_modular_kernel.md) document.
11 +The output activation format (standard or batched) corresponds to the output of the prepare step of the `FusedMoEPrepareAndFinalizeModular` subclass, and the finalize step requires the same format. All the backend `prepare` methods expect activations in the standard format and all the `finalize` methods return activations in standard format. More details on the formats can be found in the [Fused MoE Modular Kernel](./fused_moe_modular_kernel.md) document.
12
13 -The quantization types and formats enumerate which quantization schemes are supported by each `FusedMoEPrepareAndFinalize` class. The quantization can happen before or after the dispatch based on the format the all2all backend supports, e.g. deepep_high_throughput supports only block-quantized fp8 format. Any other format will result in dispatching in higher precision and quantizing afterwards. The output of the prepare step for each backend is the quantized type. The finalize step generally requires the same input type as the original activations, e.g. if the original input is bfloat16 and the quantization scheme is fp8 with per-tensor scales, `prepare` will return fp8/per-tensor scale activations and `finalize` will take bfloat16 activations. See the diagrams in [Fused MoE Modular Kernel](./fused_moe_modular_kernel.md) for more details on the types and formats of activations at each step of the MoE process. If no quantization type is specified, the kernel operates on float16 and/or bfloat16.
13 +The quantization types and formats enumerate which quantization schemes are supported by each `FusedMoEPrepareAndFinalizeModular` class. The quantization can happen before or after the dispatch based on the format the all2all backend supports, e.g. deepep_high_throughput supports only block-quantized fp8 format. Any other format will result in dispatching in higher precision and quantizing afterwards. The output of the prepare step for each backend is the quantized type. The finalize step generally requires the same input type as the original activations, e.g. if the original input is bfloat16 and the quantization scheme is fp8 with per-tensor scales, `prepare` will return fp8/per-tensor scale activations and `finalize` will take bfloat16 activations. See the diagrams in [Fused MoE Modular Kernel](./fused_moe_modular_kernel.md) for more details on the types and formats of activations at each step of the MoE process. If no quantization type is specified, the kernel operates on float16 and/or bfloat16.
14
15 Async backends support the use of DBO (Dual Batch Overlap) and shared expert overlap (where shared experts are computed during the combine step).
16
17 -Certain models require the topk weights to be applied to the input activations rather than the output activations when topk==1, e.g. Llama. For modular kernels, this feature is supported by the `FusedMoEPrepareAndFinalize` subclass. For non-modular kernels, it is up to the experts function to deal with this flag.
17 +Certain models require the topk weights to be applied to the input activations rather than the output activations when topk==1, e.g. Llama. For modular kernels, this feature is supported by the `FusedMoEPrepareAndFinalizeModular` subclass. For non-modular kernels, it is up to the experts function to deal with this flag.
18
19 Unless otherwise specified, backends are controlled via the `--all2all-backend` command-line argument (or the `all2all_backend` parameter in `ParallelConfig`). All backends except `flashinfer` only work with EP+DP or EP+TP. `Flashinfer` can work with EP or DP without EP.
20
@@ -36,8 +36,6 @@ th {
36 | deepep_high_throughput | standard | fp8 | G(128),A,T<sup>2</sup> | Y | Y | [`DeepEPHTPrepareAndFinalize`][vllm.model_executor.layers.fused_moe.deepep_ht_prepare_finalize.DeepEPHTPrepareAndFinalize] |
37 | deepep_low_latency | batched | fp8 | G(128),A,T<sup>3</sup> | Y | Y | [`DeepEPLLPrepareAndFinalize`][vllm.model_executor.layers.fused_moe.deepep_ll_prepare_finalize.DeepEPLLPrepareAndFinalize] |
38 | flashinfer_all2allv | standard | nvfp4,fp8 | G,A,T | N | N | [`FlashInferA2APrepareAndFinalize`][vllm.model_executor.layers.fused_moe.flashinfer_a2a_prepare_finalize.FlashInferA2APrepareAndFinalize] |
39 -| MoEPrepareAndFinalizeNoEP<sup>5</sup> | standard | fp8,int8 | G,A,T | N | Y | [`MoEPrepareAndFinalizeNoEP`][vllm.model_executor.layers.fused_moe.prepare_finalize.MoEPrepareAndFinalizeNoEP] |
40 -| BatchedPrepareAndFinalize<sup>5</sup> | batched | fp8,int8 | G,A,T | N | Y | [`BatchedPrepareAndFinalize`][vllm.model_executor.layers.fused_moe.fused_batched_moe.BatchedPrepareAndFinalize] |
39
40 !!! info "Table key"
41 1. All types: mxfp4, nvfp4, int4, int8, fp8
@@ -75,9 +73,9 @@ Each experts kernel supports one or more activation functions, e.g. silu or gelu
73
74 As with the backends, some experts support applying topk weights on the input activations. The entries in the column in this table only apply to the non-modular experts.
75
78 -Most experts flavors include an equivalent modular interface which will be a subclass of `FusedMoEPermuteExpertsUnpermute`.
76 +Most experts flavors include an equivalent modular interface which will be a subclass of `FusedMoEExpertsModular`.
77
80 -To be used with a particular `FusedMoEPrepareAndFinalize` subclass, MoE kernels must have compatible activation formats, quantization types and quantization formats.
78 +To be used with a particular `FusedMoEPrepareAndFinalizeModular` subclass, MoE kernels must have compatible activation formats, quantization types and quantization formats.
79
80 | Kernel | Input act. format | Quant. types | Quant. format | Activation function | Apply Weight On Input | Modular | Source |
81 |--------|-------------------|--------------|---------------|---------------------|-----------------------|---------|--------|
@@ -106,7 +104,7 @@ To be used with a particular `FusedMoEPrepareAndFinalize` subclass, MoE kernels
104
105 The following table shows "families" of modular kernels that are intended to work together. There are some combinations which may work but have not yet been tested, e.g. flashinfer with other fp8 experts. Note that the "naive" backend will work with any non-modular experts.
106
109 -| backend | `FusedMoEPrepareAndFinalize` subclasses | `FusedMoEPermuteExpertsUnpermute` subclasses |
107 +| backend | `FusedMoEPrepareAndFinalizeModular` subclasses | `FusedMoEExpertsModular` subclasses |
108 |---------|-----------------------------------------|----------------------------------------------|
109 | deepep_high_throughput | `DeepEPHTPrepareAndFinalize` | `DeepGemmExperts`,</br>`TritonExperts`,</br>`TritonOrDeepGemmExperts`,</br>`CutlassExpertsFp8`, </br>`MarlinExperts` |
110 | deepep_low_latency | `DeepEPLLPrepareAndFinalize` | `BatchedDeepGemmExperts`,</br>`BatchedTritonExperts`,</br>`CutlassBatchedExpertsFp8`,</br>`BatchedMarlinExperts` |
tests/kernels/moe/modular_kernel_tools/cli_args.py
+2 -2
@@ -17,13 +17,13 @@ from .mk_objects import (
17
18
19 def make_config_arg_parser(description: str):
20 - def to_pf_class_type(s: str) -> mk.FusedMoEPrepareAndFinalize:
20 + def to_pf_class_type(s: str) -> mk.FusedMoEPrepareAndFinalizeModular:
21 for pf in MK_ALL_PREPARE_FINALIZE_TYPES:
22 if pf.__name__ == s:
23 return pf
24 raise ValueError(f"Cannot find a PrepareFinalize type that matches {s}")
25
26 - def to_experts_class_type(s: str) -> mk.FusedMoEPermuteExpertsUnpermute:
26 + def to_experts_class_type(s: str) -> mk.FusedMoEExpertsModular:
27 for fe in MK_FUSED_EXPERT_TYPES:
28 if fe.__name__ == s:
29 return fe
tests/kernels/moe/modular_kernel_tools/common.py
+5 -4
@@ -66,7 +66,7 @@ class Config:
66 quant_config: TestMoEQuantConfig | None
67
68 prepare_finalize_type: mk.FusedMoEPrepareAndFinalize
69 - fused_experts_type: mk.FusedMoEPermuteExpertsUnpermute
69 + fused_experts_type: mk.FusedMoEExperts
70
71 fused_moe_chunk_size: int | None
72 world_size: int
@@ -566,7 +566,7 @@ def make_modular_kernel(
566 config: Config,
567 vllm_config: VllmConfig,
568 quant_config: FusedMoEQuantConfig,
569 -) -> mk.FusedMoEModularKernel:
569 +) -> mk.FusedMoEKernel:
570 def next_power_of_2(x):
571 import math
572
@@ -613,7 +613,7 @@ def make_modular_kernel(
613 config.N,
614 )
615
616 - modular_kernel = mk.FusedMoEModularKernel(
616 + modular_kernel = mk.FusedMoEKernel(
617 prepare_finalize=prepare_finalize,
618 fused_experts=fused_experts,
619 inplace=False,
@@ -667,6 +667,7 @@ def run_modular_kernel(
667 "w2": rank_weights.w2,
668 "topk_weights": rank_tensors.topk_weights,
669 "topk_ids": topk_ids,
670 + "activation": MoEActivation.SILU,
671 "expert_map": rank_tensors.expert_map,
672 "global_num_experts": config.E,
673 "apply_router_weight_on_input": config.topk == 1
@@ -684,6 +685,6 @@ def run_modular_kernel(
685 num_tokens=num_tokens,
686 num_tokens_across_dp=num_tokens_across_dp,
687 ):
687 - out = mk.forward(**mk_kwargs)
688 + out = mk.apply(**mk_kwargs)
689
690 return out
tests/kernels/moe/modular_kernel_tools/mk_objects.py
+14 -12
@@ -20,7 +20,7 @@ from vllm.model_executor.layers.fused_moe.fused_batched_moe import (
20 NaiveBatchedExperts,
21 )
22 from vllm.model_executor.layers.fused_moe.prepare_finalize import (
23 - MoEPrepareAndFinalizeNoEP,
23 + MoEPrepareAndFinalizeNoDPEPModular,
24 )
25 from vllm.model_executor.layers.fused_moe.triton_deep_gemm_moe import (
26 TritonOrDeepGemmExperts,
@@ -71,12 +71,14 @@ class ExpertInfo:
71 needs_aiter: bool = False
72
73
74 -PREPARE_FINALIZE_INFO: dict[mk.FusedMoEPrepareAndFinalize, PrepareFinalizeInfo] = {}
75 -EXPERT_INFO: dict[mk.FusedMoEPermuteExpertsUnpermute, ExpertInfo] = {}
76 -MK_ALL_PREPARE_FINALIZE_TYPES: list[mk.FusedMoEPrepareAndFinalize] = []
77 -MK_MULTI_GPU_PREPARE_FINALIZE_TYPES: list[mk.FusedMoEPrepareAndFinalize] = []
78 -MK_SINGLE_GPU_PREPARE_FINALIZE_TYPES: list[mk.FusedMoEPrepareAndFinalize] = []
79 -MK_FUSED_EXPERT_TYPES: list[mk.FusedMoEPermuteExpertsUnpermute] = []
74 +PREPARE_FINALIZE_INFO: dict[
75 + mk.FusedMoEPrepareAndFinalizeModular, PrepareFinalizeInfo
76 +] = {}
77 +EXPERT_INFO: dict[mk.FusedMoEExpertsModular, ExpertInfo] = {}
78 +MK_ALL_PREPARE_FINALIZE_TYPES: list[mk.FusedMoEPrepareAndFinalizeModular] = []
79 +MK_MULTI_GPU_PREPARE_FINALIZE_TYPES: list[mk.FusedMoEPrepareAndFinalizeModular] = []
80 +MK_SINGLE_GPU_PREPARE_FINALIZE_TYPES: list[mk.FusedMoEPrepareAndFinalizeModular] = []
81 +MK_FUSED_EXPERT_TYPES: list[mk.FusedMoEExpertsModular] = []
82
83 standard_format = mk.FusedMoEActivationFormat.Standard
84 batched_format = mk.FusedMoEActivationFormat.BatchedExperts
@@ -162,7 +164,7 @@ def expert_info(kind) -> ExpertInfo:
164
165
166 register_prepare_and_finalize(
165 - MoEPrepareAndFinalizeNoEP,
167 + MoEPrepareAndFinalizeNoDPEPModular,
168 standard_format,
169 common_float_types,
170 blocked_quantization_support=True,
@@ -239,14 +241,14 @@ if has_mori():
241
242 if has_flashinfer_cutlass_fused_moe() and current_platform.has_device_capability(100):
243 from vllm.model_executor.layers.fused_moe.flashinfer_a2a_prepare_finalize import ( # noqa: E501
242 - FlashInferCutlassMoEPrepareAndFinalize,
244 + FlashInferA2APrepareAndFinalize,
245 )
246 from vllm.model_executor.layers.fused_moe.flashinfer_cutlass_moe import (
247 FlashInferExperts,
248 )
249
250 register_prepare_and_finalize(
249 - FlashInferCutlassMoEPrepareAndFinalize,
251 + FlashInferA2APrepareAndFinalize,
252 standard_format,
253 nvfp4_types + fp8_types,
254 blocked_quantization_support=True,
@@ -430,12 +432,12 @@ def make_cutlass_strides(
432
433
434 def make_fused_experts(
433 - fused_experts_type: mk.FusedMoEPermuteExpertsUnpermute,
435 + fused_experts_type: mk.FusedMoEExpertsModular,
436 moe: FusedMoEConfig,
437 quant_config: FusedMoEQuantConfig,
438 num_dispatchers: int,
439 N: int,
438 -) -> mk.FusedMoEPermuteExpertsUnpermute:
440 +) -> mk.FusedMoEExpertsModular:
441 if (
442 fused_experts_type.activation_format()
443 == mk.FusedMoEActivationFormat.BatchedExperts
tests/kernels/moe/modular_kernel_tools/profile_modular_kernel.py
+1 -1
@@ -72,7 +72,7 @@ def profile_modular_kernel(
72 "apply_router_weight_on_input": config.topk == 1,
73 }
74
75 - do_profile(mk.forward, mk_kwargs, pgi, config)
75 + do_profile(mk.apply, mk_kwargs, pgi, config)
76
77
78 def rank_worker(
tests/kernels/moe/test_batched_deepgemm.py
+12 -5
@@ -4,6 +4,7 @@
4 import pytest
5 import torch
6
7 +from vllm.model_executor.layers.fused_moe.activation import MoEActivation
8 from vllm.model_executor.layers.fused_moe.batched_deep_gemm_moe import (
9 BatchedDeepGemmExperts,
10 )
@@ -12,7 +13,7 @@ from vllm.model_executor.layers.fused_moe.fused_batched_moe import (
13 BatchedPrepareAndFinalize,
14 BatchedTritonExperts,
15 )
15 -from vllm.model_executor.layers.fused_moe.modular_kernel import FusedMoEModularKernel
16 +from vllm.model_executor.layers.fused_moe.modular_kernel import FusedMoEKernel
17 from vllm.utils.deep_gemm import calc_diff, is_deep_gemm_supported
18
19 from .test_deepgemm import make_block_quant_fp8_weights
@@ -74,19 +75,22 @@ def test_batched_deepgemm_vs_triton(
75 quant_config=quant_config,
76 moe_config=make_dummy_moe_config(),
77 )
77 - mk_triton = FusedMoEModularKernel(
78 + mk_triton = FusedMoEKernel(
79 prep_finalize,
80 triton_experts,
81 inplace=False,
82 )
83
83 - out_triton = mk_triton(
84 + out_triton = mk_triton.apply(
85 hidden_states=a,
86 w1=w1,
87 w2=w2,
88 topk_weights=topk_weights,
89 topk_ids=topk_ids,
90 + activation=MoEActivation.SILU,
91 global_num_experts=E,
92 + expert_map=None,
93 + apply_router_weight_on_input=False,
94 )
95
96 # deepgemm
@@ -96,19 +100,22 @@ def test_batched_deepgemm_vs_triton(
100 quant_config=quant_config,
101 moe_config=make_dummy_moe_config(),
102 )
99 - mk_deepgemm = FusedMoEModularKernel(
103 + mk_deepgemm = FusedMoEKernel(
104 prep_finalize,
105 deepgemm_experts,
106 inplace=False,
107 )
108
105 - out_deepgemm = mk_deepgemm(
109 + out_deepgemm = mk_deepgemm.apply(
110 hidden_states=a,
111 w1=w1,
112 w2=w2,
113 topk_weights=topk_weights,
114 topk_ids=topk_ids,
115 + activation=MoEActivation.SILU,
116 global_num_experts=E,
117 + expert_map=None,
118 + apply_router_weight_on_input=False,
119 )
120
121 diff = calc_diff(out_deepgemm, out_triton)
tests/kernels/moe/test_block_fp8.py
+29 -8
@@ -21,15 +21,16 @@ from vllm.model_executor.layers.fused_moe import (
21 fused_experts,
22 fused_topk,
23 )
24 +from vllm.model_executor.layers.fused_moe.activation import MoEActivation
25 +from vllm.model_executor.layers.fused_moe.all2all_utils import (
26 + maybe_make_prepare_finalize,
27 +)
28 from vllm.model_executor.layers.fused_moe.config import (
29 fp8_w8a8_moe_quant_config,
30 )
31 from vllm.model_executor.layers.fused_moe.deep_gemm_moe import (
32 _valid_deep_gemm_shape,
33 )
30 -from vllm.model_executor.layers.fused_moe.prepare_finalize import (
31 - MoEPrepareAndFinalizeNoEP,
32 -)
34 from vllm.model_executor.layers.fused_moe.triton_deep_gemm_moe import (
35 TritonOrDeepGemmExperts,
36 )
@@ -193,7 +194,17 @@ def test_w8a8_block_fp8_fused_moe(
194 a, w1, w2, topk_weights, topk_ids, quant_config=quant_config
195 )
196
196 - m_out = m_fused_moe(a, w1, w2, topk_weights, topk_ids)
197 + m_out = m_fused_moe.apply(
198 + a,
199 + w1,
200 + w2,
201 + topk_weights,
202 + topk_ids,
203 + activation=MoEActivation.SILU,
204 + apply_router_weight_on_input=False,
205 + expert_map=None,
206 + global_num_experts=w1.shape[0],
207 + )
208
209 # 0.039 only needed for M >= 8192
210 tol = 0.035 if M < 8192 else 0.039
@@ -252,23 +263,33 @@ def test_w8a8_block_fp8_deep_gemm_fused_moe(M, N, K, E, topk, seed, monkeypatch)
263 w2_scale=w2_s,
264 block_shape=block_size,
265 )
266 + moe_config = make_dummy_moe_config()
267
256 - deep_gemm_experts = mk.FusedMoEModularKernel(
257 - prepare_finalize=MoEPrepareAndFinalizeNoEP(),
268 + deep_gemm_experts = mk.FusedMoEKernel(
269 + prepare_finalize=maybe_make_prepare_finalize(
270 + moe=moe_config,
271 + quant_config=quant_config,
272 + allow_new_interface=True,
273 + use_monolithic=False,
274 + ),
275 fused_experts=TritonOrDeepGemmExperts(
259 - moe_config=make_dummy_moe_config(),
276 + moe_config=moe_config,
277 quant_config=quant_config,
278 ),
279 inplace=False,
280 )
281
282 def deep_gemm_moe_fp8(a, w1, w2, w1_s, w2_s, topk_weights, topk_ids):
266 - return deep_gemm_experts(
283 + return deep_gemm_experts.apply(
284 hidden_states=a,
285 w1=w1,
286 w2=w2,
287 topk_weights=topk_weights,
288 topk_ids=topk_ids,
289 + global_num_experts=E,
290 + activation=MoEActivation.SILU,
291 + apply_router_weight_on_input=False,
292 + expert_map=False,
293 )
294
295 # Set the context to avoid lots of warning spam.
tests/kernels/moe/test_cutlass_moe.py
+37 -21
@@ -13,6 +13,9 @@ from vllm import _custom_ops as ops
13 from vllm.config import ParallelConfig, VllmConfig, set_current_vllm_config
14 from vllm.model_executor.layers.fused_moe import fused_experts, fused_topk
15 from vllm.model_executor.layers.fused_moe.activation import MoEActivation
16 +from vllm.model_executor.layers.fused_moe.all2all_utils import (
17 + maybe_make_prepare_finalize,
18 +)
19 from vllm.model_executor.layers.fused_moe.config import (
20 FUSED_MOE_UNQUANTIZED_CONFIG,
21 FusedMoEQuantConfig,
@@ -22,9 +25,6 @@ from vllm.model_executor.layers.fused_moe.cutlass_moe import (
25 CutlassExpertsFp8,
26 run_cutlass_moe_fp8,
27 )
25 -from vllm.model_executor.layers.fused_moe.prepare_finalize import (
26 - MoEPrepareAndFinalizeNoEP,
27 -)
28 from vllm.model_executor.layers.fused_moe.utils import moe_kernel_quantize_input
29 from vllm.platforms import current_platform
30 from vllm.utils.torch_utils import set_random_seed
@@ -197,20 +197,26 @@ def run_with_expert_maps(
197 for kwargs, new_quant_config in slice_experts():
198 w2 = kwargs["w2"]
199 a = kwargs["hidden_states"]
200 - kernel = mk.FusedMoEModularKernel(
201 - MoEPrepareAndFinalizeNoEP(),
200 + moe_config = make_dummy_moe_config(
201 + num_experts=w2.shape[0],
202 + hidden_dim=w2.shape[1],
203 + intermediate_size_per_partition=w2.shape[2],
204 + in_dtype=a.dtype,
205 + )
206 + kernel = mk.FusedMoEKernel(
207 + maybe_make_prepare_finalize(
208 + moe=moe_config,
209 + quant_config=new_quant_config,
210 + allow_new_interface=True,
211 + use_monolithic=False,
212 + ),
213 CutlassExpertsFp8(
203 - moe_config=make_dummy_moe_config(
204 - num_experts=w2.shape[0],
205 - hidden_dim=w2.shape[1],
206 - intermediate_size_per_partition=w2.shape[2],
207 - in_dtype=a.dtype,
208 - ),
214 + moe_config=moe_config,
215 quant_config=new_quant_config,
216 ),
217 inplace=False,
218 )
213 - out_tensor = out_tensor + kernel(**kwargs)
219 + out_tensor = out_tensor + kernel.apply(**kwargs)
220
221 return out_tensor
222
@@ -252,25 +258,35 @@ def run_8_bit(
258 "w2": moe_tensors.w2_q, # type: ignore[union-attr]
259 "topk_weights": topk_weights,
260 "topk_ids": topk_ids,
261 + "global_num_experts": moe_tensors.w1_q.shape[0], # type: ignore[union-attr]
262 + "activation": MoEActivation.SILU,
263 + "expert_map": None,
264 + "apply_router_weight_on_input": False,
265 }
266
267 num_experts = moe_tensors.w1.size(0) # type: ignore[attr-defined]
268 with_ep = num_local_experts is not None or num_local_experts == num_experts
269 if not with_ep:
260 - kernel = mk.FusedMoEModularKernel(
261 - MoEPrepareAndFinalizeNoEP(),
270 + moe_config = make_dummy_moe_config(
271 + num_experts=moe_tensors.w2_q.shape[0], # type: ignore[union-attr]
272 + hidden_dim=moe_tensors.w2_q.shape[1], # type: ignore[union-attr]
273 + intermediate_size_per_partition=moe_tensors.w2_q.shape[2], # type: ignore[union-attr]
274 + in_dtype=moe_tensors.a.dtype,
275 + )
276 + kernel = mk.FusedMoEKernel(
277 + maybe_make_prepare_finalize(
278 + moe=moe_config,
279 + quant_config=quant_config,
280 + allow_new_interface=True,
281 + use_monolithic=False,
282 + ),
283 CutlassExpertsFp8(
263 - moe_config=make_dummy_moe_config(
264 - num_experts=moe_tensors.w2_q.shape[0], # type: ignore[union-attr]
265 - hidden_dim=moe_tensors.w2_q.shape[1], # type: ignore[union-attr]
266 - intermediate_size_per_partition=moe_tensors.w2_q.shape[2], # type: ignore[union-attr]
267 - in_dtype=moe_tensors.a.dtype,
268 - ),
284 + moe_config=moe_config,
285 quant_config=quant_config,
286 ),
287 inplace=False,
288 )
273 - return kernel(**kwargs)
289 + return kernel.apply(**kwargs)
290
291 assert num_local_experts is not None
292 return run_with_expert_maps(
tests/kernels/moe/test_deepep_deepgemm_moe.py
+9 -9
@@ -22,7 +22,7 @@ from vllm.model_executor.layers.fused_moe.config import (
22 fp8_w8a8_moe_quant_config,
23 )
24 from vllm.model_executor.layers.fused_moe.fused_moe import fused_experts
25 -from vllm.model_executor.layers.fused_moe.modular_kernel import FusedMoEModularKernel
25 +from vllm.model_executor.layers.fused_moe.modular_kernel import FusedMoEKernel
26 from vllm.utils.deep_gemm import (
27 get_mk_alignment_for_contiguous_layout,
28 is_deep_gemm_e8m0_used,
@@ -170,7 +170,7 @@ def make_ll_modular_kernel(
170 q_dtype: torch.dtype | None,
171 test_config: TestConfig,
172 quant_config: FusedMoEQuantConfig,
173 -) -> FusedMoEModularKernel:
173 +) -> FusedMoEKernel:
174 assert test_config.low_latency
175 assert test_config.use_fp8_dispatch is not None
176
@@ -195,7 +195,7 @@ def make_ll_modular_kernel(
195 quant_config=quant_config,
196 moe_config=make_dummy_moe_config(),
197 )
198 - return FusedMoEModularKernel(
198 + return FusedMoEKernel(
199 prepare_finalize=a2a,
200 fused_experts=fused_experts,
201 inplace=False,
@@ -210,7 +210,7 @@ def make_ht_modular_kernel(
210 q_dtype: torch.dtype | None,
211 test_config: TestConfig,
212 quant_config: FusedMoEQuantConfig,
213 -) -> FusedMoEModularKernel:
213 +) -> FusedMoEKernel:
214 assert not test_config.low_latency
215 assert test_config.use_fp8_dispatch is None
216
@@ -228,7 +228,7 @@ def make_ht_modular_kernel(
228 moe_config=make_dummy_moe_config(),
229 quant_config=quant_config,
230 )
231 - return FusedMoEModularKernel(
231 + return FusedMoEKernel(
232 prepare_finalize=a2a,
233 fused_experts=fused_experts,
234 inplace=False,
@@ -242,11 +242,11 @@ def make_modular_kernel(
242 num_local_experts: int,
243 test_tensors: TestTensors,
244 quant_config: FusedMoEQuantConfig,
245 -) -> FusedMoEModularKernel:
245 +) -> FusedMoEKernel:
246 q_dtype = torch.float8_e4m3fn
247 test_config = test_tensors.config
248
249 - mk: FusedMoEModularKernel
249 + mk: FusedMoEKernel
250 # Make modular kernel
251 if test_config.low_latency:
252 max_tokens_per_rank = max(64, next_power_of_2(test_tensors.rank_tokens.size(0)))
@@ -307,7 +307,7 @@ def deepep_deepgemm_moe_impl(
307 )
308
309 # Make modular kernel
310 - mk: FusedMoEModularKernel = make_modular_kernel(
310 + mk: FusedMoEKernel = make_modular_kernel(
311 pg=pg,
312 pgi=pgi,
313 dp_size=dp_size,
@@ -319,7 +319,7 @@ def deepep_deepgemm_moe_impl(
319 with with_dp_metadata(
320 M=test_tensors.rank_tokens.size(0), world_size=pgi.world_size
321 ):
322 - out = mk.forward(
322 + out = mk.apply(
323 hidden_states=test_tensors.rank_tokens,
324 w1=w1,
325 w2=w2,
tests/kernels/moe/test_deepep_moe.py
+5 -5
@@ -20,7 +20,7 @@ from vllm.model_executor.layers.fused_moe.config import (
20 FusedMoEQuantConfig,
21 )
22 from vllm.model_executor.layers.fused_moe.fused_batched_moe import BatchedTritonExperts
23 -from vllm.model_executor.layers.fused_moe.modular_kernel import FusedMoEModularKernel
23 +from vllm.model_executor.layers.fused_moe.modular_kernel import FusedMoEKernel
24 from vllm.model_executor.layers.quantization.utils.fp8_utils import (
25 per_token_group_quant_fp8,
26 )
@@ -135,7 +135,7 @@ def make_modular_kernel(
135 q_dtype: torch.dtype | None,
136 use_fp8_dispatch: bool,
137 quant_config: FusedMoEQuantConfig,
138 -) -> FusedMoEModularKernel:
138 +) -> FusedMoEKernel:
139 ht_args: DeepEPHTArgs | None = None
140 ll_args: DeepEPLLArgs | None = None
141
@@ -180,7 +180,7 @@ def make_modular_kernel(
180 quant_config=quant_config,
181 )
182
183 - mk = FusedMoEModularKernel(
183 + mk = FusedMoEKernel(
184 prepare_finalize=a2a,
185 fused_experts=fused_experts,
186 inplace=False,
@@ -242,7 +242,7 @@ def deep_ep_moe_impl(
242 )
243
244 # Make modular kernel
245 - mk: FusedMoEModularKernel = make_modular_kernel(
245 + mk: FusedMoEKernel = make_modular_kernel(
246 pg,
247 pgi,
248 low_latency_mode,
@@ -255,7 +255,7 @@ def deep_ep_moe_impl(
255 quant_config,
256 )
257
258 - out = mk.forward(
258 + out = mk.apply(
259 hidden_states=rank_tokens_chunk,
260 w1=w1,
261 w2=w2,
tests/kernels/moe/test_deepgemm.py
+20 -7
@@ -14,13 +14,16 @@ import torch
14 # vLLM fused-expert reference (Triton fallback + DeepGEMM option)
15 import vllm.model_executor.layers.fused_moe.modular_kernel as mk
16 from tests.kernels.moe.utils import make_dummy_moe_config
17 +from vllm.model_executor.layers.fused_moe.activation import (
18 + MoEActivation,
19 +)
20 +from vllm.model_executor.layers.fused_moe.all2all_utils import (
21 + maybe_make_prepare_finalize,
22 +)
23 from vllm.model_executor.layers.fused_moe.config import (
24 fp8_w8a8_moe_quant_config,
25 )
26 from vllm.model_executor.layers.fused_moe.fused_moe import fused_experts
21 -from vllm.model_executor.layers.fused_moe.prepare_finalize import (
22 - MoEPrepareAndFinalizeNoEP,
23 -)
27 from vllm.model_executor.layers.fused_moe.triton_deep_gemm_moe import (
28 TritonOrDeepGemmExperts,
29 )
@@ -108,11 +111,17 @@ def run_single_case(m, n, k, topk, num_experts, block_size):
111 a1_scale=a1_scale,
112 block_shape=block_size,
113 )
114 + moe_config = make_dummy_moe_config()
115
112 - deep_gemm_experts = mk.FusedMoEModularKernel(
113 - prepare_finalize=MoEPrepareAndFinalizeNoEP(),
116 + deep_gemm_experts = mk.FusedMoEKernel(
117 + prepare_finalize=maybe_make_prepare_finalize(
118 + moe=moe_config,
119 + quant_config=quant_config,
120 + allow_new_interface=True,
121 + use_monolithic=False,
122 + ),
123 fused_experts=TritonOrDeepGemmExperts(
115 - moe_config=make_dummy_moe_config(),
124 + moe_config=moe_config,
125 quant_config=quant_config,
126 ),
127 inplace=False,
@@ -130,12 +139,16 @@ def run_single_case(m, n, k, topk, num_experts, block_size):
139 )
140
141 # DeepGemm
133 - out_deepgemm = deep_gemm_experts(
142 + out_deepgemm = deep_gemm_experts.apply(
143 hidden_states=tokens_bf16,
144 w1=w1,
145 w2=w2,
146 topk_weights=topk_weights,
147 topk_ids=topk_ids,
148 + global_num_experts=num_experts,
149 + activation=MoEActivation.SILU,
150 + apply_router_weight_on_input=False,
151 + expert_map=None,
152 )
153 diff = calc_diff(out_deepgemm, out_triton)
154 assert diff < 0.001, f"Diff exceeded 1%: {diff}"
tests/kernels/moe/test_flashinfer.py
+49 -21
@@ -8,6 +8,9 @@ import torch
8 import vllm.model_executor.layers.fused_moe.modular_kernel as mk
9 from vllm.config import ParallelConfig, VllmConfig, set_current_vllm_config
10 from vllm.model_executor.layers.fused_moe.activation import MoEActivation
11 +from vllm.model_executor.layers.fused_moe.all2all_utils import (
12 + maybe_make_prepare_finalize,
13 +)
14 from vllm.model_executor.layers.fused_moe.config import (
15 FusedMoEConfig,
16 FusedMoEParallelConfig,
@@ -15,16 +18,14 @@ from vllm.model_executor.layers.fused_moe.config import (
18 RoutingMethodType,
19 fp8_w8a8_moe_quant_config,
20 )
21 +from vllm.model_executor.layers.fused_moe.experts.trtllm_fp8_moe import (
22 + TrtLlmFp8Experts,
23 +)
24 from vllm.model_executor.layers.fused_moe.flashinfer_cutlass_moe import (
25 FlashInferExperts,
26 )
27 from vllm.model_executor.layers.fused_moe.fused_moe import fused_experts
22 -from vllm.model_executor.layers.fused_moe.prepare_finalize import (
23 - MoEPrepareAndFinalizeNoEP,
24 -)
28 from vllm.model_executor.layers.quantization.utils.flashinfer_utils import (
26 - apply_fi_trtllm_fp8_per_tensor_moe,
27 - register_scales_for_trtllm_fp8_per_tensor_moe,
29 rotate_weights_for_fi_trtllm_fp8_per_tensor_moe,
30 swap_w13_to_w31,
31 )
@@ -115,6 +116,7 @@ class TestData:
116 e: int,
117 is_trtllm: bool,
118 activation: MoEActivation = MoEActivation.SILU,
119 + topk: int = 1,
120 ) -> "TestData":
121 is_gated = activation.is_gated
122
@@ -152,13 +154,6 @@ class TestData:
154 rotate_weights_for_fi_trtllm_fp8_per_tensor_moe(
155 layer.w13_weight, layer.w2_weight, is_gated
156 )
155 - register_scales_for_trtllm_fp8_per_tensor_moe(
156 - layer,
157 - layer.w13_weight_scale,
158 - layer.w13_input_scale,
159 - layer.w2_weight_scale,
160 - layer.w2_input_scale,
161 - )
157 layer.custom_routing_function = Llama4MoE.custom_routing_function
158 layer.routing_method_type = RoutingMethodType.Llama4
159 layer.renormalize = False
@@ -166,6 +161,21 @@ class TestData:
161 layer.ep_rank = 0
162 layer.local_num_experts = e
163
164 + layer.moe = FusedMoEConfig(
165 + num_experts=e,
166 + experts_per_token=topk,
167 + hidden_dim=k,
168 + intermediate_size_per_partition=n,
169 + num_local_experts=e,
170 + num_logical_experts=e,
171 + moe_parallel_config=layer.moe_parallel_config,
172 + in_dtype=hidden_states.dtype,
173 + is_act_and_mul=is_gated,
174 + routing_method=layer.routing_method_type,
175 + activation=activation,
176 + device=w13_quantized.device,
177 + )
178 +
179 return TestData(
180 hidden_states=hidden_states,
181 w13_quantized=w13_quantized,
@@ -230,16 +240,29 @@ def test_flashinfer_per_tensor_moe_fp8_no_graph(
240 quant_config=quant_config,
241 )
242
233 - flashinfer_output = apply_fi_trtllm_fp8_per_tensor_moe(
234 - layer=td.layer,
243 + kernel = mk.FusedMoEKernel(
244 + maybe_make_prepare_finalize(
245 + moe=td.layer.moe,
246 + quant_config=quant_config,
247 + allow_new_interface=True,
248 + use_monolithic=True,
249 + ),
250 + TrtLlmFp8Experts(
251 + moe_config=td.layer.moe,
252 + quant_config=quant_config,
253 + ),
254 + )
255 +
256 + flashinfer_output = kernel.apply_monolithic(
257 hidden_states=td.hidden_states,
258 + w1=td.layer.w13_weight,
259 + w2=td.layer.w2_weight,
260 router_logits=score,
237 - routing_bias=None,
261 + activation=activation,
262 global_num_experts=e,
239 - top_k=topk,
240 - num_expert_group=None,
241 - topk_group=None,
263 + expert_map=None,
264 apply_router_weight_on_input=True,
265 + routed_scaling_factor=1.0,
266 )
267
268 check_accuracy(
@@ -329,8 +352,13 @@ def test_flashinfer_cutlass_moe_fp8_no_graph(
352 routing_method=RoutingMethodType.TopK,
353 )
354
332 - kernel = mk.FusedMoEModularKernel(
333 - MoEPrepareAndFinalizeNoEP(),
355 + kernel = mk.FusedMoEKernel(
356 + maybe_make_prepare_finalize(
357 + moe=moe_config,
358 + quant_config=quant_config,
359 + allow_new_interface=True,
360 + use_monolithic=False,
361 + ),
362 FlashInferExperts(
363 moe_config=moe_config,
364 quant_config=quant_config,
@@ -338,7 +366,7 @@ def test_flashinfer_cutlass_moe_fp8_no_graph(
366 inplace=False,
367 )
368
341 - flashinfer_cutlass_output = kernel(
369 + flashinfer_cutlass_output = kernel.apply(
370 td.hidden_states,
371 td.layer.w13_weight,
372 td.layer.w2_weight,
tests/kernels/moe/test_flashinfer_moe.py
+15 -7
@@ -14,6 +14,9 @@ from vllm import _custom_ops as ops
14 from vllm.config import ParallelConfig, VllmConfig, set_current_vllm_config
15 from vllm.model_executor.layers.fused_moe import fused_topk
16 from vllm.model_executor.layers.fused_moe.activation import MoEActivation
17 +from vllm.model_executor.layers.fused_moe.all2all_utils import (
18 + maybe_make_prepare_finalize,
19 +)
20 from vllm.model_executor.layers.fused_moe.config import (
21 FusedMoEConfig,
22 FusedMoEParallelConfig,
@@ -23,10 +26,7 @@ from vllm.model_executor.layers.fused_moe.flashinfer_cutlass_moe import (
26 FlashInferExperts,
27 is_valid_flashinfer_cutlass_fused_moe,
28 )
26 -from vllm.model_executor.layers.fused_moe.modular_kernel import FusedMoEModularKernel
27 -from vllm.model_executor.layers.fused_moe.prepare_finalize import (
28 - MoEPrepareAndFinalizeNoEP,
29 -)
29 +from vllm.model_executor.layers.fused_moe.modular_kernel import FusedMoEKernel
30 from vllm.platforms import current_platform
31 from vllm.utils.flashinfer import has_flashinfer_cutlass_fused_moe
32 from vllm.utils.torch_utils import set_random_seed
@@ -107,19 +107,27 @@ def test_flashinfer_fp4_moe_no_graph(
107 routing_method=RoutingMethodType.TopK,
108 )
109
110 - flashinfer_experts = FusedMoEModularKernel(
111 - MoEPrepareAndFinalizeNoEP(),
110 + flashinfer_experts = FusedMoEKernel(
111 + maybe_make_prepare_finalize(
112 + moe=moe_config,
113 + quant_config=quant_config,
114 + allow_new_interface=True,
115 + use_monolithic=False,
116 + ),
117 FlashInferExperts(moe_config=moe_config, quant_config=quant_config),
118 inplace=False,
119 )
120
116 - flashinfer_output = flashinfer_experts(
121 + flashinfer_output = flashinfer_experts.apply(
122 hidden_states=a,
123 w1=w1_q,
124 w2=w2_q,
125 topk_weights=topk_weights,
126 topk_ids=topk_ids,
127 activation=activation,
128 + global_num_experts=e,
129 + expert_map=None,
130 + apply_router_weight_on_input=False,
131 )
132
133 # Reference check:
tests/kernels/moe/test_marlin_vs_trtllm_mxint4.py
+10 -11
@@ -221,16 +221,16 @@ def test_marlin_vs_trtllm_mxint4_moe_kimik2(monkeypatch, m, n, k, e, topk, group
221 )
222
223 marlin_output = fused_marlin_moe(
224 - a,
225 - w1_marlin,
226 - w2_marlin,
227 - None,
228 - None,
229 - w1_scales_marlin,
230 - w2_scales_marlin,
231 - None, # gating_output not needed when topk_weights/ids provided
232 - topk_weights,
233 - topk_ids,
224 + hidden_states=a,
225 + w1=w1_marlin,
226 + w2=w2_marlin,
227 + bias1=None,
228 + bias2=None,
229 + w1_scale=w1_scales_marlin,
230 + w2_scale=w2_scales_marlin,
231 + topk_weights=topk_weights,
232 + topk_ids=topk_ids,
233 + quant_type_id=scalar_types.uint4b8.id,
234 global_num_experts=e,
235 expert_map=None,
236 global_scale1=None,
@@ -244,7 +244,6 @@ def test_marlin_vs_trtllm_mxint4_moe_kimik2(monkeypatch, m, n, k, e, topk, group
244 w1_zeros=None,
245 w2_zeros=None,
246 input_dtype=dtype,
247 - quant_type_id=scalar_types.uint4b8.id,
247 is_k_full=True,
248 )
249
tests/kernels/moe/test_modular_kernel_combinations.py
+2 -3
@@ -168,7 +168,6 @@ FUSED_MOE_CHUNK_SIZEs = [None, 16]
168 def is_nyi_config(config: Config) -> bool:
169 # We know these configs to be legitimate. but still fail.
170 info = expert_info(config.fused_experts_type)
171 -
171 if info.needs_matching_quant:
172 # The triton kernels expect both per-act-token-quant and
173 # per-out-ch-quant or neither.
@@ -259,7 +258,7 @@ def test_modular_kernel_combinations_multigpu(
258 dtype: torch.dtype,
259 quant_config: TestMoEQuantConfig | None,
260 prepare_finalize_type: mk.FusedMoEPrepareAndFinalize,
262 - fused_experts_type: mk.FusedMoEPermuteExpertsUnpermute,
261 + fused_experts_type: mk.FusedMoEExperts,
262 chunk_size: int | None,
263 world_size: int,
264 pytestconfig,
@@ -301,7 +300,7 @@ def test_modular_kernel_combinations_singlegpu(
300 dtype: torch.dtype,
301 quant_config: TestMoEQuantConfig | None,
302 prepare_finalize_type: mk.FusedMoEPrepareAndFinalize,
304 - fused_experts_type: mk.FusedMoEPermuteExpertsUnpermute,
303 + fused_experts_type: mk.FusedMoEExperts,
304 chunk_size: int | None,
305 world_size: int,
306 pytestconfig,
tests/kernels/moe/test_modular_oai_triton_moe.py
+18 -10
@@ -7,6 +7,7 @@ Test modular OAI Triton MoE
7 import pytest
8 import torch
9
10 +from tests.utils import wait_for_gpu_memory_to_clear
11 from vllm.model_executor.layers.fused_moe.activation import MoEActivation
12 from vllm.utils.import_utils import has_triton_kernels
13
@@ -24,15 +25,15 @@ from triton_kernels.tensor_details import layout
25 from triton_kernels.testing import assert_close
26
27 from vllm.config import VllmConfig, set_current_vllm_config
28 +from vllm.model_executor.layers.fused_moe.all2all_utils import (
29 + maybe_make_prepare_finalize,
30 +)
31 from vllm.model_executor.layers.fused_moe.config import mxfp4_w4a16_moe_quant_config
32 from vllm.model_executor.layers.fused_moe.gpt_oss_triton_kernels_moe import (
33 OAITritonExperts,
34 UnfusedOAITritonExperts,
35 )
32 -from vllm.model_executor.layers.fused_moe.modular_kernel import FusedMoEModularKernel
33 -from vllm.model_executor.layers.fused_moe.prepare_finalize import (
34 - MoEPrepareAndFinalizeNoEP,
35 -)
36 +from vllm.model_executor.layers.fused_moe.modular_kernel import FusedMoEKernel
37 from vllm.platforms import current_platform
38 from vllm.utils.torch_utils import set_random_seed
39
@@ -174,19 +175,25 @@ def oai_triton_moe_impl(
175 w1_scale=w1_scale,
176 w2_scale=w2_scale,
177 )
178 + moe_config = make_dummy_moe_config()
179
180 if unfused:
179 - fused_experts = UnfusedOAITritonExperts(make_dummy_moe_config(), quant_config)
181 + fused_experts = UnfusedOAITritonExperts(moe_config, quant_config)
182 else:
181 - fused_experts = OAITritonExperts(make_dummy_moe_config(), quant_config)
182 -
183 - mk = FusedMoEModularKernel(
184 - MoEPrepareAndFinalizeNoEP(),
183 + fused_experts = OAITritonExperts(moe_config, quant_config)
184 +
185 + mk = FusedMoEKernel(
186 + maybe_make_prepare_finalize(
187 + moe=moe_config,
188 + quant_config=quant_config,
189 + allow_new_interface=True,
190 + use_monolithic=False,
191 + ),
192 fused_experts,
193 inplace=False,
194 )
195
189 - return mk.forward(
196 + return mk.apply(
197 hidden_states=x,
198 w1=w1,
199 w2=w2,
@@ -217,6 +224,7 @@ def test_oai_triton_moe(
224 unfused: bool,
225 workspace_init,
226 ):
227 + wait_for_gpu_memory_to_clear(devices=[0], threshold_ratio=0.1)
228 set_random_seed(0)
229 (
230 w1,
tests/kernels/moe/test_moe.py
+6 -2
@@ -346,14 +346,16 @@ def test_fused_moe(
346 expert_map: torch.Tensor | None = None,
347 ) -> torch.Tensor:
348 topk_weights, topk_ids, _ = fused_topk(a, score, topk, False)
349 - return m_fused_moe_fn(
349 + return m_fused_moe_fn.apply(
350 a,
351 w1,
352 w2,
353 topk_weights,
354 topk_ids,
355 + activation=MoEActivation.SILU,
356 global_num_experts=global_num_experts,
357 expert_map=expert_map,
358 + apply_router_weight_on_input=False,
359 )
360
361 fused_moe_fn = functools.partial(fused_moe, renormalize=False)
@@ -500,14 +502,16 @@ def test_naive_block_assignment_moe(
502 expert_map: torch.Tensor | None = None,
503 ) -> torch.Tensor:
504 topk_weights, topk_ids, _ = fused_topk(a, score, topk, False)
503 - return m_fused_moe_fn(
505 + return m_fused_moe_fn.apply(
506 a,
507 w1,
508 w2,
509 topk_weights,
510 topk_ids,
511 + activation=MoEActivation.SILU,
512 global_num_experts=global_num_experts,
513 expert_map=expert_map,
514 + apply_router_weight_on_input=False,
515 )
516
517 fused_moe_fn = functools.partial(fused_moe, renormalize=False)
tests/kernels/moe/test_nvfp4_moe.py
+24 -8
@@ -15,12 +15,15 @@ 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 import fused_topk
17 from vllm.model_executor.layers.fused_moe.activation import MoEActivation
18 +from vllm.model_executor.layers.fused_moe.all2all_utils import (
19 + maybe_make_prepare_finalize,
20 +)
21 from vllm.model_executor.layers.fused_moe.config import nvfp4_moe_quant_config
22 from vllm.model_executor.layers.fused_moe.cutlass_moe import (
23 CutlassExpertsFp4,
24 )
25 from vllm.model_executor.layers.fused_moe.prepare_finalize import (
23 - MoEPrepareAndFinalizeNoEP,
26 + make_moe_prepare_and_finalize_no_dp_ep,
27 )
28 from vllm.platforms import current_platform
29 from vllm.utils.torch_utils import set_random_seed
@@ -89,22 +92,32 @@ def test_cutlass_fp4_moe_no_graph(
92 w1_scale=w1_blockscale,
93 w2_scale=w2_blockscale,
94 )
95 + moe_config = make_dummy_moe_config()
96
93 - kernel = mk.FusedMoEModularKernel(
94 - MoEPrepareAndFinalizeNoEP(),
97 + kernel = mk.FusedMoEKernel(
98 + maybe_make_prepare_finalize(
99 + moe=moe_config,
100 + quant_config=quant_config,
101 + allow_new_interface=True,
102 + use_monolithic=False,
103 + ),
104 CutlassExpertsFp4(
96 - moe_config=make_dummy_moe_config(),
105 + moe_config=moe_config,
106 quant_config=quant_config,
107 ),
108 inplace=False,
109 )
110
102 - cutlass_output = kernel(
111 + cutlass_output = kernel.apply(
112 hidden_states=a,
113 w1=w1_q,
114 w2=w2_q,
115 topk_weights=topk_weights,
116 topk_ids=topk_ids,
117 + global_num_experts=e,
118 + activation=mk.MoEActivation.SILU,
119 + apply_router_weight_on_input=False,
120 + expert_map=None,
121 )
122
123 # Reference check:
@@ -207,8 +220,8 @@ def test_cutlass_fp4_moe_swiglustep(
220 w2_scale=w2_blockscale,
221 )
222
210 - kernel = mk.FusedMoEModularKernel(
211 - MoEPrepareAndFinalizeNoEP(),
223 + kernel = mk.FusedMoEKernel(
224 + make_moe_prepare_and_finalize_no_dp_ep(use_monolithic=False),
225 CutlassExpertsFp4(
226 moe_config=make_dummy_moe_config(),
227 quant_config=quant_config,
@@ -216,13 +229,16 @@ def test_cutlass_fp4_moe_swiglustep(
229 inplace=False,
230 )
231
219 - cutlass_output = kernel(
232 + cutlass_output = kernel.apply(
233 hidden_states=a,
234 w1=w1_q,
235 w2=w2_q,
236 topk_weights=topk_weights,
237 topk_ids=topk_ids,
238 activation=MoEActivation.SWIGLUSTEP,
239 + global_num_experts=e,
240 + expert_map=None,
241 + apply_router_weight_on_input=False,
242 )
243
244 # Reference: dequantize everything and run torch_moe with swiglustep
tests/kernels/moe/utils.py
+41 -13
@@ -8,6 +8,9 @@ from tests.kernels.quant_utils import per_block_cast_to_int8
8 from tests.kernels.quantization.nvfp4_utils import FLOAT4_E2M1_MAX, FLOAT8_E4M3_MAX
9 from vllm.model_executor.layers.activation import SiluAndMul
10 from vllm.model_executor.layers.fused_moe.activation import MoEActivation
11 +from vllm.model_executor.layers.fused_moe.all2all_utils import (
12 + maybe_make_prepare_finalize,
13 +)
14 from vllm.model_executor.layers.fused_moe.config import (
15 FusedMoEConfig,
16 FusedMoEParallelConfig,
@@ -23,10 +26,7 @@ from vllm.model_executor.layers.fused_moe.fused_moe import (
26 TritonExperts,
27 fused_experts,
28 )
26 -from vllm.model_executor.layers.fused_moe.modular_kernel import FusedMoEModularKernel
27 -from vllm.model_executor.layers.fused_moe.prepare_finalize import (
28 - MoEPrepareAndFinalizeNoEP,
29 -)
29 +from vllm.model_executor.layers.fused_moe.modular_kernel import FusedMoEKernel
30 from vllm.model_executor.layers.fused_moe.router.fused_topk_router import fused_topk
31 from vllm.model_executor.layers.fused_moe.utils import moe_kernel_quantize_input
32 from vllm.utils.deep_gemm import per_block_cast_to_fp8
@@ -125,7 +125,9 @@ def batched_moe(
125 a2_scale=a2_scale,
126 )
127
128 - fused_experts = FusedMoEModularKernel(
128 + moe_config = make_dummy_moe_config()
129 +
130 + fused_experts = FusedMoEKernel(
131 BatchedPrepareAndFinalize(
132 max_num_tokens, num_dispatchers=1, num_local_experts=w1.shape[0], rank=0
133 ),
@@ -133,12 +135,22 @@ def batched_moe(
135 max_num_tokens=max_num_tokens,
136 num_dispatchers=1,
137 quant_config=quant_config,
136 - moe_config=make_dummy_moe_config(),
138 + moe_config=moe_config,
139 ),
140 inplace=False,
141 )
142
141 - return fused_experts(a, w1, w2, topk_weight, topk_ids)
143 + return fused_experts.apply(
144 + a,
145 + w1,
146 + w2,
147 + topk_weight,
148 + topk_ids,
149 + global_num_experts=w1.shape[0],
150 + activation=moe_config.activation,
151 + apply_router_weight_on_input=False,
152 + expert_map=None,
153 + )
154
155
156 def naive_batched_moe(
@@ -166,8 +178,9 @@ def naive_batched_moe(
178 a1_scale=a1_scale,
179 a2_scale=a2_scale,
180 )
181 + moe_config = make_dummy_moe_config()
182
170 - fused_experts = FusedMoEModularKernel(
183 + fused_experts = FusedMoEKernel(
184 BatchedPrepareAndFinalize(
185 max_num_tokens, num_dispatchers=1, num_local_experts=w1.shape[0], rank=0
186 ),
@@ -175,12 +188,22 @@ def naive_batched_moe(
188 max_num_tokens=max_num_tokens,
189 num_dispatchers=1,
190 quant_config=quant_config,
178 - moe_config=make_dummy_moe_config(),
191 + moe_config=moe_config,
192 ),
193 inplace=False,
194 )
195
183 - return fused_experts(a, w1, w2, topk_weight, topk_ids)
196 + return fused_experts.apply(
197 + a,
198 + w1,
199 + w2,
200 + topk_weight,
201 + topk_ids,
202 + global_num_experts=w1.shape[0],
203 + activation=moe_config.activation,
204 + apply_router_weight_on_input=False,
205 + expert_map=None,
206 + )
207
208
209 def chunk_scales(
@@ -581,9 +604,14 @@ def modular_triton_fused_moe(
604 moe_config: FusedMoEConfig,
605 quant_config: FusedMoEQuantConfig,
606 shared_experts: torch.nn.Module | None = None,
584 -) -> FusedMoEModularKernel:
585 - return FusedMoEModularKernel(
586 - MoEPrepareAndFinalizeNoEP(),
607 +) -> FusedMoEKernel:
608 + return FusedMoEKernel(
609 + maybe_make_prepare_finalize(
610 + moe=moe_config,
611 + quant_config=quant_config,
612 + allow_new_interface=True,
613 + use_monolithic=False,
614 + ),
615 TritonExperts(moe_config, quant_config),
616 shared_experts,
617 inplace=False,
tests/quantization/test_blackwell_moe.py
+80
@@ -127,6 +127,14 @@ def test_deepseek_fp8_block_moe_deep_gemm(monkeypatch: pytest.MonkeyPatch):
127 )
128
129
130 +def test_deepseek_fp8_block_moe_vllm_triton(monkeypatch: pytest.MonkeyPatch):
131 + can_initialize(
132 + "deepseek-ai/DeepSeek-V3.1",
133 + hf_overrides=HF_OVERRIDE_TEXT,
134 + extra_args=["--moe-backend=triton"],
135 + )
136 +
137 +
138 @pytest.mark.skip(
139 reason=(
140 "Known issue: lack of kernel support. "
@@ -149,6 +157,14 @@ def test_deepseek_fp8_block_moe_flashinfer_trtllm(monkeypatch: pytest.MonkeyPatc
157 )
158
159
160 +def test_deepseek_nvfp4_moe_flashinfer_vllm(monkeypatch: pytest.MonkeyPatch):
161 + can_initialize(
162 + "nvidia/DeepSeek-R1-0528-FP4-v2",
163 + hf_overrides=HF_OVERRIDE_TEXT,
164 + extra_args=["--moe-backend=cutlass"],
165 + )
166 +
167 +
168 def test_deepseek_nvfp4_moe_flashinfer_cutlass(monkeypatch: pytest.MonkeyPatch):
169 can_initialize(
170 "nvidia/DeepSeek-R1-0528-FP4-v2",
@@ -200,3 +216,67 @@ def test_qwen3_next_bf16_moe_flashinfer_trtllm(monkeypatch: pytest.MonkeyPatch):
216 hf_overrides=HF_OVERRIDE_TEXT,
217 extra_args=["--moe-backend=flashinfer_trtllm"],
218 )
219 +
220 +
221 +## NemoTron ##
222 +
223 +
224 +def test_nemotron_fp8_moe_flashinfer_throughput(monkeypatch: pytest.MonkeyPatch):
225 + can_initialize(
226 + "nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-FP8",
227 + hf_overrides=HF_OVERRIDE_TEXT,
228 + extra_args=["--moe-backend=flashinfer_cutlass"],
229 + )
230 +
231 +
232 +@pytest.mark.skip(
233 + reason=(
234 + "FP8 MoE backend FLASHINFER_TRTLLM does not support the "
235 + "deployment configuration since kernel does not support "
236 + "no act_and_mul MLP layer."
237 + )
238 +)
239 +def test_nemotron_fp8_moe_flashinfer_latency(monkeypatch: pytest.MonkeyPatch):
240 + can_initialize(
241 + "nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-FP8",
242 + hf_overrides=HF_OVERRIDE_TEXT,
243 + extra_args=["--moe-backend=flashinfer_trtllm"],
244 + )
245 +
246 +
247 +@pytest.mark.skip(
248 + reason=(
249 + "FP8 MoE backend TRITON does not support the "
250 + "deployment configuration since kernel does not support "
251 + "no act_and_mul MLP layer."
252 + )
253 +)
254 +def test_nemotron_fp8_moe_vllm_triton(monkeypatch: pytest.MonkeyPatch):
255 + can_initialize(
256 + "nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-FP8",
257 + hf_overrides=HF_OVERRIDE_TEXT,
258 + extra_args=["--moe-backend=triton"],
259 + )
260 +
261 +
262 +def test_nemotron_fp4_moe_flashinfer_throughput(monkeypatch: pytest.MonkeyPatch):
263 + can_initialize(
264 + "nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-NVFP4",
265 + hf_overrides=HF_OVERRIDE_TEXT,
266 + extra_args=["--moe-backend=flashinfer_cutlass"],
267 + )
268 +
269 +
270 +@pytest.mark.skip(
271 + reason=(
272 + "FP4 MoE backend FLASHINFER_TRTLLM does not support the "
273 + "deployment configuration since kernel does not support "
274 + "hidden_dim % 512 != 0."
275 + )
276 +)
277 +def test_nemotron_fp4_moe_flashinfer_latency(monkeypatch: pytest.MonkeyPatch):
278 + can_initialize(
279 + "nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-NVFP4",
280 + hf_overrides=HF_OVERRIDE_TEXT,
281 + extra_args=["--moe-backend=flashinfer_trtllm"],
282 + )
vllm/lora/layers/fused_moe.py
+10 -9
@@ -32,10 +32,10 @@ from vllm.model_executor.layers.fused_moe.gpt_oss_triton_kernels_moe import (
32 UnfusedOAITritonExperts,
33 )
34 from vllm.model_executor.layers.fused_moe.modular_kernel import (
35 - FusedMoEModularKernel,
35 + FusedMoEKernel,
36 )
37 from vllm.model_executor.layers.fused_moe.prepare_finalize import (
38 - MoEPrepareAndFinalizeNoEP,
38 + MoEPrepareAndFinalizeNoDPEPModular,
39 )
40
41 from .utils import _get_lora_device, try_get_optimal_moe_lora_config
@@ -136,7 +136,7 @@ class FusedMoEWithLoRA(BaseLayerWithLoRA):
136
137 if getattr(self.base_layer.quant_method, "supports_internal_mk", False):
138 # Use the existing modular kernel from the quant method
139 - m_fused_moe_fn = self.base_layer.quant_method.moe_mk
139 + m_fused_moe_fn = self.base_layer.quant_method.moe_kernel
140 # Don't let the kernel own shared experts so the runner can
141 # overlap them with routed experts via a separate CUDA stream.
142 m_fused_moe_fn.shared_experts = None
@@ -144,8 +144,8 @@ class FusedMoEWithLoRA(BaseLayerWithLoRA):
144 # Create a new modular kernel via select_gemm_impl.
145 # Don't pass shared_experts to the kernel so the runner can
146 # overlap them with routed experts via a separate CUDA stream.
147 - prepare_finalize = MoEPrepareAndFinalizeNoEP()
148 - m_fused_moe_fn = FusedMoEModularKernel(
147 + prepare_finalize = MoEPrepareAndFinalizeNoDPEPModular()
148 + m_fused_moe_fn = FusedMoEKernel(
149 prepare_finalize,
150 self.base_layer.quant_method.select_gemm_impl(
151 prepare_finalize, self.base_layer
@@ -154,10 +154,11 @@ class FusedMoEWithLoRA(BaseLayerWithLoRA):
154
155 if quant_config.use_mxfp4_w4a16:
156 assert isinstance(
157 - m_fused_moe_fn.fused_experts, (MarlinExperts, UnfusedOAITritonExperts)
157 + m_fused_moe_fn.impl.fused_experts,
158 + (MarlinExperts, UnfusedOAITritonExperts),
159 )
160 else:
160 - assert isinstance(m_fused_moe_fn.fused_experts, TritonExperts)
161 + assert isinstance(m_fused_moe_fn.impl.fused_experts, TritonExperts)
162
163 def fwd_decorator(layer, func):
164 def wrapper(*args, **kwargs):
@@ -337,9 +338,9 @@ class FusedMoEWithLoRA(BaseLayerWithLoRA):
338
339 return wrapper
340
340 - fused_experts = m_fused_moe_fn.fused_experts
341 + fused_experts = m_fused_moe_fn.impl.fused_experts
342
342 - m_fused_moe_fn.forward = fwd_decorator(self.base_layer, m_fused_moe_fn.forward)
343 + m_fused_moe_fn.apply = fwd_decorator(self.base_layer, m_fused_moe_fn.apply)
344 fused_experts.activation = act_decorator(
345 self.base_layer, fused_experts.activation
346 )
vllm/model_executor/layers/fused_moe/__init__.py
+4 -4
@@ -22,8 +22,8 @@ from vllm.model_executor.layers.fused_moe.layer import (
22 )
23 from vllm.model_executor.layers.fused_moe.modular_kernel import (
24 FusedMoEActivationFormat,
25 - FusedMoEPermuteExpertsUnpermute,
26 - FusedMoEPrepareAndFinalize,
25 + FusedMoEExpertsModular,
26 + FusedMoEPrepareAndFinalizeModular,
27 )
28 from vllm.model_executor.layers.fused_moe.router.fused_moe_router import (
29 FusedMoERouter,
@@ -62,9 +62,9 @@ __all__ = [
62 "MoEActivation",
63 "UnquantizedFusedMoEMethod",
64 "FusedMoeWeightScaleSupported",
65 - "FusedMoEPermuteExpertsUnpermute",
65 + "FusedMoEExpertsModular",
66 "FusedMoEActivationFormat",
67 - "FusedMoEPrepareAndFinalize",
67 + "FusedMoEPrepareAndFinalizeModular",
68 "GateLinear",
69 "RoutingMethodType",
70 "SharedFusedMoE",
vllm/model_executor/layers/fused_moe/all2all_utils.py
+9 -6
@@ -21,8 +21,8 @@ from vllm.model_executor.layers.fused_moe.modular_kernel import (
21 FusedMoEPrepareAndFinalize,
22 )
23 from vllm.model_executor.layers.fused_moe.prepare_finalize import (
24 - MoEPrepareAndFinalizeNaiveEP,
25 - MoEPrepareAndFinalizeNoEP,
24 + make_moe_prepare_and_finalize_naive_dp_ep,
25 + make_moe_prepare_and_finalize_no_dp_ep,
26 )
27 from vllm.platforms import current_platform
28 from vllm.utils.import_utils import has_deep_ep, has_mori
@@ -77,6 +77,7 @@ def maybe_make_prepare_finalize(
77 quant_config: FusedMoEQuantConfig | None,
78 routing_tables: tuple[torch.Tensor, torch.Tensor, torch.Tensor] | None = None,
79 allow_new_interface: bool = False,
80 + use_monolithic: bool = False,
81 ) -> FusedMoEPrepareAndFinalize | None:
82 # NOTE(rob): we are migrating each quant_method to hold the MK
83 # in all cases. The allow_new_interface=False flag allow us to fall
@@ -102,14 +103,15 @@ def maybe_make_prepare_finalize(
103 "Detected DP deployment with no --enable-expert-parallel. "
104 "Falling back to AllGather+ReduceScatter dispatch/combine."
105 )
105 - return MoEPrepareAndFinalizeNaiveEP(
106 + return make_moe_prepare_and_finalize_naive_dp_ep(
107 is_sequence_parallel=moe.moe_parallel_config.is_sequence_parallel,
108 num_dispatchers=(
109 get_ep_group().device_communicator.all2all_manager.world_size
110 ),
111 + use_monolithic=use_monolithic,
112 )
113 else:
112 - return MoEPrepareAndFinalizeNoEP()
114 + return make_moe_prepare_and_finalize_no_dp_ep(use_monolithic)
115
116 all2all_manager = get_ep_group().device_communicator.all2all_manager
117 assert all2all_manager is not None
@@ -201,8 +203,9 @@ def maybe_make_prepare_finalize(
203 )
204
205 elif moe.use_naive_all2all_kernels and allow_new_interface:
204 - prepare_finalize = MoEPrepareAndFinalizeNaiveEP(
205 - is_sequence_parallel=(moe.moe_parallel_config.is_sequence_parallel),
206 + prepare_finalize = make_moe_prepare_and_finalize_naive_dp_ep(
207 + use_monolithic=use_monolithic,
208 + is_sequence_parallel=moe.moe_parallel_config.is_sequence_parallel,
209 num_dispatchers=all2all_manager.world_size,
210 )
211
vllm/model_executor/layers/fused_moe/batched_deep_gemm_moe.py
+1 -1
@@ -261,7 +261,7 @@ def persistent_masked_m_silu_mul_quant(
261 return y_q, y_s
262
263
264 -class BatchedDeepGemmExperts(mk.FusedMoEPermuteExpertsUnpermute):
264 +class BatchedDeepGemmExperts(mk.FusedMoEExpertsModular):
265 def __init__(
266 self,
267 moe_config: FusedMoEConfig,
vllm/model_executor/layers/fused_moe/config.py
+6
@@ -228,6 +228,7 @@ class FusedMoEQuantConfig:
228 _a2: FusedMoEQuantDesc
229 _w1: FusedMoEQuantDesc
230 _w2: FusedMoEQuantDesc
231 + is_nvfp4_scale_swizzled: bool = True
232
233 def __post_init__(self):
234 assert not self.per_act_token_quant or self.block_shape is None, (
@@ -475,6 +476,7 @@ class FusedMoEQuantConfig:
476 w1_zp: torch.Tensor | None = None,
477 w2_zp: torch.Tensor | None = None,
478 weight_dtype: torch.dtype | str | None = None,
479 + is_nvfp4_scale_swizzled: bool = True,
480 ) -> "FusedMoEQuantConfig":
481 """
482 General builder function for a FusedMoEQuantConfig.
@@ -504,6 +506,7 @@ class FusedMoEQuantConfig:
506 - w2_bias: Optional biases for w1 (GPT OSS Triton).
507 - w1_zp: Optional w1 zero points for int4/int8 quantization.
508 - w2_zp: Optional w2 zero points for int4/int8 quantization.
509 + - is_nvfp4_scale_swizzled: Whether to swizzle the nvfp4 scale swizzling.
510 """
511 assert not isinstance(quant_dtype, str) or quant_dtype in {
512 "nvfp4",
@@ -536,6 +539,7 @@ class FusedMoEQuantConfig:
539 _w2=FusedMoEQuantDesc(
540 weight_dtype, w_shape, w2_scale, g2_alphas, w2_zp, w2_bias
541 ),
542 + is_nvfp4_scale_swizzled=is_nvfp4_scale_swizzled,
543 )
544 assert quant_config.per_act_token_quant == per_act_token_quant
545 assert quant_config.per_out_ch_quant == per_out_ch_quant
@@ -737,6 +741,7 @@ def nvfp4_moe_quant_config(
741 w2_scale: torch.Tensor,
742 w1_bias: torch.Tensor | None = None,
743 w2_bias: torch.Tensor | None = None,
744 + is_nvfp4_scale_swizzled: bool = True,
745 ) -> FusedMoEQuantConfig:
746 """
747 Construct a quant config for mxfp4 activations and nvp4 weights.
@@ -754,6 +759,7 @@ def nvfp4_moe_quant_config(
759 per_act_token_quant=False,
760 per_out_ch_quant=False,
761 block_shape=None,
762 + is_nvfp4_scale_swizzled=is_nvfp4_scale_swizzled,
763 )
764
765
vllm/model_executor/layers/fused_moe/cutlass_moe.py
+7 -8
@@ -21,7 +21,7 @@ from vllm.model_executor.layers.fused_moe.moe_permute_unpermute import (
21 moe_unpermute,
22 )
23 from vllm.model_executor.layers.fused_moe.prepare_finalize import (
24 - MoEPrepareAndFinalizeNoEP,
24 + MoEPrepareAndFinalizeNoDPEPModular,
25 )
26 from vllm.model_executor.layers.fused_moe.topk_weight_and_reduce import (
27 TopKWeightAndReduceDelegate,
@@ -262,7 +262,7 @@ def run_cutlass_moe_fp8(
262 )
263
264
265 -class CutlassExpertsFp8Base(mk.FusedMoEPermuteExpertsUnpermute):
265 +class CutlassExpertsFp8Base(mk.FusedMoEExpertsModular):
266 def __init__(
267 self,
268 moe_config: FusedMoEConfig,
@@ -661,7 +661,7 @@ def run_cutlass_moe_fp4(
661 return
662
663
664 -class CutlassExpertsFp4(mk.FusedMoEPermuteExpertsUnpermute):
664 +class CutlassExpertsFp4(mk.FusedMoEExpertsModular):
665 """CUTLASS FP4 fused MoE expert implementation."""
666
667 @property
@@ -928,7 +928,7 @@ def run_cutlass_moe_w4a8_fp8(
928 )
929
930
931 -class CutlassExpertsW4A8Fp8(mk.FusedMoEPermuteExpertsUnpermute):
931 +class CutlassExpertsW4A8Fp8(mk.FusedMoEExpertsModular):
932 def __init__(
933 self,
934 out_dtype: torch.dtype | None,
@@ -1170,8 +1170,8 @@ def cutlass_moe_w4a8_fp8(
1170
1171 num_experts = global_num_experts if global_num_experts != -1 else w1_q.size(0)
1172
1173 - fn = mk.FusedMoEModularKernel(
1174 - MoEPrepareAndFinalizeNoEP(),
1173 + fn = mk.FusedMoEKernel(
1174 + MoEPrepareAndFinalizeNoDPEPModular(),
1175 CutlassExpertsW4A8Fp8(
1176 out_dtype=a.dtype,
1177 a_strides1=a_strides1,
@@ -1186,10 +1186,9 @@ def cutlass_moe_w4a8_fp8(
1186 quant_config=quant_config,
1187 group_size=group_size,
1188 ),
1189 - inplace=False,
1189 )
1190
1192 - return fn(
1191 + return fn.apply(
1192 a,
1193 w1_q,
1194 w2_q,
vllm/model_executor/layers/fused_moe/deep_gemm_moe.py
+1 -1
@@ -113,7 +113,7 @@ def _valid_deep_gemm(
113 return True
114
115
116 -class DeepGemmExperts(mk.FusedMoEPermuteExpertsUnpermute):
116 +class DeepGemmExperts(mk.FusedMoEExpertsModular):
117 """DeepGemm-based fused MoE expert implementation."""
118
119 def __init__(self, moe_config: FusedMoEConfig, quant_config: FusedMoEQuantConfig):
vllm/model_executor/layers/fused_moe/deepep_ht_prepare_finalize.py
+2 -1
@@ -25,7 +25,7 @@ from vllm.v1.worker.ubatching import (
25 )
26
27
28 -class DeepEPHTPrepareAndFinalize(mk.FusedMoEPrepareAndFinalize):
28 +class DeepEPHTPrepareAndFinalize(mk.FusedMoEPrepareAndFinalizeModular):
29 """
30 Prepare/Finalize using DeepEP High-Throughput kernels.
31 """
@@ -239,6 +239,7 @@ class DeepEPHTPrepareAndFinalize(mk.FusedMoEPrepareAndFinalize):
239 quant_dtype=quant_config.quant_dtype,
240 per_act_token_quant=False,
241 block_shape=quant_config.block_shape,
242 + is_fp4_scale_swizzled=quant_config.is_nvfp4_scale_swizzled,
243 )
244
245 return (
vllm/model_executor/layers/fused_moe/deepep_ll_prepare_finalize.py
+2 -2
@@ -49,7 +49,7 @@ def dequant_fp8(
49 return (expert_x_fp32 * expert_x_scales).view(expert_x_fp8.size())
50
51
52 -class DeepEPLLPrepareAndFinalize(mk.FusedMoEPrepareAndFinalize):
52 +class DeepEPLLPrepareAndFinalize(mk.FusedMoEPrepareAndFinalizeModular):
53 """
54 Prepare/Finalize using DeepEP low-latency kernels.
55 """
@@ -119,7 +119,7 @@ class DeepEPLLPrepareAndFinalize(mk.FusedMoEPrepareAndFinalize):
119 # time. This setting is handled by post_init_setup.
120 self.use_ue8m0_dispatch = False
121
122 - def post_init_setup(self, fused_experts: mk.FusedMoEPermuteExpertsUnpermute):
122 + def post_init_setup(self, fused_experts: mk.FusedMoEExperts):
123 if not fused_experts.supports_packed_ue8m0_act_scales():
124 # Early exit.
125 return
vllm/model_executor/layers/fused_moe/experts/__init__.py
vllm/model_executor/layers/fused_moe/experts/trtllm_fp8_moe.py new
+335
@@ -0,0 +1,335 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +
4 +import torch
5 +
6 +import vllm.model_executor.layers.fused_moe.modular_kernel as mk
7 +from vllm.model_executor.layers.fused_moe.activation import MoEActivation
8 +from vllm.model_executor.layers.fused_moe.config import (
9 + FusedMoEConfig,
10 + FusedMoEParallelConfig,
11 + FusedMoEQuantConfig,
12 + RoutingMethodType,
13 +)
14 +from vllm.model_executor.layers.quantization.utils.flashinfer_utils import (
15 + activation_to_flashinfer_int,
16 +)
17 +from vllm.model_executor.layers.quantization.utils.quant_utils import (
18 + QuantKey,
19 + kFp8Dynamic128Sym,
20 + kFp8Static128BlockSym,
21 + kFp8StaticTensorSym,
22 +)
23 +from vllm.platforms import current_platform
24 +
25 +
26 +class TrtLlmFp8Experts(mk.FusedMoEExpertsMonolithic):
27 + """
28 + Fp8 TRTLLM-Gen MoE kernels. Supports monolithic interface.
29 + """
30 +
31 + def __init__(
32 + self,
33 + moe_config: FusedMoEConfig,
34 + quant_config: FusedMoEQuantConfig,
35 + ):
36 + super().__init__(moe_config, quant_config)
37 +
38 + if moe_config.moe_parallel_config.use_ep and quant_config.is_per_tensor:
39 + raise NotImplementedError(
40 + "EP parallelism is not supported with TRTLLM"
41 + "per-tensor FP8 quantization."
42 + )
43 +
44 + self.routing_method_type = moe_config.routing_method
45 + self.topk = moe_config.experts_per_token
46 + self.intermediate_size_per_partition = (
47 + moe_config.intermediate_size_per_partition
48 + )
49 + self.hidden_dim = moe_config.hidden_dim
50 + self.local_num_experts = moe_config.num_local_experts
51 + self.ep_rank = moe_config.moe_parallel_config.ep_rank
52 +
53 + # Make additional scales for per-tensor interface.
54 + if self.quant_config.is_per_tensor:
55 + w1_scale = self.quant_config.w1_scale
56 + assert w1_scale is not None
57 + a1_scale = self.quant_config.a1_scale
58 + assert a1_scale is not None
59 + w2_scale = self.quant_config.w2_scale
60 + assert w2_scale is not None
61 + a2_scale = self.quant_config.a2_scale
62 + assert a2_scale is not None
63 +
64 + self._g1_alphas = (w1_scale * a1_scale).squeeze()
65 + self._g2_alphas = (w2_scale * a2_scale).squeeze()
66 + self._g1_scale_c = (
67 + self._g1_alphas / self.quant_config.a2_scale
68 + if moe_config.is_act_and_mul
69 + else torch.ones_like(self._g1_alphas) / self.quant_config.a2_scale
70 + )
71 +
72 + @staticmethod
73 + def activation_format() -> mk.FusedMoEActivationFormat:
74 + return mk.FusedMoEActivationFormat.Standard
75 +
76 + @staticmethod
77 + def _supports_current_device() -> bool:
78 + """Supports only Blackwell-family GPUs."""
79 + p = current_platform
80 + # Add check flashinfer trtllm is available
81 + return p.is_cuda() and p.is_device_capability_family(100)
82 +
83 + @staticmethod
84 + def _supports_no_act_and_mul() -> bool:
85 + """Does not support non-gated MoE (i.e. Nanotron-3-Nano)."""
86 + return True
87 +
88 + @staticmethod
89 + def _supports_quant_scheme(
90 + weight_key: QuantKey | None,
91 + activation_key: QuantKey | None,
92 + ) -> bool:
93 + """Supports Fp8 per-tensor and Fp8 block."""
94 + SUPPORTED_W_A = [
95 + (kFp8Static128BlockSym, kFp8Dynamic128Sym),
96 + (kFp8StaticTensorSym, kFp8StaticTensorSym),
97 + ]
98 + return (weight_key, activation_key) in SUPPORTED_W_A
99 +
100 + @staticmethod
101 + def _supports_activation(activation: MoEActivation) -> bool:
102 + """Supports only SiLU and RELU^2 non-gated activation."""
103 + return activation in [MoEActivation.SILU, MoEActivation.RELU2_NO_MUL]
104 +
105 + @staticmethod
106 + def _supports_routing_method(
107 + routing_method: RoutingMethodType,
108 + weight_key: QuantKey | None,
109 + activation_key: QuantKey | None,
110 + ) -> bool:
111 + """Monolithic kernels need to express router support."""
112 + # NOTE(dbari): TopK routing could also be enabled, but need to validate models
113 + # NOTE(dbari): Default is not implemented and should not be enabled until it is
114 + if (weight_key, activation_key) == (kFp8Static128BlockSym, kFp8Dynamic128Sym):
115 + # NOTE(rob): potentially allow others here. This is a conservative list.
116 + return routing_method in [
117 + RoutingMethodType.DeepSeekV3,
118 + RoutingMethodType.Renormalize,
119 + RoutingMethodType.RenormalizeNaive,
120 + ]
121 + elif (weight_key, activation_key) == (kFp8StaticTensorSym, kFp8StaticTensorSym):
122 + # NOTE(dbari): as above, potentially allow others here.
123 + return routing_method in [
124 + RoutingMethodType.DeepSeekV3,
125 + RoutingMethodType.Llama4,
126 + RoutingMethodType.Renormalize,
127 + RoutingMethodType.RenormalizeNaive,
128 + ]
129 + else:
130 + raise ValueError("Unsupported quantization scheme.")
131 +
132 + @staticmethod
133 + def _supports_parallel_config(moe_parallel_config: FusedMoEParallelConfig) -> bool:
134 + """Monolithic kernel so only use with naive DP/EP and TP."""
135 + return (
136 + not moe_parallel_config.use_all2all_kernels
137 + or moe_parallel_config.use_naive_all2all_kernels
138 + ) and not moe_parallel_config.enable_eplb
139 +
140 + @staticmethod
141 + def _supports_router_logits_dtype(
142 + router_logits_dtype: torch.dtype | None,
143 + routing_method: RoutingMethodType,
144 + ) -> bool:
145 + """
146 + The FlashInfer TRTLLM FP8 kernel expects bfloat16 router_logits by default.
147 + Only DeepSeekV3 routing supports float32 router_logits (which is converted
148 + internally in the kernel).
149 + """
150 + if router_logits_dtype == torch.float32:
151 + # Only DeepSeekV3 routing handles float32 logits
152 + # https://github.com/flashinfer-ai/flashinfer/issues/2469
153 + return routing_method == RoutingMethodType.DeepSeekV3
154 + return True
155 +
156 + def supports_chunking(self) -> bool:
157 + return False
158 +
159 + def supports_expert_map(self) -> bool:
160 + return False
161 +
162 + def _apply_per_block(
163 + self,
164 + hidden_states: torch.Tensor,
165 + w1: torch.Tensor,
166 + w2: torch.Tensor,
167 + router_logits: torch.Tensor,
168 + activation: MoEActivation,
169 + global_num_experts: int,
170 + expert_map: torch.Tensor | None,
171 + a1q_scale: torch.Tensor | None,
172 + apply_router_weight_on_input: bool,
173 + # grouped topk + fused topk bias parameters
174 + num_expert_group: int | None = None,
175 + e_score_correction_bias: torch.Tensor | None = None,
176 + routed_scaling_factor: float | None = None,
177 + topk_group: int | None = None,
178 + ) -> torch.Tensor:
179 + # Delay import for non-CUDA.
180 + import flashinfer
181 +
182 + assert not apply_router_weight_on_input
183 + assert activation == MoEActivation.SILU
184 +
185 + if e_score_correction_bias is not None:
186 + e_score_correction_bias = e_score_correction_bias.to(hidden_states.dtype)
187 +
188 + if self.routing_method_type == RoutingMethodType.DeepSeekV3:
189 + router_logits = router_logits.to(torch.float32)
190 +
191 + assert self.topk <= global_num_experts
192 + assert self.topk <= 10
193 + assert global_num_experts % 4 == 0
194 + assert self.quant_config.block_shape == [128, 128]
195 + # Routing kernel expects #experts <= #threads 512
196 + assert global_num_experts <= 512
197 +
198 + # Kernel requires transposed hidden state scales
199 + # TODO: fuse into the quant kernel.
200 + assert a1q_scale is not None
201 + a1q_scale_t = a1q_scale.t().contiguous()
202 +
203 + return flashinfer.fused_moe.trtllm_fp8_block_scale_moe(
204 + routing_logits=router_logits,
205 + routing_bias=e_score_correction_bias,
206 + hidden_states=hidden_states,
207 + hidden_states_scale=a1q_scale_t,
208 + gemm1_weights=w1,
209 + gemm1_weights_scale=self.quant_config.w1_scale,
210 + gemm2_weights=w2,
211 + gemm2_weights_scale=self.quant_config.w2_scale,
212 + num_experts=global_num_experts,
213 + top_k=self.topk,
214 + n_group=(num_expert_group or 0),
215 + topk_group=(topk_group or 0),
216 + intermediate_size=self.intermediate_size_per_partition,
217 + local_expert_offset=self.ep_rank * self.local_num_experts,
218 + local_num_experts=self.local_num_experts,
219 + routed_scaling_factor=routed_scaling_factor,
220 + routing_method_type=self.routing_method_type,
221 + use_shuffled_weight=False,
222 + )
223 +
224 + def _apply_per_tensor(
225 + self,
226 + hidden_states: torch.Tensor,
227 + w1: torch.Tensor,
228 + w2: torch.Tensor,
229 + router_logits: torch.Tensor,
230 + activation: MoEActivation,
231 + global_num_experts: int,
232 + expert_map: torch.Tensor | None,
233 + a1q_scale: torch.Tensor | None,
234 + apply_router_weight_on_input: bool,
235 + # grouped topk + fused topk bias parameters
236 + num_expert_group: int | None = None,
237 + e_score_correction_bias: torch.Tensor | None = None,
238 + routed_scaling_factor: float | None = None,
239 + topk_group: int | None = None,
240 + ) -> torch.Tensor:
241 + # Delay import for non-CUDA.
242 + import flashinfer
243 + from flashinfer.fused_moe.core import ActivationType
244 +
245 + # Confirm supported activation function.
246 + assert activation in [MoEActivation.SILU, MoEActivation.RELU2_NO_MUL]
247 +
248 + activation_type = ActivationType(activation_to_flashinfer_int(activation))
249 +
250 + # Confirm Llama-4 routing is proper.
251 + if self.routing_method_type == RoutingMethodType.Llama4:
252 + assert apply_router_weight_on_input
253 + else:
254 + assert not apply_router_weight_on_input
255 +
256 + # The DeepSeekV3 routing method requires float32 router logits.
257 + if self.routing_method_type == RoutingMethodType.DeepSeekV3:
258 + router_logits = router_logits.to(torch.float32)
259 +
260 + out = flashinfer.fused_moe.trtllm_fp8_per_tensor_scale_moe(
261 + routing_logits=router_logits,
262 + routing_bias=e_score_correction_bias,
263 + hidden_states=hidden_states,
264 + gemm1_weights=w1,
265 + output1_scales_scalar=self._g1_scale_c,
266 + output1_scales_gate_scalar=self._g1_alphas,
267 + gemm2_weights=w2,
268 + output2_scales_scalar=self._g2_alphas,
269 + num_experts=global_num_experts,
270 + top_k=self.topk,
271 + n_group=num_expert_group or 0,
272 + topk_group=topk_group or 0,
273 + intermediate_size=self.intermediate_size_per_partition,
274 + local_expert_offset=self.ep_rank * self.local_num_experts,
275 + local_num_experts=self.local_num_experts,
276 + routed_scaling_factor=routed_scaling_factor,
277 + use_routing_scales_on_input=apply_router_weight_on_input,
278 + routing_method_type=self.routing_method_type,
279 + activation_type=activation_type,
280 + )
281 + return out
282 +
283 + def apply(
284 + self,
285 + hidden_states: torch.Tensor,
286 + w1: torch.Tensor,
287 + w2: torch.Tensor,
288 + router_logits: torch.Tensor,
289 + activation: MoEActivation,
290 + global_num_experts: int,
291 + expert_map: torch.Tensor | None,
292 + a1q_scale: torch.Tensor | None,
293 + apply_router_weight_on_input: bool,
294 + # grouped topk + fused topk bias parameters
295 + num_expert_group: int | None = None,
296 + e_score_correction_bias: torch.Tensor | None = None,
297 + routed_scaling_factor: float | None = None,
298 + topk_group: int | None = None,
299 + ) -> torch.Tensor:
300 + if self.quant_config.block_shape is not None:
301 + return self._apply_per_block(
302 + hidden_states,
303 + w1,
304 + w2,
305 + router_logits,
306 + activation,
307 + global_num_experts,
308 + expert_map,
309 + a1q_scale,
310 + apply_router_weight_on_input,
311 + num_expert_group=num_expert_group,
312 + e_score_correction_bias=e_score_correction_bias,
313 + routed_scaling_factor=routed_scaling_factor,
314 + topk_group=topk_group,
315 + )
316 + elif self.quant_config.is_per_tensor:
317 + return self._apply_per_tensor(
318 + hidden_states,
319 + w1,
320 + w2,
321 + router_logits,
322 + activation,
323 + global_num_experts,
324 + expert_map,
325 + a1q_scale,
326 + apply_router_weight_on_input,
327 + num_expert_group=num_expert_group,
328 + e_score_correction_bias=e_score_correction_bias,
329 + routed_scaling_factor=routed_scaling_factor,
330 + )
331 + else:
332 + raise NotImplementedError(
333 + "Only per-block and per-tensor quantization are supported in "
334 + f"{self.__class__.__name__}."
335 + )
vllm/model_executor/layers/fused_moe/experts/trtllm_nvfp4_moe.py new
+326
@@ -0,0 +1,326 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +
4 +import flashinfer
5 +import torch
6 +
7 +import vllm.model_executor.layers.fused_moe.modular_kernel as mk
8 +from vllm.model_executor.layers.fused_moe.activation import MoEActivation
9 +from vllm.model_executor.layers.fused_moe.config import (
10 + FusedMoEConfig,
11 + FusedMoEParallelConfig,
12 + FusedMoEQuantConfig,
13 + RoutingMethodType,
14 +)
15 +from vllm.model_executor.layers.fused_moe.topk_weight_and_reduce import (
16 + TopKWeightAndReduceNoOP,
17 +)
18 +from vllm.model_executor.layers.quantization.utils.flashinfer_utils import (
19 + activation_to_flashinfer_int,
20 +)
21 +from vllm.model_executor.layers.quantization.utils.quant_utils import (
22 + QuantKey,
23 + kNvfp4Dynamic,
24 + kNvfp4Static,
25 +)
26 +from vllm.platforms import current_platform
27 +
28 +
29 +class TrtLlmNvFp4ExpertsBase:
30 + """
31 + NvFp4 TRTLLM-Gen MoE kernels. Supports modular and monolithic interface.
32 + """
33 +
34 + def __init__(
35 + self,
36 + moe_config: FusedMoEConfig,
37 + quant_config: FusedMoEQuantConfig,
38 + ):
39 + self.moe_config = moe_config
40 + self.quant_config = quant_config
41 +
42 + self.routing_method_type = self.moe_config.routing_method
43 + self.topk = moe_config.experts_per_token
44 + self.intermediate_size_per_partition = (
45 + moe_config.intermediate_size_per_partition
46 + )
47 + self.hidden_dim = moe_config.hidden_dim
48 + self.local_num_experts = moe_config.num_local_experts
49 + self.ep_rank = moe_config.moe_parallel_config.ep_rank
50 +
51 + assert self.quant_config.g1_alphas is not None
52 + assert self.quant_config.a2_gscale is not None
53 + if moe_config.is_act_and_mul:
54 + # g1_alpha_s = a13_scale * w13_scale_2
55 + # a2_gscale = (1 / a2_scale)
56 + # g1_scale_c = a13_scale * w13_scale_2 / a2_scale
57 + self.g1_scale_c = self.quant_config.g1_alphas * self.quant_config.a2_gscale
58 + else:
59 + self.g1_scale_c = (
60 + torch.ones_like(self.quant_config.a1_gscale)
61 + * self.quant_config.a2_gscale
62 + )
63 +
64 + @staticmethod
65 + def _supports_current_device() -> bool:
66 + """Supports only Blackwell-family GPUs."""
67 + p = current_platform
68 + return p.is_cuda() and p.is_device_capability_family(100)
69 +
70 + @staticmethod
71 + def _supports_no_act_and_mul() -> bool:
72 + """Supports non-gated MoE (i.e. Nemotron-Nano)."""
73 + return True
74 +
75 + @staticmethod
76 + def _supports_quant_scheme(
77 + weight_key: QuantKey | None,
78 + activation_key: QuantKey | None,
79 + ) -> bool:
80 + """Supports Nvfp4 quantization."""
81 + SUPPORTED_W_A = [
82 + (kNvfp4Static, kNvfp4Dynamic),
83 + ]
84 + return (weight_key, activation_key) in SUPPORTED_W_A
85 +
86 + @staticmethod
87 + def _supports_activation(activation: MoEActivation) -> bool:
88 + """Supports only SiLU and RELU^2 non-gated activation."""
89 + return activation in [MoEActivation.SILU, MoEActivation.RELU2_NO_MUL]
90 +
91 + @staticmethod
92 + def _supports_shape(hidden_dim: int) -> bool:
93 + """Requires hidden dim to be multiple of 512."""
94 + return hidden_dim % 512 == 0
95 +
96 + @staticmethod
97 + def activation_format() -> mk.FusedMoEActivationFormat:
98 + return mk.FusedMoEActivationFormat.Standard
99 +
100 + def supports_chunking(self) -> bool:
101 + return False
102 +
103 + def supports_expert_map(self) -> bool:
104 + return False
105 +
106 +
107 +class TrtLlmNvFp4ExpertsModular(TrtLlmNvFp4ExpertsBase, mk.FusedMoEExpertsModular):
108 + """
109 + Modular version of the implementation (just the experts).
110 + """
111 +
112 + @staticmethod
113 + def _supports_parallel_config(moe_parallel_config: FusedMoEParallelConfig) -> bool:
114 + """The modular implementation supports all parallel configs."""
115 + return True
116 +
117 + def workspace_shapes(
118 + self,
119 + M: int,
120 + N: int,
121 + K: int,
122 + topk: int,
123 + global_num_experts: int,
124 + local_num_experts: int,
125 + expert_tokens_meta: mk.ExpertTokensMetadata | None,
126 + activation: MoEActivation,
127 + ) -> tuple[tuple[int, ...], tuple[int, ...], tuple[int, ...]]:
128 + # The workspaces for this implementation are managed by flashinfer.
129 + workspace1 = (0,)
130 + workspace2 = (0,)
131 +
132 + # Hidden states are Nvfp4, packed into int8 dtype, so we
133 + # need to multiply K by 2 to get the output shape right.
134 + assert self.hidden_dim == K * 2
135 + output = (M, self.hidden_dim)
136 +
137 + return (workspace1, workspace2, output)
138 +
139 + def finalize_weight_and_reduce_impl(self) -> mk.TopKWeightAndReduce:
140 + return TopKWeightAndReduceNoOP()
141 +
142 + def apply(
143 + self,
144 + output: torch.Tensor,
145 + hidden_states: torch.Tensor,
146 + w1: torch.Tensor,
147 + w2: torch.Tensor,
148 + topk_weights: torch.Tensor,
149 + topk_ids: torch.Tensor,
150 + activation: MoEActivation,
151 + global_num_experts: int,
152 + expert_map: torch.Tensor | None,
153 + a1q_scale: torch.Tensor | None,
154 + a2_scale: torch.Tensor | None,
155 + workspace13: torch.Tensor,
156 + workspace2: torch.Tensor,
157 + expert_tokens_meta: mk.ExpertTokensMetadata | None,
158 + apply_router_weight_on_input: bool,
159 + ):
160 + assert activation in [MoEActivation.SILU, MoEActivation.RELU2_NO_MUL]
161 + assert a1q_scale is not None
162 + assert self.quant_config.w1_scale is not None
163 + assert self.quant_config.w2_scale is not None
164 +
165 + # Pack topk ids and weights into format expected by the kernel.
166 + packed_tensor = (topk_ids.to(torch.int32) << 16) | topk_weights.to(
167 + torch.bfloat16
168 + ).view(torch.int16)
169 +
170 + # trtllm_fp4_block_scale_routed_moe does not support autotuning
171 + # so skip this kernel during dummy run for autotuning.
172 + import vllm.utils.flashinfer as fi_utils
173 +
174 + if fi_utils._is_fi_autotuning:
175 + return hidden_states
176 +
177 + # Invoke kernel.
178 + flashinfer.fused_moe.trtllm_fp4_block_scale_routed_moe(
179 + topk_ids=packed_tensor,
180 + routing_bias=None,
181 + hidden_states=hidden_states,
182 + hidden_states_scale=a1q_scale.view(torch.float8_e4m3fn).reshape(
183 + *hidden_states.shape[:-1], -1
184 + ),
185 + gemm1_weights=w1,
186 + gemm1_weights_scale=self.quant_config.w1_scale.view(torch.float8_e4m3fn),
187 + gemm1_bias=None,
188 + gemm1_alpha=None,
189 + gemm1_beta=None,
190 + gemm1_clamp_limit=None,
191 + gemm2_weights=w2,
192 + gemm2_weights_scale=self.quant_config.w2_scale.view(torch.float8_e4m3fn),
193 + gemm2_bias=None,
194 + output1_scale_scalar=self.g1_scale_c,
195 + output1_scale_gate_scalar=self.quant_config.g1_alphas,
196 + output2_scale_scalar=self.quant_config.g2_alphas,
197 + num_experts=global_num_experts,
198 + top_k=self.topk,
199 + n_group=0,
200 + topk_group=0,
201 + intermediate_size=self.intermediate_size_per_partition,
202 + local_expert_offset=self.ep_rank * self.local_num_experts,
203 + local_num_experts=self.local_num_experts,
204 + routed_scaling_factor=None,
205 + routing_method_type=1,
206 + do_finalize=True,
207 + activation_type=activation_to_flashinfer_int(activation),
208 + output=output,
209 + )
210 +
211 +
212 +class TrtLlmNvFp4ExpertsMonolithic(
213 + TrtLlmNvFp4ExpertsBase, mk.FusedMoEExpertsMonolithic
214 +):
215 + """
216 + Monolithic version of the kernel (router + experts).
217 + """
218 +
219 + @staticmethod
220 + def _supports_parallel_config(moe_parallel_config: FusedMoEParallelConfig) -> bool:
221 + """The modular implementation should be used for the Dp/Ep or EPLB case."""
222 + return (
223 + not moe_parallel_config.use_all2all_kernels
224 + and not moe_parallel_config.enable_eplb
225 + )
226 +
227 + @staticmethod
228 + def _supports_routing_method(
229 + routing_method_type: RoutingMethodType,
230 + weight_key: QuantKey | None,
231 + activation_key: QuantKey | None,
232 + ) -> bool:
233 + # NOTE(rob): this is a conservative list.
234 + return routing_method_type in [
235 + RoutingMethodType.DeepSeekV3,
236 + RoutingMethodType.Renormalize,
237 + RoutingMethodType.RenormalizeNaive,
238 + RoutingMethodType.Llama4,
239 + ]
240 +
241 + @staticmethod
242 + def _supports_router_logits_dtype(
243 + router_logits_dtype: torch.dtype | None,
244 + routing_method: RoutingMethodType,
245 + ) -> bool:
246 + """
247 + The FlashInfer TRTLLM NvFp4 kernel expects bfloat16 router_logits by default.
248 + Only DeepSeekV3 routing supports float32 router_logits (which is converted
249 + internally in the kernel).
250 + """
251 + if router_logits_dtype == torch.float32:
252 + # Only DeepSeekV3 routing handles float32 logits
253 + # https://github.com/flashinfer-ai/flashinfer/issues/2469
254 + return routing_method == RoutingMethodType.DeepSeekV3
255 + return True
256 +
257 + def apply(
258 + self,
259 + hidden_states: torch.Tensor,
260 + w1: torch.Tensor,
261 + w2: torch.Tensor,
262 + router_logits: torch.Tensor,
263 + activation: MoEActivation,
264 + global_num_experts: int,
265 + expert_map: torch.Tensor | None,
266 + a1q_scale: torch.Tensor | None,
267 + apply_router_weight_on_input: bool,
268 + # grouped topk + fused topk bias parameters
269 + num_expert_group: int | None = None,
270 + e_score_correction_bias: torch.Tensor | None = None,
271 + routed_scaling_factor: float | None = None,
272 + topk_group: int | None = None,
273 + ) -> torch.Tensor:
274 + assert activation in [MoEActivation.SILU, MoEActivation.RELU2_NO_MUL]
275 + assert a1q_scale is not None
276 + assert self.quant_config.w1_scale is not None
277 + assert self.quant_config.w2_scale is not None
278 + assert (
279 + apply_router_weight_on_input
280 + and self.routing_method_type == RoutingMethodType.Llama4
281 + ) or (
282 + not apply_router_weight_on_input
283 + and self.routing_method_type != RoutingMethodType.Llama4
284 + )
285 +
286 + # Prepare routing bias into kernel format.
287 + routing_bias = e_score_correction_bias
288 + if routing_bias is not None:
289 + routing_bias = routing_bias.to(torch.bfloat16)
290 + router_logits = (
291 + router_logits.to(torch.float32)
292 + if self.routing_method_type == RoutingMethodType.DeepSeekV3
293 + else router_logits
294 + )
295 +
296 + # Invoke kernel.
297 + return flashinfer.fused_moe.trtllm_fp4_block_scale_moe(
298 + routing_logits=router_logits,
299 + routing_bias=routing_bias,
300 + hidden_states=hidden_states,
301 + hidden_states_scale=a1q_scale.view(torch.float8_e4m3fn).reshape(
302 + *hidden_states.shape[:-1], -1
303 + ),
304 + gemm1_weights=w1,
305 + gemm1_weights_scale=self.quant_config.w1_scale.view(torch.float8_e4m3fn),
306 + gemm1_bias=None,
307 + gemm1_alpha=None,
308 + gemm1_beta=None,
309 + gemm1_clamp_limit=None,
310 + gemm2_weights=w2,
311 + gemm2_weights_scale=self.quant_config.w2_scale.view(torch.float8_e4m3fn),
312 + gemm2_bias=None,
313 + output1_scale_scalar=self.g1_scale_c,
314 + output1_scale_gate_scalar=self.quant_config.g1_alphas,
315 + output2_scale_scalar=self.quant_config.g2_alphas,
316 + num_experts=global_num_experts,
317 + top_k=self.topk,
318 + n_group=(num_expert_group or 0),
319 + topk_group=(topk_group or 0),
320 + intermediate_size=self.intermediate_size_per_partition,
321 + local_expert_offset=self.ep_rank * self.local_num_experts,
322 + local_num_experts=self.local_num_experts,
323 + routed_scaling_factor=routed_scaling_factor,
324 + routing_method_type=self.routing_method_type,
325 + do_finalize=True,
326 + )[0]
vllm/model_executor/layers/fused_moe/fallback.py
+6 -6
@@ -11,13 +11,13 @@ from vllm.model_executor.layers.fused_moe.config import FusedMoEParallelConfig
11 from vllm.model_executor.layers.quantization.utils.quant_utils import QuantKey
12
13
14 -class FallbackExperts(mk.FusedMoEPermuteExpertsUnpermute, ABC):
14 +class FallbackExperts(mk.FusedMoEExpertsModular, ABC):
15 """Base class for runtime dispatching of expert implementations."""
16
17 def __init__(
18 self,
19 - experts: mk.FusedMoEPermuteExpertsUnpermute,
20 - fallback_experts: mk.FusedMoEPermuteExpertsUnpermute,
19 + experts: mk.FusedMoEExpertsModular,
20 + fallback_experts: mk.FusedMoEExpertsModular,
21 ):
22 super().__init__(
23 moe_config=experts.moe_config, quant_config=experts.quant_config
@@ -27,8 +27,8 @@ class FallbackExperts(mk.FusedMoEPermuteExpertsUnpermute, ABC):
27
28 @staticmethod
29 def get_clses() -> tuple[
30 - type[mk.FusedMoEPermuteExpertsUnpermute],
31 - type[mk.FusedMoEPermuteExpertsUnpermute],
30 + type[mk.FusedMoEExpertsModular],
31 + type[mk.FusedMoEExpertsModular],
32 ]:
33 """
34 Get the cls for the experts and fallback experts.
@@ -149,7 +149,7 @@ class FallbackExperts(mk.FusedMoEPermuteExpertsUnpermute, ABC):
149 hidden_states: torch.Tensor,
150 w1: torch.Tensor,
151 w2: torch.Tensor,
152 - ) -> mk.FusedMoEPermuteExpertsUnpermute:
152 + ) -> mk.FusedMoEExpertsModular:
153 raise NotImplementedError
154
155 def apply(
vllm/model_executor/layers/fused_moe/flashinfer_a2a_prepare_finalize.py
+3 -3
@@ -18,7 +18,7 @@ def get_local_sizes():
18 return get_forward_context().dp_metadata.get_chunk_sizes_across_dp_rank()
19
20
21 -class FlashInferA2APrepareAndFinalize(mk.FusedMoEPrepareAndFinalize):
21 +class FlashInferA2APrepareAndFinalize(mk.FusedMoEPrepareAndFinalizeModular):
22 """Base class for FlashInfer MoE prepare and finalize operations."""
23
24 def __init__(
@@ -185,8 +185,8 @@ def flashinfer_alltoall_dispatch(
185 ep_size,
186 )
187
188 - # Swizzle after the A2A if nvfp4.
189 - if quant_config.quant_dtype == "nvfp4":
188 + # Swizzle after the A2A if MoE kernel expects swizzled scales.
189 + if quant_config.quant_dtype == "nvfp4" and quant_config.is_nvfp4_scale_swizzled:
190 if x_sf.element_size() == 1:
191 x_sf = x_sf.view(torch.uint8)
192 x_sf = nvfp4_block_scale_interleave(x_sf)
vllm/model_executor/layers/fused_moe/flashinfer_cutedsl_moe.py
+1 -1
@@ -30,7 +30,7 @@ from vllm.utils.flashinfer import (
30 logger = init_logger(__name__)
31
32
33 -class FlashInferCuteDSLExperts(mk.FusedMoEPermuteExpertsUnpermute):
33 +class FlashInferCuteDSLExperts(mk.FusedMoEExpertsModular):
34 def __init__(
35 self,
36 moe_config: FusedMoEConfig,
vllm/model_executor/layers/fused_moe/flashinfer_cutlass_moe.py
+1 -1
@@ -60,7 +60,7 @@ def is_valid_flashinfer_cutlass_fused_moe(
60 return True
61
62
63 -class FlashInferExperts(mk.FusedMoEPermuteExpertsUnpermute):
63 +class FlashInferExperts(mk.FusedMoEExpertsModular):
64 def __init__(
65 self,
66 moe_config: mk.FusedMoEConfig,
vllm/model_executor/layers/fused_moe/flashinfer_trtllm_moe.py
-298
@@ -10,16 +10,6 @@ from vllm.model_executor.layers.fused_moe.config import (
10 FusedMoEParallelConfig,
11 RoutingMethodType,
12 )
13 -from vllm.model_executor.layers.fused_moe.utils import moe_kernel_quantize_input
14 -from vllm.model_executor.layers.quantization.utils.fp8_utils import (
15 - per_token_group_quant_fp8,
16 -)
17 -from vllm.model_executor.layers.quantization.utils.quant_utils import (
18 - QuantKey,
19 - kFp8Dynamic128Sym,
20 - kFp8Static128BlockSym,
21 - kFp8StaticTensorSym,
22 -)
13 from vllm.platforms import current_platform
14 from vllm.utils.torch_utils import direct_register_custom_op
15
@@ -39,49 +29,10 @@ def _supports_no_act_and_mul() -> bool:
29 return True
30
31
42 -def _supports_quant_scheme(
43 - weight_key: QuantKey | None,
44 - activation_key: QuantKey | None,
45 -) -> bool:
46 - """Supports Fp8 per-tensor and Fp8 block."""
47 - SUPPORTED_W_A = [
48 - (kFp8Static128BlockSym, kFp8Dynamic128Sym),
49 - (kFp8StaticTensorSym, kFp8StaticTensorSym),
50 - ]
51 - return (weight_key, activation_key) in SUPPORTED_W_A
52 -
53 -
32 def _supports_activation(activation: MoEActivation) -> bool:
33 return activation in [MoEActivation.SILU, MoEActivation.RELU2_NO_MUL]
34
35
58 -def _supports_routing_method(
59 - weight_key: QuantKey | None,
60 - activation_key: QuantKey | None,
61 - routing_method: RoutingMethodType,
62 -) -> bool:
63 - """Monolithic kernels need to express router support."""
64 - # NOTE(dbari): TopK routing could also be enabled, but need to validate models
65 - # NOTE(dbari): Default is not implemented and should not be enabled until it is
66 - if (weight_key, activation_key) == (kFp8Static128BlockSym, kFp8Dynamic128Sym):
67 - # NOTE(rob): potentially allow others here. This is a conservative list.
68 - return routing_method in [
69 - RoutingMethodType.DeepSeekV3,
70 - RoutingMethodType.Renormalize,
71 - RoutingMethodType.RenormalizeNaive,
72 - ]
73 - elif (weight_key, activation_key) == (kFp8StaticTensorSym, kFp8StaticTensorSym):
74 - # NOTE(dbari): as above, potentially allow others here.
75 - return routing_method in [
76 - RoutingMethodType.DeepSeekV3,
77 - RoutingMethodType.Llama4,
78 - RoutingMethodType.Renormalize,
79 - RoutingMethodType.RenormalizeNaive,
80 - ]
81 - else:
82 - raise ValueError("Unsupported quantization scheme.")
83 -
84 -
36 def _supports_routing_method_bf16(
37 routing_method: RoutingMethodType,
38 ) -> bool:
@@ -99,62 +50,6 @@ def _supports_parallel_config(moe_parallel_config: FusedMoEParallelConfig) -> bo
50 return not moe_parallel_config.enable_eplb
51
52
102 -def _supports_router_logits_dtype(
103 - router_logits_dtype: torch.dtype | None,
104 - routing_method: RoutingMethodType,
105 -) -> bool:
106 - """
107 - The FlashInfer TRTLLM FP8 kernel expects bfloat16 router_logits by default.
108 - Only DeepSeekV3 routing supports float32 router_logits (which is converted
109 - internally in the kernel).
110 - """
111 - if router_logits_dtype == torch.float32:
112 - # Only DeepSeekV3 routing handles float32 logits
113 - # https://github.com/flashinfer-ai/flashinfer/issues/2469
114 - return routing_method == RoutingMethodType.DeepSeekV3
115 - return True
116 -
117 -
118 -def is_supported_config_trtllm_fp8(
119 - moe_config: FusedMoEConfig,
120 - weight_key: QuantKey | None,
121 - activation_key: QuantKey | None,
122 - activation_format: mk.FusedMoEActivationFormat,
123 -) -> tuple[bool, str | None]:
124 - """
125 - This method mirrors mk.FusedMoEPermuteExpertsUnpermute.is_supported_config
126 - """
127 -
128 - def _make_reason(reason: str) -> str:
129 - return f"kernel does not support {reason}"
130 -
131 - if not _supports_current_device():
132 - return False, _make_reason(f"current device {current_platform.device_name}")
133 - elif not (moe_config.is_act_and_mul or _supports_no_act_and_mul()):
134 - return False, _make_reason("no act_and_mul MLP layer")
135 - elif not _supports_activation(moe_config.activation):
136 - return False, _make_reason(f"{moe_config.activation} activation")
137 - elif not _supports_quant_scheme(weight_key, activation_key):
138 - return False, _make_reason(f"quantization scheme {weight_key}x{activation_key}")
139 - elif not _supports_parallel_config(moe_config.moe_parallel_config):
140 - return False, _make_reason(f"parallel config {moe_config.moe_parallel_config}")
141 - elif not _supports_routing_method(
142 - weight_key, activation_key, moe_config.routing_method
143 - ):
144 - return False, _make_reason(f"routing method {moe_config.routing_method}")
145 - elif activation_format != mk.FusedMoEActivationFormat.Standard:
146 - return False, _make_reason(f"activation format {activation_format}")
147 - elif not _supports_router_logits_dtype(
148 - moe_config.router_logits_dtype, moe_config.routing_method
149 - ):
150 - return False, _make_reason(
151 - "float32 router_logits with non-DeepSeekV3 routing "
152 - f"{moe_config.router_logits_dtype}x{moe_config.routing_method}"
153 - )
154 -
155 - return True, None
156 -
157 -
53 def is_supported_config_trtllm_bf16(
54 moe_config: FusedMoEConfig,
55 activation_format: mk.FusedMoEActivationFormat,
@@ -183,199 +78,6 @@ def is_supported_config_trtllm_bf16(
78 return True, None
79
80
186 -def flashinfer_fused_moe_blockscale_fp8(
187 - routing_logits: torch.Tensor,
188 - routing_bias: torch.Tensor | None,
189 - x: torch.Tensor,
190 - w13_weight: torch.Tensor,
191 - w13_weight_scale_inv: torch.Tensor,
192 - w2_weight: torch.Tensor,
193 - w2_weight_scale_inv: torch.Tensor,
194 - global_num_experts: int,
195 - top_k: int,
196 - num_expert_group: int | None,
197 - topk_group: int | None,
198 - intermediate_size: int,
199 - expert_offset: int,
200 - local_num_experts: int,
201 - block_shape: list[int],
202 - routing_method_type: int,
203 - routed_scaling: float | None = 1.0,
204 -) -> torch.Tensor:
205 - from vllm.utils.flashinfer import flashinfer_trtllm_fp8_block_scale_moe
206 -
207 - num_expert_group = num_expert_group if num_expert_group is not None else 0
208 - topk_group = topk_group if topk_group is not None else 0
209 - assert top_k <= global_num_experts
210 - assert top_k <= 10
211 - assert global_num_experts % 4 == 0
212 - assert block_shape == [128, 128]
213 - # Routing kernel expects #experts <= #threads 512
214 - assert global_num_experts <= 512
215 -
216 - # The DeepSeekV3 routing method requires float32 router logits.
217 - if routing_method_type == RoutingMethodType.DeepSeekV3:
218 - routing_logits = routing_logits.to(torch.float32)
219 -
220 - if routing_bias is not None:
221 - routing_bias = routing_bias.to(x.dtype)
222 -
223 - a_q, a_sf = per_token_group_quant_fp8(x, block_shape[1])
224 - # NOTE: scales of hidden states have to be transposed!
225 - a_sf_t = a_sf.t().contiguous()
226 - return flashinfer_trtllm_fp8_block_scale_moe(
227 - routing_logits=routing_logits,
228 - routing_bias=routing_bias,
229 - hidden_states=a_q,
230 - hidden_states_scale=a_sf_t,
231 - gemm1_weights=w13_weight,
232 - gemm1_weights_scale=w13_weight_scale_inv,
233 - gemm2_weights=w2_weight,
234 - gemm2_weights_scale=w2_weight_scale_inv,
235 - num_experts=global_num_experts,
236 - top_k=top_k,
237 - n_group=num_expert_group,
238 - topk_group=topk_group,
239 - intermediate_size=intermediate_size,
240 - local_expert_offset=expert_offset,
241 - local_num_experts=local_num_experts,
242 - routed_scaling_factor=routed_scaling,
243 - routing_method_type=routing_method_type,
244 - use_shuffled_weight=False,
245 - )
246 -
247 -
248 -def flashinfer_fused_moe_blockscale_fp8_fake(
249 - routing_logits: torch.Tensor,
250 - routing_bias: torch.Tensor | None,
251 - x: torch.Tensor,
252 - w13_weight: torch.Tensor,
253 - w13_weight_scale_inv: torch.Tensor,
254 - w2_weight: torch.Tensor,
255 - w2_weight_scale_inv: torch.Tensor,
256 - global_num_experts: int,
257 - top_k: int,
258 - num_expert_group: int,
259 - topk_group: int,
260 - intermediate_size: int,
261 - expert_offset: int,
262 - local_num_experts: int,
263 - block_shape: list[int],
264 - routing_method_type: int,
265 - routed_scaling: float = 1.0,
266 -) -> torch.Tensor:
267 - return torch.empty_like(x)
268 -
269 -
270 -# TODO(bnell): Does this really need to be a torch.op?
271 -direct_register_custom_op(
272 - op_name="flashinfer_fused_moe_blockscale_fp8",
273 - op_func=flashinfer_fused_moe_blockscale_fp8,
274 - fake_impl=flashinfer_fused_moe_blockscale_fp8_fake,
275 - tags=(torch.Tag.needs_fixed_stride_order,),
276 -)
277 -
278 -
279 -def fi_trtllm_fp8_per_tensor_moe(
280 - routing_logits: torch.Tensor,
281 - routing_bias: torch.Tensor | None,
282 - hidden_states: torch.Tensor,
283 - input_scale: torch.Tensor,
284 - gemm1_weights: torch.Tensor,
285 - gemm2_weights: torch.Tensor,
286 - output1_scales_scalar: torch.Tensor,
287 - output1_scales_gate_scalar: torch.Tensor,
288 - output2_scales_scalar: torch.Tensor,
289 - num_experts: int,
290 - top_k: int,
291 - num_expert_group: int | None,
292 - topk_group: int | None,
293 - intermediate_size: int,
294 - local_expert_offset: int,
295 - local_num_experts: int,
296 - use_routing_scales_on_input: bool,
297 - routing_method_type: int,
298 - activation_type: int,
299 - routed_scaling_factor: float = 1.0,
300 -) -> torch.Tensor:
301 - num_expert_group = num_expert_group if num_expert_group is not None else 0
302 - topk_group = topk_group if topk_group is not None else 0
303 -
304 - quant_hidden_states, _ = moe_kernel_quantize_input(
305 - hidden_states,
306 - input_scale,
307 - quant_dtype=torch.float8_e4m3fn,
308 - per_act_token_quant=False,
309 - )
310 -
311 - from flashinfer.fused_moe.core import ActivationType
312 -
313 - from vllm.utils.flashinfer import flashinfer_trtllm_fp8_per_tensor_scale_moe
314 -
315 - # The DeepSeekV3 routing method requires float32 router logits.
316 - if routing_method_type == RoutingMethodType.DeepSeekV3:
317 - routing_logits = routing_logits.to(torch.float32)
318 -
319 - return flashinfer_trtllm_fp8_per_tensor_scale_moe(
320 - routing_logits=routing_logits,
321 - routing_bias=routing_bias,
322 - hidden_states=quant_hidden_states,
323 - gemm1_weights=gemm1_weights,
324 - output1_scales_scalar=output1_scales_scalar,
325 - output1_scales_gate_scalar=output1_scales_gate_scalar,
326 - gemm2_weights=gemm2_weights,
327 - output2_scales_scalar=output2_scales_scalar,
328 - num_experts=num_experts,
329 - top_k=top_k,
330 - n_group=num_expert_group,
331 - topk_group=topk_group,
332 - intermediate_size=intermediate_size,
333 - local_expert_offset=local_expert_offset,
334 - local_num_experts=local_num_experts,
335 - routed_scaling_factor=routed_scaling_factor,
336 - use_routing_scales_on_input=use_routing_scales_on_input,
337 - routing_method_type=routing_method_type,
338 - # TODO: enum type Required for flashinfer==0.6.3, remove with update
339 - # https://github.com/flashinfer-ai/flashinfer/pull/2508
340 - activation_type=ActivationType(activation_type),
341 - )
342 -
343 -
344 -def fi_trtllm_fp8_per_tensor_moe_fake(
345 - routing_logits: torch.Tensor,
346 - routing_bias: torch.Tensor | None,
347 - hidden_states: torch.Tensor,
348 - input_scale: torch.Tensor,
349 - gemm1_weights: torch.Tensor,
350 - gemm2_weights: torch.Tensor,
351 - output1_scales_scalar: torch.Tensor,
352 - output1_scales_gate_scalar: torch.Tensor,
353 - output2_scales_scalar: torch.Tensor,
354 - num_experts: int,
355 - top_k: int,
356 - num_expert_group: int | None,
357 - topk_group: int | None,
358 - intermediate_size: int,
359 - local_expert_offset: int,
360 - local_num_experts: int,
361 - use_routing_scales_on_input: bool,
362 - routing_method_type: int,
363 - activation_type: int,
364 - routed_scaling_factor: float = 1.0,
365 -) -> torch.Tensor:
366 - return torch.empty_like(hidden_states)
367 -
368 -
369 -# TODO(bnell): Does this really need to be a torch.op?
370 -direct_register_custom_op(
371 - op_name="fi_trtllm_fp8_per_tensor_moe",
372 - op_func=fi_trtllm_fp8_per_tensor_moe,
373 - mutates_args=["hidden_states"],
374 - fake_impl=fi_trtllm_fp8_per_tensor_moe_fake,
375 - tags=(torch.Tag.needs_fixed_stride_order,),
376 -)
377 -
378 -
81 def flashinfer_fused_moe_bf16(
82 routing_logits: torch.Tensor,
83 routing_bias: torch.Tensor | None,
vllm/model_executor/layers/fused_moe/fused_batched_moe.py
+3 -3
@@ -489,7 +489,7 @@ def invoke_moe_batched_triton_kernel(
489 )
490
491
492 -class BatchedPrepareAndFinalize(mk.FusedMoEPrepareAndFinalize):
492 +class BatchedPrepareAndFinalize(mk.FusedMoEPrepareAndFinalizeModular):
493 """
494 A reference prepare/finalize class that reorganizes the tokens into
495 expert batched format, i.e. E x max_num_tokens x K. This is the format
@@ -645,7 +645,7 @@ class BatchedPrepareAndFinalize(mk.FusedMoEPrepareAndFinalize):
645 )
646
647
648 -class NaiveBatchedExperts(mk.FusedMoEPermuteExpertsUnpermute):
648 +class NaiveBatchedExperts(mk.FusedMoEExpertsModular):
649 """
650 A reference MoE expert class that operates on expert batched format,
651 i.e. E x max_num_tokens x K. This is the format that the batched
@@ -877,7 +877,7 @@ def batched_moe_kernel_quantize_input(
877 return A_q, A_q_scale
878
879
880 -class BatchedTritonExperts(mk.FusedMoEPermuteExpertsUnpermute):
880 +class BatchedTritonExperts(mk.FusedMoEExpertsModular):
881 """
882 A Triton based MoE expert class that operates on expert batched format,
883 i.e. E x max_num_tokens x K. This is the format that the batched
vllm/model_executor/layers/fused_moe/fused_marlin_moe.py
+1 -1
@@ -526,7 +526,7 @@ def batched_fused_marlin_moe(
526 return output
527
528
529 -class MarlinExpertsBase(mk.FusedMoEPermuteExpertsUnpermute):
529 +class MarlinExpertsBase(mk.FusedMoEExpertsModular):
530 def __init__(
531 self,
532 moe_config: FusedMoEConfig,
vllm/model_executor/layers/fused_moe/fused_moe.py
+2 -2
@@ -1736,7 +1736,7 @@ def fused_experts_impl(
1736 intermediate_cache3 = cache13[: M * top_k_num * K].view(M, top_k_num, K)
1737
1738 # This needs separate memory since it's used concurrently with cache1
1739 - activation_out_dim = mk.FusedMoEPermuteExpertsUnpermute.adjust_N_for_activation(
1739 + activation_out_dim = mk.FusedMoEExpertsModular.adjust_N_for_activation(
1740 N, activation_enum
1741 )
1742 intermediate_cache2 = torch.empty(
@@ -1924,7 +1924,7 @@ def fused_experts_impl(
1924 return out_hidden_states
1925
1926
1927 -class TritonExperts(mk.FusedMoEPermuteExpertsUnpermute):
1927 +class TritonExperts(mk.FusedMoEExpertsModular):
1928 """Triton-based fused MoE expert implementation."""
1929
1930 def __init__(
vllm/model_executor/layers/fused_moe/fused_moe_method_base.py
+24 -27
@@ -12,8 +12,8 @@ from vllm.model_executor.layers.fused_moe.config import (
12 FusedMoEQuantConfig,
13 )
14 from vllm.model_executor.layers.fused_moe.modular_kernel import (
15 - FusedMoEPermuteExpertsUnpermute,
16 - FusedMoEPrepareAndFinalize,
15 + FusedMoEExpertsModular,
16 + FusedMoEPrepareAndFinalizeModular,
17 )
18 from vllm.model_executor.layers.quantization.base_config import (
19 QuantizeMethodBase,
@@ -27,19 +27,21 @@ class FusedMoEMethodBase(QuantizeMethodBase):
27 super().__init__()
28 self.moe: FusedMoEConfig = moe
29 self.moe_quant_config: FusedMoEQuantConfig | None = None
30 - self.moe_mk: mk.FusedMoEModularKernel | None = None
30 + self.moe_kernel: mk.FusedMoEKernel | None = None
31
32 @property
33 def supports_internal_mk(self) -> bool:
34 # NOTE(rob): temporary attribute to indicate support for
35 # completed migration to the new internal MK interface.
36 - return self.moe_mk is not None
36 + return self.moe_kernel is not None
37
38 @property
39 def mk_owns_shared_expert(self) -> bool:
40 # NOTE(rob): temporary attribute to indicate support for
41 # completed migration to the new internal MK interface.
42 - return self.moe_mk is not None and self.moe_mk.shared_experts is not None
42 + return (
43 + self.moe_kernel is not None and self.moe_kernel.shared_experts is not None
44 + )
45
46 @abstractmethod
47 def create_weights(
@@ -66,35 +68,25 @@ class FusedMoEMethodBase(QuantizeMethodBase):
68 def maybe_make_prepare_finalize(
69 self,
70 routing_tables: tuple[torch.Tensor, torch.Tensor, torch.Tensor] | None = None,
69 - ) -> FusedMoEPrepareAndFinalize | None:
71 + ) -> FusedMoEPrepareAndFinalizeModular | None:
72 from .all2all_utils import maybe_make_prepare_finalize
73
72 - return maybe_make_prepare_finalize(
74 + pf = maybe_make_prepare_finalize(
75 self.moe, self.moe_quant_config, routing_tables
76 )
77 + assert pf is None or isinstance(pf, FusedMoEPrepareAndFinalizeModular)
78 + return pf
79
80 def select_gemm_impl(
81 self,
78 - prepare_finalize: FusedMoEPrepareAndFinalize,
82 + prepare_finalize: FusedMoEPrepareAndFinalizeModular,
83 layer: torch.nn.Module,
80 - ) -> FusedMoEPermuteExpertsUnpermute:
84 + ) -> FusedMoEExpertsModular:
85 # based on the all2all implementation, select the appropriate
86 # gemm implementation
83 - raise NotImplementedError(
84 - f"{self.__class__.__name__} must select appropriate gemm "
85 - "implementation based on the prepare_finalize"
86 - )
87 -
88 - def prepare_dp_allgather_tensor(
89 - self,
90 - layer: "FusedMoE", # type: ignore[name-defined] # noqa: F821
91 - hidden_states: torch.Tensor,
92 - router_logits: torch.Tensor,
93 - ) -> tuple[torch.Tensor, list[torch.Tensor]]:
94 - """Hook to prepare tensors and extra tensors for DP allgather + EP dispatch."""
95 - raise NotImplementedError(
96 - "Method 'prepare_dp_allgather_tensor' is not implemented in "
97 - f"{self.__class__.__name__}."
87 + raise ValueError(
88 + f"{self.__class__.__name__} uses the new modular kernel initialization "
89 + "logic. This function should not be called."
90 )
91
92 @abstractmethod
@@ -105,8 +97,8 @@ class FusedMoEMethodBase(QuantizeMethodBase):
97
98 @property
99 def topk_indices_dtype(self) -> torch.dtype | None:
108 - if self.moe_mk is not None:
109 - return self.moe_mk.prepare_finalize.topk_indices_dtype()
100 + if self.moe_kernel is not None:
101 + return self.moe_kernel.prepare_finalize.topk_indices_dtype()
102 return None
103
104 @property
@@ -119,7 +111,12 @@ class FusedMoEMethodBase(QuantizeMethodBase):
111
112 @property
113 def is_monolithic(self) -> bool:
122 - return False
114 + if self.moe_kernel is None:
115 + if hasattr(self, "experts_cls"):
116 + return self.experts_cls.is_monolithic()
117 + else:
118 + return False
119 + return self.moe_kernel.is_monolithic
120
121 def apply(
122 self,
vllm/model_executor/layers/fused_moe/fused_moe_modular_method.py
+9 -9
@@ -13,8 +13,8 @@ from vllm.model_executor.layers.fused_moe.fused_moe_method_base import (
13 FusedMoEMethodBase,
14 )
15 from vllm.model_executor.layers.fused_moe.modular_kernel import (
16 - FusedMoEModularKernel,
17 - FusedMoEPrepareAndFinalize,
16 + FusedMoEKernel,
17 + FusedMoEPrepareAndFinalizeModular,
18 )
19
20 logger = init_logger(__name__)
@@ -26,15 +26,15 @@ class FusedMoEModularMethod(FusedMoEMethodBase, CustomOp):
26 # --8<-- [end:modular_fused_moe]
27
28 def __init__(
29 - self, old_quant_method: FusedMoEMethodBase, experts: FusedMoEModularKernel
29 + self, old_quant_method: FusedMoEMethodBase, moe_kernel: FusedMoEKernel
30 ):
31 super().__init__(old_quant_method.moe)
32 self.moe_quant_config = old_quant_method.moe_quant_config
33 - self.moe_mk = experts
33 + self.moe_kernel = moe_kernel
34 self.disable_expert_map = getattr(
35 old_quant_method,
36 "disable_expert_map",
37 - not self.moe_mk.supports_expert_map(),
37 + not self.moe_kernel.supports_expert_map(),
38 )
39 self.old_quant_method = old_quant_method
40 logger.debug("Swapping out %s", self.old_quant_method.__class__.__name__)
@@ -43,13 +43,13 @@ class FusedMoEModularMethod(FusedMoEMethodBase, CustomOp):
43 def make(
44 moe_layer: torch.nn.Module,
45 old_quant_method: FusedMoEMethodBase,
46 - prepare_finalize: FusedMoEPrepareAndFinalize,
46 + prepare_finalize: FusedMoEPrepareAndFinalizeModular,
47 shared_experts: torch.nn.Module | None,
48 inplace: bool = False,
49 ) -> "FusedMoEModularMethod":
50 return FusedMoEModularMethod(
51 old_quant_method,
52 - FusedMoEModularKernel(
52 + FusedMoEKernel(
53 prepare_finalize,
54 old_quant_method.select_gemm_impl(prepare_finalize, moe_layer),
55 shared_experts,
@@ -90,8 +90,8 @@ class FusedMoEModularMethod(FusedMoEMethodBase, CustomOp):
90 topk_ids: torch.Tensor,
91 shared_experts_input: torch.Tensor | None,
92 ) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]:
93 - assert self.moe_mk is not None
94 - return self.moe_mk(
93 + assert self.moe_kernel is not None
94 + return self.moe_kernel.apply(
95 hidden_states=x,
96 w1=layer.w13_weight,
97 w2=layer.w2_weight,
vllm/model_executor/layers/fused_moe/gpt_oss_triton_kernels_moe.py
+1 -1
@@ -511,7 +511,7 @@ def make_routing_data(
511 return routing_data, gather_indx, scatter_indx
512
513
514 -class BaseOAITritonExperts(mk.FusedMoEPermuteExpertsUnpermute):
514 +class BaseOAITritonExperts(mk.FusedMoEExpertsModular):
515 @staticmethod
516 def _supports_current_device() -> bool:
517 raise NotImplementedError(
vllm/model_executor/layers/fused_moe/modular_kernel.py
+518 -156
@@ -20,6 +20,7 @@ from vllm.model_executor.layers.fused_moe.config import (
20 FusedMoEConfig,
21 FusedMoEParallelConfig,
22 FusedMoEQuantConfig,
23 + RoutingMethodType,
24 )
25 from vllm.model_executor.layers.fused_moe.utils import (
26 _resize_cache,
@@ -56,25 +57,25 @@ logger = init_logger(__name__)
57 # MoE kernel implementations.
58 #
59 # The following main classes are defined:
59 -# * FusedMoEPrepareAndFinalize - an abstract base class for preparation of MoE
60 +# * FusedMoEPrepareAndFinalizeModular - an abstract base class for preparation of MoE
61 # inputs (e.g. quantization, distribution) and finalization of Moe outputs.
62 # The prepare method must take care of any needed quantization and the
62 -# finalize method, informed by the FusedMoEPermuteExpertsUnpermute method,
63 +# finalize method, informed by the FusedMoEExpertsModular method,
64 # may apply weights and/or do the final reduction of the output.
64 -# * FusedMoEPermuteExpertsUnpermute - an abstract base class for the main fused
65 +# * FusedMoEExpertsModular - an abstract base class for the main fused
66 # MoE operation, i.e matmul + act_mul + optionally quant + matmul.
66 -# Some FusedMoEPermuteExpertsUnpermute implementations may choose to do
67 +# Some FusedMoEExpertsModular implementations may choose to do
68 # the weight application and/or reduction. The class communicates this
69 # to [Finalize] via a TopKWeightAndReduce object.
70 # * FusedMoEModularKernel - an interface class that combines a
70 -# FusedMoEPrepareAndFinalize and a FusedMoEPermuteExpertsUnpermute to
71 +# FusedMoEPrepareAndFinalizeModular and a FusedMoEExpertsModular to
72 # provide the standard fused MoE kernel interface.
73 # * TopKWeightAndReduce - A TopKWeightAndReduce implementation chosen
73 -# by the FusedMoEPermuteExpertsUnpermute implementation that is passed
74 +# by the FusedMoEExpertsModular implementation that is passed
75 # on to [Finalize].
76 #
77 # [Quantize-Prepare] and [Finalize] functionality are bundled into a single
77 -# class `FusedMoEPrepareAndFinalize` since they could use collective
78 +# class `FusedMoEPrepareAndFinalizeModular` since they could use collective
79 # communication mechanisms that need to be consistent.
80 #
81
@@ -155,25 +156,96 @@ PrepareResultType = tuple[
156 torch.Tensor | None,
157 ]
158
159 +#
160 +# PrepareResultType is a tuple of:
161 +# - quantized + dispatched a.
162 +# - quantized + dispatched a1_scales.
163 +# - dispatched router logits.
164 +#
165 +# See `prepare_monolithic` method below.
166 +#
167 +PrepareMonolithicResultType = tuple[
168 + torch.Tensor,
169 + torch.Tensor | None,
170 + torch.Tensor,
171 +]
172 +
173 ReceiverType = Callable[[], PrepareResultType]
174
175 +################################################################################
176 +# Prepare/Finalize
177 +################################################################################
178 +
179
161 -# TODO: pass FusedMoEParallelConfig in as ctor parameter?
180 class FusedMoEPrepareAndFinalize(ABC):
181 """
182 An abstract base class for the [Quantize-Prepare] and [Finalize] steps
183 described above.
184 +
185 + There are two variants of this class:
186 + * FusedMoEPrepareAndFinalizeModular - this operates on topk ids and weights
187 + * FusedMoEPrepareAndFinalizeMonolithic - the operates on router_logits
188 """
189
168 - def post_init_setup(self, fused_experts: "FusedMoEPermuteExpertsUnpermute"):
190 + def post_init_setup(self, fused_experts: "FusedMoEExperts"):
191 """
170 - Initialize FusedMoEPrepareAndFinalize settings that depend on
171 - FusedMoEPermuteExpertsUnpermute experts object.
172 - The FusedMoEPrepareAndFinalize implementations that have such
192 + Initialize FusedMoEPrepareAndFinalizeModular settings that depend on
193 + FusedMoEExpertsModular experts object.
194 + The FusedMoEPrepareAndFinalizeModular implementations that have such
195 dependencies may choose to override this function.
196 """
197 return
198
199 + @property
200 + @abstractmethod
201 + def activation_format(self) -> FusedMoEActivationFormat:
202 + """
203 + A property indicating the output format of the activations for the
204 + 'prepare' method.
205 + """
206 + raise NotImplementedError
207 +
208 + @abstractmethod
209 + def topk_indices_dtype(self) -> torch.dtype | None:
210 + """
211 + The PrepareFinalize All2All implementations generally constrain the
212 + dtype of the topk_ids they support. This function returns the
213 + required topk indices dtype so it can be respected.
214 + Return None if there are no such restrictions.
215 + """
216 + raise NotImplementedError
217 +
218 + @abstractmethod
219 + def max_num_tokens_per_rank(self) -> int | None:
220 + """
221 + Some PrepareFinalize All2All implementations are batched. Meaning,
222 + they can process only as set of tokens at a time. This
223 + function returns the batch size i.e the maximum number of tokens
224 + the implementation can process at a time.
225 + Return None if there are no such restrictions.
226 + """
227 + raise NotImplementedError
228 +
229 + @abstractmethod
230 + def num_dispatchers(self) -> int:
231 + raise NotImplementedError
232 +
233 + @abstractmethod
234 + def output_is_reduced(self) -> bool:
235 + """
236 + Indicates whether or not the output of finalize is reduced across all
237 + ranks.
238 + """
239 + raise NotImplementedError
240 +
241 +
242 +# TODO: pass FusedMoEParallelConfig in as ctor parameter?
243 +class FusedMoEPrepareAndFinalizeModular(FusedMoEPrepareAndFinalize):
244 + """
245 + An abstract base class for the [Quantize-Prepare] and [Finalize] steps
246 + described above for the Modular case.
247 + """
248 +
249 @abstractmethod
250 def prepare(
251 self,
@@ -198,7 +270,7 @@ class FusedMoEPrepareAndFinalize(ABC):
270 activations, before quantization + dispatching.
271 - quant_config: Quantization info provided by the fused experts.
272 - defer_input_quant: Runtime parameter indicating whether or not to
201 - defer input quantization to the FusedMoEPermuteExpertsUnpermute
273 + defer input quantization to the FusedMoEExpertsModular
274 in cases where the compute kernel expects unquantized inputs
275
276 Returns a tuple of:
@@ -245,7 +317,7 @@ class FusedMoEPrepareAndFinalize(ABC):
317 - apply_router_weight_on_input: When True, apply the weights to the
318 activations, before quantization + dispatching.
319 - defer_input_quant: Runtime parameter indicating whether or not to
248 - defer input quantization to the FusedMoEPermuteExpertsUnpermute
320 + defer input quantization to the FusedMoEExpertsModular
321 in cases where the compute kernel expects unquantized inputs
322
323 Returns a callback or a hook callback pair that when invoked waits for
@@ -338,56 +410,58 @@ class FusedMoEPrepareAndFinalize(ABC):
410 """
411 raise NotImplementedError
412
341 - @property
342 - @abstractmethod
343 - def activation_format(self) -> FusedMoEActivationFormat:
344 - """
345 - A property indicating the output format of the activations for the
346 - 'prepare' method.
347 - """
348 - raise NotImplementedError
413 +
414 +class FusedMoEPrepareAndFinalizeMonolithic(FusedMoEPrepareAndFinalize):
415 + """
416 + An abstract base class for the [Quantize-Prepare] and [Finalize] steps
417 + described above for the monolithic case.
418 + """
419
420 @abstractmethod
351 - def topk_indices_dtype(self) -> torch.dtype | None:
421 + def prepare(
422 + self,
423 + a1: torch.Tensor,
424 + router_logits: torch.Tensor,
425 + quant_config: FusedMoEQuantConfig,
426 + defer_input_quant: bool = False,
427 + ) -> PrepareMonolithicResultType:
428 """
353 - The PrepareFinalize All2All implementations generally constrain the
354 - dtype of the topk_ids they support. This function returns the
355 - required topk indices dtype so it can be respected.
356 - Return None if there are no such restrictions.
429 + Optional method for subclasses compatible with monolithic
430 + FusedMoEExpertsModular kernels.
431 +
432 + Perform any quantization (and/or) dispatching needed for this kernel.
433 + - a1: The (unquantized) input to the MoE layer.
434 + - quant_config: Quantization info provided by the fused experts.
435 + - defer_input_quant: Runtime parameter indicating whether or not to
436 + defer input quantization to the FusedMoEExpertsModular
437 +
438 + Returns a tuple of:
439 + - quantized + dispatched a.
440 + - Optional quantized + dispatched a1_scales.
441 """
442 raise NotImplementedError
443
444 @abstractmethod
361 - def max_num_tokens_per_rank(self) -> int | None:
445 + def finalize(self, fused_expert_output: torch.Tensor) -> torch.Tensor:
446 """
363 - Some PrepareFinalize All2All implementations are batched. Meaning,
364 - they can process only as set of tokens at a time. This
365 - function returns the batch size i.e the maximum number of tokens
366 - the implementation can process at a time.
367 - Return None if there are no such restrictions.
447 + Optional method for subclasses compatible with monolithic
448 + FusedMoEExpertsModular kernels.
449 +
450 + Perform any combine plus apply weights and perform a reduction on the
451 + fused experts output.
452 + - fused_expert_output: The unweighted, unreduced output of the fused
453 + experts, it will have (M, topk, K) shape.
454 """
455 raise NotImplementedError
456
371 - @abstractmethod
372 - def num_dispatchers(self) -> int:
373 - raise NotImplementedError
457
375 - @abstractmethod
376 - def output_is_reduced(self) -> bool:
377 - """
378 - Indicates whether or not the output of finalize is reduced across all
379 - ranks.
380 - """
381 - raise NotImplementedError
458 +################################################################################
459 +# Experts
460 +################################################################################
461
462
463 # TODO: add supported activations method (return string)
385 -class FusedMoEPermuteExpertsUnpermute(ABC):
386 - """
387 - An abstract base class for the [Permute-Experts-Unpermute] step described
388 - above.
389 - """
390 -
464 +class FusedMoEExperts(ABC):
465 def __init__(
466 self,
467 moe_config: FusedMoEConfig,
@@ -419,6 +493,10 @@ class FusedMoEPermuteExpertsUnpermute(ABC):
493 self.max_num_tokens = max_num_tokens
494 self.num_dispatchers = num_dispatchers
495
496 + @staticmethod
497 + def is_monolithic() -> bool:
498 + raise NotImplementedError("Implemented by subclasses.")
499 +
500 @property
501 def expects_unquantized_inputs(self) -> bool:
502 """
@@ -439,49 +517,6 @@ class FusedMoEPermuteExpertsUnpermute(ABC):
517 """
518 raise NotImplementedError
519
442 - def moe_problem_size(
443 - self,
444 - a1: torch.Tensor,
445 - w1: torch.Tensor,
446 - w2: torch.Tensor,
447 - topk_ids: torch.Tensor,
448 - ) -> tuple[int, int, int, int, int]:
449 - """
450 - Extract the MoE problem size from the given tensor arguments:
451 - - a: The hidden states, input to the MoE layer.
452 - - w1: The first set of expert weights.
453 - - w2: The second set of expert weights.
454 - - topk_ids: The topk ids.
455 -
456 - Note: extracting the problem shape from the weight and activation
457 - tensors is not obvious. It needs to be done this way specifically
458 - due to subtle issues with particular kernels, e.g. the int4 kernels
459 - divide the trailing dimension by two, so it's not "correct" to
460 - extract N or K from the trailing dimension of w1 or w2. Similarly,
461 - some kernels transpose the weights, so this needs to be kept in mind.
462 -
463 - Note: This implementation covers most cases. However, if experts
464 - require a specialized implementation, like MarlinExperts, they are free
465 - to override this function.
466 - """
467 - assert w1.dim() == 3 and w2.dim() == 3
468 - E, N, _ = w1.size()
469 - K = a1.size(-1)
470 -
471 - if a1.dim() == 2:
472 - # Make sure we are using the correct a1 (pre-permute).
473 - assert topk_ids.size(0) == a1.size(0), f"{topk_ids.size(0)} != {a1.size(0)}"
474 - M = a1.size(0)
475 - else:
476 - assert a1.dim() == 3
477 - assert a1.size(0) == E, f"{a1.size(0)} == {E}"
478 - M = a1.size(1) # This is max_num_tokens
479 -
480 - assert topk_ids.dim() == 2
481 - topk = topk_ids.size(1)
482 -
483 - return E, M, N, K, topk
484 -
520 #
521 # Various helpers for registering support for various features.
522 # Used by the oracle to select a particular kernel for a deployment.
@@ -489,7 +524,7 @@ class FusedMoEPermuteExpertsUnpermute(ABC):
524
525 @staticmethod
526 def is_supported_config(
492 - cls: type["FusedMoEPermuteExpertsUnpermute"],
527 + cls: type["FusedMoEExperts"],
528 moe_config: FusedMoEConfig,
529 weight_key: QuantKey | None,
530 activation_key: QuantKey | None,
@@ -512,6 +547,21 @@ class FusedMoEPermuteExpertsUnpermute(ABC):
547 return False, _make_reason(
548 f"parallel config {moe_config.moe_parallel_config}"
549 )
550 + elif not cls._supports_routing_method(
551 + moe_config.routing_method, weight_key, activation_key
552 + ):
553 + return False, _make_reason(f"routing method {moe_config.routing_method}")
554 + elif not cls._supports_router_logits_dtype(
555 + moe_config.router_logits_dtype,
556 + moe_config.routing_method,
557 + ):
558 + return False, _make_reason(
559 + f"router logits dtype {moe_config.router_logits_dtype}"
560 + )
561 + elif not cls._supports_shape(moe_config.hidden_dim):
562 + return False, _make_reason(
563 + f"{moe_config.hidden_dim} hidden dim is not supported"
564 + )
565 elif activation_format != cls.activation_format():
566 return False, _make_reason(f"{activation_format.value} activation format")
567 return True, None
@@ -554,10 +604,48 @@ class FusedMoEPermuteExpertsUnpermute(ABC):
604 @abstractmethod
605 def _supports_parallel_config(moe_parallel_config: FusedMoEParallelConfig) -> bool:
606 """
557 - Whether the kernel supports deployment in expert parallel.
607 + Whether the kernel supports deployment in particular parallel config.
608 +
609 + Can be overriden if a kernel does not support EP, SP or some other
610 + configuration.
611 """
612 raise NotImplementedError
613
614 + @staticmethod
615 + def _supports_routing_method(
616 + routing_method: RoutingMethodType,
617 + weight_key: QuantKey | None,
618 + activation_key: QuantKey | None,
619 + ) -> bool:
620 + """
621 + Whether the kernel supports a routing method (e.g. GroupedTopK).
622 +
623 + Can be overriden by monolithic kernels that execute the router
624 + in addition to the experts if certain routers are not supported.
625 + """
626 + return True
627 +
628 + @staticmethod
629 + def _supports_router_logits_dtype(
630 + router_logits_dtype: torch.dtype | None,
631 + routing_method: RoutingMethodType,
632 + ) -> bool:
633 + """
634 + Whether a kernel supports a particular dtype for router logits input.
635 +
636 + Can be overriden by monolithic kernels that execute the router
637 + in addition to the experts if certain dtypes are not supported.
638 + """
639 + return True
640 +
641 + @staticmethod
642 + def _supports_shape(hidden_dim: int) -> bool:
643 + """
644 + Whether a kernel supports a particular shape. Can be overridden if a kernel
645 + has specific shape requirements.
646 + """
647 + return True
648 +
649 #
650 # Various helpers for accessing quantization parameters from the
651 # quant_config.
@@ -654,6 +742,65 @@ class FusedMoEPermuteExpertsUnpermute(ABC):
742 """
743 return False
744
745 + def enable_chunking(self):
746 + return (
747 + envs.VLLM_ENABLE_FUSED_MOE_ACTIVATION_CHUNKING and self.supports_chunking()
748 + )
749 +
750 +
751 +class FusedMoEExpertsModular(FusedMoEExperts):
752 + """
753 + An abstract base class for the [Permute-Experts-Unpermute] step described
754 + above.
755 + """
756 +
757 + @staticmethod
758 + def is_monolithic() -> bool:
759 + return False
760 +
761 + def moe_problem_size(
762 + self,
763 + a1: torch.Tensor,
764 + w1: torch.Tensor,
765 + w2: torch.Tensor,
766 + topk_ids: torch.Tensor,
767 + ) -> tuple[int, int, int, int, int]:
768 + """
769 + Extract the MoE problem size from the given tensor arguments:
770 + - a: The hidden states, input to the MoE layer.
771 + - w1: The first set of expert weights.
772 + - w2: The second set of expert weights.
773 + - topk_ids: The topk ids.
774 +
775 + Note: extracting the problem shape from the weight and activation
776 + tensors is not obvious. It needs to be done this way specifically
777 + due to subtle issues with particular kernels, e.g. the int4 kernels
778 + divide the trailing dimension by two, so it's not "correct" to
779 + extract N or K from the trailing dimension of w1 or w2. Similarly,
780 + some kernels transpose the weights, so this needs to be kept in mind.
781 +
782 + Note: This implementation covers most cases. However, if experts
783 + require a specialized implementation, like MarlinExperts, they are free
784 + to override this function.
785 + """
786 + assert w1.dim() == 3 and w2.dim() == 3
787 + E, N, _ = w1.size()
788 + K = a1.size(-1)
789 +
790 + if a1.dim() == 2:
791 + # Make sure we are using the correct a1 (pre-permute).
792 + assert topk_ids.size(0) == a1.size(0), f"{topk_ids.size(0)} != {a1.size(0)}"
793 + M = a1.size(0)
794 + else:
795 + assert a1.dim() == 3
796 + assert a1.size(0) == E, f"{a1.size(0)} == {E}"
797 + M = a1.size(1) # This is max_num_tokens
798 +
799 + assert topk_ids.dim() == 2
800 + topk = topk_ids.size(1)
801 +
802 + return E, M, N, K, topk
803 +
804 def workspace_dtype(self, act_dtype: torch.dtype) -> torch.dtype:
805 """
806 Workspace type: The dtype to use for the workspace tensors.
@@ -726,11 +873,7 @@ class FusedMoEPermuteExpertsUnpermute(ABC):
873 ) -> None:
874 apply_moe_activation(activation, output, input)
875
729 - def enable_chunking(self):
730 - return (
731 - envs.VLLM_ENABLE_FUSED_MOE_ACTIVATION_CHUNKING and self.supports_chunking()
732 - )
733 -
876 + @abstractmethod
877 def finalize_weight_and_reduce_impl(self) -> TopKWeightAndReduce:
878 raise NotImplementedError
879
@@ -791,6 +934,67 @@ class FusedMoEPermuteExpertsUnpermute(ABC):
934 raise NotImplementedError
935
936
937 +class FusedMoEExpertsMonolithic(FusedMoEExperts):
938 + """
939 + An abstract base class for the [Permute-Experts-Unpermute] step described
940 + above, but with the monolithic interface (accepts router logits
941 + rather than topk ids and weights).
942 + """
943 +
944 + @staticmethod
945 + def _supports_routing_method(
946 + routing_method: RoutingMethodType,
947 + weight_key: QuantKey | None,
948 + activation_key: QuantKey | None,
949 + ) -> bool:
950 + """
951 + Whether the kernel supports a routing method (e.g. GroupedTopK).
952 +
953 + Monolithic kernels should explicitly opt-in to support.
954 + """
955 + raise NotImplementedError
956 +
957 + @staticmethod
958 + def _supports_router_logits_dtype(
959 + router_logits_dtype: torch.dtype | None,
960 + routing_method: RoutingMethodType,
961 + ) -> bool:
962 + """
963 + Whether the kernel supports a dtype for router logits.
964 +
965 + Modular kernels should opt-in to support.
966 + """
967 + raise NotImplementedError
968 +
969 + @staticmethod
970 + def is_monolithic() -> bool:
971 + return True
972 +
973 + def apply(
974 + self,
975 + hidden_states: torch.Tensor,
976 + w1: torch.Tensor,
977 + w2: torch.Tensor,
978 + router_logits: torch.Tensor,
979 + activation: MoEActivation,
980 + global_num_experts: int,
981 + expert_map: torch.Tensor | None,
982 + a1q_scale: torch.Tensor | None,
983 + apply_router_weight_on_input: bool,
984 + # grouped topk + fused topk bias parameters
985 + num_expert_group: int | None = None,
986 + e_score_correction_bias: torch.Tensor | None = None,
987 + routed_scaling_factor: float | None = None,
988 + topk_group: int | None = None,
989 + ) -> torch.Tensor:
990 + """
991 + Same as apply(), except uses router_logits as opposed
992 + to the topk_ids and topk_weights. This is useful for kernels
993 + with fused router and fused_experts (e.g. FLASHINFER_TRTLLM).
994 + """
995 + raise NotImplementedError
996 +
997 +
998 def _slice_scales(
999 scales: torch.Tensor | None, start: int, end: int
1000 ) -> torch.Tensor | None:
@@ -802,75 +1006,32 @@ def _slice_scales(
1006 return None
1007
1008
805 -@final
806 -class FusedMoEModularKernel(torch.nn.Module):
807 - """
808 - This class combines a FusedMoEPrepareAndFinalize instance and
809 - a FusedMoEPermuteExpertsUnpermute to provide an interface that
810 - is compatible with the `fused_experts` function in fused_moe.py.
1009 +################################################################################
1010 +# Kernel
1011 +################################################################################
1012
812 - It takes care of managing any required scratch space.
813 -
814 - Note: Instances of this class should only be used for a single model
815 - layer due to any layer specific state that may be used by the component
816 - objects.
817 - """
1013
1014 +@final
1015 +class FusedMoEKernelModularImpl:
1016 def __init__(
1017 self,
821 - prepare_finalize: FusedMoEPrepareAndFinalize,
822 - fused_experts: FusedMoEPermuteExpertsUnpermute,
1018 + prepare_finalize: FusedMoEPrepareAndFinalizeModular,
1019 + fused_experts: FusedMoEExpertsModular,
1020 shared_experts: torch.nn.Module | None = None,
1021 moe_parallel_config: FusedMoEParallelConfig | None = None,
1022 inplace: bool = False,
1023 ):
827 - super().__init__()
1024 self.prepare_finalize = prepare_finalize
1025 self.fused_experts = fused_experts
1026 self.shared_experts = shared_experts
1027 + self.moe_parallel_config = moe_parallel_config
1028 self.inplace = inplace
832 -
833 - # prefer an explicit FusedMoEParallelConfig when available (from
834 - # FusedMoE layers / tests).
835 - # if not provided, assume this kernel is
836 - # running in a non-DP+EP context
837 - self.moe_parallel_config: FusedMoEParallelConfig | None = moe_parallel_config
1029 self.is_dp_ep = (
1030 moe_parallel_config is not None
1031 and moe_parallel_config.dp_size > 1
1032 and moe_parallel_config.use_ep
1033 )
1034
844 - self._post_init_setup()
845 - assert (
846 - prepare_finalize.activation_format == fused_experts.activation_format()
847 - ), (
848 - f"{prepare_finalize.__class__.__name__}."
849 - f"{prepare_finalize.activation_format} == "
850 - f"{fused_experts.__class__.__name__}."
851 - f"{fused_experts.activation_format()}"
852 - )
853 -
854 - def _post_init_setup(self):
855 - """
856 - Resolve any leftover setup dependencies between self.prepare_finalize
857 - and self.fused_experts here.
858 - """
859 - self.prepare_finalize.post_init_setup(self.fused_experts)
860 -
861 - def supports_expert_map(self) -> bool:
862 - """
863 - A flag indicating whether or not this class supports expert maps.
864 - """
865 - return self.fused_experts.supports_expert_map()
866 -
867 - def output_is_reduced(self) -> bool:
868 - """
869 - Indicates whether or not the output of fused MoE kernel
870 - is reduced across all ranks.
871 - """
872 - return self.prepare_finalize.output_is_reduced()
873 -
1035 def _chunk_info(self, M: int) -> tuple[int, int]:
1036 """
1037 Compute number of chunks and chunk size for given M.
@@ -919,7 +1080,7 @@ class FusedMoEModularKernel(torch.nn.Module):
1080 workspace_dtype = self.fused_experts.workspace_dtype(out_dtype)
1081
1082 # Force worst-case allocation in profiling run for
922 - # "mk.FusedMoEModularKernel.Standard" formats where this is only bounded
1083 + # "mk.FusedMoEKernel.Standard" formats where this is only bounded
1084 # by `VLLM_FUSED_MOE_CHUNK_SIZE` and may not be seen during profiling with
1085 # DP+EP due to the random token routing.
1086 is_profile_run = (
@@ -1313,13 +1474,13 @@ class FusedMoEModularKernel(torch.nn.Module):
1474 assert shared_output is not None
1475 return shared_output, output
1476
1316 - def forward(
1477 + def apply(
1478 self,
1479 hidden_states: torch.Tensor,
1480 w1: torch.Tensor,
1481 w2: torch.Tensor,
1321 - topk_weights: torch.Tensor,
1482 topk_ids: torch.Tensor,
1483 + topk_weights: torch.Tensor,
1484 activation: MoEActivation = MoEActivation.SILU,
1485 global_num_experts: int = -1,
1486 expert_map: torch.Tensor | None = None,
@@ -1334,8 +1495,7 @@ class FusedMoEModularKernel(torch.nn.Module):
1495 - hidden_states: (torch.Tensor): The input tensor to the MoE layer.
1496 - w1 (torch.Tensor): The first set of expert weights.
1497 - w2 (torch.Tensor): The second set of expert weights.
1337 - - topk_weights (torch.Tensor): The topk weights applied at the end of
1338 - the layer.
1498 + - topk_weights (torch.Tensor): The topk weights applied at the end of the layer.
1499 - topk_ids (torch.Tensor): A map of row to expert id.
1500 - activation (MoEActivation): The activation function to apply after the first
1501 MoE layer.
@@ -1354,7 +1514,6 @@ class FusedMoEModularKernel(torch.nn.Module):
1514 Returns:
1515 - torch.Tensor: The output tensor after applying the MoE layer.
1516 """
1357 -
1517 if self.inplace:
1518 assert self.shared_experts is None
1519 assert not disable_inplace()
@@ -1400,3 +1559,206 @@ class FusedMoEModularKernel(torch.nn.Module):
1559 apply_router_weight_on_input,
1560 shared_experts_input=shared_experts_input,
1561 )
1562 +
1563 +
1564 +@final
1565 +class FusedMoEKernelMonolithicImpl:
1566 + def __init__(
1567 + self,
1568 + prepare_finalize: FusedMoEPrepareAndFinalizeMonolithic,
1569 + fused_experts: FusedMoEExpertsMonolithic,
1570 + ):
1571 + self.prepare_finalize = prepare_finalize
1572 + self.fused_experts = fused_experts
1573 +
1574 + def apply(
1575 + self,
1576 + hidden_states: torch.Tensor,
1577 + w1: torch.Tensor,
1578 + w2: torch.Tensor,
1579 + router_logits: torch.Tensor,
1580 + activation: MoEActivation,
1581 + global_num_experts: int,
1582 + expert_map: torch.Tensor | None,
1583 + apply_router_weight_on_input: bool,
1584 + # grouped topk + fused topk bias parameters
1585 + num_expert_group: int | None = None,
1586 + e_score_correction_bias: torch.Tensor | None = None,
1587 + routed_scaling_factor: float | None = None,
1588 + topk_group: int | None = None,
1589 + ) -> torch.Tensor:
1590 + """
1591 + Same as forward(), except uses router_logits as opposed
1592 + to the topk_ids and topk_weights. This is used for kernels
1593 + that have fused router + experts (e.g. FLASHINFER_TRTLLM).
1594 + """
1595 +
1596 + # TODO(rob): add inplace support.
1597 + a1q, a1q_scale, router_logits = self.prepare_finalize.prepare(
1598 + hidden_states,
1599 + router_logits=router_logits,
1600 + quant_config=self.fused_experts.quant_config,
1601 + defer_input_quant=self.fused_experts.expects_unquantized_inputs,
1602 + )
1603 +
1604 + fused_out = self.fused_experts.apply(
1605 + hidden_states=a1q,
1606 + w1=w1,
1607 + w2=w2,
1608 + router_logits=router_logits,
1609 + activation=activation,
1610 + global_num_experts=global_num_experts,
1611 + expert_map=expert_map,
1612 + apply_router_weight_on_input=apply_router_weight_on_input,
1613 + a1q_scale=a1q_scale,
1614 + # grouped topk + fused topk bias parameters
1615 + num_expert_group=num_expert_group,
1616 + e_score_correction_bias=e_score_correction_bias,
1617 + routed_scaling_factor=routed_scaling_factor,
1618 + topk_group=topk_group,
1619 + )
1620 +
1621 + output = self.prepare_finalize.finalize(fused_out)
1622 +
1623 + return output
1624 +
1625 +
1626 +@final
1627 +class FusedMoEKernel:
1628 + def __init__(
1629 + self,
1630 + prepare_finalize: FusedMoEPrepareAndFinalize,
1631 + fused_experts: FusedMoEExperts,
1632 + shared_experts: torch.nn.Module | None = None,
1633 + moe_parallel_config: FusedMoEParallelConfig | None = None,
1634 + inplace: bool = False,
1635 + ):
1636 + super().__init__()
1637 + self.shared_experts = shared_experts # NOTE: check if we can remove
1638 +
1639 + # Initialize the implementation (monolithic or modular).
1640 + self.impl: FusedMoEKernelModularImpl | FusedMoEKernelMonolithicImpl
1641 + if isinstance(
1642 + prepare_finalize, FusedMoEPrepareAndFinalizeModular
1643 + ) and isinstance(fused_experts, FusedMoEExpertsModular):
1644 + self.impl = FusedMoEKernelModularImpl(
1645 + prepare_finalize,
1646 + fused_experts,
1647 + shared_experts,
1648 + moe_parallel_config,
1649 + inplace,
1650 + )
1651 +
1652 + elif isinstance(
1653 + prepare_finalize, FusedMoEPrepareAndFinalizeMonolithic
1654 + ) and isinstance(fused_experts, FusedMoEExpertsMonolithic):
1655 + assert shared_experts is None
1656 + assert not inplace
1657 + self.impl = FusedMoEKernelMonolithicImpl(
1658 + prepare_finalize,
1659 + fused_experts,
1660 + )
1661 +
1662 + else:
1663 + raise ValueError(
1664 + "prepare_finalize and fused_experts must both be either monolithic "
1665 + f"or non-monolithic but got {prepare_finalize.__class__.__name__} "
1666 + f"and {fused_experts.__class__.__name__}"
1667 + )
1668 +
1669 + self._post_init_setup()
1670 +
1671 + @property
1672 + def is_monolithic(self) -> bool:
1673 + return isinstance(self.impl, FusedMoEKernelMonolithicImpl)
1674 +
1675 + @property
1676 + def prepare_finalize(self) -> FusedMoEPrepareAndFinalize:
1677 + return self.impl.prepare_finalize
1678 +
1679 + @property
1680 + def fused_experts(self) -> FusedMoEExperts:
1681 + return self.impl.fused_experts
1682 +
1683 + def _post_init_setup(self):
1684 + """
1685 + Resolve any leftover setup dependencies between self.prepare_finalize
1686 + and self.fused_experts here.
1687 + """
1688 + self.prepare_finalize.post_init_setup(self.impl.fused_experts)
1689 + assert (
1690 + self.prepare_finalize.activation_format
1691 + == self.fused_experts.activation_format()
1692 + )
1693 +
1694 + def supports_expert_map(self) -> bool:
1695 + """
1696 + A flag indicating whether or not this class supports expert maps.
1697 + """
1698 + return self.fused_experts.supports_expert_map()
1699 +
1700 + def output_is_reduced(self) -> bool:
1701 + """
1702 + Indicates whether or not the output of fused MoE kernel
1703 + is reduced across all ranks.
1704 + """
1705 + return self.prepare_finalize.output_is_reduced()
1706 +
1707 + def apply_monolithic(
1708 + self,
1709 + hidden_states: torch.Tensor,
1710 + w1: torch.Tensor,
1711 + w2: torch.Tensor,
1712 + router_logits: torch.Tensor | tuple[torch.Tensor, torch.Tensor],
1713 + activation: MoEActivation,
1714 + global_num_experts: int,
1715 + expert_map: torch.Tensor | None,
1716 + apply_router_weight_on_input: bool,
1717 + # grouped topk + fused topk bias parameters
1718 + num_expert_group: int | None = None,
1719 + e_score_correction_bias: torch.Tensor | None = None,
1720 + routed_scaling_factor: float | None = None,
1721 + topk_group: int | None = None,
1722 + ) -> torch.Tensor:
1723 + assert isinstance(self.impl, FusedMoEKernelMonolithicImpl)
1724 + return self.impl.apply(
1725 + hidden_states=hidden_states,
1726 + w1=w1,
1727 + w2=w2,
1728 + router_logits=router_logits,
1729 + activation=activation,
1730 + global_num_experts=global_num_experts,
1731 + expert_map=expert_map,
1732 + apply_router_weight_on_input=apply_router_weight_on_input,
1733 + num_expert_group=num_expert_group,
1734 + e_score_correction_bias=e_score_correction_bias,
1735 + routed_scaling_factor=routed_scaling_factor,
1736 + topk_group=topk_group,
1737 + )
1738 +
1739 + def apply(
1740 + self,
1741 + hidden_states: torch.Tensor,
1742 + w1: torch.Tensor,
1743 + w2: torch.Tensor,
1744 + topk_weights: torch.Tensor,
1745 + topk_ids: torch.Tensor,
1746 + activation: MoEActivation,
1747 + global_num_experts: int,
1748 + expert_map: torch.Tensor | None,
1749 + apply_router_weight_on_input: bool,
1750 + shared_experts_input: torch.Tensor | None = None,
1751 + ) -> torch.Tensor:
1752 + assert isinstance(self.impl, FusedMoEKernelModularImpl)
1753 + return self.impl.apply(
1754 + hidden_states=hidden_states,
1755 + w1=w1,
1756 + w2=w2,
1757 + topk_weights=topk_weights,
1758 + topk_ids=topk_ids,
1759 + activation=activation,
1760 + global_num_experts=global_num_experts,
1761 + expert_map=expert_map,
1762 + apply_router_weight_on_input=apply_router_weight_on_input,
1763 + shared_experts_input=shared_experts_input,
1764 + )
vllm/model_executor/layers/fused_moe/mori_prepare_finalize.py
+1 -1
@@ -12,7 +12,7 @@ from vllm.platforms import current_platform
12 logger = init_logger(__name__)
13
14
15 -class MoriPrepareAndFinalize(mk.FusedMoEPrepareAndFinalize):
15 +class MoriPrepareAndFinalize(mk.FusedMoEPrepareAndFinalizeModular):
16 """
17 Prepare/Finalize using MoRI kernels.
18 """
vllm/model_executor/layers/fused_moe/oracle/fp8.py
+41 -89
@@ -18,13 +18,9 @@ from vllm.model_executor.layers.fused_moe.config import (
18 fp8_w8a8_moe_quant_config,
19 fp8_w8a16_moe_quant_config,
20 )
21 -from vllm.model_executor.layers.fused_moe.flashinfer_trtllm_moe import (
22 - is_supported_config_trtllm_fp8,
23 -)
21 from vllm.model_executor.layers.quantization.utils.flashinfer_utils import (
22 FlashinferMoeBackend,
23 get_flashinfer_moe_backend,
27 - make_fp8_moe_alpha_scales_for_fi,
24 prepare_fp8_moe_layer_for_fi,
25 )
26 from vllm.model_executor.layers.quantization.utils.fp8_utils import (
@@ -103,9 +99,13 @@ def _get_priority_backends(
99
100 def backend_to_kernel_cls(
101 backend: Fp8MoeBackend,
106 -) -> type[mk.FusedMoEPermuteExpertsUnpermute]:
102 +) -> type[mk.FusedMoEExperts]:
103 if backend == Fp8MoeBackend.FLASHINFER_TRTLLM:
108 - raise NotImplementedError
104 + from vllm.model_executor.layers.fused_moe.experts.trtllm_fp8_moe import ( # noqa: E501
105 + TrtLlmFp8Experts,
106 + )
107 +
108 + return TrtLlmFp8Experts
109
110 elif backend == Fp8MoeBackend.FLASHINFER_CUTLASS:
111 from vllm.model_executor.layers.fused_moe.flashinfer_cutlass_moe import (
@@ -205,13 +205,11 @@ def select_fp8_moe_backend(
205 weight_key: QuantKey | None,
206 activation_key: QuantKey | None,
207 allow_vllm_cutlass: bool = False,
208 -) -> tuple[Fp8MoeBackend, type[mk.FusedMoEPermuteExpertsUnpermute] | None]:
208 +) -> tuple[Fp8MoeBackend, type[mk.FusedMoEExperts] | None]:
209 """
210 Select the primary FP8 MoE backend
211 Note: Shape-specific fallbacks may still occur at runtime.
212 """
213 - k_cls: type[mk.FusedMoEPermuteExpertsUnpermute] | None = None
214 -
213 if config.is_lora_enabled:
214 return Fp8MoeBackend.TRITON, backend_to_kernel_cls(Fp8MoeBackend.TRITON)
215
@@ -252,7 +250,7 @@ def select_fp8_moe_backend(
250 weight_key: QuantKey | None,
251 activation_key: QuantKey | None,
252 activation_format: mk.FusedMoEActivationFormat,
255 - ) -> tuple[Fp8MoeBackend, type[mk.FusedMoEPermuteExpertsUnpermute]]:
253 + ) -> tuple[Fp8MoeBackend, type[mk.FusedMoEExperts]]:
254 k_cls = backend_to_kernel_cls(backend)
255 supported, reason = k_cls.is_supported_config(
256 k_cls, config, weight_key, activation_key, activation_format
@@ -287,16 +285,6 @@ def select_fp8_moe_backend(
285 "vLLM CUTLASS FP8 MoE backend is disabled for this configuration."
286 )
287
290 - # Handle FLASHINFER_TRTLLM specially (no kernel class).
291 - if requested_backend == Fp8MoeBackend.FLASHINFER_TRTLLM:
292 - supported, reason = is_supported_config_trtllm_fp8(
293 - config, weight_key, activation_key, activation_format
294 - )
295 - if supported:
296 - logger.info_once(_make_log_backend(requested_backend))
297 - return requested_backend, None
298 - raise ValueError(_make_log_unsupported(requested_backend, reason))
299 -
288 return _return_or_raise(
289 requested_backend, config, weight_key, activation_key, activation_format
290 )
@@ -311,51 +299,32 @@ def select_fp8_moe_backend(
299 elif envs.is_set("VLLM_FLASHINFER_MOE_BACKEND"):
300 # If user is explicit about backend, validate it.
301 fi_backend = get_flashinfer_moe_backend()
314 -
315 - if fi_backend == FlashinferMoeBackend.TENSORRT_LLM:
316 - backend = Fp8MoeBackend.FLASHINFER_TRTLLM
317 - supported, reason = is_supported_config_trtllm_fp8(
318 - config, weight_key, activation_key, activation_format
319 - )
320 - if supported:
321 - logger.info_once(_make_log_backend(backend))
322 - return backend, None
323 - else:
324 - raise ValueError(_make_log_unsupported(backend, reason))
325 -
326 - elif fi_backend == FlashinferMoeBackend.CUTLASS:
302 + if fi_backend == FlashinferMoeBackend.CUTLASS:
303 backend = Fp8MoeBackend.FLASHINFER_CUTLASS
328 - return _return_or_raise(
329 - backend, config, weight_key, activation_key, activation_format
330 - )
331 -
304 + elif fi_backend == FlashinferMoeBackend.TENSORRT_LLM:
305 + backend = Fp8MoeBackend.FLASHINFER_TRTLLM
306 else:
333 - assert fi_backend == FlashinferMoeBackend.CUTEDSL
334 - raise ValueError("FlashInfer MaskedGEMM not supported for FP8")
335 -
307 + raise ValueError(
308 + f"FlashInfer MOE backend {fi_backend} does not support FP8 MoE."
309 + )
310 + k_cls = backend_to_kernel_cls(backend)
311 + return _return_or_raise(
312 + backend, config, weight_key, activation_key, activation_format
313 + )
314 else:
315 # If the user is not explicit about the backend, try both.
316 for backend in [
317 Fp8MoeBackend.FLASHINFER_TRTLLM,
318 Fp8MoeBackend.FLASHINFER_CUTLASS,
319 ]:
342 - if backend == Fp8MoeBackend.FLASHINFER_TRTLLM:
343 - k_cls = None
344 - supported, reason = is_supported_config_trtllm_fp8(
345 - config,
346 - weight_key,
347 - activation_key,
348 - activation_format,
349 - )
350 - else:
351 - k_cls = backend_to_kernel_cls(backend)
352 - supported, reason = k_cls.is_supported_config(
353 - k_cls,
354 - config,
355 - weight_key,
356 - activation_key,
357 - activation_format,
358 - )
320 + k_cls = backend_to_kernel_cls(backend)
321 + supported, reason = k_cls.is_supported_config(
322 + k_cls,
323 + config,
324 + weight_key,
325 + activation_key,
326 + activation_format,
327 + )
328
329 if supported:
330 logger.info_once(_make_log_backend(backend), scope="local")
@@ -408,23 +377,14 @@ def select_fp8_moe_backend(
377
378 # Select kernels in order of backend.
379 for backend in AVAILABLE_BACKENDS:
411 - if backend == Fp8MoeBackend.FLASHINFER_TRTLLM:
412 - k_cls = None
413 - supported, reason = is_supported_config_trtllm_fp8(
414 - config,
415 - weight_key,
416 - activation_key,
417 - activation_format,
418 - )
419 - else:
420 - k_cls = backend_to_kernel_cls(backend)
421 - supported, reason = k_cls.is_supported_config(
422 - k_cls,
423 - config,
424 - weight_key,
425 - activation_key,
426 - activation_format,
427 - )
380 + k_cls = backend_to_kernel_cls(backend)
381 + supported, reason = k_cls.is_supported_config(
382 + k_cls,
383 + config,
384 + weight_key,
385 + activation_key,
386 + activation_format,
387 + )
388
389 if supported:
390 logger.info_once(_make_log_backend(backend), scope="local")
@@ -510,7 +470,7 @@ def make_fp8_moe_quant_config(
470 block_shape: list[int] | None = None,
471 per_act_token_quant: bool = False,
472 per_out_ch_quant: bool = False,
513 -) -> FusedMoEQuantConfig | None:
473 +) -> FusedMoEQuantConfig:
474 """
475 Create FusedMoEQuantConfig for the specified FP8 Backend.
476 The FusedMoEQuantConfig holds the scales that are used
@@ -523,9 +483,6 @@ def make_fp8_moe_quant_config(
483 In a future PR, we will have this function should be
484 a method of the modular kernel itself.
485 """
526 - # TRTLLM does not use Modular Kernel abstraction yet.
527 - if fp8_backend == Fp8MoeBackend.FLASHINFER_TRTLLM:
528 - return None
486
487 # MARLIN is mixed precision W8A16 config.
488 if fp8_backend == Fp8MoeBackend.MARLIN:
@@ -539,12 +496,6 @@ def make_fp8_moe_quant_config(
496 # (alpha = w_scale * a_scale) and inverse a2 scale.
497 if fp8_backend == Fp8MoeBackend.FLASHINFER_CUTLASS and block_shape is None:
498 assert a1_scale is not None and a2_scale is not None
542 - g1_alphas, g2_alphas = make_fp8_moe_alpha_scales_for_fi(
543 - w1_scale,
544 - a1_scale,
545 - w2_scale,
546 - a2_scale,
547 - )
499 return fp8_w8a8_moe_quant_config(
500 w1_scale=w1_scale,
501 w2_scale=w2_scale,
@@ -552,8 +503,8 @@ def make_fp8_moe_quant_config(
503 a2_scale=a2_scale,
504 a1_gscale=(1.0 / a1_scale),
505 a2_gscale=(1.0 / a2_scale),
555 - g1_alphas=g1_alphas,
556 - g2_alphas=g2_alphas,
506 + g1_alphas=(w1_scale * a1_scale).squeeze(),
507 + g2_alphas=(w2_scale * a2_scale).squeeze(),
508 )
509 # All other backends use normal config.
510 return fp8_w8a8_moe_quant_config(
@@ -570,17 +521,18 @@ def make_fp8_moe_quant_config(
521 def make_fp8_moe_kernel(
522 moe_quant_config: FusedMoEQuantConfig,
523 moe_config: FusedMoEConfig,
573 - experts_cls: type[mk.FusedMoEPermuteExpertsUnpermute],
524 + experts_cls: type[mk.FusedMoEExperts],
525 fp8_backend: Fp8MoeBackend,
526 routing_tables: tuple[torch.Tensor, torch.Tensor, torch.Tensor] | None = None,
527 shared_experts: torch.nn.Module | None = None,
577 -) -> mk.FusedMoEModularKernel:
528 +) -> mk.FusedMoEKernel:
529 # Create Prepare/Finalize.
530 prepare_finalize = maybe_make_prepare_finalize(
531 moe=moe_config,
532 quant_config=moe_quant_config,
533 routing_tables=routing_tables,
534 allow_new_interface=True,
535 + use_monolithic=issubclass(experts_cls, mk.FusedMoEExpertsMonolithic),
536 )
537 assert prepare_finalize is not None
538
@@ -605,7 +557,7 @@ def make_fp8_moe_kernel(
557 # NOTE(rob): we only want the mk to control the shared_expert
558 # if using all2all (for SBO). bnell is making this explicit in
559 # the new MoE runner class.
608 - kernel = mk.FusedMoEModularKernel(
560 + kernel = mk.FusedMoEKernel(
561 prepare_finalize,
562 experts,
563 shared_experts=(
vllm/model_executor/layers/fused_moe/oracle/nvfp4.py
+52 -84
@@ -19,7 +19,6 @@ from vllm.model_executor.layers.fused_moe.config import (
19 nvfp4_w4a16_moe_quant_config,
20 )
21 from vllm.model_executor.layers.quantization.utils.flashinfer_fp4_moe import (
22 - is_supported_config_trtllm,
22 prepare_nvfp4_moe_layer_for_fi_or_cutlass,
23 )
24 from vllm.model_executor.layers.quantization.utils.flashinfer_utils import (
@@ -67,39 +66,46 @@ def is_global_sf_supported_for_nvfp4_backend(backend: NvFp4MoeBackend) -> bool:
66
67 def backend_to_kernel_cls(
68 backend: NvFp4MoeBackend,
70 -) -> type[mk.FusedMoEPermuteExpertsUnpermute]:
69 +) -> list[type[mk.FusedMoEExperts]]:
70 if backend == NvFp4MoeBackend.FLASHINFER_TRTLLM:
72 - raise NotImplementedError(
73 - "FLASHINFER_TRTLLM doesn't support Modular Kernel Interface"
71 + from vllm.model_executor.layers.fused_moe.experts.trtllm_nvfp4_moe import (
72 + TrtLlmNvFp4ExpertsModular,
73 + TrtLlmNvFp4ExpertsMonolithic,
74 )
75
76 + # NOTE: prefer Monolthic > Modular, so return Monolithic first.
77 + return [
78 + TrtLlmNvFp4ExpertsMonolithic,
79 + TrtLlmNvFp4ExpertsModular,
80 + ]
81 +
82 elif backend == NvFp4MoeBackend.FLASHINFER_CUTLASS:
83 from vllm.model_executor.layers.fused_moe.flashinfer_cutlass_moe import (
84 FlashInferExperts,
85 )
86
81 - return FlashInferExperts
87 + return [FlashInferExperts]
88
89 elif backend == NvFp4MoeBackend.FLASHINFER_CUTEDSL:
90 from vllm.model_executor.layers.fused_moe.flashinfer_cutedsl_moe import (
91 FlashInferCuteDSLExperts,
92 )
93
88 - return FlashInferCuteDSLExperts
94 + return [FlashInferCuteDSLExperts]
95
96 elif backend == NvFp4MoeBackend.VLLM_CUTLASS:
97 from vllm.model_executor.layers.fused_moe.cutlass_moe import (
98 CutlassExpertsFp4,
99 )
100
95 - return CutlassExpertsFp4
101 + return [CutlassExpertsFp4]
102
103 elif backend == NvFp4MoeBackend.MARLIN:
104 from vllm.model_executor.layers.fused_moe.fused_marlin_moe import (
105 MarlinExperts,
106 )
107
102 - return MarlinExperts
108 + return [MarlinExperts]
109 else:
110 raise ValueError(f"Unknown NvFP4 MoE backend: {backend.value}")
111
@@ -125,7 +131,7 @@ def select_nvfp4_moe_backend(
131 config: FusedMoEConfig,
132 weight_key: QuantKey | None,
133 activation_key: QuantKey | None,
128 -) -> tuple[NvFp4MoeBackend, type[mk.FusedMoEPermuteExpertsUnpermute] | None]:
134 +) -> tuple[NvFp4MoeBackend, type[mk.FusedMoEExperts]]:
135 """
136 Select the primary NvFP4 MoE backend
137 Note: Shape-specific fallbacks may still occur at runtime.
@@ -175,29 +181,21 @@ def select_nvfp4_moe_backend(
181 weight_key: QuantKey | None,
182 activation_key: QuantKey | None,
183 activation_format: mk.FusedMoEActivationFormat,
178 - ) -> tuple[NvFp4MoeBackend, type[mk.FusedMoEPermuteExpertsUnpermute]]:
179 - k_cls = backend_to_kernel_cls(backend)
180 - supported, reason = k_cls.is_supported_config(
181 - k_cls, config, weight_key, activation_key, activation_format
182 - )
183 - if supported:
184 - logger.info_once(_make_log_backend(backend))
185 - return backend, k_cls
184 + ) -> tuple[NvFp4MoeBackend, type[mk.FusedMoEExperts]]:
185 + for k_cls in backend_to_kernel_cls(backend):
186 + supported, reason = k_cls.is_supported_config(
187 + k_cls, config, weight_key, activation_key, activation_format
188 + )
189 + if supported:
190 + logger.info_once(_make_log_backend(backend))
191 + return backend, k_cls
192 +
193 raise ValueError(_make_log_unsupported(backend, reason))
194
195 # Handle explicit moe_backend from user.
196 runner_backend = config.moe_backend
197 if runner_backend != "auto":
198 requested_backend = map_nvfp4_backend(runner_backend)
192 - if requested_backend == NvFp4MoeBackend.FLASHINFER_TRTLLM:
193 - supported, reason = is_supported_config_trtllm(
194 - config, weight_key, activation_key, activation_format
195 - )
196 - if supported:
197 - logger.info_once(_make_log_backend(requested_backend))
198 - return requested_backend, None
199 - raise ValueError(_make_log_unsupported(requested_backend, reason))
200 -
199 return _return_or_raise(
200 requested_backend, config, weight_key, activation_key, activation_format
201 )
@@ -210,36 +208,14 @@ def select_nvfp4_moe_backend(
208
209 elif envs.is_set("VLLM_FLASHINFER_MOE_BACKEND"):
210 # If user is explicit about backend, validate it.
213 - fi_backend = get_flashinfer_moe_backend()
214 -
215 - if fi_backend == FlashinferMoeBackend.TENSORRT_LLM:
216 - backend = NvFp4MoeBackend.FLASHINFER_TRTLLM
217 - supported, reason = is_supported_config_trtllm(
218 - config, weight_key, activation_key, activation_format
219 - )
220 - if supported:
221 - logger.info_once(_make_log_backend(backend))
222 - return backend, None
223 - else:
224 - raise ValueError(_make_log_unsupported(backend, reason))
225 - else:
226 - backend = fi_2_vllm_backend_map[fi_backend]
227 - return _return_or_raise(
228 - backend, config, weight_key, activation_key, activation_format
229 - )
211 + backend = fi_2_vllm_backend_map[get_flashinfer_moe_backend()]
212 + return _return_or_raise(
213 + backend, config, weight_key, activation_key, activation_format
214 + )
215 else:
216 # If the user is not explicit about the backend, try each.
217 for backend in FLASHINFER_NVFP4_MOE_BACKENDS:
233 - if backend == NvFp4MoeBackend.FLASHINFER_TRTLLM:
234 - k_cls = None
235 - supported, reason = is_supported_config_trtllm(
236 - config,
237 - weight_key,
238 - activation_key,
239 - activation_format,
240 - )
241 - else:
242 - k_cls = backend_to_kernel_cls(backend)
218 + for k_cls in backend_to_kernel_cls(backend):
219 supported, reason = k_cls.is_supported_config(
220 k_cls,
221 config,
@@ -247,13 +223,13 @@ def select_nvfp4_moe_backend(
223 activation_key,
224 activation_format,
225 )
250 - if supported:
251 - logger.info_once(_make_log_backend(backend), scope="local")
252 - return backend, None
253 - else:
254 - logger.debug_once(
255 - _make_log_unsupported(backend, reason), scope="local"
256 - )
226 + if supported:
227 + logger.info_once(_make_log_backend(backend), scope="local")
228 + return backend, k_cls
229 + else:
230 + logger.debug_once(
231 + _make_log_unsupported(backend, reason), scope="local"
232 + )
233
234 raise NotImplementedError(
235 "Found VLLM_USE_FLASHINFER_MOE_FP4=1, but no "
@@ -268,16 +244,7 @@ def select_nvfp4_moe_backend(
244
245 # Select kernels in order of backend.
246 for backend in AVAILABLE_BACKENDS:
271 - if backend == NvFp4MoeBackend.FLASHINFER_TRTLLM:
272 - k_cls = None # type: ignore[assignment]
273 - supported, reason = is_supported_config_trtllm(
274 - config,
275 - weight_key,
276 - activation_key,
277 - activation_format,
278 - )
279 - else:
280 - k_cls = backend_to_kernel_cls(backend)
247 + for k_cls in backend_to_kernel_cls(backend):
248 supported, reason = k_cls.is_supported_config(
249 k_cls,
250 config,
@@ -286,11 +253,11 @@ def select_nvfp4_moe_backend(
253 activation_format,
254 )
255
289 - if supported:
290 - logger.info_once(_make_log_backend(backend), scope="local")
291 - return backend, k_cls
292 - else:
293 - logger.debug_once(_make_log_unsupported(backend, reason), scope="local")
256 + if supported:
257 + logger.info_once(_make_log_backend(backend), scope="local")
258 + return backend, k_cls
259 + else:
260 + logger.debug_once(_make_log_unsupported(backend, reason), scope="local")
261
262 raise NotImplementedError(
263 "No NvFp4 MoE backend supports the deployment configuration."
@@ -398,12 +365,8 @@ def make_nvfp4_moe_quant_config(
365 w2_scale_2: torch.Tensor,
366 a13_scale: torch.Tensor,
367 a2_scale: torch.Tensor,
401 -) -> FusedMoEQuantConfig | None:
402 - UNSUPPORTED = [NvFp4MoeBackend.FLASHINFER_TRTLLM]
403 - if backend in UNSUPPORTED:
404 - return None
405 -
406 - elif backend == NvFp4MoeBackend.MARLIN:
368 +) -> FusedMoEQuantConfig:
369 + if backend == NvFp4MoeBackend.MARLIN:
370 return nvfp4_w4a16_moe_quant_config(
371 g1_alphas=w13_scale_2,
372 g2_alphas=w2_scale_2,
@@ -420,22 +383,27 @@ def make_nvfp4_moe_quant_config(
383 a2_gscale=(1.0 / a2_scale),
384 w1_scale=w13_scale,
385 w2_scale=w2_scale,
386 + # NOTE(rob): this is a hack until the MoE kernels
387 + # create their own quant configs. TRTLLM kernel
388 + # does not accept swizzled input quant scales.
389 + is_nvfp4_scale_swizzled=(backend != NvFp4MoeBackend.FLASHINFER_TRTLLM),
390 )
391
392
393 def make_nvfp4_moe_kernel(
394 moe_quant_config: FusedMoEQuantConfig,
395 moe_config: FusedMoEConfig,
429 - experts_cls: type[mk.FusedMoEPermuteExpertsUnpermute],
396 + experts_cls: type[mk.FusedMoEExperts],
397 routing_tables: tuple[torch.Tensor, torch.Tensor, torch.Tensor] | None = None,
398 shared_experts: torch.nn.Module | None = None,
432 -) -> mk.FusedMoEModularKernel:
399 +) -> mk.FusedMoEKernel:
400 # Create Prepare/Finalize.
401 prepare_finalize = maybe_make_prepare_finalize(
402 moe=moe_config,
403 quant_config=moe_quant_config,
404 routing_tables=routing_tables,
405 allow_new_interface=True,
406 + use_monolithic=issubclass(experts_cls, mk.FusedMoEExpertsMonolithic),
407 )
408 assert prepare_finalize is not None
409
@@ -460,7 +428,7 @@ def make_nvfp4_moe_kernel(
428 # NOTE(rob): we only want the mk to control the shared_expert
429 # if using all2all (for SBO). bnell is making this explicit in
430 # the new MoE runner class.
463 - kernel = mk.FusedMoEModularKernel(
431 + kernel = mk.FusedMoEKernel(
432 prepare_finalize,
433 experts,
434 shared_experts=(
vllm/model_executor/layers/fused_moe/oracle/unquantized.py
+10 -10
@@ -19,7 +19,7 @@ from vllm.model_executor.layers.fused_moe.flashinfer_trtllm_moe import (
19 is_supported_config_trtllm_bf16,
20 )
21 from vllm.model_executor.layers.fused_moe.prepare_finalize import (
22 - MoEPrepareAndFinalizeNoEP,
22 + MoEPrepareAndFinalizeNoDPEPModular,
23 )
24 from vllm.model_executor.layers.quantization.utils.flashinfer_utils import (
25 swap_w13_to_w31,
@@ -209,7 +209,7 @@ def make_unquantized_moe_kernel(
209 backend: UnquantizedMoeBackend,
210 quant_config: FusedMoEQuantConfig,
211 moe_config: FusedMoEConfig,
212 -) -> mk.FusedMoEModularKernel | None:
212 +) -> mk.FusedMoEKernel | None:
213 if backend in UNSUPPORTED_BACKEND:
214 return None
215
@@ -218,8 +218,8 @@ def make_unquantized_moe_kernel(
218 FlashInferExperts,
219 )
220
221 - kernel = mk.FusedMoEModularKernel(
222 - MoEPrepareAndFinalizeNoEP(),
221 + kernel = mk.FusedMoEKernel(
222 + MoEPrepareAndFinalizeNoDPEPModular(),
223 FlashInferExperts(
224 moe_config=moe_config,
225 quant_config=quant_config,
@@ -232,8 +232,8 @@ def make_unquantized_moe_kernel(
232 AiterExperts,
233 )
234
235 - kernel = mk.FusedMoEModularKernel(
236 - MoEPrepareAndFinalizeNoEP(),
235 + kernel = mk.FusedMoEKernel(
236 + MoEPrepareAndFinalizeNoDPEPModular(),
237 AiterExperts(
238 moe_config=moe_config,
239 quant_config=quant_config,
@@ -243,8 +243,8 @@ def make_unquantized_moe_kernel(
243 elif backend == UnquantizedMoeBackend.TRITON:
244 from vllm.model_executor.layers.fused_moe import TritonExperts
245
246 - kernel = mk.FusedMoEModularKernel(
247 - MoEPrepareAndFinalizeNoEP(),
246 + kernel = mk.FusedMoEKernel(
247 + MoEPrepareAndFinalizeNoDPEPModular(),
248 TritonExperts(
249 moe_config=moe_config,
250 quant_config=quant_config,
@@ -254,8 +254,8 @@ def make_unquantized_moe_kernel(
254 elif backend == UnquantizedMoeBackend.XPU:
255 from vllm.model_executor.layers.fused_moe import XPUExperts
256
257 - kernel = mk.FusedMoEModularKernel(
258 - MoEPrepareAndFinalizeNoEP(),
257 + kernel = mk.FusedMoEKernel(
258 + MoEPrepareAndFinalizeNoDPEPModular(),
259 XPUExperts(
260 moe_config=moe_config,
261 quant_config=quant_config,
vllm/model_executor/layers/fused_moe/prepare_finalize.py deleted
-209
@@ -1,209 +0,0 @@
1 -# SPDX-License-Identifier: Apache-2.0
2 -# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 -
4 -import torch
5 -
6 -import vllm.model_executor.layers.fused_moe.modular_kernel as mk
7 -from vllm.distributed import get_ep_group
8 -from vllm.model_executor.layers.fused_moe.config import FusedMoEQuantConfig
9 -from vllm.model_executor.layers.fused_moe.topk_weight_and_reduce import (
10 - TopKWeightAndReduceContiguous,
11 - TopKWeightAndReduceDelegate,
12 -)
13 -from vllm.model_executor.layers.fused_moe.utils import moe_kernel_quantize_input
14 -from vllm.utils.flashinfer import nvfp4_block_scale_interleave
15 -
16 -
17 -class MoEPrepareAndFinalizeNaiveEP(mk.FusedMoEPrepareAndFinalize):
18 - def __init__(
19 - self,
20 - is_sequence_parallel: bool = False,
21 - num_dispatchers: int = 1,
22 - ) -> None:
23 - super().__init__()
24 - self.is_sequence_parallel = is_sequence_parallel
25 - self._num_dispatchers = num_dispatchers
26 -
27 - @property
28 - def activation_format(self) -> mk.FusedMoEActivationFormat:
29 - return mk.FusedMoEActivationFormat.Standard
30 -
31 - def max_num_tokens_per_rank(self) -> int | None:
32 - return None
33 -
34 - def topk_indices_dtype(self) -> torch.dtype | None:
35 - return None
36 -
37 - def num_dispatchers(self) -> int:
38 - return self._num_dispatchers
39 -
40 - def output_is_reduced(self) -> bool:
41 - return False
42 -
43 - def prepare(
44 - self,
45 - a1: torch.Tensor,
46 - topk_weights: torch.Tensor,
47 - topk_ids: torch.Tensor,
48 - num_experts: int,
49 - expert_map: torch.Tensor | None,
50 - apply_router_weight_on_input: bool,
51 - quant_config: FusedMoEQuantConfig,
52 - defer_input_quant: bool = False,
53 - ) -> mk.PrepareResultType:
54 - if apply_router_weight_on_input:
55 - topk = topk_ids.size(1)
56 - assert topk == 1, (
57 - "apply_router_weight_on_input is only implemented for topk=1"
58 - )
59 - # Note: do not use inplace for shared experts overlap
60 - a1 = a1 * topk_weights.to(a1.dtype)
61 -
62 - # Defer input quantization to the MoE kernel.
63 - use_nvfp4 = quant_config.use_nvfp4_w4a4
64 - if defer_input_quant:
65 - a1q = a1
66 - a1q_scale = None
67 - else:
68 - a1q, a1q_scale = moe_kernel_quantize_input(
69 - a1,
70 - quant_config.a1_gscale if use_nvfp4 else quant_config.a1_scale,
71 - quant_config.quant_dtype,
72 - quant_config.per_act_token_quant,
73 - quant_config.block_shape,
74 - # NOTE: swizzling pads the scales to multiple of 128
75 - # which makes the scales tensor different shape than
76 - # the hidden states, breaking the A2A kernel. So, we
77 - # delay the swizzling until after the A2A.
78 - is_fp4_scale_swizzled=False,
79 - )
80 -
81 - # Skip gathering scales if we have static quantization
82 - # (the scale is a scalar, replicated on all ranks) or
83 - # if quantization is deferred.
84 - skip_gather_scales = a1q_scale is None or a1q_scale.ndim == 0
85 - scales = None if skip_gather_scales else [a1q_scale]
86 -
87 - res = get_ep_group().dispatch(
88 - a1q,
89 - topk_weights,
90 - topk_ids,
91 - is_sequence_parallel=self.is_sequence_parallel,
92 - extra_tensors=scales,
93 - )
94 - if skip_gather_scales:
95 - a1q, topk_weights, topk_ids = res
96 - else:
97 - a1q, topk_weights, topk_ids, scales = res
98 - assert scales is not None and len(scales) == 1
99 - a1q_scale = scales[0]
100 - if quant_config.quant_dtype == "nvfp4":
101 - assert a1q_scale is not None
102 - if a1q_scale.element_size() == 1:
103 - a1q_scale = a1q_scale.view(torch.uint8)
104 - a1q_scale = nvfp4_block_scale_interleave(a1q_scale)
105 -
106 - return a1q, a1q_scale, None, topk_ids, topk_weights
107 -
108 - def finalize(
109 - self,
110 - output: torch.Tensor,
111 - fused_expert_output: torch.Tensor,
112 - topk_weights: torch.Tensor,
113 - topk_ids: torch.Tensor,
114 - apply_router_weight_on_input: bool,
115 - weight_and_reduce_impl: mk.TopKWeightAndReduce,
116 - ) -> None:
117 - if isinstance(weight_and_reduce_impl, TopKWeightAndReduceDelegate):
118 - weight_and_reduce_impl = TopKWeightAndReduceContiguous()
119 -
120 - out = weight_and_reduce_impl.apply(
121 - output=None,
122 - fused_expert_output=fused_expert_output,
123 - topk_weights=topk_weights,
124 - topk_ids=topk_ids,
125 - apply_router_weight_on_input=apply_router_weight_on_input,
126 - )
127 -
128 - output.copy_(
129 - get_ep_group().combine(out, is_sequence_parallel=self.is_sequence_parallel)
130 - )
131 -
132 -
133 -class MoEPrepareAndFinalizeNoEP(mk.FusedMoEPrepareAndFinalize):
134 - """MoE prepare and finalize without expert parallelism."""
135 -
136 - @property
137 - def activation_format(self) -> mk.FusedMoEActivationFormat:
138 - return mk.FusedMoEActivationFormat.Standard
139 -
140 - def max_num_tokens_per_rank(self) -> int | None:
141 - return None
142 -
143 - def topk_indices_dtype(self) -> torch.dtype | None:
144 - return None
145 -
146 - def num_dispatchers(self) -> int:
147 - return 1
148 -
149 - def output_is_reduced(self) -> bool:
150 - return False
151 -
152 - def prepare(
153 - self,
154 - a1: torch.Tensor,
155 - topk_weights: torch.Tensor,
156 - topk_ids: torch.Tensor,
157 - num_experts: int,
158 - expert_map: torch.Tensor | None,
159 - apply_router_weight_on_input: bool,
160 - quant_config: FusedMoEQuantConfig,
161 - defer_input_quant: bool = False,
162 - ) -> mk.PrepareResultType:
163 - if apply_router_weight_on_input:
164 - topk = topk_ids.size(1)
165 - # TODO: this only works for topK=1, will need to update for topK>1
166 - assert topk == 1, (
167 - "apply_router_weight_on_input is only implemented for topk=1"
168 - )
169 - # Note: do not use inplace for shared experts overlap
170 - a1 = a1 * topk_weights.to(a1.dtype)
171 -
172 - # Defer input quant to moe kernel for backends (e.g. AITER, FI)
173 - # which use a single kernel call for quant + experts.
174 - if defer_input_quant:
175 - return a1, None, None, None, None
176 -
177 - input_sf = (
178 - quant_config.a1_gscale
179 - if quant_config.use_nvfp4_w4a4
180 - else quant_config.a1_scale
181 - )
182 - a1q, a1q_scale = moe_kernel_quantize_input(
183 - a1,
184 - input_sf,
185 - quant_config.quant_dtype,
186 - quant_config.per_act_token_quant,
187 - quant_config.block_shape,
188 - )
189 -
190 - return a1q, a1q_scale, None, None, None
191 -
192 - def finalize(
193 - self,
194 - output: torch.Tensor,
195 - fused_expert_output: torch.Tensor,
196 - topk_weights: torch.Tensor,
197 - topk_ids: torch.Tensor,
198 - apply_router_weight_on_input: bool,
199 - weight_and_reduce_impl: mk.TopKWeightAndReduce,
200 - ) -> None:
201 - if isinstance(weight_and_reduce_impl, TopKWeightAndReduceDelegate):
202 - weight_and_reduce_impl = TopKWeightAndReduceContiguous()
203 - weight_and_reduce_impl.apply(
204 - output=output,
205 - fused_expert_output=fused_expert_output,
206 - topk_weights=topk_weights,
207 - topk_ids=topk_ids,
208 - apply_router_weight_on_input=apply_router_weight_on_input,
209 - )
vllm/model_executor/layers/fused_moe/prepare_finalize/__init__.py new
+22
@@ -0,0 +1,22 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +
4 +from vllm.model_executor.layers.fused_moe.prepare_finalize.naive_dp_ep import (
5 + MoEPrepareAndFinalizeNaiveDPEPModular,
6 + MoEPrepareAndFinalizeNaiveDPEPMonolithic,
7 + make_moe_prepare_and_finalize_naive_dp_ep,
8 +)
9 +from vllm.model_executor.layers.fused_moe.prepare_finalize.no_dp_ep import (
10 + MoEPrepareAndFinalizeNoDPEPModular,
11 + MoEPrepareAndFinalizeNoDPEPMonolithic,
12 + make_moe_prepare_and_finalize_no_dp_ep,
13 +)
14 +
15 +__all__ = [
16 + "MoEPrepareAndFinalizeNaiveDPEPMonolithic",
17 + "MoEPrepareAndFinalizeNaiveDPEPModular",
18 + "make_moe_prepare_and_finalize_naive_dp_ep",
19 + "MoEPrepareAndFinalizeNoDPEPMonolithic",
20 + "MoEPrepareAndFinalizeNoDPEPModular",
21 + "make_moe_prepare_and_finalize_no_dp_ep",
22 +]
vllm/model_executor/layers/fused_moe/prepare_finalize/naive_dp_ep.py new
+253
@@ -0,0 +1,253 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +import torch
4 +
5 +import vllm.model_executor.layers.fused_moe.modular_kernel as mk
6 +from vllm.distributed import get_ep_group
7 +from vllm.model_executor.layers.fused_moe.config import FusedMoEQuantConfig
8 +from vllm.model_executor.layers.fused_moe.topk_weight_and_reduce import (
9 + TopKWeightAndReduceContiguous,
10 + TopKWeightAndReduceDelegate,
11 +)
12 +from vllm.model_executor.layers.fused_moe.utils import moe_kernel_quantize_input
13 +from vllm.utils.flashinfer import nvfp4_block_scale_interleave
14 +
15 +
16 +def _quantize_and_setup_dispatch(
17 + a1: torch.Tensor,
18 + quant_config: FusedMoEQuantConfig,
19 + defer_input_quant: bool = False,
20 +) -> tuple[torch.Tensor, list[torch.Tensor] | None]:
21 + # Defer input quantization to the MoE kernel.
22 + if defer_input_quant:
23 + a1q = a1
24 + a1q_scale = None
25 + else:
26 + input_sf = (
27 + quant_config.a1_gscale
28 + if quant_config.use_nvfp4_w4a4
29 + else quant_config.a1_scale
30 + )
31 +
32 + # NOTE: swizzling pads the scales to multiple of 128
33 + # which makes the scales tensor different shape than
34 + # the hidden states, breaking the A2A kernel. So, we
35 + # delay the swizzling until after the A2A.
36 + a1q, a1q_scale = a1q, a1q_scale = moe_kernel_quantize_input(
37 + a1,
38 + input_sf,
39 + quant_dtype=quant_config.quant_dtype,
40 + per_act_token_quant=quant_config.per_act_token_quant,
41 + block_shape=quant_config.block_shape,
42 + is_fp4_scale_swizzled=False,
43 + )
44 +
45 + # Skip gathering scales if we have static quantization
46 + # (the scale is a scalar, replicated on all ranks) or
47 + # if quantization is deferred.
48 + skip_gather_scales = a1q_scale is None or a1q_scale.ndim == 0
49 + scales = None if skip_gather_scales else [a1q_scale]
50 +
51 + return a1q, scales
52 +
53 +
54 +def _unwrap_scale_and_prepare_for_moe(
55 + scales: list[torch.Tensor] | None,
56 + quant_config: FusedMoEQuantConfig,
57 +) -> torch.Tensor:
58 + assert scales is not None and len(scales) == 1
59 + a1q_scale = scales[0]
60 + # Apply swizzling after a2a if the MoE kernel needs it.
61 + if quant_config.quant_dtype == "nvfp4" and quant_config.is_nvfp4_scale_swizzled:
62 + assert a1q_scale is not None
63 + if a1q_scale.element_size() == 1:
64 + a1q_scale = a1q_scale.view(torch.uint8)
65 + a1q_scale = nvfp4_block_scale_interleave(a1q_scale)
66 +
67 + return a1q_scale
68 +
69 +
70 +class MoEPrepareAndFinalizeNaiveDPEPModular(mk.FusedMoEPrepareAndFinalizeModular):
71 + """
72 + Naive Prepare/Finalize for Dp/Ep case for Modular Kernels.
73 +
74 + Uses Torch AR/RS or AR for dispatch/combine operations, applied
75 + to the topk weights and ids.
76 + """
77 +
78 + def __init__(
79 + self,
80 + is_sequence_parallel: bool = False,
81 + num_dispatchers: int = 1,
82 + ) -> None:
83 + super().__init__()
84 + self.is_sequence_parallel = is_sequence_parallel
85 + self._num_dispatchers = num_dispatchers
86 +
87 + @property
88 + def activation_format(self) -> mk.FusedMoEActivationFormat:
89 + return mk.FusedMoEActivationFormat.Standard
90 +
91 + def max_num_tokens_per_rank(self) -> int | None:
92 + return None
93 +
94 + def topk_indices_dtype(self) -> torch.dtype | None:
95 + return None
96 +
97 + def num_dispatchers(self) -> int:
98 + return self._num_dispatchers
99 +
100 + def output_is_reduced(self) -> bool:
101 + return False
102 +
103 + def prepare(
104 + self,
105 + a1: torch.Tensor,
106 + topk_weights: torch.Tensor,
107 + topk_ids: torch.Tensor,
108 + num_experts: int,
109 + expert_map: torch.Tensor | None,
110 + apply_router_weight_on_input: bool,
111 + quant_config: FusedMoEQuantConfig,
112 + defer_input_quant: bool = False,
113 + ) -> mk.PrepareResultType:
114 + """Quantize and Dispatch Topk Weights and Topk Ids."""
115 +
116 + if apply_router_weight_on_input:
117 + topk = topk_ids.size(1)
118 + assert topk == 1, (
119 + "apply_router_weight_on_input is only implemented for topk=1"
120 + )
121 + # Note: do not use inplace for shared experts overlap
122 + a1 = a1 * topk_weights.to(a1.dtype)
123 +
124 + a1q, scales = _quantize_and_setup_dispatch(a1, quant_config, defer_input_quant)
125 +
126 + res = get_ep_group().dispatch(
127 + a1q,
128 + topk_weights,
129 + topk_ids,
130 + is_sequence_parallel=self.is_sequence_parallel,
131 + extra_tensors=scales,
132 + )
133 +
134 + if scales is None:
135 + a1q, topk_weights, topk_ids = res
136 + a1q_scale = None
137 + else:
138 + a1q, topk_weights, topk_ids, scales = res
139 + a1q_scale = _unwrap_scale_and_prepare_for_moe(scales, quant_config)
140 +
141 + return a1q, a1q_scale, None, topk_ids, topk_weights
142 +
143 + def finalize(
144 + self,
145 + output: torch.Tensor,
146 + fused_expert_output: torch.Tensor,
147 + topk_weights: torch.Tensor,
148 + topk_ids: torch.Tensor,
149 + apply_router_weight_on_input: bool,
150 + weight_and_reduce_impl: mk.TopKWeightAndReduce,
151 + ) -> None:
152 + if isinstance(weight_and_reduce_impl, TopKWeightAndReduceDelegate):
153 + weight_and_reduce_impl = TopKWeightAndReduceContiguous()
154 +
155 + out = weight_and_reduce_impl.apply(
156 + output=None,
157 + fused_expert_output=fused_expert_output,
158 + topk_weights=topk_weights,
159 + topk_ids=topk_ids,
160 + apply_router_weight_on_input=apply_router_weight_on_input,
161 + )
162 +
163 + output.copy_(
164 + get_ep_group().combine(out, is_sequence_parallel=self.is_sequence_parallel)
165 + )
166 +
167 +
168 +class MoEPrepareAndFinalizeNaiveDPEPMonolithic(mk.FusedMoEPrepareAndFinalizeMonolithic):
169 + """
170 + Naive Prepare/Finalize for Dp/Ep case for Modular Kernels.
171 +
172 + Uses Torch AR/RS or AR for dispatch/combine operations, applied
173 + to the router logits (the MoE kernel runs the router internally).
174 + """
175 +
176 + def __init__(
177 + self,
178 + is_sequence_parallel: bool = False,
179 + num_dispatchers: int = 1,
180 + ) -> None:
181 + super().__init__()
182 + self.is_sequence_parallel = is_sequence_parallel
183 + self._num_dispatchers = num_dispatchers
184 +
185 + @property
186 + def activation_format(self) -> mk.FusedMoEActivationFormat:
187 + return mk.FusedMoEActivationFormat.Standard
188 +
189 + def max_num_tokens_per_rank(self) -> int | None:
190 + return None
191 +
192 + def topk_indices_dtype(self) -> torch.dtype | None:
193 + return None
194 +
195 + def num_dispatchers(self) -> int:
196 + return self._num_dispatchers
197 +
198 + def output_is_reduced(self) -> bool:
199 + return False
200 +
201 + def prepare(
202 + self,
203 + a1: torch.Tensor,
204 + router_logits: torch.Tensor,
205 + quant_config: FusedMoEQuantConfig,
206 + defer_input_quant: bool = False,
207 + ) -> mk.PrepareMonolithicResultType:
208 + """Quantize and Dispatch Router Logits."""
209 +
210 + a1q, scales = _quantize_and_setup_dispatch(a1, quant_config, defer_input_quant)
211 +
212 + res = get_ep_group().dispatch_router_logits(
213 + a1q,
214 + router_logits,
215 + is_sequence_parallel=self.is_sequence_parallel,
216 + extra_tensors=scales,
217 + )
218 +
219 + if scales is None:
220 + a1q, router_logits = res
221 + a1q_scale = None
222 + else:
223 + a1q, router_logits, scales = res
224 + a1q_scale = _unwrap_scale_and_prepare_for_moe(scales, quant_config)
225 +
226 + return a1q, a1q_scale, router_logits
227 +
228 + def finalize(
229 + self,
230 + fused_expert_output: torch.Tensor,
231 + ) -> torch.Tensor:
232 + out = get_ep_group().combine(
233 + fused_expert_output, is_sequence_parallel=self.is_sequence_parallel
234 + )
235 + return out
236 +
237 +
238 +def make_moe_prepare_and_finalize_naive_dp_ep(
239 + use_monolithic: bool,
240 + is_sequence_parallel: bool = False,
241 + num_dispatchers: int = 1,
242 +) -> MoEPrepareAndFinalizeNaiveDPEPModular | MoEPrepareAndFinalizeNaiveDPEPMonolithic:
243 + return (
244 + MoEPrepareAndFinalizeNaiveDPEPMonolithic(
245 + is_sequence_parallel=is_sequence_parallel,
246 + num_dispatchers=num_dispatchers,
247 + )
248 + if use_monolithic
249 + else MoEPrepareAndFinalizeNaiveDPEPModular(
250 + is_sequence_parallel=is_sequence_parallel,
251 + num_dispatchers=num_dispatchers,
252 + )
253 + )
vllm/model_executor/layers/fused_moe/prepare_finalize/no_dp_ep.py new
+141
@@ -0,0 +1,141 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +import torch
4 +
5 +import vllm.model_executor.layers.fused_moe.modular_kernel as mk
6 +from vllm.model_executor.layers.fused_moe.config import FusedMoEQuantConfig
7 +from vllm.model_executor.layers.fused_moe.topk_weight_and_reduce import (
8 + TopKWeightAndReduceContiguous,
9 + TopKWeightAndReduceDelegate,
10 +)
11 +from vllm.model_executor.layers.fused_moe.utils import moe_kernel_quantize_input
12 +
13 +
14 +def _quantize_input(
15 + a1: torch.Tensor,
16 + quant_config: FusedMoEQuantConfig,
17 + defer_input_quant: bool = False,
18 +) -> tuple[torch.Tensor, torch.Tensor | None]:
19 + # Defer input quant to moe kernel for backends (e.g. AITER, FI)
20 + # which use a single kernel call for quant + experts.
21 + if defer_input_quant:
22 + return a1, None
23 +
24 + input_sf = (
25 + quant_config.a1_gscale if quant_config.use_nvfp4_w4a4 else quant_config.a1_scale
26 + )
27 + a1q, a1q_scale = moe_kernel_quantize_input(
28 + a1,
29 + input_sf,
30 + quant_dtype=quant_config.quant_dtype,
31 + per_act_token_quant=quant_config.per_act_token_quant,
32 + block_shape=quant_config.block_shape,
33 + is_fp4_scale_swizzled=quant_config.is_nvfp4_scale_swizzled,
34 + )
35 +
36 + return a1q, a1q_scale
37 +
38 +
39 +class MoEPrepareAndFinalizeNoDPEPModular(mk.FusedMoEPrepareAndFinalizeModular):
40 + @property
41 + def activation_format(self) -> mk.FusedMoEActivationFormat:
42 + return mk.FusedMoEActivationFormat.Standard
43 +
44 + def max_num_tokens_per_rank(self) -> int | None:
45 + return None
46 +
47 + def topk_indices_dtype(self) -> torch.dtype | None:
48 + return None
49 +
50 + def num_dispatchers(self) -> int:
51 + return 1
52 +
53 + def output_is_reduced(self) -> bool:
54 + return False
55 +
56 + def prepare(
57 + self,
58 + a1: torch.Tensor,
59 + topk_weights: torch.Tensor,
60 + topk_ids: torch.Tensor,
61 + num_experts: int,
62 + expert_map: torch.Tensor | None,
63 + apply_router_weight_on_input: bool,
64 + quant_config: FusedMoEQuantConfig,
65 + defer_input_quant: bool = False,
66 + ) -> mk.PrepareResultType:
67 + if apply_router_weight_on_input:
68 + topk = topk_ids.size(1)
69 + # TODO: this only works for topK=1, will need to update for topK>1
70 + assert topk == 1, (
71 + "apply_router_weight_on_input is only implemented for topk=1"
72 + )
73 + # Note: do not use inplace for shared experts overlap
74 + a1 = a1 * topk_weights.to(a1.dtype)
75 +
76 + a1q, a1q_scale = _quantize_input(a1, quant_config, defer_input_quant)
77 +
78 + return a1q, a1q_scale, None, None, None
79 +
80 + def finalize(
81 + self,
82 + output: torch.Tensor,
83 + fused_expert_output: torch.Tensor,
84 + topk_weights: torch.Tensor,
85 + topk_ids: torch.Tensor,
86 + apply_router_weight_on_input: bool,
87 + weight_and_reduce_impl: mk.TopKWeightAndReduce,
88 + ) -> None:
89 + if isinstance(weight_and_reduce_impl, TopKWeightAndReduceDelegate):
90 + weight_and_reduce_impl = TopKWeightAndReduceContiguous()
91 + weight_and_reduce_impl.apply(
92 + output=output,
93 + fused_expert_output=fused_expert_output,
94 + topk_weights=topk_weights,
95 + topk_ids=topk_ids,
96 + apply_router_weight_on_input=apply_router_weight_on_input,
97 + )
98 +
99 +
100 +class MoEPrepareAndFinalizeNoDPEPMonolithic(mk.FusedMoEPrepareAndFinalizeMonolithic):
101 + @property
102 + def activation_format(self) -> mk.FusedMoEActivationFormat:
103 + return mk.FusedMoEActivationFormat.Standard
104 +
105 + def max_num_tokens_per_rank(self) -> int | None:
106 + return None
107 +
108 + def topk_indices_dtype(self) -> torch.dtype | None:
109 + return None
110 +
111 + def num_dispatchers(self) -> int:
112 + return 1
113 +
114 + def output_is_reduced(self) -> bool:
115 + return False
116 +
117 + def prepare(
118 + self,
119 + a1: torch.Tensor,
120 + router_logits: torch.Tensor,
121 + quant_config: FusedMoEQuantConfig,
122 + defer_input_quant: bool = False,
123 + ) -> mk.PrepareMonolithicResultType:
124 + a1q, a1q_scale = _quantize_input(a1, quant_config, defer_input_quant)
125 + return a1q, a1q_scale, router_logits
126 +
127 + def finalize(
128 + self,
129 + fused_expert_output: torch.Tensor,
130 + ) -> torch.Tensor:
131 + return fused_expert_output
132 +
133 +
134 +def make_moe_prepare_and_finalize_no_dp_ep(
135 + use_monolithic: bool,
136 +) -> MoEPrepareAndFinalizeNoDPEPModular | MoEPrepareAndFinalizeNoDPEPMonolithic:
137 + return (
138 + MoEPrepareAndFinalizeNoDPEPMonolithic()
139 + if use_monolithic
140 + else MoEPrepareAndFinalizeNoDPEPModular()
141 + )
vllm/model_executor/layers/fused_moe/rocm_aiter_fused_moe.py
+1 -1
@@ -292,7 +292,7 @@ def rocm_aiter_fused_experts(
292 )
293
294
295 -class AiterExperts(mk.FusedMoEPermuteExpertsUnpermute):
295 +class AiterExperts(mk.FusedMoEExpertsModular):
296 @property
297 def expects_unquantized_inputs(self) -> bool:
298 return True
vllm/model_executor/layers/fused_moe/router/base_router.py
+1 -1
@@ -64,7 +64,7 @@ if current_platform.is_cuda_alike():
64
65 # TODO(bowen): When using `FusedMoEModularKernel`, this
66 # can be done in a more unified way, since
67 - # `FusedMoEPrepareAndFinalize` will return the expert
67 + # `FusedMoEPrepareAndFinalizeModular` will return the expert
68 # token count, in some cases directly from the kernel.
69 # However, now there are many code paths not using
70 # the modular kernel, e.g. calling `fused_experts`,
vllm/model_executor/layers/fused_moe/runner/default_moe_runner.py
+16 -53
@@ -320,8 +320,8 @@ class DefaultMoERunner(MoERunner):
320 """
321 assert self.quant_method is not None
322 return (
323 - self.quant_method.moe_mk is not None
324 - and self.quant_method.moe_mk.output_is_reduced()
323 + self.quant_method.moe_kernel is not None
324 + and self.quant_method.moe_kernel.output_is_reduced()
325 )
326
327 def maybe_all_reduce_tensor_model_parallel(self, final_hidden_states: torch.Tensor):
@@ -640,45 +640,6 @@ class DefaultMoERunner(MoERunner):
640 )
641
642 with sp_ctx:
643 - extra_tensors = None
644 - if do_naive_dispatch_combine:
645 - post_quant_allgather = (
646 - self.quant_method is not None
647 - and self.moe_config.dp_size > 1
648 - and self.moe_config.use_ep
649 - and getattr(self.quant_method, "do_post_quant_allgather", False)
650 - )
651 - if post_quant_allgather:
652 - hidden_states_to_dispatch, extra_tensors = (
653 - self.quant_method.prepare_dp_allgather_tensor(
654 - layer, hidden_states, router_logits
655 - )
656 - )
657 - else:
658 - hidden_states_to_dispatch = hidden_states
659 -
660 - dispatch_res = get_ep_group().dispatch_router_logits(
661 - hidden_states_to_dispatch,
662 - router_logits,
663 - self.moe_config.is_sequence_parallel,
664 - extra_tensors=extra_tensors,
665 - )
666 - if extra_tensors is not None:
667 - (
668 - orig_hidden_states,
669 - router_logits,
670 - extra_tensors_combined,
671 - ) = dispatch_res
672 - hidden_states_combined = (
673 - orig_hidden_states,
674 - extra_tensors_combined[0],
675 - )
676 - else:
677 - hidden_states_combined, router_logits = dispatch_res
678 - orig_hidden_states = hidden_states_combined
679 - else:
680 - orig_hidden_states = hidden_states
681 -
643 # Run shared experts before matrix multiply.
644 # because matrix multiply maybe modify the hidden_states.
645 if has_separate_shared_experts and not use_shared_experts_stream:
@@ -688,6 +649,17 @@ class DefaultMoERunner(MoERunner):
649 )
650 shared_output = self.shared_experts(shared_input)
651
652 + # For naive dispatch/combine Dp/Ep, dispatch the hidden states and
653 + # router logits to all experts.
654 + # NOTE: this will be removed once all kernels are migrated into the
655 + # MoEKernel framework.
656 + if do_naive_dispatch_combine:
657 + hidden_states, router_logits = get_ep_group().dispatch_router_logits(
658 + hidden_states,
659 + router_logits,
660 + self.moe_config.is_sequence_parallel,
661 + )
662 +
663 # NOTE: Similar with DP, PCP also needs dispatch and combine. For
664 # simplicity, AgRsAll2All was added separately for PCP here. Maybe
665 # we should modify All2AllManager abstract to better support PCP.
@@ -701,31 +673,22 @@ class DefaultMoERunner(MoERunner):
673 dim=0,
674 )
675
704 - # TODO(bnell): deal with fp4 flashinfer tuple hidden states hack (#30014).
705 - # Figure out nicer way to do this.
706 - if do_naive_dispatch_combine:
707 - x = hidden_states_combined
708 - x_orig = orig_hidden_states
709 - else:
710 - x = hidden_states
711 - x_orig = hidden_states
712 -
676 # Matrix multiply.
677 if self.quant_method.is_monolithic:
678 final_hidden_states = self.quant_method.apply_monolithic(
679 layer=layer,
717 - x=x,
680 + x=hidden_states,
681 router_logits=router_logits,
682 )
683 else:
684 topk_weights, topk_ids = self.router.select_experts(
722 - hidden_states=x_orig,
685 + hidden_states=hidden_states,
686 router_logits=router_logits,
687 )
688
689 final_hidden_states = self.quant_method.apply(
690 layer=layer,
728 - x=x, # The type signture of this is wrong due to the hack.
691 + x=hidden_states,
692 topk_weights=topk_weights,
693 topk_ids=topk_ids,
694 shared_experts_input=shared_input,
vllm/model_executor/layers/fused_moe/topk_weight_and_reduce.py
+2 -2
@@ -10,7 +10,7 @@ import vllm.model_executor.layers.fused_moe.modular_kernel as mk
10
11 class TopKWeightAndReduceDelegate(mk.TopKWeightAndReduce):
12 """
13 - Useful in the case when some FusedMoEPermuteExpertsUnpermute
13 + Useful in the case when some FusedMoEExpertsModular
14 implementation does not perform weight application and reduction
15 but cannot address the needs of all the compatible PrepareAndFinalize
16 implementations.
@@ -62,7 +62,7 @@ class TopKWeightAndReduceNoOP(mk.TopKWeightAndReduce):
62 if output is None:
63 return fused_expert_output
64
65 - # MoEPrepareAndFinalizeNoEP needs the output to be in the `output`
65 + # MoEPrepareAndFinalizeNoDPEPModular needs the output to be in the `output`
66 # tensor.
67 assert output.size() == fused_expert_output.size(), (
68 "output shape is expected to match the fused_expert_output shape. "
vllm/model_executor/layers/fused_moe/triton_cutlass_moe.py
+3 -3
@@ -32,8 +32,8 @@ class TritonOrCutlassExperts(FallbackExperts):
32
33 @staticmethod
34 def get_clses() -> tuple[
35 - type[mk.FusedMoEPermuteExpertsUnpermute],
36 - type[mk.FusedMoEPermuteExpertsUnpermute],
35 + type[mk.FusedMoEExpertsModular],
36 + type[mk.FusedMoEExpertsModular],
37 ]:
38 return (CutlassExpertsFp8, TritonExperts)
39
@@ -77,7 +77,7 @@ class TritonOrCutlassExperts(FallbackExperts):
77 hidden_states: torch.Tensor,
78 w1: torch.Tensor,
79 w2: torch.Tensor,
80 - ) -> mk.FusedMoEPermuteExpertsUnpermute:
80 + ) -> mk.FusedMoEExpertsModular:
81 # Small batch fallback for sm100.
82 if self.is_sm100 and hidden_states.shape[0] <= 8:
83 return self.fallback_experts
vllm/model_executor/layers/fused_moe/triton_deep_gemm_moe.py
+3 -3
@@ -32,8 +32,8 @@ class TritonOrDeepGemmExperts(FallbackExperts):
32
33 @staticmethod
34 def get_clses() -> tuple[
35 - type[mk.FusedMoEPermuteExpertsUnpermute],
36 - type[mk.FusedMoEPermuteExpertsUnpermute],
35 + type[mk.FusedMoEExpertsModular],
36 + type[mk.FusedMoEExpertsModular],
37 ]:
38 return (DeepGemmExperts, TritonExperts)
39
@@ -79,7 +79,7 @@ class TritonOrDeepGemmExperts(FallbackExperts):
79 hidden_states: torch.Tensor,
80 w1: torch.Tensor,
81 w2: torch.Tensor,
82 - ) -> mk.FusedMoEPermuteExpertsUnpermute:
82 + ) -> mk.FusedMoEExpertsModular:
83 if is_deep_gemm_e8m0_used() or _valid_deep_gemm(hidden_states, w1, w2):
84 return self.experts
85 else:
vllm/model_executor/layers/fused_moe/trtllm_moe.py
+1 -1
@@ -18,7 +18,7 @@ from vllm.model_executor.layers.quantization.utils.quant_utils import (
18 )
19
20
21 -class TrtLlmGenExperts(mk.FusedMoEPermuteExpertsUnpermute):
21 +class TrtLlmGenExperts(mk.FusedMoEExpertsModular):
22 """TensorRT-LLM-based fused MoE expert implementation."""
23
24 def __init__(
vllm/model_executor/layers/fused_moe/unquantized_fused_moe_method.py
+7 -7
@@ -24,8 +24,8 @@ from vllm.model_executor.layers.fused_moe.fused_moe_method_base import (
24 )
25 from vllm.model_executor.layers.fused_moe.modular_kernel import (
26 FusedMoEActivationFormat,
27 - FusedMoEPermuteExpertsUnpermute,
28 - FusedMoEPrepareAndFinalize,
27 + FusedMoEExpertsModular,
28 + FusedMoEPrepareAndFinalizeModular,
29 )
30 from vllm.model_executor.layers.fused_moe.oracle.unquantized import (
31 UnquantizedMoeBackend,
@@ -70,7 +70,7 @@ class UnquantizedFusedMoEMethod(FusedMoEMethodBase, CustomOp):
70 self.rocm_aiter_moe_enabled = (
71 rocm_aiter_ops.is_fused_moe_enabled() and moe.is_act_and_mul
72 )
73 - self.kernel: mk.FusedMoEModularKernel | None = None
73 + self.kernel: mk.FusedMoEKernel | None = None
74 self._is_monolithic = (
75 current_platform.is_cpu()
76 or self.unquantized_backend == UnquantizedMoeBackend.FLASHINFER_TRTLLM
@@ -107,7 +107,7 @@ class UnquantizedFusedMoEMethod(FusedMoEMethodBase, CustomOp):
107 def maybe_make_prepare_finalize(
108 self,
109 routing_tables: tuple[torch.Tensor, torch.Tensor, torch.Tensor] | None = None,
110 - ) -> FusedMoEPrepareAndFinalize | None:
110 + ) -> FusedMoEPrepareAndFinalizeModular | None:
111 if self.unquantized_backend == UnquantizedMoeBackend.AITER:
112 return None
113 else:
@@ -115,9 +115,9 @@ class UnquantizedFusedMoEMethod(FusedMoEMethodBase, CustomOp):
115
116 def select_gemm_impl(
117 self,
118 - prepare_finalize: FusedMoEPrepareAndFinalize,
118 + prepare_finalize: FusedMoEPrepareAndFinalizeModular,
119 layer: torch.nn.Module,
120 - ) -> FusedMoEPermuteExpertsUnpermute:
120 + ) -> FusedMoEExpertsModular:
121 assert self.moe_quant_config is not None
122 if (
123 prepare_finalize.activation_format
@@ -325,7 +325,7 @@ class UnquantizedFusedMoEMethod(FusedMoEMethodBase, CustomOp):
325 ) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]:
326 assert self.kernel is not None
327
328 - return self.kernel(
328 + return self.kernel.apply(
329 hidden_states=x,
330 w1=layer.w13_weight,
331 w2=layer.w2_weight,
vllm/model_executor/layers/fused_moe/xpu_fused_moe.py
+1 -1
@@ -23,7 +23,7 @@ if current_platform.is_xpu():
23 from vllm_xpu_kernels.fused_moe_interface import xpu_fused_moe
24
25
26 -class XPUExperts(mk.FusedMoEPermuteExpertsUnpermute):
26 +class XPUExperts(mk.FusedMoEExpertsModular):
27 def __init__(
28 self,
29 moe_config: FusedMoEConfig,
vllm/model_executor/layers/quantization/compressed_tensors/compressed_tensors_moe.py
+71 -168
@@ -19,8 +19,8 @@ from vllm.logger import init_logger
19 from vllm.model_executor.layers.fused_moe import (
20 FusedMoE,
21 FusedMoEActivationFormat,
22 + FusedMoEExpertsModular,
23 FusedMoEMethodBase,
23 - FusedMoEPermuteExpertsUnpermute,
24 FusedMoeWeightScaleSupported,
25 UnquantizedFusedMoEMethod,
26 )
@@ -40,7 +40,6 @@ from vllm.model_executor.layers.fused_moe.fused_marlin_moe import (
40 fused_marlin_moe,
41 )
42 from vllm.model_executor.layers.fused_moe.oracle.fp8 import (
43 - Fp8MoeBackend,
43 convert_to_fp8_moe_kernel_format,
44 make_fp8_moe_kernel,
45 make_fp8_moe_quant_config,
@@ -59,18 +58,11 @@ from vllm.model_executor.layers.quantization.compressed_tensors.schemes.compress
58 WNA16_SUPPORTED_BITS,
59 WNA16_SUPPORTED_TYPES_MAP,
60 )
62 -from vllm.model_executor.layers.quantization.utils.flashinfer_fp4_moe import (
63 - flashinfer_trtllm_fp4_moe,
64 - flashinfer_trtllm_fp4_routed_moe,
65 -)
61 from vllm.model_executor.layers.quantization.utils.flashinfer_mxint4_moe import (
62 flashinfer_trtllm_mxint4_moe,
63 is_flashinfer_mxint4_moe_available,
64 prepare_static_weights_for_trtllm_mxint4_moe,
65 )
71 -from vllm.model_executor.layers.quantization.utils.flashinfer_utils import (
72 - apply_fi_trtllm_fp8_per_tensor_moe,
73 -)
66 from vllm.model_executor.layers.quantization.utils.fp8_utils import (
67 process_fp8_input_tensor_strategy_moe,
68 process_fp8_weight_tensor_strategy_moe,
@@ -336,7 +328,7 @@ class CompressedTensorsW4A4Mxfp4MoEMethod(CompressedTensorsMoEMethod):
328
329 self.moe_quant_config = self.get_fused_moe_quant_config(layer)
330 if self.moe_quant_config is not None:
339 - self.moe_mk = make_nvfp4_moe_kernel(
331 + self.moe_kernel = make_nvfp4_moe_kernel(
332 moe_quant_config=self.moe_quant_config,
333 moe_config=self.moe,
334 experts_cls=self.experts_cls,
@@ -352,8 +344,8 @@ class CompressedTensorsW4A4Mxfp4MoEMethod(CompressedTensorsMoEMethod):
344 topk_ids: torch.Tensor,
345 shared_experts_input: torch.Tensor | None,
346 ) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]:
355 - assert self.moe_mk is not None
356 - return self.moe_mk(
347 + assert self.moe_kernel is not None
348 + return self.moe_kernel.apply(
349 x,
350 layer.w13_weight,
351 layer.w2_weight,
@@ -562,43 +554,27 @@ class CompressedTensorsW4A4Nvfp4MoEMethod(CompressedTensorsMoEMethod):
554 layer.w13_input_scale = a13_scale
555 layer.w2_input_scale = a2_scale
556
565 - # Setup modular kernel for TP case and naive DP/EP case.
566 - # In non-naive DP/EP case, we will create a ModularKernelMethod.
567 - # TODO(rob): unify these so FP8MoEMethod owns the ModularKernel
568 - # in both cases.
557 + # Setup modular kernel.
558 self.moe_quant_config = self.get_fused_moe_quant_config(layer)
570 - if self.moe_quant_config:
571 - assert self.experts_cls is not None
572 - self.moe_mk = make_nvfp4_moe_kernel(
573 - moe_quant_config=self.moe_quant_config,
574 - moe_config=self.moe,
575 - experts_cls=self.experts_cls,
576 - shared_experts=layer.shared_experts,
577 - routing_tables=layer._maybe_init_expert_routing_tables(),
578 - )
559 + assert self.experts_cls is not None
560 + self.moe_kernel = make_nvfp4_moe_kernel(
561 + moe_quant_config=self.moe_quant_config,
562 + moe_config=self.moe,
563 + experts_cls=self.experts_cls,
564 + shared_experts=layer.shared_experts,
565 + routing_tables=layer._maybe_init_expert_routing_tables(),
566 + )
567
568 def maybe_make_prepare_finalize(
569 self,
570 routing_tables: tuple[torch.Tensor, torch.Tensor, torch.Tensor] | None = None,
583 - ) -> mk.FusedMoEPrepareAndFinalize | None:
584 - raise ValueError(
585 - f"{self.__class__.__name__} uses the new modular kernel initialization "
586 - "logic. This function should not be called."
587 - )
588 -
589 - def select_gemm_impl(
590 - self,
591 - prepare_finalize: mk.FusedMoEPrepareAndFinalize,
592 - layer: torch.nn.Module,
593 - ) -> mk.FusedMoEPermuteExpertsUnpermute:
571 + ) -> mk.FusedMoEPrepareAndFinalizeModular | None:
572 raise ValueError(
573 f"{self.__class__.__name__} uses the new modular kernel initialization "
574 "logic. This function should not be called."
575 )
576
599 - def get_fused_moe_quant_config(
600 - self, layer: torch.nn.Module
601 - ) -> FusedMoEQuantConfig | None:
577 + def get_fused_moe_quant_config(self, layer: torch.nn.Module) -> FusedMoEQuantConfig:
578 return make_nvfp4_moe_quant_config(
579 backend=self.nvfp4_backend,
580 w13_scale=layer.w13_weight_scale,
@@ -609,13 +585,6 @@ class CompressedTensorsW4A4Nvfp4MoEMethod(CompressedTensorsMoEMethod):
585 a2_scale=layer.w2_input_scale,
586 )
587
612 - @property
613 - def is_monolithic(self) -> bool:
614 - return (
615 - self.nvfp4_backend == NvFp4MoeBackend.FLASHINFER_TRTLLM
616 - and not self.moe.moe_parallel_config.enable_eplb
617 - )
618 -
588 def apply_monolithic(
589 self,
590 layer: FusedMoE,
@@ -623,24 +592,20 @@ class CompressedTensorsW4A4Nvfp4MoEMethod(CompressedTensorsMoEMethod):
592 router_logits: torch.Tensor,
593 ) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]:
594 assert self.is_monolithic
626 - assert layer.activation == MoEActivation.SILU, (
627 - f"Only SiLU activation is supported, not {layer.activation}."
628 - )
629 - assert (
630 - self.nvfp4_backend == NvFp4MoeBackend.FLASHINFER_TRTLLM
631 - and not layer.enable_eplb
632 - )
633 - return flashinfer_trtllm_fp4_moe(
634 - layer=layer,
635 - x=x,
636 - router_logits=router_logits,
637 - top_k=layer.top_k,
595 + assert self.moe_kernel is not None
596 + return self.moe_kernel.apply_monolithic(
597 + x,
598 + layer.w13_weight,
599 + layer.w2_weight,
600 + router_logits,
601 activation=layer.activation,
602 global_num_experts=layer.global_num_experts,
603 + expert_map=layer.expert_map,
604 + apply_router_weight_on_input=layer.apply_router_weight_on_input,
605 num_expert_group=layer.num_expert_group,
606 topk_group=layer.topk_group,
642 - custom_routing_function=layer.custom_routing_function,
607 e_score_correction_bias=layer.e_score_correction_bias,
608 + routed_scaling_factor=layer.routed_scaling_factor,
609 )
610
611 def apply(
@@ -651,34 +616,19 @@ class CompressedTensorsW4A4Nvfp4MoEMethod(CompressedTensorsMoEMethod):
616 topk_ids: torch.Tensor,
617 shared_experts_input: torch.Tensor | None,
618 ) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]:
654 - assert not self.is_monolithic
655 -
656 - # EPLB path
657 - if self.nvfp4_backend == NvFp4MoeBackend.FLASHINFER_TRTLLM:
658 - assert layer.enable_eplb
659 - return flashinfer_trtllm_fp4_routed_moe(
660 - layer=layer,
661 - x=x,
662 - topk_ids=topk_ids,
663 - topk_weights=topk_weights,
664 - top_k=layer.top_k,
665 - activation=layer.activation,
666 - global_num_experts=layer.global_num_experts,
667 - )
668 - else:
669 - assert self.moe_mk is not None
670 - return self.moe_mk(
671 - x,
672 - layer.w13_weight,
673 - layer.w2_weight,
674 - topk_weights,
675 - topk_ids,
676 - activation=layer.activation,
677 - global_num_experts=layer.global_num_experts,
678 - expert_map=layer.expert_map,
679 - apply_router_weight_on_input=layer.apply_router_weight_on_input,
680 - shared_experts_input=shared_experts_input,
681 - )
619 + assert self.moe_kernel is not None
620 + return self.moe_kernel.apply(
621 + x,
622 + layer.w13_weight,
623 + layer.w2_weight,
624 + topk_weights,
625 + topk_ids,
626 + activation=layer.activation,
627 + global_num_experts=layer.global_num_experts,
628 + expert_map=layer.expert_map,
629 + apply_router_weight_on_input=layer.apply_router_weight_on_input,
630 + shared_experts_input=shared_experts_input,
631 + )
632
633
634 class CompressedTensorsW8A8Fp8MoEMethod(CompressedTensorsMoEMethod):
@@ -966,7 +916,7 @@ class CompressedTensorsW8A8Fp8MoEMethod(CompressedTensorsMoEMethod):
916 self.moe_quant_config = self.get_fused_moe_quant_config(layer)
917 if self.moe_quant_config:
918 assert self.experts_cls is not None
969 - self.moe_mk = make_fp8_moe_kernel(
919 + self.moe_kernel = make_fp8_moe_kernel(
920 moe_quant_config=self.moe_quant_config,
921 moe_config=self.moe,
922 fp8_backend=self.fp8_backend,
@@ -978,94 +928,47 @@ class CompressedTensorsW8A8Fp8MoEMethod(CompressedTensorsMoEMethod):
928 def maybe_make_prepare_finalize(
929 self,
930 routing_tables: tuple[torch.Tensor, torch.Tensor, torch.Tensor] | None = None,
981 - ) -> mk.FusedMoEPrepareAndFinalize | None:
982 - raise ValueError(
983 - f"{self.__class__.__name__} uses the new modular kernel initialization "
984 - "logic. This function should not be called."
985 - )
986 -
987 - def select_gemm_impl(
988 - self,
989 - prepare_finalize: mk.FusedMoEPrepareAndFinalize,
990 - layer: torch.nn.Module,
991 - ) -> mk.FusedMoEPermuteExpertsUnpermute:
931 + ) -> mk.FusedMoEPrepareAndFinalizeModular | None:
932 raise ValueError(
933 f"{self.__class__.__name__} uses the new modular kernel initialization "
934 "logic. This function should not be called."
935 )
936
997 - def get_fused_moe_quant_config(
998 - self, layer: torch.nn.Module
999 - ) -> FusedMoEQuantConfig | None:
1000 - w1_scale = layer.w13_weight_scale
1001 - w2_scale = layer.w2_weight_scale
1002 - a1_scale = layer.w13_input_scale
1003 - a2_scale = layer.w2_input_scale
1004 -
937 + def get_fused_moe_quant_config(self, layer: torch.nn.Module) -> FusedMoEQuantConfig:
938 + is_per_token = self.input_quant.strategy == QuantizationStrategy.TOKEN
939 return make_fp8_moe_quant_config(
940 fp8_backend=self.fp8_backend,
1007 - w1_scale=w1_scale,
1008 - w2_scale=w2_scale,
1009 - a1_scale=a1_scale,
1010 - a2_scale=a2_scale,
1011 - per_act_token_quant=(
1012 - self.input_quant.strategy == QuantizationStrategy.TOKEN
1013 - ),
1014 - per_out_ch_quant=(self.input_quant.strategy == QuantizationStrategy.TOKEN),
941 + w1_scale=layer.w13_weight_scale,
942 + w2_scale=layer.w2_weight_scale,
943 + a1_scale=layer.w13_input_scale,
944 + a2_scale=layer.w2_input_scale,
945 + per_act_token_quant=is_per_token,
946 + per_out_ch_quant=is_per_token,
947 block_shape=self.weight_block_size,
948 )
949
1018 - @property
1019 - def is_monolithic(self) -> bool:
1020 - return self.fp8_backend == Fp8MoeBackend.FLASHINFER_TRTLLM
1021 -
950 def apply_monolithic(
951 self,
952 layer: FusedMoE,
953 x: torch.Tensor,
954 router_logits: torch.Tensor,
955 ) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]:
1028 - assert self.is_monolithic
1029 - assert self.fp8_backend == Fp8MoeBackend.FLASHINFER_TRTLLM
1030 - assert layer.activation == MoEActivation.SILU, (
1031 - f"Only SiLU activation is supported, not {layer.activation}."
956 + assert self.moe_kernel is not None
957 + return self.moe_kernel.apply_monolithic(
958 + x,
959 + layer.w13_weight,
960 + layer.w2_weight,
961 + router_logits,
962 + activation=layer.activation,
963 + global_num_experts=layer.global_num_experts,
964 + expert_map=layer.expert_map,
965 + apply_router_weight_on_input=layer.apply_router_weight_on_input,
966 + num_expert_group=layer.num_expert_group,
967 + topk_group=layer.topk_group,
968 + e_score_correction_bias=layer.e_score_correction_bias,
969 + routed_scaling_factor=layer.routed_scaling_factor,
970 )
971
1034 - if self.block_quant:
1035 - import vllm.model_executor.layers.fused_moe.flashinfer_trtllm_moe # noqa: E501, F401
1036 -
1037 - return torch.ops.vllm.flashinfer_fused_moe_blockscale_fp8(
1038 - routing_logits=router_logits,
1039 - routing_bias=layer.e_score_correction_bias,
1040 - x=x,
1041 - w13_weight=layer.w13_weight,
1042 - w13_weight_scale_inv=layer.w13_weight_scale,
1043 - w2_weight=layer.w2_weight,
1044 - w2_weight_scale_inv=layer.w2_weight_scale,
1045 - global_num_experts=layer.global_num_experts,
1046 - top_k=layer.top_k,
1047 - num_expert_group=layer.num_expert_group,
1048 - topk_group=layer.topk_group,
1049 - intermediate_size=layer.intermediate_size_per_partition,
1050 - expert_offset=layer.ep_rank * layer.local_num_experts,
1051 - local_num_experts=layer.local_num_experts,
1052 - block_shape=self.weight_block_size,
1053 - routing_method_type=layer.routing_method_type,
1054 - routed_scaling=layer.routed_scaling_factor,
1055 - )
1056 - else:
1057 - return apply_fi_trtllm_fp8_per_tensor_moe(
1058 - layer=layer,
1059 - hidden_states=x,
1060 - router_logits=router_logits,
1061 - routing_bias=layer.e_score_correction_bias,
1062 - global_num_experts=layer.global_num_experts,
1063 - top_k=layer.top_k,
1064 - num_expert_group=layer.num_expert_group,
1065 - topk_group=layer.topk_group,
1066 - apply_router_weight_on_input=layer.apply_router_weight_on_input,
1067 - )
1068 -
972 def apply(
973 self,
974 layer: FusedMoE,
@@ -1075,8 +978,8 @@ class CompressedTensorsW8A8Fp8MoEMethod(CompressedTensorsMoEMethod):
978 shared_experts_input: torch.Tensor | None,
979 ) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]:
980 assert not self.is_monolithic
1078 - assert self.moe_mk is not None
1079 - return self.moe_mk(
981 + assert self.moe_kernel is not None
982 + return self.moe_kernel.apply(
983 x,
984 layer.w13_weight,
985 layer.w2_weight,
@@ -1652,9 +1555,9 @@ class CompressedTensorsWNA16MarlinMoEMethod(CompressedTensorsMoEMethod):
1555
1556 def select_gemm_impl(
1557 self,
1655 - prepare_finalize: mk.FusedMoEPrepareAndFinalize,
1558 + prepare_finalize: mk.FusedMoEPrepareAndFinalizeModular,
1559 layer: torch.nn.Module,
1657 - ) -> mk.FusedMoEPermuteExpertsUnpermute:
1560 + ) -> mk.FusedMoEExpertsModular:
1561 assert self.num_bits == 4, "only supporting w4"
1562 layer.w13_weight = layer.w13_weight_packed
1563 layer.w2_weight = layer.w2_weight_packed
@@ -1943,9 +1846,9 @@ class CompressedTensorsWNA16MoEMethod(CompressedTensorsMoEMethod):
1846
1847 def select_gemm_impl(
1848 self,
1946 - prepare_finalize: mk.FusedMoEPrepareAndFinalize,
1849 + prepare_finalize: mk.FusedMoEPrepareAndFinalizeModular,
1850 layer: torch.nn.Module,
1948 - ) -> mk.FusedMoEPermuteExpertsUnpermute:
1851 + ) -> mk.FusedMoEExpertsModular:
1852 if self.moe.is_lora_enabled:
1853 assert self.moe_quant_config is not None
1854 from vllm.triton_utils import HAS_TRITON
@@ -2527,7 +2430,7 @@ class CompressedTensorsW4A8Fp8MoEMethod(CompressedTensorsMoEMethod):
2430 def maybe_make_prepare_finalize(
2431 self,
2432 routing_tables: tuple[torch.Tensor, torch.Tensor, torch.Tensor] | None = None,
2530 - ) -> mk.FusedMoEPrepareAndFinalize | None:
2433 + ) -> mk.FusedMoEPrepareAndFinalizeModular | None:
2434 return super().maybe_make_prepare_finalize(routing_tables)
2435
2436 def get_fused_moe_quant_config(
@@ -2548,9 +2451,9 @@ class CompressedTensorsW4A8Fp8MoEMethod(CompressedTensorsMoEMethod):
2451
2452 def select_gemm_impl(
2453 self,
2551 - prepare_finalize: mk.FusedMoEPrepareAndFinalize,
2454 + prepare_finalize: mk.FusedMoEPrepareAndFinalizeModular,
2455 layer: torch.nn.Module,
2553 - ) -> mk.FusedMoEPermuteExpertsUnpermute:
2456 + ) -> mk.FusedMoEExpertsModular:
2457 assert self.moe_quant_config is not None
2458 assert (
2459 prepare_finalize.activation_format == FusedMoEActivationFormat.Standard
@@ -2558,7 +2461,7 @@ class CompressedTensorsW4A8Fp8MoEMethod(CompressedTensorsMoEMethod):
2461
2462 from vllm.model_executor.layers.fused_moe import CutlassExpertsW4A8Fp8
2463
2561 - experts: FusedMoEPermuteExpertsUnpermute
2464 + experts: FusedMoEExpertsModular
2465
2466 logger.debug("CutlassExpertsW4A8Fp8(%s)", self.__class__.__name__)
2467 experts = CutlassExpertsW4A8Fp8(
vllm/model_executor/layers/quantization/fp8.py
+19 -78
@@ -23,17 +23,13 @@ from vllm.model_executor.layers.batch_invariant import (
23 from vllm.model_executor.layers.fused_moe import (
24 FusedMoE,
25 FusedMoEMethodBase,
26 - FusedMoEPermuteExpertsUnpermute,
27 - FusedMoEPrepareAndFinalize,
26 FusedMoeWeightScaleSupported,
29 - MoEActivation,
27 )
28 from vllm.model_executor.layers.fused_moe.config import (
29 FusedMoEQuantConfig,
30 )
31 from vllm.model_executor.layers.fused_moe.layer import UnquantizedFusedMoEMethod
32 from vllm.model_executor.layers.fused_moe.oracle.fp8 import (
36 - Fp8MoeBackend,
33 convert_to_fp8_moe_kernel_format,
34 make_fp8_moe_kernel,
35 make_fp8_moe_quant_config,
@@ -50,9 +46,6 @@ from vllm.model_executor.layers.quantization.base_config import (
46 QuantizeMethodBase,
47 )
48 from vllm.model_executor.layers.quantization.kv_cache import BaseKVCacheMethod
53 -from vllm.model_executor.layers.quantization.utils.flashinfer_utils import (
54 - apply_fi_trtllm_fp8_per_tensor_moe,
55 -)
49 from vllm.model_executor.layers.quantization.utils.fp8_utils import (
50 W8A8BlockFp8LinearOp,
51 create_fp8_input_scale,
@@ -860,14 +853,10 @@ class Fp8MoEMethod(FusedMoEMethodBase):
853 replace_parameter(layer, f"w13_{self.weight_scale_name}", w13_scale)
854 replace_parameter(layer, f"w2_{self.weight_scale_name}", w2_scale)
855
863 - # Setup modular kernel for TP case and naive DP/EP case.
864 - # In non-naive DP/EP case, we will create a ModularKernelMethod.
865 - # TODO(rob): unify these so FP8MoEMethod owns the ModularKernel
866 - # in both cases.
856 self.moe_quant_config = self.get_fused_moe_quant_config(layer)
857 if self.moe_quant_config:
858 assert self.experts_cls is not None
870 - self.moe_mk = make_fp8_moe_kernel(
859 + self.moe_kernel = make_fp8_moe_kernel(
860 moe_quant_config=self.moe_quant_config,
861 moe_config=self.moe,
862 fp8_backend=self.fp8_backend,
@@ -930,29 +919,13 @@ class Fp8MoEMethod(FusedMoEMethodBase):
919 def maybe_make_prepare_finalize(
920 self,
921 routing_tables: tuple[torch.Tensor, torch.Tensor, torch.Tensor] | None = None,
933 - ) -> mk.FusedMoEPrepareAndFinalize | None:
934 - raise ValueError(
935 - f"{self.__class__.__name__} uses the new modular kernel initialization "
936 - "logic. This function should not be called."
937 - )
938 -
939 - def select_gemm_impl(
940 - self,
941 - prepare_finalize: FusedMoEPrepareAndFinalize,
942 - layer: torch.nn.Module,
943 - ) -> FusedMoEPermuteExpertsUnpermute:
922 + ) -> mk.FusedMoEPrepareAndFinalizeModular | None:
923 raise ValueError(
924 f"{self.__class__.__name__} uses the new modular kernel initialization "
925 "logic. This function should not be called."
926 )
927
949 - def get_fused_moe_quant_config(
950 - self, layer: torch.nn.Module
951 - ) -> FusedMoEQuantConfig | None:
952 - # TRTLLM does not use Modular Kernel.
953 - if self.fp8_backend == Fp8MoeBackend.FLASHINFER_TRTLLM:
954 - return None
955 -
928 + def get_fused_moe_quant_config(self, layer: torch.nn.Module) -> FusedMoEQuantConfig:
929 w1_scale = getattr(layer, f"w13_{self.weight_scale_name}")
930 w2_scale = getattr(layer, f"w2_{self.weight_scale_name}")
931 a1_scale = layer.w13_input_scale
@@ -983,10 +956,6 @@ class Fp8MoEMethod(FusedMoEMethodBase):
956 def supports_eplb(self) -> bool:
957 return True
958
986 - @property
987 - def is_monolithic(self) -> bool:
988 - return self.fp8_backend == Fp8MoeBackend.FLASHINFER_TRTLLM
989 -
959 def apply_monolithic(
960 self,
961 layer: FusedMoE,
@@ -994,50 +963,22 @@ class Fp8MoEMethod(FusedMoEMethodBase):
963 router_logits: torch.Tensor,
964 ) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]:
965 assert self.is_monolithic
997 - assert self.fp8_backend == Fp8MoeBackend.FLASHINFER_TRTLLM
998 -
999 - # TODO(rob): convert this to MK.
1000 - if layer.enable_eplb:
1001 - raise NotImplementedError("EPLB not supported for `Fp8MoEMethod` yet.")
1002 - assert layer.activation == MoEActivation.SILU, (
1003 - f"Expected 'silu' activation but got {layer.activation}"
966 + assert self.moe_kernel is not None
967 + return self.moe_kernel.apply_monolithic(
968 + x,
969 + layer.w13_weight,
970 + layer.w2_weight,
971 + router_logits,
972 + activation=layer.activation,
973 + global_num_experts=layer.global_num_experts,
974 + expert_map=layer.expert_map,
975 + apply_router_weight_on_input=layer.apply_router_weight_on_input,
976 + num_expert_group=layer.num_expert_group,
977 + topk_group=layer.topk_group,
978 + e_score_correction_bias=layer.e_score_correction_bias,
979 + routed_scaling_factor=layer.routed_scaling_factor,
980 )
981
1006 - if self.block_quant:
1007 - import vllm.model_executor.layers.fused_moe.flashinfer_trtllm_moe # noqa: E501, F401
1008 -
1009 - return torch.ops.vllm.flashinfer_fused_moe_blockscale_fp8(
1010 - routing_logits=router_logits,
1011 - routing_bias=layer.e_score_correction_bias,
1012 - x=x,
1013 - w13_weight=layer.w13_weight,
1014 - w13_weight_scale_inv=layer.w13_weight_scale_inv,
1015 - w2_weight=layer.w2_weight,
1016 - w2_weight_scale_inv=layer.w2_weight_scale_inv,
1017 - global_num_experts=layer.global_num_experts,
1018 - top_k=layer.top_k,
1019 - num_expert_group=layer.num_expert_group,
1020 - topk_group=layer.topk_group,
1021 - intermediate_size=layer.intermediate_size_per_partition,
1022 - expert_offset=layer.ep_rank * layer.local_num_experts,
1023 - local_num_experts=layer.local_num_experts,
1024 - block_shape=self.weight_block_size,
1025 - routing_method_type=layer.routing_method_type,
1026 - routed_scaling=layer.routed_scaling_factor,
1027 - )
1028 - else:
1029 - return apply_fi_trtllm_fp8_per_tensor_moe(
1030 - layer=layer,
1031 - hidden_states=x,
1032 - router_logits=router_logits,
1033 - routing_bias=layer.e_score_correction_bias,
1034 - global_num_experts=layer.global_num_experts,
1035 - top_k=layer.top_k,
1036 - num_expert_group=layer.num_expert_group,
1037 - topk_group=layer.topk_group,
1038 - apply_router_weight_on_input=layer.apply_router_weight_on_input,
1039 - )
1040 -
982 def apply(
983 self,
984 layer: FusedMoE,
@@ -1046,9 +987,9 @@ class Fp8MoEMethod(FusedMoEMethodBase):
987 topk_ids: torch.Tensor,
988 shared_experts_input: torch.Tensor | None,
989 ) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]:
1049 - assert self.moe_mk is not None
990 assert not self.is_monolithic
1051 - return self.moe_mk(
991 + assert self.moe_kernel is not None
992 + return self.moe_kernel.apply(
993 x,
994 layer.w13_weight,
995 layer.w2_weight,
vllm/model_executor/layers/quantization/modelopt.py
+63 -166
@@ -13,7 +13,6 @@ from vllm.model_executor.kernels.linear import (
13 init_fp8_linear_kernel,
14 )
15 from vllm.model_executor.layers.attention import Attention, MLAAttention
16 -from vllm.model_executor.layers.fused_moe.activation import MoEActivation
16 from vllm.model_executor.layers.fused_moe.config import (
17 FusedMoEConfig,
18 FusedMoEQuantConfig,
@@ -24,14 +23,12 @@ from vllm.model_executor.layers.fused_moe.layer import (
23 FusedMoeWeightScaleSupported,
24 )
25 from vllm.model_executor.layers.fused_moe.oracle.fp8 import (
27 - Fp8MoeBackend,
26 convert_to_fp8_moe_kernel_format,
27 make_fp8_moe_kernel,
28 make_fp8_moe_quant_config,
29 select_fp8_moe_backend,
30 )
31 from vllm.model_executor.layers.fused_moe.oracle.nvfp4 import (
34 - NvFp4MoeBackend,
32 convert_to_nvfp4_moe_kernel_format,
33 is_global_sf_supported_for_nvfp4_backend,
34 make_nvfp4_moe_kernel,
@@ -49,13 +46,6 @@ from vllm.model_executor.layers.quantization.base_config import (
46 QuantizeMethodBase,
47 )
48 from vllm.model_executor.layers.quantization.kv_cache import BaseKVCacheMethod
52 -from vllm.model_executor.layers.quantization.utils.flashinfer_fp4_moe import (
53 - flashinfer_trtllm_fp4_moe,
54 - flashinfer_trtllm_fp4_routed_moe,
55 -)
56 -from vllm.model_executor.layers.quantization.utils.flashinfer_utils import (
57 - apply_fi_trtllm_fp8_per_tensor_moe,
58 -)
49 from vllm.model_executor.layers.quantization.utils.fp8_utils import (
50 W8A8BlockFp8LinearOp,
51 process_fp8_input_tensor_strategy_moe,
@@ -746,7 +736,7 @@ class ModelOptFp8MoEMethod(FusedMoEMethodBase):
736 def maybe_make_prepare_finalize(
737 self,
738 routing_tables: tuple[torch.Tensor, torch.Tensor, torch.Tensor] | None = None,
749 - ) -> mk.FusedMoEPrepareAndFinalize | None:
739 + ) -> mk.FusedMoEPrepareAndFinalizeModular | None:
740 raise ValueError(
741 f"{self.__class__.__name__} uses the new modular kernel initialization "
742 "logic. This function should not be called."
@@ -754,9 +744,9 @@ class ModelOptFp8MoEMethod(FusedMoEMethodBase):
744
745 def select_gemm_impl(
746 self,
757 - prepare_finalize: mk.FusedMoEPrepareAndFinalize,
747 + prepare_finalize: mk.FusedMoEPrepareAndFinalizeModular,
748 layer: torch.nn.Module,
759 - ) -> mk.FusedMoEPermuteExpertsUnpermute:
749 + ) -> mk.FusedMoEExpertsModular:
750 raise ValueError(
751 f"{self.__class__.__name__} uses the new modular kernel initialization "
752 "logic. This function should not be called."
@@ -871,16 +861,15 @@ class ModelOptFp8MoEMethod(FusedMoEMethodBase):
861
862 # Setup modular kernel.
863 self.moe_quant_config = self.get_fused_moe_quant_config(layer)
874 - if self.moe_quant_config:
875 - assert self.experts_cls is not None
876 - self.moe_mk = make_fp8_moe_kernel(
877 - moe_quant_config=self.moe_quant_config,
878 - moe_config=self.moe,
879 - fp8_backend=self.fp8_backend,
880 - experts_cls=self.experts_cls,
881 - routing_tables=layer._maybe_init_expert_routing_tables(),
882 - shared_experts=layer.shared_experts,
883 - )
864 + assert self.experts_cls is not None
865 + self.moe_kernel = make_fp8_moe_kernel(
866 + moe_quant_config=self.moe_quant_config,
867 + moe_config=self.moe,
868 + fp8_backend=self.fp8_backend,
869 + experts_cls=self.experts_cls,
870 + routing_tables=layer._maybe_init_expert_routing_tables(),
871 + shared_experts=layer.shared_experts,
872 + )
873
874 def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
875 w13 = layer.w13_weight
@@ -913,9 +902,7 @@ class ModelOptFp8MoEMethod(FusedMoEMethodBase):
902 layer, w13, w2, w13_scale, w2_scale, w13_input_scale, w2_input_scale
903 )
904
916 - def get_fused_moe_quant_config(
917 - self, layer: torch.nn.Module
918 - ) -> FusedMoEQuantConfig | None:
905 + def get_fused_moe_quant_config(self, layer: torch.nn.Module) -> FusedMoEQuantConfig:
906 w1_scale = layer.w13_weight_scale
907 w2_scale = layer.w2_weight_scale
908 a1_scale = layer.w13_input_scale
@@ -929,10 +916,6 @@ class ModelOptFp8MoEMethod(FusedMoEMethodBase):
916 a2_scale=a2_scale,
917 )
918
932 - @property
933 - def is_monolithic(self) -> bool:
934 - return self.fp8_backend == Fp8MoeBackend.FLASHINFER_TRTLLM
935 -
919 def apply_monolithic(
920 self,
921 layer: FusedMoE,
@@ -940,28 +923,20 @@ class ModelOptFp8MoEMethod(FusedMoEMethodBase):
923 router_logits: torch.Tensor,
924 ) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]:
925 assert self.is_monolithic
943 - assert self.fp8_backend == Fp8MoeBackend.FLASHINFER_TRTLLM
944 - if layer.enable_eplb:
945 - raise NotImplementedError(
946 - "EPLB not supported for FlashInfer TRTLLM FP8 MoE Backend."
947 - )
948 - # TODO(rob): this validation should happen at kernel selection
949 - # time in the oracle rather than here.
950 - SUPPORTED_ACTIVATIONS = [MoEActivation.SILU, MoEActivation.RELU2_NO_MUL]
951 - assert layer.activation in SUPPORTED_ACTIVATIONS, (
952 - f"Only {SUPPORTED_ACTIVATIONS} activations are supported for FlashInfer "
953 - f"TRTLLM FP4 MoE, {layer.activation} found instead."
954 - )
955 - return apply_fi_trtllm_fp8_per_tensor_moe(
956 - layer=layer,
957 - hidden_states=x,
958 - router_logits=router_logits,
959 - routing_bias=layer.e_score_correction_bias,
926 + assert self.moe_kernel is not None
927 + return self.moe_kernel.apply_monolithic(
928 + x,
929 + layer.w13_weight,
930 + layer.w2_weight,
931 + router_logits,
932 + activation=layer.activation,
933 global_num_experts=layer.global_num_experts,
961 - top_k=layer.top_k,
934 + expert_map=layer.expert_map,
935 + apply_router_weight_on_input=layer.apply_router_weight_on_input,
936 num_expert_group=layer.num_expert_group,
937 topk_group=layer.topk_group,
964 - apply_router_weight_on_input=layer.apply_router_weight_on_input,
938 + e_score_correction_bias=layer.e_score_correction_bias,
939 + routed_scaling_factor=layer.routed_scaling_factor,
940 )
941
942 def apply(
@@ -973,25 +948,13 @@ class ModelOptFp8MoEMethod(FusedMoEMethodBase):
948 shared_experts_input: torch.Tensor | None,
949 ) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]:
950 assert not self.is_monolithic
976 -
977 - # TODO(rob): this validation should happen at kernel selection
978 - # time in the oracle rather than here.
979 - if self.fp8_backend == Fp8MoeBackend.FLASHINFER_CUTLASS:
980 - assert layer.activation in (
981 - MoEActivation.SILU,
982 - MoEActivation.RELU2_NO_MUL,
983 - ), (
984 - "Expected activation to be in ('silu', 'relu2_no_mul'),"
985 - f"but got {layer.activation}"
986 - )
987 -
988 - assert self.moe_mk is not None
989 - return self.moe_mk(
990 - hidden_states=x,
991 - w1=layer.w13_weight,
992 - w2=layer.w2_weight,
993 - topk_weights=topk_weights,
994 - topk_ids=topk_ids,
951 + assert self.moe_kernel is not None
952 + return self.moe_kernel.apply(
953 + x,
954 + layer.w13_weight,
955 + layer.w2_weight,
956 + topk_weights,
957 + topk_ids,
958 activation=layer.activation,
959 global_num_experts=layer.global_num_experts,
960 expert_map=layer.expert_map,
@@ -1235,17 +1198,7 @@ class ModelOptNvFp4FusedMoE(FusedMoEMethodBase):
1198 def maybe_make_prepare_finalize(
1199 self,
1200 routing_tables: tuple[torch.Tensor, torch.Tensor, torch.Tensor] | None = None,
1238 - ) -> mk.FusedMoEPrepareAndFinalize | None:
1239 - raise ValueError(
1240 - f"{self.__class__.__name__} uses the new modular kernel initialization "
1241 - "logic. This function should not be called."
1242 - )
1243 -
1244 - def select_gemm_impl(
1245 - self,
1246 - prepare_finalize: mk.FusedMoEPrepareAndFinalize,
1247 - layer: torch.nn.Module,
1248 - ) -> mk.FusedMoEPermuteExpertsUnpermute:
1201 + ) -> mk.FusedMoEPrepareAndFinalizeModular | None:
1202 raise ValueError(
1203 f"{self.__class__.__name__} uses the new modular kernel initialization "
1204 "logic. This function should not be called."
@@ -1420,51 +1373,18 @@ class ModelOptNvFp4FusedMoE(FusedMoEMethodBase):
1373 replace_parameter(layer, "w2_weight_scale_2", w2_scale_2)
1374 replace_parameter(layer, "w2_input_scale", a2_scale)
1375
1423 - # Setup modular kernel for TP case and naive DP/EP case.
1424 - # In non-naive DP/EP case, we will create a ModularKernelMethod.
1425 - # TODO(rob): unify these so FP8MoEMethod owns the ModularKernel
1426 - # in both cases.
1376 + # Setup modular kernel.
1377 self.moe_quant_config = self.get_fused_moe_quant_config(layer)
1428 - if self.moe_quant_config:
1429 - assert self.experts_cls is not None
1430 - self.moe_mk = make_nvfp4_moe_kernel(
1431 - moe_quant_config=self.moe_quant_config,
1432 - moe_config=self.moe,
1433 - experts_cls=self.experts_cls,
1434 - shared_experts=layer.shared_experts,
1435 - routing_tables=layer._maybe_init_expert_routing_tables(),
1436 - )
1437 -
1438 - @property
1439 - def do_post_quant_allgather(self):
1440 - return self.nvfp4_backend == NvFp4MoeBackend.FLASHINFER_TRTLLM
1441 -
1442 - def prepare_dp_allgather_tensor(
1443 - self,
1444 - layer: FusedMoE,
1445 - hidden_states: torch.Tensor,
1446 - router_logits: torch.Tensor,
1447 - ) -> tuple[torch.Tensor, list[torch.Tensor]]:
1448 - """Optionally prepare extra tensors to carry through DP allgather/EP."""
1449 - if self.nvfp4_backend != NvFp4MoeBackend.FLASHINFER_TRTLLM:
1450 - raise RuntimeError(
1451 - "prepare_dp_allgather_tensor is only supported for "
1452 - "FlashInfer TRTLLM NVFP4 MoE backend."
1453 - )
1454 -
1455 - import flashinfer
1456 -
1457 - hidden_states_fp4, hidden_states_sf = flashinfer.fp4_quantize(
1458 - hidden_states,
1459 - layer.a1_gscale,
1460 - is_sf_swizzled_layout=False,
1378 + assert self.experts_cls is not None
1379 + self.moe_kernel = make_nvfp4_moe_kernel(
1380 + moe_quant_config=self.moe_quant_config,
1381 + moe_config=self.moe,
1382 + experts_cls=self.experts_cls,
1383 + shared_experts=layer.shared_experts,
1384 + routing_tables=layer._maybe_init_expert_routing_tables(),
1385 )
1462 - extra_tensors: list[torch.Tensor] = [hidden_states_sf]
1463 - return hidden_states_fp4, extra_tensors
1386
1465 - def get_fused_moe_quant_config(
1466 - self, layer: torch.nn.Module
1467 - ) -> FusedMoEQuantConfig | None:
1387 + def get_fused_moe_quant_config(self, layer: torch.nn.Module) -> FusedMoEQuantConfig:
1388 return make_nvfp4_moe_quant_config(
1389 backend=self.nvfp4_backend,
1390 w13_scale=layer.w13_weight_scale,
@@ -1479,13 +1399,6 @@ class ModelOptNvFp4FusedMoE(FusedMoEMethodBase):
1399 def supports_eplb(self) -> bool:
1400 return True
1401
1482 - @property
1483 - def is_monolithic(self) -> bool:
1484 - return (
1485 - self.nvfp4_backend == NvFp4MoeBackend.FLASHINFER_TRTLLM
1486 - and not self.moe.moe_parallel_config.enable_eplb
1487 - )
1488 -
1402 def apply_monolithic(
1403 self,
1404 layer: FusedMoE,
@@ -1493,22 +1406,20 @@ class ModelOptNvFp4FusedMoE(FusedMoEMethodBase):
1406 router_logits: torch.Tensor,
1407 ) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]:
1408 assert self.is_monolithic
1496 - assert (
1497 - self.nvfp4_backend == NvFp4MoeBackend.FLASHINFER_TRTLLM
1498 - and not layer.enable_eplb
1499 - )
1500 -
1501 - return flashinfer_trtllm_fp4_moe(
1502 - layer=layer,
1503 - x=x,
1504 - router_logits=router_logits,
1505 - top_k=layer.top_k,
1409 + assert self.moe_kernel is not None
1410 + return self.moe_kernel.apply_monolithic(
1411 + x,
1412 + layer.w13_weight,
1413 + layer.w2_weight,
1414 + router_logits,
1415 activation=layer.activation,
1416 global_num_experts=layer.global_num_experts,
1417 + expert_map=layer.expert_map,
1418 + apply_router_weight_on_input=layer.apply_router_weight_on_input,
1419 num_expert_group=layer.num_expert_group,
1420 topk_group=layer.topk_group,
1510 - custom_routing_function=layer.custom_routing_function,
1421 e_score_correction_bias=layer.e_score_correction_bias,
1422 + routed_scaling_factor=layer.routed_scaling_factor,
1423 )
1424
1425 def apply(
@@ -1520,33 +1431,19 @@ class ModelOptNvFp4FusedMoE(FusedMoEMethodBase):
1431 shared_experts_input: torch.Tensor | None,
1432 ) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]:
1433 assert not self.is_monolithic
1523 -
1524 - # EPLB path
1525 - if self.nvfp4_backend == NvFp4MoeBackend.FLASHINFER_TRTLLM:
1526 - assert layer.enable_eplb
1527 - return flashinfer_trtllm_fp4_routed_moe(
1528 - layer=layer,
1529 - x=x,
1530 - topk_ids=topk_ids,
1531 - topk_weights=topk_weights,
1532 - top_k=layer.top_k,
1533 - activation=layer.activation,
1534 - global_num_experts=layer.global_num_experts,
1535 - )
1536 - else:
1537 - assert self.moe_mk is not None
1538 - return self.moe_mk(
1539 - hidden_states=x,
1540 - w1=layer.w13_weight,
1541 - w2=layer.w2_weight,
1542 - topk_weights=topk_weights,
1543 - topk_ids=topk_ids,
1544 - activation=layer.activation,
1545 - global_num_experts=layer.global_num_experts,
1546 - expert_map=layer.expert_map,
1547 - apply_router_weight_on_input=layer.apply_router_weight_on_input,
1548 - shared_experts_input=shared_experts_input,
1549 - )
1434 + assert self.moe_kernel is not None
1435 + return self.moe_kernel.apply(
1436 + x,
1437 + layer.w13_weight,
1438 + layer.w2_weight,
1439 + topk_weights,
1440 + topk_ids,
1441 + activation=layer.activation,
1442 + global_num_experts=layer.global_num_experts,
1443 + expert_map=layer.expert_map,
1444 + apply_router_weight_on_input=layer.apply_router_weight_on_input,
1445 + shared_experts_input=shared_experts_input,
1446 + )
1447
1448
1449 ModelOptNvFp4Config.LinearMethodCls = ModelOptNvFp4LinearMethod
vllm/model_executor/layers/quantization/mxfp4.py
+7 -7
@@ -266,7 +266,7 @@ class Mxfp4MoEMethod(FusedMoEMethodBase):
266 )
267 self._cache_permute_indices: dict[torch.Size, torch.Tensor] = {}
268 # Initialized in process_weights_after_loading for CUTLASS/SM90 backends
269 - self.moe_mk: mk.FusedMoEModularKernel | None = None
269 + self.moe_kernel: mk.FusedMoEKernel | None = None
270
271 def create_weights(
272 self,
@@ -440,7 +440,7 @@ class Mxfp4MoEMethod(FusedMoEMethodBase):
440 )
441 assert prepare_finalize is not None
442
443 - self.moe_mk = mk.FusedMoEModularKernel(
443 + self.moe_kernel = mk.FusedMoEKernel(
444 prepare_finalize,
445 MarlinExperts(
446 self.moe,
@@ -789,7 +789,7 @@ class Mxfp4MoEMethod(FusedMoEMethodBase):
789 )
790 assert prepare_finalize is not None
791
792 - self.moe_mk = mk.FusedMoEModularKernel(
792 + self.moe_kernel = mk.FusedMoEKernel(
793 prepare_finalize,
794 FlashInferExperts(
795 moe_config=self.moe,
@@ -954,9 +954,9 @@ class Mxfp4MoEMethod(FusedMoEMethodBase):
954
955 def select_gemm_impl(
956 self,
957 - prepare_finalize: mk.FusedMoEPrepareAndFinalize,
957 + prepare_finalize: mk.FusedMoEPrepareAndFinalizeModular,
958 layer: torch.nn.Module,
959 - ) -> mk.FusedMoEPermuteExpertsUnpermute:
959 + ) -> mk.FusedMoEExpertsModular:
960 if (
961 prepare_finalize.activation_format
962 == mk.FusedMoEActivationFormat.BatchedExperts
@@ -1043,8 +1043,8 @@ class Mxfp4MoEMethod(FusedMoEMethodBase):
1043 or self.mxfp4_backend == Mxfp4Backend.MARLIN
1044 )
1045
1046 - assert self.moe_mk is not None
1047 - return self.moe_mk(
1046 + assert self.moe_kernel is not None
1047 + return self.moe_kernel.apply(
1048 hidden_states=x,
1049 w1=layer.w13_weight,
1050 w2=layer.w2_weight,
vllm/model_executor/layers/quantization/utils/flashinfer_fp4_moe.py
+13 -283
@@ -6,28 +6,18 @@ from typing import TYPE_CHECKING
6
7 import torch
8
9 -import vllm.model_executor.layers.fused_moe.modular_kernel as mk
10 -from vllm import _custom_ops as ops
9 +import vllm.envs as envs
10 from vllm.logger import init_logger
12 -from vllm.model_executor.layers.fused_moe.activation import MoEActivation
13 -from vllm.model_executor.layers.fused_moe.config import (
14 - FusedMoEConfig,
15 - FusedMoEParallelConfig,
16 - RoutingMethodType,
17 -)
11 from vllm.model_executor.layers.quantization.utils.flashinfer_utils import (
19 - activation_to_flashinfer_int,
12 align_fp4_moe_weights_for_fi,
13 )
14 from vllm.model_executor.layers.quantization.utils.nvfp4_utils import (
15 swizzle_blockscale,
16 )
25 -from vllm.model_executor.layers.quantization.utils.quant_utils import (
26 - QuantKey,
27 - kNvfp4Dynamic,
28 - kNvfp4Static,
29 -)
17 from vllm.platforms import current_platform
18 +from vllm.utils.flashinfer import (
19 + has_flashinfer_cutlass_fused_moe,
20 +)
21
22 if TYPE_CHECKING:
23 from vllm.model_executor.layers.fused_moe.layer import FusedMoE
@@ -42,92 +32,15 @@ __all__ = [
32 "reorder_w1w3_to_w3w1",
33 ]
34
45 -#
46 -# Methods used by the oracle for kernel selection.
47 -#
48 -
49 -
50 -def _supports_current_device() -> bool:
51 - """Supports only Blackwell-family GPUs."""
52 - p = current_platform
53 - return p.is_cuda() and p.is_device_capability_family(100)
54 -
55 -
56 -def _supports_no_act_and_mul() -> bool:
57 - """Supports non-gated MoE."""
58 - return True
59 -
60 -
61 -def _supports_quant_scheme(
62 - weight_key: QuantKey | None,
63 - activation_key: QuantKey | None,
64 -) -> bool:
65 - """Supports Nvfp4 quantization."""
66 - SUPPORTED_W_A = [
67 - (kNvfp4Static, kNvfp4Dynamic),
68 - ]
69 - return (weight_key, activation_key) in SUPPORTED_W_A
70 -
71 -
72 -def _supports_activation(activation: MoEActivation) -> bool:
73 - return activation in [MoEActivation.SILU, MoEActivation.RELU2_NO_MUL]
74 -
75 -
76 -def _supports_routing_method(
77 - routing_method: RoutingMethodType,
78 -) -> bool:
79 - """Monolithic kernels need to express router support."""
80 - # NOTE(rob): potentially allow others here. This is a conservative list.
81 - return routing_method in [
82 - RoutingMethodType.DeepSeekV3,
83 - RoutingMethodType.Renormalize,
84 - RoutingMethodType.RenormalizeNaive,
85 - RoutingMethodType.Llama4,
86 - ]
87 -
88 -
89 -def _supports_parallel_config(moe_parallel_config: FusedMoEParallelConfig) -> bool:
90 - """
91 - TRTLLM is a monolithic kernel that requires dispatch_router_logits() for
92 - the naive dispatch/combine path. DeepEP HT only implements dispatch() for
93 - the modular kernel path, so TRTLLM is incompatible with DeepEP HT.
94 - """
95 - return not moe_parallel_config.use_deepep_ht_kernels
96 -
97 -
98 -def is_supported_config_trtllm(
99 - moe_config: FusedMoEConfig,
100 - weight_key: QuantKey | None,
101 - activation_key: QuantKey | None,
102 - activation_format: mk.FusedMoEActivationFormat,
103 -) -> tuple[bool, str | None]:
104 - """
105 - This method mirrors mk.FusedMoEPermuteExpertsUnpermute.is_supported_config
106 - """
107 -
108 - def _make_reason(reason: str) -> str:
109 - return f"kernel does not support {reason}"
110 -
111 - if not _supports_current_device():
112 - return False, _make_reason(f"current device {current_platform.device_name}")
113 - elif not (moe_config.is_act_and_mul or _supports_no_act_and_mul()):
114 - return False, _make_reason("no act_and_mul MLP layer")
115 - elif not _supports_activation(moe_config.activation):
116 - return False, _make_reason(f"{moe_config.activation} activation")
117 - elif not _supports_quant_scheme(weight_key, activation_key):
118 - return False, _make_reason(f"quantization scheme {weight_key}x{activation_key}")
119 - elif not _supports_parallel_config(moe_config.moe_parallel_config):
120 - return False, _make_reason(f"parallel config {moe_config.moe_parallel_config}")
121 - elif not _supports_routing_method(moe_config.routing_method):
122 - return False, _make_reason(f"routing method {moe_config.routing_method}")
123 - elif activation_format != mk.FusedMoEActivationFormat.Standard:
124 - return False, _make_reason(f"activation format {activation_format}")
125 - elif moe_config.hidden_dim % 512 != 0:
126 - return False, _make_reason(
127 - f"hidden_dim must be divisible by 512, found {moe_config.hidden_dim}"
128 - )
35
130 - return True, None
36 +def is_flashinfer_fp4_cutlass_moe_available() -> bool:
37 + """Return `True` when FlashInfer CUTLASS NV-FP4 kernels can be used."""
38 + return (
39 + envs.VLLM_USE_FLASHINFER_MOE_FP4
40 + and has_flashinfer_cutlass_fused_moe()
41 + and current_platform.is_cuda()
42 + and current_platform.has_device_capability(100)
43 + )
44
45
46 def reorder_w1w3_to_w3w1(
@@ -276,190 +189,6 @@ def prepare_static_weights_for_trtllm_fp4_moe(
189 )
190
191
279 -def flashinfer_trtllm_fp4_moe(
280 - layer: torch.nn.Module,
281 - x: torch.Tensor | tuple[torch.Tensor, torch.Tensor],
282 - router_logits: torch.Tensor,
283 - top_k: int,
284 - activation: MoEActivation,
285 - global_num_experts: int,
286 - num_expert_group: int | None,
287 - topk_group: int | None,
288 - custom_routing_function: object | None,
289 - e_score_correction_bias: torch.Tensor | None,
290 -) -> torch.Tensor:
291 - """
292 - Apply FlashInfer TensorRT-LLM FP4 MoE kernel.
293 -
294 - Args:
295 - layer: The MoE layer with weights and scales
296 - x: Input tensor
297 - router_logits: Router logits for expert selection
298 - top_k: Number of experts to select per token
299 - activation: Activation function to use
300 - global_num_experts: Total number of experts across all ranks
301 - num_expert_group: Number of expert groups (for grouped routing)
302 - topk_group: Top-k within each group
303 - custom_routing_function: Custom routing function (e.g., Llama4)
304 - e_score_correction_bias: Optional routing bias correction
305 -
306 - Returns:
307 - Output tensor from the MoE layer
308 - """
309 - import flashinfer
310 -
311 - from vllm.model_executor.models.llama4 import Llama4MoE
312 -
313 - SUPPORTED_ACTIVATIONS = [MoEActivation.SILU, MoEActivation.RELU2_NO_MUL]
314 - assert activation in SUPPORTED_ACTIVATIONS, (
315 - f"Only {SUPPORTED_ACTIVATIONS} activations are supported for FlashInfer "
316 - f"TRTLLM FP4 MoE, {activation} found instead."
317 - )
318 -
319 - # Quantize input to FP4
320 - if isinstance(x, tuple):
321 - hidden_states_fp4, hidden_states_scale_linear_fp4 = x
322 - else:
323 - # hidden_states is the already quantized
324 - (hidden_states_fp4, hidden_states_scale_linear_fp4) = ops.scaled_fp4_quant(
325 - x, layer.a1_gscale, is_sf_swizzled_layout=False
326 - )
327 -
328 - # Determine routing method type
329 - use_llama4_routing = custom_routing_function is Llama4MoE.custom_routing_function
330 - routing_method_type = layer.routing_method_type
331 - if use_llama4_routing:
332 - routing_method_type = flashinfer.RoutingMethodType.Llama4
333 -
334 - # Cast to Fp32 (required by kernel).
335 - router_logits = (
336 - router_logits.to(torch.float32)
337 - if routing_method_type == RoutingMethodType.DeepSeekV3
338 - else router_logits
339 - )
340 -
341 - # Determine activation type
342 - activation_type = activation_to_flashinfer_int(layer.activation)
343 -
344 - # Call TRT-LLM FP4 block-scale MoE kernel
345 - out = flashinfer.fused_moe.trtllm_fp4_block_scale_moe(
346 - routing_logits=router_logits,
347 - routing_bias=e_score_correction_bias,
348 - hidden_states=hidden_states_fp4,
349 - hidden_states_scale=hidden_states_scale_linear_fp4.view(
350 - torch.float8_e4m3fn
351 - ).reshape(*hidden_states_fp4.shape[:-1], -1),
352 - gemm1_weights=layer.w13_weight.data,
353 - gemm1_weights_scale=layer.w13_weight_scale.data.view(torch.float8_e4m3fn),
354 - gemm1_bias=None,
355 - gemm1_alpha=None,
356 - gemm1_beta=None,
357 - gemm1_clamp_limit=None,
358 - gemm2_weights=layer.w2_weight.data,
359 - gemm2_weights_scale=layer.w2_weight_scale.data.view(torch.float8_e4m3fn),
360 - gemm2_bias=None,
361 - output1_scale_scalar=layer.g1_scale_c.data,
362 - output1_scale_gate_scalar=layer.g1_alphas.data,
363 - output2_scale_scalar=layer.g2_alphas.data,
364 - num_experts=global_num_experts,
365 - top_k=top_k,
366 - n_group=num_expert_group if num_expert_group is not None else 0,
367 - topk_group=topk_group if topk_group is not None else 0,
368 - intermediate_size=layer.intermediate_size_per_partition,
369 - local_expert_offset=layer.ep_rank * layer.local_num_experts,
370 - local_num_experts=layer.local_num_experts,
371 - routed_scaling_factor=None,
372 - routing_method_type=routing_method_type,
373 - do_finalize=True,
374 - activation_type=activation_type,
375 - )[0]
376 -
377 - return out
378 -
379 -
380 -def flashinfer_trtllm_fp4_routed_moe(
381 - layer: torch.nn.Module,
382 - x: torch.Tensor,
383 - topk_ids: torch.Tensor,
384 - topk_weights: torch.Tensor,
385 - top_k: int,
386 - activation: MoEActivation,
387 - global_num_experts: int,
388 -) -> torch.Tensor:
389 - """
390 - Apply FlashInfer TensorRT-LLM FP4 MoE kernel. Uses packed
391 - input top k expert indices and scores rather than computing
392 - top k expert indices from scores.
393 -
394 - Args:
395 - layer: The MoE layer with weights and scales
396 - x: Input tensor
397 - topk_ids: Ids of selected experts
398 - top_k: Number of experts to select per token
399 - activation: Activation function to use
400 - global_num_experts: Total number of experts across all ranks
401 -
402 - Returns:
403 - Output tensor from the MoE layer
404 - """
405 - import flashinfer
406 -
407 - # https://github.com/flashinfer-ai/flashinfer/blob/f0277fd1bff90e309e5c19cab36c5dae056d685d/flashinfer/fused_moe/core.py#L2535
408 - assert activation == MoEActivation.SILU, (
409 - "Only SiLU activation is supported for FlashInfer TRTLLM FP4 Routed MoE. "
410 - f"{activation} found instead."
411 - )
412 -
413 - # Pack top k ids and expert weights into a single int32 tensor, as
414 - # required by TRT-LLM
415 - packed_tensor = (topk_ids.to(torch.int32) << 16) | topk_weights.to(
416 - torch.bfloat16
417 - ).view(torch.int16)
418 -
419 - if isinstance(x, tuple):
420 - # Hidden_states is the already quantized
421 - hidden_states_fp4, hidden_states_scale_linear_fp4 = x
422 - else:
423 - # Quantize input to FP4
424 - (hidden_states_fp4, hidden_states_scale_linear_fp4) = ops.scaled_fp4_quant(
425 - x, layer.a1_gscale, is_sf_swizzled_layout=False
426 - )
427 -
428 - # Call TRT-LLM FP4 block-scale MoE kernel
429 - out = flashinfer.fused_moe.trtllm_fp4_block_scale_routed_moe(
430 - topk_ids=packed_tensor,
431 - routing_bias=None,
432 - hidden_states=hidden_states_fp4,
433 - hidden_states_scale=hidden_states_scale_linear_fp4.view(
434 - torch.float8_e4m3fn
435 - ).reshape(*hidden_states_fp4.shape[:-1], -1),
436 - gemm1_weights=layer.w13_weight.data,
437 - gemm1_weights_scale=layer.w13_weight_scale.data.view(torch.float8_e4m3fn),
438 - gemm1_bias=None,
439 - gemm1_alpha=None,
440 - gemm1_beta=None,
441 - gemm1_clamp_limit=None,
442 - gemm2_weights=layer.w2_weight.data,
443 - gemm2_weights_scale=layer.w2_weight_scale.data.view(torch.float8_e4m3fn),
444 - gemm2_bias=None,
445 - output1_scale_scalar=layer.g1_scale_c.data,
446 - output1_scale_gate_scalar=layer.g1_alphas.data,
447 - output2_scale_scalar=layer.g2_alphas.data,
448 - num_experts=global_num_experts,
449 - top_k=top_k,
450 - n_group=0,
451 - topk_group=0,
452 - intermediate_size=layer.intermediate_size_per_partition,
453 - local_expert_offset=layer.ep_rank * layer.local_num_experts,
454 - local_num_experts=layer.local_num_experts,
455 - routed_scaling_factor=None,
456 - routing_method_type=1,
457 - do_finalize=True,
458 - )[0]
459 -
460 - return out
461 -
462 -
192 def prepare_nvfp4_moe_layer_for_fi_or_cutlass(
193 backend: "NvFp4MoeBackend",
194 layer: "FusedMoE",
@@ -526,6 +255,7 @@ def prepare_nvfp4_moe_layer_for_fi_or_cutlass(
255 )
256 )
257 layer.intermediate_size_per_partition = padded_intermediate
258 + layer.moe_config.intermediate_size_per_partition = padded_intermediate
259
260 w13, w13_scale, w2, w2_scale = prepare_static_weights_for_trtllm_fp4_moe(
261 w13,
vllm/model_executor/layers/quantization/utils/flashinfer_utils.py
+11 -108
@@ -1,6 +1,7 @@
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 from enum import Enum
4 +from typing import TYPE_CHECKING
5
6 import torch
7
@@ -10,6 +11,9 @@ from vllm.model_executor.layers.fused_moe.activation import MoEActivation
11 from vllm.platforms import current_platform
12 from vllm.utils.math_utils import round_up
13
14 +if TYPE_CHECKING:
15 + from flashinfer.fused_moe.core import ActivationType
16 +
17 logger = init_logger(__name__)
18
19
@@ -20,6 +24,10 @@ class FlashinferMoeBackend(Enum):
24
25
26 def activation_to_flashinfer_int(activation: MoEActivation) -> int:
27 + return activation_to_flashinfer_type(activation).value
28 +
29 +
30 +def activation_to_flashinfer_type(activation: MoEActivation) -> "ActivationType":
31 from flashinfer.fused_moe.core import ActivationType
32
33 # silu and gelu are mapped to their gated versions SwiGLU and GeGLU respectively
@@ -30,7 +38,7 @@ def activation_to_flashinfer_int(activation: MoEActivation) -> int:
38 MoEActivation.GELU: ActivationType.Geglu,
39 MoEActivation.RELU2_NO_MUL: ActivationType.Relu2,
40 }
33 - return ACTIVATION_TO_FI_ACTIVATION[activation].value
41 + return ACTIVATION_TO_FI_ACTIVATION[activation]
42
43
44 def swap_w13_to_w31(x: torch.Tensor) -> torch.Tensor:
@@ -87,104 +95,6 @@ def rotate_weights_for_fi_trtllm_fp8_per_tensor_moe(
95 )
96
97
90 -def register_scales_for_trtllm_fp8_per_tensor_moe(
91 - layer: torch.nn.Module,
92 - w13_scale: torch.Tensor,
93 - w13_input_scale: torch.Tensor,
94 - w2_scale: torch.Tensor,
95 - w2_input_scale: torch.Tensor,
96 -) -> None:
97 - """Register necessary scales for FlashInfer TRTLLM FP8 MoE kernel"""
98 - g1_alphas, g2_alphas = make_fp8_moe_alpha_scales_for_fi(
99 - w13_scale=w13_scale,
100 - w13_input_scale=w13_input_scale,
101 - w2_scale=w2_scale,
102 - w2_input_scale=w2_input_scale,
103 - )
104 - layer.w2_input_scale_inv = 1.0 / w2_input_scale
105 - layer.output1_scales_gate_scalar = g1_alphas
106 -
107 - if layer.activation.is_gated:
108 - layer.output1_scales_scalar = g1_alphas * layer.w2_input_scale_inv
109 - else:
110 - layer.output1_scales_scalar = (
111 - torch.ones_like(g1_alphas) * layer.w2_input_scale_inv
112 - )
113 - layer.output2_scales_scalar = g2_alphas
114 -
115 -
116 -def apply_fi_trtllm_fp8_per_tensor_moe(
117 - layer: torch.nn.Module,
118 - hidden_states: torch.Tensor,
119 - router_logits: torch.Tensor,
120 - routing_bias: torch.Tensor | None,
121 - top_k: int,
122 - num_expert_group: int | None,
123 - topk_group: int | None,
124 - global_num_experts: int,
125 - apply_router_weight_on_input: bool,
126 -) -> torch.Tensor:
127 - from flashinfer.fused_moe import RoutingMethodType
128 -
129 - import vllm.model_executor.layers.fused_moe.flashinfer_trtllm_moe # noqa: E501, F401
130 - from vllm.model_executor.models.llama4 import Llama4MoE
131 -
132 - # Added to the layer by: register_scales_for_trtllm_fp8_per_tensor_moe
133 - assert (
134 - hasattr(layer, "output1_scales_scalar")
135 - and hasattr(layer, "output1_scales_gate_scalar")
136 - and hasattr(layer, "output2_scales_scalar")
137 - )
138 -
139 - if layer.routing_method_type == RoutingMethodType.Llama4:
140 - assert (
141 - not layer.renormalize
142 - and layer.custom_routing_function == Llama4MoE.custom_routing_function
143 - ), (
144 - "FusedMoE flashinfer kernels with Llama4 routing method are only "
145 - "supported for Llama4"
146 - )
147 - else:
148 - assert layer.custom_routing_function is None, (
149 - "Custom routing function is only supported for Llama4"
150 - )
151 - activation_type = activation_to_flashinfer_int(layer.activation)
152 -
153 - return torch.ops.vllm.fi_trtllm_fp8_per_tensor_moe(
154 - routing_logits=router_logits,
155 - routing_bias=routing_bias,
156 - hidden_states=hidden_states,
157 - input_scale=layer.w13_input_scale,
158 - gemm1_weights=layer.w13_weight,
159 - gemm2_weights=layer.w2_weight,
160 - output1_scales_scalar=layer.output1_scales_scalar,
161 - output1_scales_gate_scalar=layer.output1_scales_gate_scalar,
162 - output2_scales_scalar=layer.output2_scales_scalar,
163 - num_experts=global_num_experts,
164 - top_k=top_k,
165 - num_expert_group=num_expert_group,
166 - topk_group=topk_group,
167 - intermediate_size=layer.intermediate_size_per_partition,
168 - local_expert_offset=layer.ep_rank * layer.local_num_experts,
169 - local_num_experts=layer.local_num_experts,
170 - use_routing_scales_on_input=apply_router_weight_on_input,
171 - routing_method_type=layer.routing_method_type,
172 - activation_type=activation_type,
173 - )
174 -
175 -
176 -def make_fp8_moe_alpha_scales_for_fi(
177 - w13_scale: torch.Tensor,
178 - w13_input_scale: torch.Tensor,
179 - w2_scale: torch.Tensor,
180 - w2_input_scale: torch.Tensor,
181 -) -> tuple[torch.Tensor, torch.Tensor]:
182 - g1_alphas = (w13_scale * w13_input_scale).squeeze()
183 - g2_alphas = (w2_scale * w2_input_scale).squeeze()
184 -
185 - return g1_alphas, g2_alphas
186 -
187 -
98 def get_flashinfer_moe_backend() -> FlashinferMoeBackend:
99 backend_map = {
100 "throughput": FlashinferMoeBackend.CUTLASS,
@@ -432,6 +342,7 @@ def prepare_fp8_moe_layer_for_fi(
342 min_alignment,
343 )
344 layer.intermediate_size_per_partition = new_intermediate
345 + layer.moe_config.intermediate_size_per_partition = new_intermediate
346
347 # FI kernels require W31 layout rather than W13.
348 if layer.moe_config.is_act_and_mul:
@@ -440,20 +351,12 @@ def prepare_fp8_moe_layer_for_fi(
351 w13_scale = swap_w13_to_w31(w13_scale)
352
353 # FI TRT-LLM FP8 per-tensor MoE kernel requires weight shuffle
443 - # and registration of alpha scales. Note that we do not register
444 - # as nn.Parameters since they are not needed for weight-reloading.
354 + # and registration of alpha scales.
355 if is_trtllm and not block_quant:
356 assert w13_input_scale is not None
357 assert w2_input_scale is not None
358
359 rotate_weights_for_fi_trtllm_fp8_per_tensor_moe(w13, w2, is_gated)
450 - register_scales_for_trtllm_fp8_per_tensor_moe(
451 - layer,
452 - w13_scale=w13_scale,
453 - w13_input_scale=w13_input_scale,
454 - w2_scale=w2_scale,
455 - w2_input_scale=w2_input_scale,
456 - )
360
361 # Clamp block scales to avoid NaN from the FlashInfer CUTLASS kernel.
362 # Some FP8 models have near-zero block scales (~1e-23) for dead/unused
vllm/model_executor/warmup/deep_gemm_warmup.py
+1 -1
@@ -172,7 +172,7 @@ def _fused_moe_grouped_gemm_may_use_deep_gemm(module: torch.nn.Module) -> bool:
172
173 # Further check if the ModularKernel implementation uses the DeepGemmExperts
174 return isinstance(
175 - module.quant_method.moe_mk, (DeepGemmExperts, TritonOrDeepGemmExperts)
175 + module.quant_method.moe_kernel, (DeepGemmExperts, TritonOrDeepGemmExperts)
176 )
177
178
vllm/model_executor/warmup/kernel_warmup.py
+9 -2
@@ -88,9 +88,14 @@ def flashinfer_autotune(runner: "GPUModelRunner") -> None:
88 Without autotuning, FlashInfer will rely on heuristics, which may
89 be significantly slower.
90 """
91 - from vllm.utils.flashinfer import autotune
91 + import vllm.utils.flashinfer as fi_utils
92 +
93 + with torch.inference_mode(), fi_utils.autotune():
94 + # Certain FlashInfer kernels (e.g. nvfp4 routed moe) are
95 + # incompatible with autotuning. This state is used to skip
96 + # those kernels during the autotuning process.
97 + fi_utils._is_fi_autotuning = True
98
93 - with torch.inference_mode(), autotune():
99 # We skip EPLB here since we don't want to record dummy metrics
100 # When autotuning with number of tokens m, flashinfer will autotune
101 # operations for all number of tokens up to m.
@@ -100,3 +105,5 @@ def flashinfer_autotune(runner: "GPUModelRunner") -> None:
105 skip_eplb=True,
106 is_profile=True,
107 )
108 +
109 + fi_utils._is_fi_autotuning = False
vllm/utils/flashinfer.py
+1
@@ -140,6 +140,7 @@ autotune = _lazy_import_wrapper(
140 "autotune",
141 fallback_fn=lambda *args, **kwargs: contextlib.nullcontext(),
142 )
143 +_is_fi_autotuning: bool = False
144
145
146 @functools.cache