[MoE] Share apply_moe_activation support metadata (#44359)

Signed-off-by: mgoin <mgoin64@gmail.com> Co-authored-by: OpenAI Codex <codex@openai.com>

Michael Goin committed Aug 5, 2026 at 15:05 UTC 8f158d0ee2475c64f66ee9ebd55a3606209db92f
13 files changed +425 -241
tests/kernels/moe/test_cutlass_moe.py
+78 -6
@@ -11,8 +11,15 @@ import vllm.model_executor.layers.fused_moe.modular_kernel as mk
11 from tests.kernels.moe.utils import make_dummy_moe_config
12 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
14 +from vllm.model_executor.layers.fused_moe import (
15 + ApplyMoEActivationConfig,
16 + fused_experts,
17 + fused_topk,
18 +)
19 +from vllm.model_executor.layers.fused_moe.activation import (
20 + MoEActivation,
21 + apply_moe_activation_supported,
22 +)
23 from vllm.model_executor.layers.fused_moe.all2all_utils import (
24 maybe_make_prepare_finalize,
25 )
@@ -24,8 +31,11 @@ from vllm.model_executor.layers.fused_moe.config import (
31 from vllm.model_executor.layers.fused_moe.experts.cutlass_moe import (
32 CutlassExpertsFp4,
33 CutlassExpertsFp8,
34 + CutlassExpertsMxfp4,
35 + CutlassExpertsW4A8Fp8,
36 run_cutlass_moe_fp8,
37 )
38 +from vllm.model_executor.layers.fused_moe.oracle import nvfp4 as nvfp4_oracle
39 from vllm.model_executor.layers.fused_moe.utils import moe_kernel_quantize_input
40 from vllm.platforms import current_platform
41 from vllm.utils.torch_utils import set_random_seed
@@ -53,10 +63,72 @@ MNK_FACTORS = [
63 vllm_config = VllmConfig(parallel_config=ParallelConfig(pipeline_parallel_size=1))
64
65
56 -def test_cutlass_moe_supports_gelu_tanh_activation_metadata():
57 - assert CutlassExpertsFp8._supports_activation(MoEActivation.GELU_TANH)
58 - assert CutlassExpertsFp4._supports_activation(MoEActivation.GELU_TANH)
59 - assert CutlassExpertsFp4._supports_activation(MoEActivation.GELU_TANH_NO_MUL)
66 +@pytest.mark.parametrize(
67 + "experts_cls",
68 + [
69 + CutlassExpertsFp8,
70 + CutlassExpertsFp4,
71 + CutlassExpertsMxfp4,
72 + CutlassExpertsW4A8Fp8,
73 + ],
74 +)
75 +@pytest.mark.parametrize("activation", list(MoEActivation))
76 +def test_cutlass_moe_activation_metadata_tracks_shared_apply(
77 + experts_cls: type[mk.FusedMoEExperts], activation: MoEActivation
78 +):
79 + supports_shape = experts_cls._supports_no_act_and_mul() or activation.is_gated
80 + expected = supports_shape and apply_moe_activation_supported(activation)
81 +
82 + assert experts_cls._supports_activation(activation) == expected
83 +
84 +
85 +def test_cutlass_moe_forwards_shared_activation_parameters():
86 + moe_config = make_dummy_moe_config()
87 + moe_config.swiglu_limit = 7.0
88 + moe_config.swiglu_alpha = 1.5
89 + moe_config.swiglu_beta = 0.25
90 + moe_config.activation_situ_beta = 2.0
91 + moe_config.activation_situ_linear_beta = 3.0
92 + quant_config = FusedMoEQuantConfig.make(gemm1_alpha=1.75)
93 +
94 + assert ApplyMoEActivationConfig.from_configs(
95 + moe_config, quant_config
96 + ) == ApplyMoEActivationConfig(
97 + clamp_limit=7.0,
98 + alpha=1.75,
99 + beta=0.25,
100 + activation_situ_beta=2.0,
101 + activation_situ_linear_beta=3.0,
102 + )
103 +
104 +
105 +@pytest.mark.parametrize(
106 + ("backend", "expected"),
107 + [
108 + ("cutlass", nvfp4_oracle.NvFp4MoeBackend.VLLM_CUTLASS),
109 + ("humming", nvfp4_oracle.NvFp4MoeBackend.HUMMING),
110 + ],
111 +)
112 +def test_nvfp4_clamp_allows_shared_activation_backends(
113 + monkeypatch, backend: str, expected: nvfp4_oracle.NvFp4MoeBackend
114 +):
115 + class SupportedExperts:
116 + @staticmethod
117 + def is_supported_config(*args, **kwargs):
118 + return True, None
119 +
120 + monkeypatch.setattr(
121 + nvfp4_oracle, "backend_to_kernel_cls", lambda backend: [SupportedExperts]
122 + )
123 + moe_config = make_dummy_moe_config()
124 + moe_config.moe_backend = backend
125 + moe_config.swiglu_limit = 7.0
126 +
127 + selected, _ = nvfp4_oracle.select_nvfp4_moe_backend(
128 + moe_config, weight_key=None, activation_key=None
129 + )
130 +
131 + assert selected == expected
132
133
134 @dataclasses.dataclass
tests/kernels/moe/test_moe.py
+68
@@ -27,6 +27,11 @@ from vllm.model_executor.layers.fused_moe import (
27 MoEActivation,
28 fused_topk,
29 )
30 +from vllm.model_executor.layers.fused_moe.activation import (
31 + ApplyMoEActivationConfig,
32 + apply_moe_activation,
33 + apply_moe_activation_supported,
34 +)
35 from vllm.model_executor.layers.fused_moe.config import (
36 FUSED_MOE_UNQUANTIZED_CONFIG,
37 int4_w4a16_moe_quant_config,
@@ -996,6 +1001,23 @@ def test_fused_marlin_moe(
1001 per_act_token_quant=True,
1002 )
1003
1004 + def instance_activation(
1005 + activation: MoEActivation,
1006 + output: torch.Tensor,
1007 + input: torch.Tensor,
1008 + *,
1009 + topk_ids: torch.Tensor | None = None,
1010 + expert_map: torch.Tensor | None = None,
1011 + ) -> None:
1012 + apply_moe_activation(
1013 + activation,
1014 + output,
1015 + input,
1016 + activation_config=ApplyMoEActivationConfig(),
1017 + topk_ids=topk_ids,
1018 + expert_map=expert_map,
1019 + )
1020 +
1021 marlin_output = fused_marlin_moe(
1022 a,
1023 w1_data.qweight,
@@ -1021,6 +1043,7 @@ def test_fused_marlin_moe(
1043 input_dtype=a_dtype,
1044 quant_type_id=b_type.id,
1045 is_k_full=is_k_full,
1046 + activation_func=instance_activation,
1047 )
1048
1049 torch.testing.assert_close(marlin_output, torch_output, atol=4e-2, rtol=0)
@@ -1249,6 +1272,51 @@ def _make_humming_indexed_experts(activation: MoEActivation):
1272 return experts
1273
1274
1275 +@pytest.mark.parametrize("activation", list(MoEActivation))
1276 +def test_humming_activation_metadata_tracks_shared_apply(activation: MoEActivation):
1277 + from vllm.model_executor.layers.fused_moe.experts.fused_humming_moe import (
1278 + HummingExpertsBase,
1279 + )
1280 +
1281 + assert HummingExpertsBase._supports_activation(
1282 + activation
1283 + ) == apply_moe_activation_supported(activation)
1284 +
1285 +
1286 +def test_humming_delegates_to_instance_activation():
1287 + from types import SimpleNamespace
1288 + from unittest.mock import Mock
1289 +
1290 + from vllm.model_executor.layers.fused_moe.experts.fused_humming_moe import (
1291 + HummingExpertsBase,
1292 + )
1293 +
1294 + activation_func = Mock()
1295 + activation_config = ApplyMoEActivationConfig(
1296 + clamp_limit=7.0,
1297 + alpha=1.5,
1298 + beta=0.25,
1299 + activation_situ_beta=2.0,
1300 + activation_situ_linear_beta=3.0,
1301 + )
1302 + experts = SimpleNamespace(
1303 + activation=activation_func,
1304 + activation_config=activation_config,
1305 + )
1306 + input = torch.empty(1, 2)
1307 + output = torch.empty(1, 1)
1308 +
1309 + HummingExpertsBase.apply_activation(
1310 + experts, MoEActivation.SWIGLUOAI_UNINTERLEAVE, output, input
1311 + )
1312 +
1313 + activation_func.assert_called_once_with(
1314 + activation=MoEActivation.SWIGLUOAI_UNINTERLEAVE,
1315 + input=input,
1316 + output=output,
1317 + )
1318 +
1319 +
1320 @pytest.mark.parametrize(
1321 "activation",
1322 [
tests/kernels/moe/test_triton_moe_no_act_mul.py
+49 -1
@@ -11,7 +11,12 @@ import pytest
11 import torch
12
13 from tests.kernels.moe.utils import make_dummy_moe_config
14 -from vllm.model_executor.layers.fused_moe.activation import MoEActivation
14 +from vllm.model_executor.layers.fused_moe.activation import (
15 + ApplyMoEActivationConfig,
16 + MoEActivation,
17 + apply_moe_activation,
18 + apply_moe_activation_supported,
19 +)
20 from vllm.model_executor.layers.fused_moe.config import (
21 FUSED_MOE_UNQUANTIZED_CONFIG,
22 )
@@ -31,6 +36,49 @@ NO_MUL_ACTIVATIONS = [
36 ]
37
38
39 +def test_apply_moe_activation_supported_contract():
40 + supported = {
41 + activation
42 + for activation in MoEActivation
43 + if apply_moe_activation_supported(activation)
44 + }
45 +
46 + assert supported == set(MoEActivation) - {MoEActivation.RELU2}
47 +
48 +
49 +@pytest.mark.skipif(not current_platform.is_cuda(), reason="Requires CUDA")
50 +@pytest.mark.parametrize(
51 + "activation",
52 + [
53 + activation
54 + for activation in MoEActivation
55 + if apply_moe_activation_supported(activation)
56 + ],
57 +)
58 +@torch.inference_mode()
59 +def test_supported_apply_moe_activation_executes(activation: MoEActivation):
60 + output_width = 64
61 + input_width = output_width * 2 if activation.is_gated else output_width
62 + input = torch.randn(2, input_width, device="cuda", dtype=torch.bfloat16)
63 + output = torch.empty(2, output_width, device="cuda", dtype=torch.bfloat16)
64 + activation_config = ApplyMoEActivationConfig()
65 + if activation == MoEActivation.SITU:
66 + activation_config = ApplyMoEActivationConfig(activation_situ_beta=1.0)
67 + elif activation == MoEActivation.SWIGLUOAI_UNINTERLEAVE:
68 + activation_config = ApplyMoEActivationConfig(clamp_limit=7.0)
69 +
70 + apply_moe_activation(activation, output, input, activation_config=activation_config)
71 +
72 + assert torch.isfinite(output).all()
73 +
74 +
75 +@pytest.mark.parametrize("activation", list(MoEActivation))
76 +def test_triton_activation_metadata_tracks_shared_apply(activation: MoEActivation):
77 + assert TritonExperts._supports_activation(
78 + activation
79 + ) == apply_moe_activation_supported(activation)
80 +
81 +
82 def make_test_tensors(
83 m: int,
84 n: int,
vllm/model_executor/layers/fused_moe/__init__.py
+2
@@ -5,6 +5,7 @@ from contextlib import contextmanager
5 from typing import Any
6
7 from vllm.model_executor.layers.fused_moe.activation import (
8 + ApplyMoEActivationConfig,
9 MoEActivation,
10 activation_without_mul,
11 apply_moe_activation,
@@ -63,6 +64,7 @@ def get_config() -> dict[str, Any] | None:
64
65
66 __all__ = [
67 + "ApplyMoEActivationConfig",
68 "FusedMoEFactory",
69 "FusedMoERouter",
70 "FusedMoEConfig",
vllm/model_executor/layers/fused_moe/activation.py
+91 -16
@@ -2,11 +2,19 @@
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 """MoE activation function enum and utilities."""
4
5 +from dataclasses import dataclass
6 from enum import Enum
7 +from typing import TYPE_CHECKING
8
9 import torch
10 import torch.nn.functional as F
11
12 +if TYPE_CHECKING:
13 + from vllm.model_executor.layers.fused_moe.config import (
14 + FusedMoEConfig,
15 + FusedMoEQuantConfig,
16 + )
17 +
18
19 class MoEActivation(Enum):
20 """Activation functions for MoE layers."""
@@ -109,6 +117,66 @@ def activation_without_mul(activation: str) -> str:
117 return MoEActivation.from_str(activation).without_mul().value
118
119
120 +_APPLY_MOE_ACTIVATIONS = frozenset(
121 + {
122 + MoEActivation.SILU,
123 + MoEActivation.GELU,
124 + MoEActivation.GELU_TANH,
125 + MoEActivation.SITU,
126 + MoEActivation.SWIGLUOAI,
127 + MoEActivation.SWIGLUOAI_UNINTERLEAVE,
128 + MoEActivation.SWIGLUSTEP,
129 + MoEActivation.SILU_NO_MUL,
130 + MoEActivation.GELU_NO_MUL,
131 + MoEActivation.GELU_TANH_NO_MUL,
132 + MoEActivation.RELU2_NO_MUL,
133 + }
134 +)
135 +
136 +
137 +def apply_moe_activation_supported(activation: MoEActivation) -> bool:
138 + """Whether ``apply_moe_activation`` supports an activation."""
139 + return activation in _APPLY_MOE_ACTIVATIONS
140 +
141 +
142 +@dataclass(frozen=True)
143 +class ApplyMoEActivationConfig:
144 + """Configuration forwarded to ``apply_moe_activation``."""
145 +
146 + clamp_limit: float | None = None
147 + alpha: float = 1.0
148 + beta: float = 0.0
149 + activation_situ_beta: float | None = None
150 + activation_situ_linear_beta: float | None = None
151 +
152 + @classmethod
153 + def from_configs(
154 + cls,
155 + moe_config: "FusedMoEConfig",
156 + quant_config: "FusedMoEQuantConfig",
157 + ) -> "ApplyMoEActivationConfig":
158 + """Build from the model and quantization configurations."""
159 + clamp_limit = quant_config.gemm1_clamp_limit
160 + if clamp_limit is None:
161 + clamp_limit = moe_config.swiglu_limit
162 + alpha = quant_config.gemm1_alpha
163 + if alpha is None:
164 + alpha = moe_config.swiglu_alpha
165 + beta = quant_config.gemm1_beta
166 + if beta is None:
167 + beta = moe_config.swiglu_beta
168 + return cls(
169 + clamp_limit=clamp_limit,
170 + alpha=1.0 if alpha is None else alpha,
171 + beta=0.0 if beta is None else beta,
172 + activation_situ_beta=moe_config.activation_situ_beta,
173 + activation_situ_linear_beta=moe_config.activation_situ_linear_beta,
174 + )
175 +
176 +
177 +_DEFAULT_APPLY_MOE_ACTIVATION_CONFIG = ApplyMoEActivationConfig()
178 +
179 +
180 def silu_and_mul_with_clamp(
181 output: torch.Tensor,
182 input: torch.Tensor,
@@ -130,20 +198,21 @@ def apply_moe_activation(
198 output: torch.Tensor,
199 input: torch.Tensor,
200 *,
133 - clamp_limit: float | None = None,
134 - alpha: float = 1.0,
135 - beta: float = 0.0,
201 + activation_config: ApplyMoEActivationConfig | None = None,
202 topk_ids: torch.Tensor | None = None,
203 expert_map: torch.Tensor | None = None,
138 - activation_situ_beta: float | None = None,
139 - activation_situ_linear_beta: float | None = None,
204 ) -> torch.Tensor:
205 """Apply MoE activation function.
206
143 - ``clamp_limit``/``alpha``/``beta`` (from the quant config) drive the clamped
144 - SwiGLU kernels: ``SILU`` + ``clamp_limit`` and ``SWIGLUOAI_UNINTERLEAVE`` both
145 - map to ``silu_and_mul_with_clamp``. Other activations ignore them.
207 + The configuration drives specialized activation behavior. Routing tensors
208 + remain per-call inputs because they depend on the current token assignment.
209 """
210 + config = (
211 + _DEFAULT_APPLY_MOE_ACTIVATION_CONFIG
212 + if activation_config is None
213 + else activation_config
214 + )
215 +
216 assert input.dim() == 2, "Input must be 2D"
217 assert output.dim() == 2, "Output must be 2D"
218 if activation.is_gated:
@@ -159,8 +228,10 @@ def apply_moe_activation(
228
229 # Activations with gated multiplication (gate × activation(up))
230 if activation == MoEActivation.SILU:
162 - if clamp_limit is not None:
163 - silu_and_mul_with_clamp(output, input, clamp_limit, topk_ids, expert_map)
231 + if config.clamp_limit is not None:
232 + silu_and_mul_with_clamp(
233 + output, input, config.clamp_limit, topk_ids, expert_map
234 + )
235 else:
236 torch.ops._C.silu_and_mul(output, input)
237 elif activation == MoEActivation.GELU:
@@ -175,23 +246,27 @@ def apply_moe_activation(
246 # bypassed the config plumbing, so fail rather than silently use 1.0.
247 # linear_beta is genuinely optional: <= 0 signals "unset" to the kernel
248 # (up passed through), matching SituAndMul(linear_beta=None).
178 - assert activation_situ_beta is not None, (
249 + assert config.activation_situ_beta is not None, (
250 "SITU requires activation_situ_beta from FusedMoEConfig"
251 )
252 torch.ops._C.situ_and_mul(
253 output,
254 input,
184 - activation_situ_beta,
255 + config.activation_situ_beta,
256 -1.0
186 - if activation_situ_linear_beta is None
187 - else activation_situ_linear_beta,
257 + if config.activation_situ_linear_beta is None
258 + else config.activation_situ_linear_beta,
259 )
260 elif activation == MoEActivation.SWIGLUOAI:
261 torch.ops._C.swigluoai_and_mul(output, input)
262 elif activation == MoEActivation.SWIGLUOAI_UNINTERLEAVE:
263 # SwiGLU-OAI on packed w13 (gate = first half, up = second half).
193 - assert clamp_limit is not None, "SWIGLUOAI_UNINTERLEAVE requires clamp_limit"
194 - torch.ops._C.silu_and_mul_with_clamp(output, input, clamp_limit, alpha, beta)
264 + assert config.clamp_limit is not None, (
265 + "SWIGLUOAI_UNINTERLEAVE requires clamp_limit"
266 + )
267 + torch.ops._C.silu_and_mul_with_clamp(
268 + output, input, config.clamp_limit, config.alpha, config.beta
269 + )
270 elif activation == MoEActivation.SWIGLUSTEP:
271 from vllm.model_executor.layers.activation import swiglustep_and_mul_triton
272
vllm/model_executor/layers/fused_moe/experts/cutlass_moe.py
+48 -37
@@ -8,8 +8,10 @@ import vllm.model_executor.layers.fused_moe.modular_kernel as mk
8 from vllm import _custom_ops as ops
9 from vllm.logger import init_logger
10 from vllm.model_executor.layers.fused_moe.activation import (
11 + ApplyMoEActivationConfig,
12 MoEActivation,
13 apply_moe_activation,
14 + apply_moe_activation_supported,
15 )
16 from vllm.model_executor.layers.fused_moe.config import (
17 FusedMoEConfig,
@@ -76,6 +78,8 @@ def run_cutlass_moe_fp8(
78 use_batched_format: bool,
79 topk_weights: torch.Tensor | None,
80 permute_scratch: MoEPermuteScratch | None,
81 + *,
82 + activation_config: ApplyMoEActivationConfig | None = None,
83 ):
84 a1q = hidden_states
85
@@ -231,7 +235,12 @@ def run_cutlass_moe_fp8(
235 per_out_ch,
236 )
237
234 - apply_moe_activation(activation, act_out, mm1_out)
238 + apply_moe_activation(
239 + activation,
240 + act_out,
241 + mm1_out,
242 + activation_config=activation_config,
243 + )
244
245 a2q, a2q_scale = ops.scaled_fp8_quant(
246 act_out, a2_scale, use_per_token_if_dynamic=per_act_token, output=quant_out
@@ -319,12 +328,7 @@ class CutlassExpertsFp8Base(mk.FusedMoEExpertsModular):
328
329 @staticmethod
330 def _supports_activation(activation: MoEActivation) -> bool:
322 - return activation in [
323 - MoEActivation.SILU,
324 - MoEActivation.GELU,
325 - MoEActivation.GELU_TANH,
326 - MoEActivation.SWIGLUOAI,
327 - ]
331 + return activation.is_gated and apply_moe_activation_supported(activation)
332
333 def finalize_weight_and_reduce_impl(self) -> mk.TopKWeightAndReduce:
334 # Let PrepareAndFinalize::finalize() decide the impl.
@@ -397,6 +401,7 @@ class CutlassExpertsFp8Base(mk.FusedMoEExpertsModular):
401 use_batched_format,
402 topk_weights,
403 self._get_permute_scratch(),
404 + activation_config=self.activation_config,
405 )
406
407
@@ -512,6 +517,8 @@ def run_cutlass_moe_fp4(
517 e: int,
518 device: torch.device,
519 apply_router_weight_on_input: bool = False,
520 + *,
521 + activation_config: ApplyMoEActivationConfig | None = None,
522 ) -> None:
523 """
524 MoE implementation for FP4 Inputs
@@ -633,7 +640,9 @@ def run_cutlass_moe_fp4(
640 blockscale_offsets[:-1],
641 )
642 del rep_a_fp4, rep_a_blockscale
636 - if activation == MoEActivation.SILU:
643 + if activation == MoEActivation.SILU and (
644 + activation_config is None or activation_config.clamp_limit is None
645 + ):
646 # Fused SiLU+Mul+NVFP4 quantization
647 # Note: c2 workspace is no longer needed since SiLU is fused with quantization.
648 # c3 reuses workspace13 after c1 is consumed.
@@ -641,7 +650,12 @@ def run_cutlass_moe_fp4(
650 c1, a2_gscale, expert_offsets, blockscale_offsets, num_topk
651 )
652 else:
644 - apply_moe_activation(activation, c2, c1)
653 + apply_moe_activation(
654 + activation,
655 + c2,
656 + c1,
657 + activation_config=activation_config,
658 + )
659 int_fp4, int_blockscale = ops.scaled_fp4_experts_quant(
660 c2, a2_gscale, expert_offsets, blockscale_offsets, num_topk
661 )
@@ -716,17 +730,7 @@ class CutlassExpertsFp4(mk.FusedMoEExpertsModular):
730 # fallback + separate fp4 quantization in run_cutlass_moe_fp4().
731 # Non-gated activations (_NO_MUL) are also supported for models
732 # like Nemotron-Nano that don't use gated MLP.
719 - return activation in [
720 - MoEActivation.SILU,
721 - MoEActivation.GELU,
722 - MoEActivation.GELU_TANH,
723 - MoEActivation.SWIGLUOAI,
724 - MoEActivation.SWIGLUSTEP,
725 - MoEActivation.SILU_NO_MUL,
726 - MoEActivation.GELU_NO_MUL,
727 - MoEActivation.GELU_TANH_NO_MUL,
728 - MoEActivation.RELU2_NO_MUL,
729 - ]
733 + return apply_moe_activation_supported(activation)
734
735 @staticmethod
736 def _supports_parallel_config(moe_parallel_config: FusedMoEParallelConfig) -> bool:
@@ -807,6 +811,7 @@ class CutlassExpertsFp4(mk.FusedMoEExpertsModular):
811 e=e,
812 device=hidden_states.device,
813 apply_router_weight_on_input=apply_router_weight_on_input,
814 + activation_config=self.activation_config,
815 )
816
817
@@ -828,6 +833,8 @@ def run_cutlass_moe_mxfp4(
833 e: int,
834 device: torch.device,
835 apply_router_weight_on_input: bool = False,
836 + *,
837 + activation_config: ApplyMoEActivationConfig | None = None,
838 ) -> None:
839 """MXFP4 x MXFP4 MoE implementation using CUTLASS grouped GEMM."""
840 is_gated = activation.is_gated
@@ -909,12 +916,19 @@ def run_cutlass_moe_mxfp4(
916 blockscale_offsets[:-1],
917 )
918 del rep_a_fp4, rep_a_blockscale
912 - if activation == MoEActivation.SILU:
919 + if activation == MoEActivation.SILU and (
920 + activation_config is None or activation_config.clamp_limit is None
921 + ):
922 int_fp4, int_blockscale = ops.silu_and_mul_mxfp4_experts_quant(
923 c1, expert_offsets, blockscale_offsets, e, num_topk
924 )
925 else:
917 - apply_moe_activation(activation, c2, c1)
926 + apply_moe_activation(
927 + activation,
928 + c2,
929 + c1,
930 + activation_config=activation_config,
931 + )
932 int_fp4, int_blockscale = ops.mxfp4_experts_quant(
933 c2, expert_offsets, blockscale_offsets, e, num_topk
934 )
@@ -1021,15 +1035,7 @@ class CutlassExpertsMxfp4(mk.FusedMoEExpertsModular):
1035
1036 @staticmethod
1037 def _supports_activation(activation: MoEActivation) -> bool:
1024 - return activation in [
1025 - MoEActivation.SILU,
1026 - MoEActivation.GELU,
1027 - MoEActivation.SWIGLUOAI,
1028 - MoEActivation.SWIGLUSTEP,
1029 - MoEActivation.SILU_NO_MUL,
1030 - MoEActivation.GELU_NO_MUL,
1031 - MoEActivation.RELU2_NO_MUL,
1032 - ]
1038 + return apply_moe_activation_supported(activation)
1039
1040 @staticmethod
1041 def _supports_parallel_config(
@@ -1102,6 +1108,7 @@ class CutlassExpertsMxfp4(mk.FusedMoEExpertsModular):
1108 e=e,
1109 device=hidden_states.device,
1110 apply_router_weight_on_input=apply_router_weight_on_input,
1111 + activation_config=self.activation_config,
1112 )
1113
1114
@@ -1139,6 +1146,8 @@ def run_cutlass_moe_w4a8_fp8(
1146 topk_weights: torch.Tensor | None,
1147 group_size: int,
1148 permute_scratch: MoEPermuteScratch | None,
1149 + *,
1150 + activation_config: ApplyMoEActivationConfig | None = None,
1151 ):
1152 a1q = hidden_states
1153 M = a1q.size(0)
@@ -1218,7 +1227,12 @@ def run_cutlass_moe_w4a8_fp8(
1227 s_strides1,
1228 )
1229
1221 - apply_moe_activation(activation, act_out, mm1_out)
1230 + apply_moe_activation(
1231 + activation,
1232 + act_out,
1233 + mm1_out,
1234 + activation_config=activation_config,
1235 + )
1236
1237 a2q, a2q_scale = ops.scaled_fp8_quant(
1238 act_out, a2_scale, use_per_token_if_dynamic=per_act_token, output=quant_out
@@ -1330,11 +1344,7 @@ class CutlassExpertsW4A8Fp8(mk.FusedMoEExpertsModular):
1344
1345 @staticmethod
1346 def _supports_activation(activation: MoEActivation) -> bool:
1333 - return activation in (
1334 - MoEActivation.SILU,
1335 - MoEActivation.GELU,
1336 - MoEActivation.SWIGLUOAI,
1337 - )
1347 + return activation.is_gated and apply_moe_activation_supported(activation)
1348
1349 @staticmethod
1350 def _supports_parallel_config(moe_parallel_config: FusedMoEParallelConfig) -> bool:
@@ -1438,4 +1448,5 @@ class CutlassExpertsW4A8Fp8(mk.FusedMoEExpertsModular):
1448 topk_weights,
1449 self.group_size,
1450 self._get_permute_scratch(),
1451 + activation_config=self.activation_config,
1452 )
vllm/model_executor/layers/fused_moe/experts/fused_humming_moe.py
+20 -17
@@ -12,7 +12,10 @@ import vllm.model_executor.layers.fused_moe.modular_kernel as mk
12 from vllm import envs
13 from vllm.forward_context import get_forward_context
14 from vllm.logger import init_logger
15 -from vllm.model_executor.layers.fused_moe.activation import MoEActivation
15 +from vllm.model_executor.layers.fused_moe.activation import (
16 + MoEActivation,
17 + apply_moe_activation_supported,
18 +)
19 from vllm.model_executor.layers.fused_moe.config import (
20 FusedMoEConfig,
21 FusedMoEParallelConfig,
@@ -246,18 +249,7 @@ class HummingExpertsBase(mk.FusedMoEExpertsModular):
249 def _supports_activation(activation: MoEActivation) -> bool:
250 # Humming uses apply_moe_activation() callback for activation,
251 # so any activation supported there can be used here.
249 - return activation in [
250 - MoEActivation.SILU,
251 - MoEActivation.GELU,
252 - MoEActivation.GELU_TANH,
253 - MoEActivation.SWIGLUOAI,
254 - MoEActivation.SITU,
255 - MoEActivation.SWIGLUSTEP,
256 - MoEActivation.SILU_NO_MUL,
257 - MoEActivation.GELU_NO_MUL,
258 - MoEActivation.GELU_TANH_NO_MUL,
259 - MoEActivation.RELU2_NO_MUL,
260 - ]
252 + return apply_moe_activation_supported(activation)
253
254 @staticmethod
255 def _supports_parallel_config(moe_parallel_config: FusedMoEParallelConfig) -> bool:
@@ -510,11 +502,22 @@ class HummingExpertsBase(mk.FusedMoEExpertsModular):
502 output: torch.Tensor,
503 input: torch.Tensor,
504 ) -> None:
513 - swiglu_limit = self.quant_config.gemm1_clamp_limit
514 - if activation == MoEActivation.SILU and swiglu_limit is not None:
515 - swiglu_limit_func(output=output, input=input, swiglu_limit=swiglu_limit)
505 + activation_config = self.activation_config
506 + if (
507 + activation == MoEActivation.SILU
508 + and activation_config.clamp_limit is not None
509 + ):
510 + swiglu_limit_func(
511 + output=output,
512 + input=input,
513 + swiglu_limit=activation_config.clamp_limit,
514 + )
515 else:
517 - self.activation(activation=activation, input=input, output=output)
516 + self.activation(
517 + activation=activation,
518 + input=input,
519 + output=output,
520 + )
521
522
523 class HummingIndexedExperts(HummingExpertsBase):
vllm/model_executor/layers/fused_moe/experts/marlin_moe.py
+40 -100
@@ -10,8 +10,10 @@ import torch
10 import vllm._custom_ops as ops
11 import vllm.model_executor.layers.fused_moe.modular_kernel as mk
12 from vllm.model_executor.layers.fused_moe.activation import (
13 + ApplyMoEActivationConfig,
14 MoEActivation,
15 apply_moe_activation,
16 + apply_moe_activation_supported,
17 )
18 from vllm.model_executor.layers.fused_moe.config import (
19 FusedMoEConfig,
@@ -72,7 +74,7 @@ def _fused_marlin_moe(
74 expert_ids: torch.Tensor,
75 num_tokens_post_padded: torch.Tensor,
76 activation: MoEActivation = MoEActivation.SILU,
75 - activation_func: Callable[..., None] = apply_moe_activation,
77 + activation_func: Callable[..., None] | None = None,
78 topk_ids: torch.Tensor | None = None,
79 input_global_scale1: torch.Tensor | None = None,
80 input_global_scale2: torch.Tensor | None = None,
@@ -90,11 +92,7 @@ def _fused_marlin_moe(
92 output: torch.Tensor | None = None,
93 input_dtype: torch.dtype | None = None,
94 is_k_full: bool = True,
93 - clamp_limit: float | None = None,
94 - gemm1_alpha: float = 1.0,
95 - gemm1_beta: float = 0.0,
96 - activation_situ_beta: float | None = None,
97 - activation_situ_linear_beta: float | None = None,
95 + activation_config: ApplyMoEActivationConfig | None = None,
96 ) -> torch.Tensor:
97 assert hidden_states.ndim == 2
98 M, K = hidden_states.size()
@@ -162,20 +160,29 @@ def _fused_marlin_moe(
160 use_fp32_reduce=True,
161 is_zp_float=False,
162 )
165 - # apply_moe_activation fuses the clamp/gate params: SILU + clamp_limit and
166 - # SWIGLUOAI_UNINTERLEAVE both map to the silu_and_mul_with_clamp kernel.
167 - activation_func(
168 - activation,
169 - intermediate_cache2,
170 - intermediate_cache1.view(-1, w13_num_shards * N),
171 - clamp_limit=clamp_limit,
172 - alpha=gemm1_alpha,
173 - beta=gemm1_beta,
174 - topk_ids=topk_ids,
175 - expert_map=expert_map,
176 - activation_situ_beta=activation_situ_beta,
177 - activation_situ_linear_beta=activation_situ_linear_beta,
178 - )
163 + activation_input = intermediate_cache1.view(-1, w13_num_shards * N)
164 + if activation_func is None:
165 + config = (
166 + ApplyMoEActivationConfig()
167 + if activation_config is None
168 + else activation_config
169 + )
170 + apply_moe_activation(
171 + activation,
172 + intermediate_cache2,
173 + activation_input,
174 + activation_config=config,
175 + topk_ids=topk_ids,
176 + expert_map=expert_map,
177 + )
178 + else:
179 + activation_func(
180 + activation,
181 + intermediate_cache2,
182 + activation_input,
183 + topk_ids=topk_ids,
184 + expert_map=expert_map,
185 + )
186
187 if output is None:
188 output = intermediate_cache3
@@ -238,7 +245,7 @@ def fused_marlin_moe(
245 apply_router_weight_on_input: bool = False,
246 global_num_experts: int = -1,
247 activation: MoEActivation = MoEActivation.SILU,
241 - activation_func: Callable[..., None] = apply_moe_activation,
248 + activation_func: Callable[..., None] | None = None,
249 moe_sum: Callable[..., torch.Tensor | None] | None = None,
250 expert_map: torch.Tensor | None = None,
251 input_global_scale1: torch.Tensor | None = None,
@@ -257,11 +264,7 @@ def fused_marlin_moe(
264 is_k_full: bool = True,
265 output: torch.Tensor | None = None,
266 input_dtype: torch.dtype | None = None,
260 - clamp_limit: float | None = None,
261 - gemm1_alpha: float = 1.0,
262 - gemm1_beta: float = 0.0,
263 - activation_situ_beta: float | None = None,
264 - activation_situ_linear_beta: float | None = None,
267 + activation_config: ApplyMoEActivationConfig | None = None,
268 ) -> torch.Tensor:
269 """
270 This function computes a Mixture of Experts (MoE) layer using two sets of
@@ -363,8 +366,7 @@ def fused_marlin_moe(
366 num_tokens_post_padded=num_tokens_post_padded,
367 activation=activation,
368 activation_func=activation_func,
366 - activation_situ_beta=activation_situ_beta,
367 - activation_situ_linear_beta=activation_situ_linear_beta,
369 + activation_config=activation_config,
370 input_global_scale1=input_global_scale1,
371 input_global_scale2=input_global_scale2,
372 global_scale1=global_scale1,
@@ -381,9 +383,6 @@ def fused_marlin_moe(
383 output=None,
384 input_dtype=input_dtype,
385 is_k_full=is_k_full,
384 - clamp_limit=clamp_limit,
385 - gemm1_alpha=gemm1_alpha,
386 - gemm1_beta=gemm1_beta,
386 ).view(-1, topk, K)
387
388 if output is None:
@@ -428,12 +427,8 @@ def batched_fused_marlin_moe(
427 is_k_full: bool = True,
428 output: torch.Tensor | None = None,
429 input_dtype: torch.dtype | None = None,
431 - clamp_limit: float | None = None,
432 - gemm1_alpha: float = 1.0,
433 - gemm1_beta: float = 0.0,
434 - activation_func: Callable[..., None] = apply_moe_activation,
435 - activation_situ_beta: float | None = None,
436 - activation_situ_linear_beta: float | None = None,
430 + activation_func: Callable[..., None] | None = None,
431 + activation_config: ApplyMoEActivationConfig | None = None,
432 ) -> torch.Tensor:
433 """
434 This function massages the inputs so the batched hidden_states can be
@@ -542,8 +537,7 @@ def batched_fused_marlin_moe(
537 apply_router_weight_on_input=apply_router_weight_on_input,
538 activation=activation,
539 activation_func=activation_func,
545 - activation_situ_beta=activation_situ_beta,
546 - activation_situ_linear_beta=activation_situ_linear_beta,
540 + activation_config=activation_config,
541 expert_map=expert_map,
542 block_size_m=block_size_m,
543 sorted_token_ids=sorted_token_ids,
@@ -565,9 +559,6 @@ def batched_fused_marlin_moe(
559 output=output.view(-1, K) if output is not None else output,
560 input_dtype=input_dtype,
561 is_k_full=is_k_full,
568 - clamp_limit=clamp_limit,
569 - gemm1_alpha=gemm1_alpha,
570 - gemm1_beta=gemm1_beta,
562 )
563
564 output = output.view(B, BATCH_TOKENS_MAX, K)
@@ -602,16 +593,6 @@ class MarlinExpertsBase(mk.FusedMoEExpertsModular):
593 self.w2_g_idx_sort_indices = w2_g_idx_sort_indices
594 self.is_k_full = is_k_full
595 self.input_dtype = get_marlin_input_dtype()
605 - self.gemm1_clamp_limit = quant_config.gemm1_clamp_limit
606 - # Gated-activation params (used by SWIGLUOAI_UNINTERLEAVE on packed w13).
607 - # silu == swigluoai with alpha=1, beta=0; configs that don't set these
608 - # (plain silu) fall back to the silu identity.
609 - self.gemm1_alpha = (
610 - quant_config.gemm1_alpha if quant_config.gemm1_alpha is not None else 1.0
611 - )
612 - self.gemm1_beta = (
613 - quant_config.gemm1_beta if quant_config.gemm1_beta is not None else 0.0
614 - )
596
597 super().__init__(
598 moe_config=moe_config,
@@ -655,19 +636,7 @@ class MarlinExpertsBase(mk.FusedMoEExpertsModular):
636 def _supports_activation(activation: MoEActivation) -> bool:
637 # Marlin uses apply_moe_activation() callback for activation,
638 # so any activation supported there can be used here.
658 - return activation in [
659 - MoEActivation.SILU,
660 - MoEActivation.GELU,
661 - MoEActivation.GELU_TANH,
662 - MoEActivation.SITU,
663 - MoEActivation.SWIGLUOAI,
664 - MoEActivation.SWIGLUOAI_UNINTERLEAVE,
665 - MoEActivation.SWIGLUSTEP,
666 - MoEActivation.SILU_NO_MUL,
667 - MoEActivation.GELU_NO_MUL,
668 - MoEActivation.GELU_TANH_NO_MUL,
669 - MoEActivation.RELU2_NO_MUL,
670 - ]
639 + return apply_moe_activation_supported(activation)
640
641 @staticmethod
642 def _supports_parallel_config(moe_parallel_config: FusedMoEParallelConfig) -> bool:
@@ -808,10 +777,7 @@ class MarlinExperts(LoRAExpertsMixin, MarlinExpertsBase):
777 global_num_experts=global_num_experts,
778 activation=activation,
779 activation_func=self.activation,
811 - activation_situ_beta=self.moe_config.activation_situ_beta,
812 - activation_situ_linear_beta=(
813 - self.moe_config.activation_situ_linear_beta
814 - ),
780 + activation_config=self.activation_config,
781 moe_sum=self.moe_sum,
782 expert_map=expert_map,
783 output=output,
@@ -825,9 +791,6 @@ class MarlinExperts(LoRAExpertsMixin, MarlinExpertsBase):
791 sort_indices2=self.w2_g_idx_sort_indices,
792 is_k_full=self.is_k_full,
793 input_dtype=self.input_dtype,
828 - clamp_limit=self.gemm1_clamp_limit,
829 - gemm1_alpha=self.gemm1_alpha,
830 - gemm1_beta=self.gemm1_beta,
794 )
795 return
796
@@ -847,13 +810,8 @@ class MarlinExperts(LoRAExpertsMixin, MarlinExpertsBase):
810 act_output: torch.Tensor,
811 act_input: torch.Tensor,
812 *,
850 - clamp_limit: float | None = None,
851 - alpha: float = 1.0,
852 - beta: float = 0.0,
813 topk_ids: torch.Tensor | None = None,
814 expert_map: torch.Tensor | None = None,
855 - activation_situ_beta: float | None = None,
856 - activation_situ_linear_beta: float | None = None,
815 ) -> None:
816 # act_input = intermediate_cache1 (M*topk, 2N for gated)
817 # act_output = intermediate_cache2 (M*topk, N)
@@ -887,13 +845,8 @@ class MarlinExperts(LoRAExpertsMixin, MarlinExpertsBase):
845 act_enum,
846 act_output,
847 act_input,
890 - clamp_limit=clamp_limit,
891 - alpha=alpha,
892 - beta=beta,
848 topk_ids=topk_ids,
849 expert_map=expert_map,
895 - activation_situ_beta=activation_situ_beta,
896 - activation_situ_linear_beta=activation_situ_linear_beta,
850 )
851 lora_state["cache2"] = act_output
852
@@ -941,8 +894,7 @@ class MarlinExperts(LoRAExpertsMixin, MarlinExpertsBase):
894 global_num_experts=global_num_experts,
895 activation=activation,
896 activation_func=activation_with_lora,
944 - activation_situ_beta=self.moe_config.activation_situ_beta,
945 - activation_situ_linear_beta=self.moe_config.activation_situ_linear_beta,
897 + activation_config=self.activation_config,
898 moe_sum=moe_sum_with_lora,
899 expert_map=expert_map,
900 output=output,
@@ -954,9 +906,6 @@ class MarlinExperts(LoRAExpertsMixin, MarlinExpertsBase):
906 sort_indices2=self.w2_g_idx_sort_indices,
907 is_k_full=self.is_k_full,
908 input_dtype=self.input_dtype,
957 - clamp_limit=self.gemm1_clamp_limit,
958 - gemm1_alpha=self.gemm1_alpha,
959 - gemm1_beta=self.gemm1_beta,
909 )
910
911 def moe_sum(
@@ -1051,9 +1000,6 @@ class BatchedMarlinExperts(MarlinExpertsBase):
1000 act: MoEActivation,
1001 act_output: torch.Tensor,
1002 act_input: torch.Tensor,
1054 - *,
1055 - activation_situ_beta: float | None = None,
1056 - activation_situ_linear_beta: float | None = None,
1003 **kwargs,
1004 ) -> None:
1005 if act != MoEActivation.SITU:
@@ -1061,20 +1007,18 @@ class BatchedMarlinExperts(MarlinExpertsBase):
1007 act,
1008 act_output,
1009 act_input,
1064 - activation_situ_beta=activation_situ_beta,
1065 - activation_situ_linear_beta=activation_situ_linear_beta,
1010 **kwargs,
1011 )
1012 return
1013
1014 num_experts, max_num_tokens = hidden_states.shape[:2]
1071 - beta = 1.0 if activation_situ_beta is None else activation_situ_beta
1072 - linear_beta = activation_situ_linear_beta
1015 + beta = self.activation_config.activation_situ_beta
1016 + linear_beta = self.activation_config.activation_situ_linear_beta
1017 torch.ops._C.masked_situ_and_mul(
1018 act_output.view(num_experts, max_num_tokens, -1),
1019 act_input.view(num_experts, max_num_tokens, -1),
1020 expert_tokens_meta.expert_num_tokens,
1077 - beta,
1021 + 1.0 if beta is None else beta,
1022 -1.0 if linear_beta is None else linear_beta,
1023 )
1024
@@ -1105,10 +1049,6 @@ class BatchedMarlinExperts(MarlinExpertsBase):
1049 w2_zeros=self.w2_zp,
1050 input_dtype=self.input_dtype,
1051 is_k_full=self.is_k_full,
1108 - clamp_limit=self.gemm1_clamp_limit,
1109 - gemm1_alpha=self.gemm1_alpha,
1110 - gemm1_beta=self.gemm1_beta,
1052 activation_func=activation_func,
1112 - activation_situ_beta=self.moe_config.activation_situ_beta,
1113 - activation_situ_linear_beta=self.moe_config.activation_situ_linear_beta,
1053 + activation_config=self.activation_config,
1054 )
vllm/model_executor/layers/fused_moe/experts/mxfp8_emulation_moe.py
+2 -5
@@ -104,16 +104,13 @@ class Mxfp8EmulationTritonExperts(Mxfp8TritonExpertsBase):
104 )
105
106 if activation == MoEActivation.SWIGLUOAI_UNINTERLEAVE:
107 - limit = self.quant_config.gemm1_clamp_limit
108 - if limit is None:
107 + if self.activation_config.clamp_limit is None:
108 raise ValueError("SWIGLUOAI_UNINTERLEAVE requires gemm1_clamp_limit")
109 apply_moe_activation(
110 activation,
111 output,
112 input,
114 - clamp_limit=float(limit),
115 - alpha=self.gemm1_alpha,
116 - beta=self.gemm1_beta,
113 + activation_config=self.activation_config,
114 )
115 return
116 super().activation(activation, output, input)
vllm/model_executor/layers/fused_moe/experts/mxfp8_native_moe.py
+4 -6
@@ -362,9 +362,7 @@ class Mxfp8NativeTritonExperts(Mxfp8TritonExpertsBase):
362 expert_tokens_meta: mk.ExpertTokensMetadata | None,
363 apply_router_weight_on_input: bool,
364 ):
365 - # `self.gemm1_alpha` and `self.gemm1_beta`` are set by `TritonExperts.__init__`.
366 - limit = self.quant_config.gemm1_clamp_limit
367 - limit = None if limit is None else float(limit)
365 + activation_config = self.activation_config
366 out = fused_moe_mxfp8_native(
367 hidden_states,
368 w1,
@@ -373,9 +371,9 @@ class Mxfp8NativeTritonExperts(Mxfp8TritonExpertsBase):
371 self.w2_scale_val,
372 topk_weights,
373 topk_ids,
376 - alpha=self.gemm1_alpha,
377 - beta=self.gemm1_beta,
378 - limit=limit,
374 + alpha=activation_config.alpha,
375 + beta=activation_config.beta,
376 + limit=activation_config.clamp_limit,
377 global_num_experts=global_num_experts,
378 expert_map=expert_map,
379 )
vllm/model_executor/layers/fused_moe/experts/triton_moe.py
+13 -33
@@ -6,7 +6,10 @@ import torch
6
7 import vllm.model_executor.layers.fused_moe.modular_kernel as mk
8 from vllm import _custom_ops as ops
9 -from vllm.model_executor.layers.fused_moe.activation import MoEActivation
9 +from vllm.model_executor.layers.fused_moe.activation import (
10 + MoEActivation,
11 + apply_moe_activation_supported,
12 +)
13 from vllm.model_executor.layers.fused_moe.config import (
14 FusedMoEConfig,
15 FusedMoEParallelConfig,
@@ -69,15 +72,6 @@ class TritonExperts(LoRAExpertsMixin, mk.FusedMoEExpertsModular):
72 self.quantization_emulation = False
73 super().__init__(moe_config, quant_config)
74
72 - self.gemm1_clamp_limit = quant_config.gemm1_clamp_limit
73 - # Gated-activation params: silu == swigluoai with alpha=1, beta=0.
74 - self.gemm1_alpha = (
75 - quant_config.gemm1_alpha if quant_config.gemm1_alpha is not None else 1.0
76 - )
77 - self.gemm1_beta = (
78 - quant_config.gemm1_beta if quant_config.gemm1_beta is not None else 0.0
79 - )
80 -
75 @staticmethod
76 def activation_format() -> mk.FusedMoEActivationFormat:
77 return mk.FusedMoEActivationFormat.Standard
@@ -136,19 +130,7 @@ class TritonExperts(LoRAExpertsMixin, mk.FusedMoEExpertsModular):
130
131 @staticmethod
132 def _supports_activation(activation: MoEActivation) -> bool:
139 - return activation in [
140 - MoEActivation.SILU,
141 - MoEActivation.GELU,
142 - MoEActivation.GELU_TANH,
143 - MoEActivation.SITU,
144 - MoEActivation.SWIGLUOAI,
145 - MoEActivation.SWIGLUOAI_UNINTERLEAVE,
146 - MoEActivation.SWIGLUSTEP,
147 - MoEActivation.SILU_NO_MUL,
148 - MoEActivation.GELU_NO_MUL,
149 - MoEActivation.GELU_TANH_NO_MUL,
150 - MoEActivation.RELU2_NO_MUL,
151 - ]
133 + return apply_moe_activation_supported(activation)
134
135 @staticmethod
136 def _supports_parallel_config(moe_parallel_config: FusedMoEParallelConfig) -> bool:
@@ -171,17 +153,18 @@ class TritonExperts(LoRAExpertsMixin, mk.FusedMoEExpertsModular):
153 input: torch.Tensor,
154 **kwargs,
155 ) -> None:
174 - gemm1_clamp_limit = self.quant_config.gemm1_clamp_limit
175 - if activation == MoEActivation.SILU and gemm1_clamp_limit is not None:
176 - swiglu_limit_func(output, input, float(gemm1_clamp_limit))
156 + activation_config = self.activation_config
157 + if (
158 + activation == MoEActivation.SILU
159 + and activation_config.clamp_limit is not None
160 + ):
161 + swiglu_limit_func(output, input, activation_config.clamp_limit)
162 return
163
164 # SWIGLUOAI_UNINTERLEAVE routes to the silu_and_mul_with_clamp kernel and
180 - # needs the clamped-SwiGLU params (gemm1_clamp_limit/alpha/beta read from
181 - # the quant config in __init__) forwarded; without a clamp_limit it
182 - # asserts. Other activations ignore alpha/beta/clamp_limit.
165 + # requires a clamp limit. Other activations ignore these parameters.
166 if activation == MoEActivation.SWIGLUOAI_UNINTERLEAVE:
184 - assert gemm1_clamp_limit is not None, (
167 + assert activation_config.clamp_limit is not None, (
168 "SWIGLUOAI_UNINTERLEAVE requires gemm1_clamp_limit"
169 )
170
@@ -189,9 +172,6 @@ class TritonExperts(LoRAExpertsMixin, mk.FusedMoEExpertsModular):
172 activation,
173 output,
174 input,
192 - clamp_limit=gemm1_clamp_limit,
193 - alpha=self.gemm1_alpha,
194 - beta=self.gemm1_beta,
175 )
176
177 def workspace_shapes(
vllm/model_executor/layers/fused_moe/modular_kernel.py
+5 -18
@@ -12,6 +12,7 @@ import torch
12 import vllm.envs as envs
13 from vllm.logger import init_logger
14 from vllm.model_executor.layers.fused_moe.activation import (
15 + ApplyMoEActivationConfig,
16 MoEActivation,
17 apply_moe_activation,
18 )
@@ -497,6 +498,9 @@ class FusedMoEExperts(ABC):
498
499 self.moe_config = moe_config
500 self.quant_config = quant_config
501 + self.activation_config = ApplyMoEActivationConfig.from_configs(
502 + moe_config, quant_config
503 + )
504 self.max_num_tokens = max_num_tokens
505 self.num_dispatchers = num_dispatchers
506
@@ -885,33 +889,16 @@ class FusedMoEExpertsModular(FusedMoEExperts):
889 output: torch.Tensor,
890 input: torch.Tensor,
891 *,
888 - clamp_limit: float | None = None,
889 - alpha: float = 1.0,
890 - beta: float = 0.0,
892 topk_ids: torch.Tensor | None = None,
893 expert_map: torch.Tensor | None = None,
893 - activation_situ_beta: float | None = None,
894 - activation_situ_linear_beta: float | None = None,
894 ) -> None:
895 apply_moe_activation(
896 activation,
897 output,
898 input,
900 - clamp_limit=clamp_limit,
901 - alpha=alpha,
902 - beta=beta,
899 + activation_config=self.activation_config,
900 topk_ids=topk_ids,
901 expert_map=expert_map,
905 - activation_situ_beta=(
906 - self.moe_config.activation_situ_beta
907 - if activation_situ_beta is None
908 - else activation_situ_beta
909 - ),
910 - activation_situ_linear_beta=(
911 - self.moe_config.activation_situ_linear_beta
912 - if activation_situ_linear_beta is None
913 - else activation_situ_linear_beta
914 - ),
902 )
903
904 @abstractmethod
vllm/model_executor/layers/fused_moe/oracle/nvfp4.py
+5 -2
@@ -190,7 +190,9 @@ def select_nvfp4_moe_backend(
190 NVFP4_BACKENDS_WITH_CLAMP = {
191 NvFp4MoeBackend.FLASHINFER_TRTLLM,
192 NvFp4MoeBackend.FLASHINFER_CUTLASS,
193 + NvFp4MoeBackend.VLLM_CUTLASS,
194 NvFp4MoeBackend.MARLIN,
195 + NvFp4MoeBackend.HUMMING,
196 }
197
198 if config.swiglu_limit is not None:
@@ -258,8 +260,9 @@ def select_nvfp4_moe_backend(
260 raise ValueError(
261 f"Model sets swiglu_limit={config.swiglu_limit}, but the "
262 f"explicitly requested moe_backend={runner_backend!r} does "
261 - f"not apply the SwiGLU clamp. Use 'flashinfer_trtllm' or "
262 - f"'flashinfer_cutlass' instead."
263 + f"not apply the SwiGLU clamp. Use 'flashinfer_trtllm', "
264 + f"'flashinfer_cutlass', 'cutlass', 'marlin', or 'humming' "
265 + f"instead."
266 )
267 return _return_or_raise(
268 requested_backend, config, weight_key, activation_key, activation_format