[MoE Refactor] Combine CompressedTensorsWNA16MarlinMoEMethod with CompressedTensorsWNA16MoEMethod (#44570)

Signed-off-by: Bill Nell <bnell@redhat.com> Signed-off-by: Taneem Ibrahim <taneem.ibrahim@gmail.com> Signed-off-by: Dobrzyniewicz, Agata <agata.dobrzyniewicz@intel.com> Signed-off-by: Tushar Jain <tushar00jain@users.noreply.github.com> Signed-off-by: yewentao256 <zhyanwentao@126.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Taneem Ibrahim <taneem.ibrahim@gmail.com> Co-authored-by: Agata Dobrzyniewicz <160237065+adobrzyn@users.noreply.github.com> Co-authored-by: Tushar Jain <8455015+tushar00jain@users.noreply.github.com> Co-authored-by: Tushar Jain <tushar00jain@users.noreply.github.com> Co-authored-by: Wentao Ye <44945378+yewentao256@users.noreply.github.com> Co-authored-by: Jiangyun Zhu <riverclouds.zhu@qq.com>

bnellnm committed Jul 31, 2026 at 17:13 UTC 454ea5b52611c933e00581723e2db56f0144cea7
14 files changed +545 -855
tests/kernels/quantization/test_marlin_tile_padding.py
+1 -1
@@ -678,7 +678,7 @@ def test_check_moe_marlin_supports_layer_padding():
678 layer.hidden_size = hidden
679 layer.apply_router_weight_on_input = False
680 layer.moe_config = SimpleNamespace(
681 - intermediate_size_per_partition_unpadded=intermediate
681 + hidden_dim=hidden, intermediate_size_per_partition_unpadded=intermediate
682 )
683 return layer
684
tests/quantization/test_compressed_tensors.py
+4 -4
@@ -928,12 +928,12 @@ def test_compressed_tensors_mxfp8_moe_setup(vllm_runner):
928 (None, 32, 64, 128, (False, 64, True)),
929 ],
930 )
931 -def test_wna16_marlin_moe_w2_scale_sharding(actorder, group_size, part, full, expected):
932 - from vllm.model_executor.layers.quantization.compressed_tensors.compressed_tensors_moe.compressed_tensors_moe_wna16_marlin import ( # noqa: E501
933 - CompressedTensorsWNA16MarlinMoEMethod,
931 +def test_wna16_moe_w2_scale_sharding(actorder, group_size, part, full, expected):
932 + from vllm.model_executor.layers.quantization.compressed_tensors.compressed_tensors_moe.compressed_tensors_moe_wna16 import ( # noqa: E501
933 + CompressedTensorsWNA16MoEMethod,
934 )
935
936 - result = CompressedTensorsWNA16MarlinMoEMethod._w2_scale_sharding(
936 + result = CompressedTensorsWNA16MoEMethod._w2_scale_sharding(
937 actorder, group_size, part, full
938 )
939 assert result == expected
tests/quantization/test_moe_wna16.py
+6
@@ -91,11 +91,17 @@ def test_map_wna16_backend_supports_triton():
91 def test_wna16_oracle_rejects_incompatible_quant_structures(
92 backend, quant_config, may_have_zp, may_have_bias, expected
93 ):
94 + from tests.kernels.moe.utils import make_dummy_moe_config
95 +
96 + moe_config = make_dummy_moe_config()
97 +
98 reason = _backend_incompatibility_reason(
99 backend=backend,
100 + moe_config=moe_config,
101 quant_config=quant_config,
102 may_have_zp=may_have_zp,
103 may_have_bias=may_have_bias,
104 + allow_tile_padding=True,
105 )
106
107 assert reason is not None
vllm/model_executor/layers/fused_moe/experts/triton_moe.py
-1
@@ -593,7 +593,6 @@ class TritonWNA16Experts(TritonExperts):
593
594 @staticmethod
595 def _supports_parallel_config(moe_parallel_config: FusedMoEParallelConfig) -> bool:
596 - # Why?
596 return not (
597 moe_parallel_config.use_fi_nvl_two_sided_kernels
598 or moe_parallel_config.use_fi_nvl_one_sided_kernels
vllm/model_executor/layers/fused_moe/oracle/int_wna16.py
+34 -3
@@ -32,6 +32,7 @@ from vllm.model_executor.layers.fused_moe.experts.trtllm_mxint4_moe import (
32 )
33 from vllm.model_executor.layers.quantization.base_config import QuantizationConfig
34 from vllm.model_executor.layers.quantization.utils.marlin_utils import (
35 + check_moe_marlin_supports_config,
36 marlin_act_int8_process_scales,
37 marlin_moe_padded_intermediate,
38 marlin_moe_permute_scales,
@@ -125,9 +126,11 @@ def _get_priority_backends() -> list[WNA16MoEBackend]:
126
127 def _backend_incompatibility_reason(
128 backend: WNA16MoEBackend,
129 + moe_config: FusedMoEConfig,
130 quant_config: QuantizationConfig | QuantizationArgs,
131 may_have_zp: bool,
132 may_have_bias: bool,
133 + allow_tile_padding: bool,
134 ) -> str | None:
135 if backend == WNA16MoEBackend.FLASHINFER_TRTLLM and (may_have_zp or may_have_bias):
136 return "zero points and bias are not supported"
@@ -149,7 +152,24 @@ def _backend_incompatibility_reason(
152 ):
153 return "group activation ordering is not supported"
154
152 - if isinstance(quant_config, MoeWNA16Config) and backend in (
155 + # Marlin only supports certain problem/group sizes.
156 + allow_marlin = not isinstance(quant_config, MoeWNA16Config)
157 +
158 + if allow_marlin and backend in (
159 + WNA16MoEBackend.MARLIN,
160 + WNA16MoEBackend.BATCHED_MARLIN,
161 + ):
162 + if isinstance(quant_config, (AutoAWQConfig, AutoGPTQConfig, QuantizationArgs)):
163 + group_size = quant_config.group_size
164 + else:
165 + return "Marlin not supported for this layer"
166 +
167 + if not check_moe_marlin_supports_config(
168 + moe_config, group_size, allow_tile_padding
169 + ):
170 + return "Marlin not supported for this layer"
171 +
172 + if not allow_marlin and backend in (
173 WNA16MoEBackend.MARLIN,
174 WNA16MoEBackend.BATCHED_MARLIN,
175 WNA16MoEBackend.EMULATION,
@@ -182,6 +202,7 @@ def select_wna16_moe_backend(
202 quant_config: QuantizationConfig | QuantizationArgs,
203 may_have_zp: bool,
204 may_have_bias: bool,
205 + allow_tile_padding: bool = False,
206 ) -> tuple[WNA16MoEBackend, type[mk.FusedMoEExperts]]:
207 """Select the WNA16 MoE backend.
208
@@ -239,7 +260,12 @@ def select_wna16_moe_backend(
260 if runner_backend != "auto":
261 requested_backend = map_wna16_backend(runner_backend)
262 reason = _backend_incompatibility_reason(
242 - requested_backend, quant_config, may_have_zp, may_have_bias
263 + requested_backend,
264 + config,
265 + quant_config,
266 + may_have_zp,
267 + may_have_bias,
268 + allow_tile_padding,
269 )
270 if reason is not None:
271 raise ValueError(_make_log_unsupported(requested_backend, reason))
@@ -252,7 +278,12 @@ def select_wna16_moe_backend(
278
279 for backend in AVAILABLE_BACKENDS:
280 reason = _backend_incompatibility_reason(
255 - backend, quant_config, may_have_zp, may_have_bias
281 + backend,
282 + config,
283 + quant_config,
284 + may_have_zp,
285 + may_have_bias,
286 + allow_tile_padding,
287 )
288 if reason is not None:
289 logger.debug_once(_make_log_unsupported(backend, reason), scope="local")
vllm/model_executor/layers/fused_moe/routed_experts.py
-2
@@ -206,7 +206,6 @@ class RoutedExperts(PluggableLayer):
206 def _needs_intermediate_size_param(self, quant_method: FusedMoEMethodBase) -> bool:
207 return quant_method.__class__.__name__ in (
208 "AutoGPTQMoEMethod",
209 - "CompressedTensorsWNA16MarlinMoEMethod",
209 "CompressedTensorsWNA16MoEMethod",
210 "CompressedTensorsW4A16FlydslMoEMethod",
211 )
@@ -659,7 +658,6 @@ class RoutedExperts(PluggableLayer):
658 # TODO (mgoin): check self.quant_method.quant_config.quant_format
659 # against known CompressionFormat enum values that have this quality
660 if quant_method_name in (
662 - "CompressedTensorsWNA16MarlinMoEMethod",
661 "CompressedTensorsWNA16MoEMethod",
662 "CompressedTensorsWNA16RDNA3MoEMethod",
663 "CompressedTensorsW4A16FlydslMoEMethod",
vllm/model_executor/layers/quantization/auto_awq.py
+1 -10
@@ -563,6 +563,7 @@ class AutoAWQMoEMethod(FusedMoEMethodBase):
563 quant_config=self.quant_config,
564 may_have_zp=self.quant_config.zero_point,
565 may_have_bias=True,
566 + allow_tile_padding=True,
567 )
568
569 def create_weights(
@@ -782,16 +783,6 @@ class AutoAWQMoEMethod(FusedMoEMethodBase):
783 gemm1_beta=getattr(layer, "swiglu_beta", None),
784 )
785
785 - def select_gemm_impl(
786 - self,
787 - prepare_finalize,
788 - layer: RoutedExperts,
789 - ):
790 - raise ValueError(
791 - f"{self.__class__.__name__} uses the new modular kernel "
792 - "initialization logic. This function should not be called."
793 - )
794 -
786 def apply(
787 self,
788 layer: RoutedExperts,
vllm/model_executor/layers/quantization/auto_gptq.py
+2 -11
@@ -490,8 +490,9 @@ class AutoGPTQMoEMethod(FusedMoEMethodBase):
490 moe,
491 weight_key,
492 quant_config=self.quant_config,
493 - may_have_zp=True,
493 + may_have_zp=not self.quant_config.is_sym,
494 may_have_bias=True,
495 + allow_tile_padding=not self.quant_config.desc_act,
496 )
497
498 def create_weights(
@@ -806,16 +807,6 @@ class AutoGPTQMoEMethod(FusedMoEMethodBase):
807 w2_bias=getattr(layer, "w2_bias", None),
808 )
809
809 - def select_gemm_impl(
810 - self,
811 - prepare_finalize,
812 - layer: RoutedExperts,
813 - ):
814 - raise ValueError(
815 - f"{self.__class__.__name__} uses the new modular kernel "
816 - "initialization logic. This function should not be called."
817 - )
818 -
810 def apply(
811 self,
812 layer: RoutedExperts,
vllm/model_executor/layers/quantization/compressed_tensors/compressed_tensors_moe/compressed_tensors_moe.py
+43 -84
@@ -5,7 +5,6 @@
5 import torch
6 from compressed_tensors import CompressionFormat
7 from compressed_tensors.quantization import (
8 - ActivationOrdering,
8 QuantizationStrategy,
9 QuantizationType,
10 )
@@ -19,9 +18,6 @@ from vllm.model_executor.layers.fused_moe import (
18 from vllm.model_executor.layers.quantization.compressed_tensors.schemes.compressed_tensors_wNa16 import ( # noqa
19 WNA16_SUPPORTED_BITS,
20 )
22 -from vllm.model_executor.layers.quantization.utils.marlin_utils import (
23 - check_moe_marlin_supports_layer,
24 -)
21 from vllm.platforms import current_platform
22
23 logger = init_logger(__name__)
@@ -78,9 +74,6 @@ class CompressedTensorsMoEMethod(FusedMoEMethodBase):
74 return CompressedTensorsW8A8Mxfp8MoEMethod(layer.moe_config)
75
76 if quant_config._is_wNa16_group_channel(weight_quant, input_quant):
81 - # group_size=None means channelwise
82 - group_size = weight_quant.group_size or -1
83 -
77 valid_format_and_bits = (
78 weight_quant.num_bits in WNA16_SUPPORTED_BITS
79 and format == CompressionFormat.pack_quantized.value
@@ -95,87 +88,53 @@ class CompressedTensorsMoEMethod(FusedMoEMethodBase):
88 f" and bits: {weight_quant.num_bits}",
89 )
90
98 - # Prefer to use the MarlinMoE kernel when it is supported.
99 - is_actorder = (
100 - weight_quant.strategy == QuantizationStrategy.GROUP
101 - and weight_quant.actorder
102 - in (ActivationOrdering.GROUP, ActivationOrdering.DYNAMIC)
103 - )
104 - if (
105 - not check_moe_marlin_supports_layer(
106 - layer, group_size, allow_tile_padding=not is_actorder
107 - )
108 - or current_platform.is_rocm()
109 - ):
110 - if is_actorder:
111 - raise ValueError(
112 - "WNA16MoE is not supported with actorder=group/dynamic."
91 + # Native ROCm HIP kernels (RDNA3, etc.)
92 + if current_platform.is_rocm():
93 + from . import rocm_moe_rdna
94 +
95 + if rocm_moe_rdna.is_supported(weight_quant):
96 + return rocm_moe_rdna.make_method(
97 + weight_quant, input_quant, layer.moe_config
98 + )
99 + from vllm.platforms.rocm import on_gfx950
100 +
101 + vllm_config = get_current_vllm_config()
102 + is_lora_disabled = vllm_config.lora_config is None
103 + moe_backend = vllm_config.kernel_config.moe_backend
104 + group_size = weight_quant.group_size or -1
105 + if (
106 + weight_quant.strategy == QuantizationStrategy.GROUP
107 + and weight_quant.type == QuantizationType.INT
108 + and group_size == 32
109 + and weight_quant.num_bits == 4
110 + and is_lora_disabled
111 + and on_gfx950()
112 + and moe_backend == "flydsl"
113 + ):
114 + from .compressed_tensors_moe_w4a16_flydsl import (
115 + CompressedTensorsW4A16FlydslMoEMethod,
116 )
117
115 - # Native ROCm HIP kernels (RDNA3, etc.)
116 - if current_platform.is_rocm():
117 - from . import rocm_moe_rdna
118 -
119 - if rocm_moe_rdna.is_supported(weight_quant):
120 - return rocm_moe_rdna.make_method(
121 - weight_quant, input_quant, layer.moe_config
122 - )
123 - from vllm.platforms.rocm import on_gfx950
124 -
125 - vllm_config = get_current_vllm_config()
126 - is_lora_disabled = vllm_config.lora_config is None
127 - moe_backend = vllm_config.kernel_config.moe_backend
128 - if (
129 - weight_quant.strategy == QuantizationStrategy.GROUP
130 - and weight_quant.type == QuantizationType.INT
131 - and group_size == 32
132 - and weight_quant.num_bits == 4
133 - and is_lora_disabled
134 - and on_gfx950()
135 - and moe_backend == "flydsl"
136 - ):
137 - from .compressed_tensors_moe_w4a16_flydsl import (
138 - CompressedTensorsW4A16FlydslMoEMethod,
139 - )
140 -
141 - logger.info_once("Using CompressedTensorsW4A16FlydslMoEMethod")
142 - return CompressedTensorsW4A16FlydslMoEMethod(
143 - weight_quant, input_quant, layer.moe_config
144 - )
145 - elif moe_backend == "emulation":
146 - # Although this is called 'Marlin', actually it selects
147 - # emulation backend by calling select_wna16_moe_backend.
148 - # TODO: we need to update CompressedTensorsWNA16MoeMethod
149 - # to honor "--moe-backend" option
150 - from .compressed_tensors_moe_wna16_marlin import (
151 - CompressedTensorsWNA16MarlinMoEMethod,
152 - )
153 -
154 - logger.info_once(
155 - "Using CompressedTensorsWNA16MarlinMoEMethod "
156 - "(emulation backend requested)"
157 - )
158 - return CompressedTensorsWNA16MarlinMoEMethod(
159 - weight_quant, input_quant, layer.moe_config, layer_name
160 - )
161 -
162 - from .compressed_tensors_moe_wna16 import (
163 - CompressedTensorsWNA16MoEMethod,
164 - )
118 + logger.info_once("Using CompressedTensorsW4A16FlydslMoEMethod")
119 + return CompressedTensorsW4A16FlydslMoEMethod(
120 + weight_quant, input_quant, layer.moe_config
121 + )
122 + elif moe_backend == "emulation":
123 + logger.info_once(
124 + "Using CompressedTensorsWNA16MoEMethod "
125 + "(emulation backend requested)"
126 + )
127
166 - logger.info_once("Using CompressedTensorsWNA16MoEMethod")
167 - return CompressedTensorsWNA16MoEMethod(
168 - weight_quant, input_quant, layer.moe_config
169 - )
170 - else:
171 - from .compressed_tensors_moe_wna16_marlin import (
172 - CompressedTensorsWNA16MarlinMoEMethod,
173 - )
128 + from .compressed_tensors_moe_wna16 import (
129 + CompressedTensorsWNA16MoEMethod,
130 + )
131
175 - logger.info_once("Using CompressedTensorsWNA16MarlinMoEMethod")
176 - return CompressedTensorsWNA16MarlinMoEMethod(
177 - weight_quant, input_quant, layer.moe_config
178 - )
132 + logger.info_once("Using CompressedTensorsWNA16MoEMethod")
133 + return CompressedTensorsWNA16MoEMethod(
134 + weight_quant,
135 + input_quant,
136 + layer.moe_config,
137 + )
138 elif quant_config._is_nvfp4_format(weight_quant):
139 from .compressed_tensors_moe_w4a4_nvfp4 import (
140 CompressedTensorsW4A4Nvfp4MoEMethod,
vllm/model_executor/layers/quantization/compressed_tensors/compressed_tensors_moe/compressed_tensors_moe_wna16.py
+435 -88
@@ -1,28 +1,51 @@
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 +from typing import Any
5
6 import torch
7 from compressed_tensors.quantization import (
8 + ActivationOrdering,
9 QuantizationArgs,
10 + QuantizationStrategy,
11 )
12
10 -import vllm.model_executor.layers.fused_moe.modular_kernel as mk
13 from vllm.logger import init_logger
14 from vllm.model_executor.layers.fused_moe import (
15 + FusedMoEExpertsModular,
16 RoutedExperts,
17 SharedExperts,
18 )
19 from vllm.model_executor.layers.fused_moe.config import (
20 FusedMoEConfig,
21 FusedMoEQuantConfig,
19 - int4_w4a16_moe_quant_config,
20 - int8_w8a16_moe_quant_config,
22 +)
23 +from vllm.model_executor.layers.fused_moe.oracle.int_wna16 import (
24 + WNA16MoEBackend,
25 + convert_to_wna16_moe_kernel_format,
26 + make_wna16_moe_kernel,
27 + make_wna16_moe_quant_config,
28 + select_wna16_moe_backend,
29 )
30 from vllm.model_executor.layers.quantization.compressed_tensors.compressed_tensors_moe import ( # noqa E501
31 CompressedTensorsMoEMethod,
32 )
25 -from vllm.model_executor.utils import set_weight_attrs
33 +from vllm.model_executor.layers.quantization.compressed_tensors.schemes.compressed_tensors_wNa16 import ( # noqa
34 + WNA16_SUPPORTED_TYPES_MAP,
35 + WNA16_ZP_SUPPORTED_TYPES_MAP,
36 +)
37 +from vllm.model_executor.layers.quantization.utils.marlin_utils import (
38 + check_moe_marlin_supports_config,
39 + get_marlin_input_dtype,
40 + marlin_make_workspace_new,
41 +)
42 +from vllm.model_executor.layers.quantization.utils.quant_utils import (
43 + QuantKey,
44 + kInt4Static32GroupScale,
45 + kInt4StaticGroupScale,
46 + kInt8StaticGroupScale,
47 +)
48 +from vllm.model_executor.utils import replace_parameter, set_weight_attrs
49
50 logger = init_logger(__name__)
51
@@ -39,19 +62,180 @@ class CompressedTensorsWNA16MoEMethod(CompressedTensorsMoEMethod):
62 self.weight_quant = weight_quant
63 self.input_quant = input_quant
64 # Extract properties from weight_quant
65 + self.symmetric = weight_quant.symmetric
66 self.num_bits = weight_quant.num_bits
67 self.packed_factor = 32 // weight_quant.num_bits
68 self.strategy = weight_quant.strategy
45 - # channelwise is not supported by this kernel
46 - assert weight_quant.strategy == "group"
69 self.group_size = weight_quant.group_size
48 - # grouped actorder isn't supported by this kernel
49 - assert weight_quant.actorder != "group"
50 - assert weight_quant.symmetric, (
51 - "Only symmetric quantization is supported for MoE. "
52 - "Try --moe-backend emulation."
70 + self.actorder = weight_quant.actorder
71 +
72 + # Extract quant_type and create weight key for oracle selection
73 + self.quant_type = (
74 + WNA16_SUPPORTED_TYPES_MAP[self.num_bits]
75 + if self.symmetric
76 + else WNA16_ZP_SUPPORTED_TYPES_MAP[self.num_bits]
77 + )
78 +
79 + if self.num_bits == 4:
80 + if self.group_size == 32:
81 + scale = kInt4Static32GroupScale
82 + else:
83 + scale = kInt4StaticGroupScale
84 + elif self.num_bits == 8:
85 + assert self.group_size == -1
86 + scale = kInt8StaticGroupScale
87 + else:
88 + raise ValueError(
89 + "CompressedTensorsWNA16MoEMethod only supports int4 and int8 now."
90 + )
91 +
92 + weight_key = QuantKey(self.quant_type, scale, symmetric=self.symmetric)
93 +
94 + is_actorder = self.strategy == QuantizationStrategy.GROUP and self.actorder in (
95 + ActivationOrdering.GROUP,
96 + ActivationOrdering.DYNAMIC,
97 )
98
99 + # Select WNA16 MoE backend via oracle.
100 + self.wna16_backend, self.experts_cls = select_wna16_moe_backend(
101 + config=self.moe,
102 + weight_key=weight_key,
103 + quant_config=self.weight_quant,
104 + may_have_zp=not self.symmetric,
105 + may_have_bias=False,
106 + allow_tile_padding=not is_actorder,
107 + )
108 +
109 + self.is_marlin = self.wna16_backend in [
110 + WNA16MoEBackend.MARLIN,
111 + WNA16MoEBackend.BATCHED_MARLIN,
112 + ]
113 + self.is_transposed = self.wna16_backend != WNA16MoEBackend.FLASHINFER_TRTLLM
114 +
115 + if self.is_marlin:
116 + assert check_moe_marlin_supports_config(
117 + self.moe, self.group_size, allow_tile_padding=not is_actorder
118 + )
119 + self.input_dtype = get_marlin_input_dtype(layer_name)
120 + else:
121 + # channelwise is not supported by this kernel
122 + assert weight_quant.strategy == "group"
123 + # grouped actorder isn't supported by this kernel
124 + assert weight_quant.actorder != "group"
125 +
126 + assert self.symmetric, "Only symmetric quantization is supported for MoE"
127 +
128 + # Non-Marlin WNA16 always uses bf16/fp16 inputs
129 + self.input_dtype = torch.bfloat16
130 +
131 + def get_weight_shape(
132 + self,
133 + weight_name: str,
134 + num_experts: int,
135 + hidden_size: int,
136 + intermediate_size_per_partition: int,
137 + num_groups_w2: int | None = None,
138 + num_groups_w13: int | None = None,
139 + ) -> tuple[int, int, int]:
140 + """
141 + Get the shape of the weight based on the weight name, number of experts
142 + hidden size, intermediate size per partition, number of groups for w2,
143 + and number of groups for w13. Pass in num_groups_w2 and num_groups_w13
144 + for weight scales/zero_points.
145 + """
146 + if weight_name in ("w13_scale", "w13_zp"):
147 + assert num_groups_w13 is not None, (
148 + "num_groups_w13 must be provided for weight scales/zero_points"
149 + )
150 + if weight_name in ("w2_scale", "w2_zp"):
151 + assert num_groups_w2 is not None, (
152 + "num_groups_w2 must be provided for weight scales/zero_points"
153 + )
154 + w13_num_shards = 2 if self.moe.is_act_and_mul else 1
155 + shape_map = {
156 + "w13_weight": {
157 + "Flashinfer": (
158 + num_experts,
159 + w13_num_shards * intermediate_size_per_partition,
160 + hidden_size // self.packed_factor,
161 + ),
162 + "Marlin": (
163 + num_experts,
164 + hidden_size // self.packed_factor,
165 + w13_num_shards * intermediate_size_per_partition,
166 + ),
167 + },
168 + "w13_scale": {
169 + "Flashinfer": (
170 + num_experts,
171 + w13_num_shards * intermediate_size_per_partition,
172 + num_groups_w13,
173 + ),
174 + "Marlin": (
175 + num_experts,
176 + num_groups_w13,
177 + w13_num_shards * intermediate_size_per_partition,
178 + ),
179 + },
180 + "w13_zp": {
181 + "Marlin": (
182 + num_experts,
183 + num_groups_w13,
184 + w13_num_shards
185 + * intermediate_size_per_partition
186 + // self.packed_factor,
187 + ),
188 + },
189 + "w2_weight": {
190 + "Flashinfer": (
191 + num_experts,
192 + hidden_size,
193 + intermediate_size_per_partition // self.packed_factor,
194 + ),
195 + "Marlin": (
196 + num_experts,
197 + intermediate_size_per_partition // self.packed_factor,
198 + hidden_size,
199 + ),
200 + },
201 + "w2_scale": {
202 + "Flashinfer": (num_experts, hidden_size, num_groups_w2),
203 + "Marlin": (num_experts, num_groups_w2, hidden_size),
204 + },
205 + "w2_zp": {
206 + "Marlin": (
207 + num_experts,
208 + num_groups_w2,
209 + hidden_size // self.packed_factor,
210 + ),
211 + },
212 + }
213 + backend_key = "Marlin" if self.is_transposed else "Flashinfer"
214 + return shape_map[weight_name][backend_key]
215 +
216 + @staticmethod
217 + def _w2_scale_sharding(
218 + actorder,
219 + group_size: int,
220 + intermediate_size_per_partition: int,
221 + intermediate_size_full: int,
222 + ) -> tuple[bool, int, bool]:
223 + """Decide how to shard w2 group scales across TP for WNA16 Marlin MoE.
224 +
225 + Only ``actorder="group"`` permutes activations by ``g_idx`` at runtime
226 + and therefore needs the full-K (unsharded) w2 scales plus ``is_k_full``.
227 + ``actorder="weight"``/``"static"`` (and ``None``) reorder weights at
228 + quantization time, so scales shard normally per TP rank.
229 + """
230 + load_full_w2 = (actorder == "group") and group_size != -1
231 + w2_scales_size = (
232 + intermediate_size_full if load_full_w2 else intermediate_size_per_partition
233 + )
234 + is_k_full = (actorder != "group") or (
235 + intermediate_size_per_partition == intermediate_size_full
236 + )
237 + return load_full_w2, w2_scales_size, is_k_full
238 +
239 def create_weights(
240 self,
241 layer: torch.nn.Module,
@@ -61,18 +245,23 @@ class CompressedTensorsWNA16MoEMethod(CompressedTensorsMoEMethod):
245 params_dtype: torch.dtype,
246 **extra_weight_attrs,
247 ):
248 + intermediate_size_full = extra_weight_attrs.pop("intermediate_size_full")
249 +
250 # Will transpose the loaded weight along the
251 # intermediate and hidden dim sizes. Will
252 # shard for TP along the transposed dims
253 extra_weight_attrs.update(
68 - {"is_transposed": True, "quant_method": self.strategy}
254 + {"is_transposed": self.is_transposed, "quant_method": self.strategy}
255 )
70 - w13_num_shards = 2 if self.moe.is_act_and_mul else 1
256 +
257 w13_weight = torch.nn.Parameter(
258 torch.empty(
73 - num_experts,
74 - hidden_size // self.packed_factor,
75 - w13_num_shards * intermediate_size_per_partition,
259 + *self.get_weight_shape(
260 + "w13_weight",
261 + num_experts,
262 + hidden_size,
263 + intermediate_size_per_partition,
264 + ),
265 dtype=torch.int32,
266 ),
267 requires_grad=False,
@@ -82,9 +271,12 @@ class CompressedTensorsWNA16MoEMethod(CompressedTensorsMoEMethod):
271
272 w2_weight = torch.nn.Parameter(
273 torch.empty(
85 - num_experts,
86 - intermediate_size_per_partition // self.packed_factor,
87 - hidden_size,
274 + *self.get_weight_shape(
275 + "w2_weight",
276 + num_experts,
277 + hidden_size,
278 + intermediate_size_per_partition,
279 + ),
280 dtype=torch.int32,
281 ),
282 requires_grad=False,
@@ -92,7 +284,12 @@ class CompressedTensorsWNA16MoEMethod(CompressedTensorsMoEMethod):
284 layer.register_parameter("w2_weight_packed", w2_weight)
285 set_weight_attrs(w2_weight, extra_weight_attrs)
286
95 - w2_scales_size = intermediate_size_per_partition
287 + load_full_w2, w2_scales_size, self.is_k_full = self._w2_scale_sharding(
288 + self.actorder,
289 + self.group_size,
290 + intermediate_size_per_partition,
291 + intermediate_size_full,
292 + )
293
294 if self.strategy == "channel":
295 num_groups_w2 = num_groups_w13 = 1
@@ -104,23 +301,34 @@ class CompressedTensorsWNA16MoEMethod(CompressedTensorsMoEMethod):
301 f"({hidden_size}) to be divisible by group_size "
302 f"({self.group_size})."
303 )
107 - if intermediate_size_per_partition % self.group_size != 0:
304 + if (
305 + not load_full_w2
306 + and intermediate_size_per_partition % self.group_size != 0
307 + ):
308 raise ValueError(
109 - "CompressedTensors WNA16 MoE with static group scales "
110 - "requires the MoE intermediate size per tensor-parallel "
111 - f"partition ({intermediate_size_per_partition}) to be "
112 - f"divisible by group_size ({self.group_size}). Scale "
113 - "groups would otherwise cross TP shard boundaries; use a "
114 - "compatible TP size or enable expert parallelism."
309 + "CompressedTensors WNA16 MoE with static group "
310 + "scales requires the MoE intermediate size per "
311 + "tensor-parallel partition "
312 + f"({intermediate_size_per_partition}) to be divisible by "
313 + f"group_size ({self.group_size}). Scale groups would "
314 + "otherwise cross TP shard boundaries; use a compatible TP "
315 + "size or enable expert parallelism."
316 )
317 num_groups_w2 = w2_scales_size // self.group_size
318 num_groups_w13 = hidden_size // self.group_size
319
320 + layer.num_groups_w13 = num_groups_w13
321 + layer.num_groups_w2 = num_groups_w2
322 +
323 w13_scale = torch.nn.Parameter(
324 torch.ones(
121 - num_experts,
122 - num_groups_w13,
123 - w13_num_shards * intermediate_size_per_partition,
325 + *self.get_weight_shape(
326 + "w13_scale",
327 + num_experts,
328 + hidden_size,
329 + intermediate_size_per_partition,
330 + num_groups_w13=num_groups_w13,
331 + ),
332 dtype=params_dtype,
333 ),
334 requires_grad=False,
@@ -129,12 +337,54 @@ class CompressedTensorsWNA16MoEMethod(CompressedTensorsMoEMethod):
337 set_weight_attrs(w13_scale, extra_weight_attrs)
338
339 w2_scale = torch.nn.Parameter(
132 - torch.ones(num_experts, num_groups_w2, hidden_size, dtype=params_dtype),
340 + torch.ones(
341 + *self.get_weight_shape(
342 + "w2_scale",
343 + num_experts,
344 + hidden_size,
345 + intermediate_size_per_partition,
346 + num_groups_w2=num_groups_w2,
347 + ),
348 + dtype=params_dtype,
349 + ),
350 requires_grad=False,
351 )
352 layer.register_parameter("w2_weight_scale", w2_scale)
353 set_weight_attrs(w2_scale, extra_weight_attrs)
137 - set_weight_attrs(w2_scale, {"load_full_w2": False})
354 + set_weight_attrs(w2_scale, {"load_full_w2": load_full_w2})
355 +
356 + if not self.symmetric:
357 + w13_zp = torch.nn.Parameter(
358 + torch.zeros(
359 + *self.get_weight_shape(
360 + "w13_zp",
361 + num_experts,
362 + hidden_size,
363 + intermediate_size_per_partition,
364 + num_groups_w13=num_groups_w13,
365 + ),
366 + dtype=torch.int32,
367 + ),
368 + requires_grad=False,
369 + )
370 + layer.register_parameter("w13_weight_zero_point", w13_zp)
371 + set_weight_attrs(w13_zp, extra_weight_attrs)
372 +
373 + w2_zp = torch.nn.Parameter(
374 + torch.zeros(
375 + *self.get_weight_shape(
376 + "w2_zp",
377 + num_experts,
378 + hidden_size,
379 + intermediate_size_per_partition,
380 + num_groups_w2=num_groups_w2,
381 + ),
382 + dtype=torch.int32,
383 + ),
384 + requires_grad=False,
385 + )
386 + layer.register_parameter("w2_weight_zero_point", w2_zp)
387 + set_weight_attrs(w2_zp, extra_weight_attrs)
388
389 w2_weight_shape = torch.nn.Parameter(
390 torch.empty(num_experts, 2), requires_grad=False
@@ -195,65 +445,161 @@ class CompressedTensorsWNA16MoEMethod(CompressedTensorsMoEMethod):
445 layer.a13_scale = None
446 layer.a2_scale = None
447
198 - def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
199 - # Reconfigure packed weights and scales to match moe_wna16 format
200 - layer.w13_weight_packed = torch.nn.Parameter(
201 - layer.w13_weight_packed.transpose(1, 2).contiguous().view(torch.uint8),
202 - requires_grad=False,
203 - )
204 - layer.w2_weight_packed = torch.nn.Parameter(
205 - layer.w2_weight_packed.transpose(1, 2).contiguous().view(torch.uint8),
206 - requires_grad=False,
207 - )
208 - layer.w13_weight_scale = torch.nn.Parameter(
209 - layer.w13_weight_scale.transpose(1, 2).contiguous(), requires_grad=False
448 + def _setup_kernel(self, layer: RoutedExperts):
449 + assert self.experts_cls is not None
450 + self.moe_quant_config = self.get_fused_moe_quant_config(layer)
451 + assert self.moe_quant_config is not None
452 +
453 + # Add Marlin-specific arguments
454 + marlin_args: dict[str, Any] = {}
455 + if self.is_marlin:
456 + marlin_args = {
457 + "w13_g_idx": layer.w13_weight_g_idx,
458 + "w2_g_idx": layer.w2_weight_g_idx,
459 + "w13_g_idx_sort_indices": layer.w13_g_idx_sort_indices,
460 + "w2_g_idx_sort_indices": layer.w2_g_idx_sort_indices,
461 + "is_k_full": self.is_k_full,
462 + }
463 +
464 + self.moe_kernel = make_wna16_moe_kernel(
465 + moe_quant_config=self.moe_quant_config,
466 + moe_config=self.moe,
467 + experts_cls=self.experts_cls,
468 + routing_tables=layer._expert_routing_tables(),
469 + **marlin_args,
470 )
211 - layer.w2_weight_scale = torch.nn.Parameter(
212 - layer.w2_weight_scale.transpose(1, 2).contiguous(), requires_grad=False
471 +
472 + def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
473 + # Process weights using the shared oracle infrastructure
474 + converted = convert_to_wna16_moe_kernel_format(
475 + backend=self.wna16_backend,
476 + layer=layer,
477 + quant_config=self.weight_quant,
478 + input_dtype=self.input_dtype,
479 + w13=layer.w13_weight_packed,
480 + w2=layer.w2_weight_packed,
481 + w13_scale=layer.w13_weight_scale,
482 + w2_scale=layer.w2_weight_scale,
483 + w13_g_idx=layer.w13_weight_g_idx,
484 + w2_g_idx=layer.w2_weight_g_idx,
485 + w13_qzeros=getattr(layer, "w13_weight_zero_point", None),
486 + w2_qzeros=getattr(layer, "w2_weight_zero_point", None),
487 )
488
489 + if converted is None:
490 + self._setup_kernel(layer)
491 + return
492 +
493 + (
494 + w13_qweight,
495 + w2_qweight,
496 + w13_scales,
497 + w2_scales,
498 + w13_g_idx_processed,
499 + w2_g_idx_processed,
500 + w13_g_idx_sort_indices,
501 + w2_g_idx_sort_indices,
502 + w13_qzeros,
503 + w2_qzeros,
504 + w13_input_global_scale,
505 + w2_input_global_scale,
506 + _, # w13_bias
507 + _, # w2_bias
508 + ) = converted
509 +
510 + # Replace common parameters
511 + replace_parameter(layer, "w13_weight_packed", w13_qweight)
512 + replace_parameter(layer, "w2_weight_packed", w2_qweight)
513 + replace_parameter(layer, "w13_weight_scale", w13_scales)
514 + replace_parameter(layer, "w2_weight_scale", w2_scales)
515 +
516 + # CPU fused_experts_cpu requires zero points even for symmetric quant
517 + if not self.symmetric or self.wna16_backend == WNA16MoEBackend.CPU:
518 + assert w13_qzeros is not None and w2_qzeros is not None
519 + replace_parameter(layer, "w13_weight_zero_point", w13_qzeros)
520 + replace_parameter(layer, "w2_weight_zero_point", w2_qzeros)
521 +
522 + # Marlin-specific parameters (not needed for Flashinfer)
523 + if self.is_marlin:
524 + if w13_g_idx_processed is not None:
525 + replace_parameter(layer, "w13_weight_g_idx", w13_g_idx_processed)
526 + if w2_g_idx_processed is not None:
527 + replace_parameter(layer, "w2_weight_g_idx", w2_g_idx_processed)
528 + if w13_g_idx_sort_indices is not None:
529 + replace_parameter(
530 + layer, "w13_g_idx_sort_indices", w13_g_idx_sort_indices
531 + )
532 + if w2_g_idx_sort_indices is not None:
533 + replace_parameter(layer, "w2_g_idx_sort_indices", w2_g_idx_sort_indices)
534 +
535 + # Register input global scales if present
536 + if w13_input_global_scale is not None:
537 + layer.register_parameter(
538 + "w13_input_global_scale",
539 + torch.nn.Parameter(w13_input_global_scale, requires_grad=False),
540 + )
541 + if w2_input_global_scale is not None:
542 + layer.register_parameter(
543 + "w2_input_global_scale",
544 + torch.nn.Parameter(w2_input_global_scale, requires_grad=False),
545 + )
546 +
547 + # Marlin workspace — only needed for Marlin-family backends, not emulation.
548 + if (
549 + self.experts_cls is not None
550 + and issubclass(self.experts_cls, FusedMoEExpertsModular)
551 + and self.wna16_backend != WNA16MoEBackend.EMULATION
552 + ):
553 + layer.workspace = marlin_make_workspace_new(
554 + layer.w13_weight_g_idx.device,
555 + 4,
556 + existing=getattr(layer, "workspace", None),
557 + )
558 +
559 + # Alias packed weights to w13_weight/w2_weight for the modular kernel interface
560 + layer.w13_weight = layer.w13_weight_packed
561 + layer.w2_weight = layer.w2_weight_packed
562 +
563 + self._setup_kernel(layer)
564 +
565 def get_fused_moe_quant_config(
566 self, layer: torch.nn.Module
567 ) -> FusedMoEQuantConfig | None:
218 - assert self.num_bits == 4 or self.num_bits == 8
219 - config_builder = (
220 - int4_w4a16_moe_quant_config
221 - if self.num_bits == 4
222 - else int8_w8a16_moe_quant_config
223 - )
224 -
225 - return config_builder(
568 + return make_wna16_moe_quant_config(
569 w1_scale=layer.w13_weight_scale,
570 w2_scale=layer.w2_weight_scale,
228 - w1_zp=None,
229 - w2_zp=None,
230 - block_shape=[0, self.group_size],
571 + group_size=self.group_size,
572 + num_bits=self.num_bits,
573 + w1_zp=getattr(layer, "w13_weight_zero_point", None),
574 + w2_zp=getattr(layer, "w2_weight_zero_point", None),
575 + gemm1_clamp_limit=getattr(layer, "swiglu_limit", None),
576 + gemm1_alpha=getattr(layer, "swiglu_alpha", None),
577 + gemm1_beta=getattr(layer, "swiglu_beta", None),
578 )
579
233 - def select_gemm_impl(
580 + def apply_monolithic(
581 self,
235 - prepare_finalize: mk.FusedMoEPrepareAndFinalizeModular,
236 - layer: torch.nn.Module,
237 - ) -> mk.FusedMoEExpertsModular:
238 - if self.moe.is_lora_enabled:
239 - assert self.moe_quant_config is not None
240 - from vllm.triton_utils import HAS_TRITON
241 -
242 - if HAS_TRITON:
243 - from vllm.model_executor.layers.fused_moe import TritonWNA16Experts
244 -
245 - layer.w13_weight = layer.w13_weight_packed
246 - layer.w2_weight = layer.w2_weight_packed
247 - return TritonWNA16Experts(
248 - moe_config=self.moe, quant_config=self.moe_quant_config
249 - )
250 - else:
251 - raise NotImplementedError(
252 - "TritonExperts requires Triton. "
253 - "Install triton or disable LoRA for MoE."
254 - )
255 -
256 - raise NotImplementedError
582 + layer: RoutedExperts,
583 + x: torch.Tensor,
584 + router_logits: torch.Tensor,
585 + input_ids: torch.Tensor | None = None,
586 + ) -> torch.Tensor:
587 + assert self.is_monolithic
588 + assert self.moe_kernel is not None
589 + return self.moe_kernel.apply_monolithic(
590 + x,
591 + layer.w13_weight,
592 + layer.w2_weight,
593 + router_logits,
594 + activation=layer.activation,
595 + global_num_experts=layer.global_num_experts,
596 + expert_map=layer.expert_map,
597 + apply_router_weight_on_input=layer.apply_router_weight_on_input,
598 + num_expert_group=layer.num_expert_group,
599 + topk_group=layer.topk_group,
600 + e_score_correction_bias=layer.e_score_correction_bias,
601 + routed_scaling_factor=layer.routed_scaling_factor,
602 + )
603
604 def apply(
605 self,
@@ -264,21 +610,22 @@ class CompressedTensorsWNA16MoEMethod(CompressedTensorsMoEMethod):
610 shared_experts: SharedExperts | None,
611 shared_experts_input: torch.Tensor | None,
612 ) -> torch.Tensor:
267 - from vllm.model_executor.layers.fused_moe import fused_experts
268 -
269 - return fused_experts(
613 + assert not self.is_monolithic
614 + assert self.moe_kernel is not None
615 + return self.moe_kernel.apply(
616 x,
271 - layer.w13_weight_packed,
272 - layer.w2_weight_packed,
617 + layer.w13_weight,
618 + layer.w2_weight,
619 topk_weights=topk_weights,
620 topk_ids=topk_ids,
621 activation=layer.activation,
276 - apply_router_weight_on_input=layer.apply_router_weight_on_input,
622 global_num_experts=layer.global_num_experts,
623 expert_map=layer.expert_map,
279 - quant_config=self.moe_quant_config,
624 + apply_router_weight_on_input=layer.apply_router_weight_on_input,
625 + shared_experts=shared_experts,
626 + shared_experts_input=shared_experts_input,
627 )
628
629 @property
630 def supports_eplb(self) -> bool:
284 - return True
631 + return self.wna16_backend == WNA16MoEBackend.TRITON
vllm/model_executor/layers/quantization/compressed_tensors/compressed_tensors_moe/compressed_tensors_moe_wna16_marlin.py deleted
-601
@@ -1,601 +0,0 @@
1 -# SPDX-License-Identifier: Apache-2.0
2 -# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 -
4 -from typing import Any
5 -
6 -import torch
7 -from compressed_tensors.quantization import (
8 - QuantizationArgs,
9 -)
10 -
11 -from vllm.logger import init_logger
12 -from vllm.model_executor.layers.fused_moe import (
13 - FusedMoEExpertsModular,
14 - RoutedExperts,
15 - SharedExperts,
16 -)
17 -from vllm.model_executor.layers.fused_moe.config import (
18 - FusedMoEConfig,
19 - FusedMoEQuantConfig,
20 -)
21 -from vllm.model_executor.layers.fused_moe.oracle.int_wna16 import (
22 - WNA16MoEBackend,
23 - convert_to_wna16_moe_kernel_format,
24 - make_wna16_moe_kernel,
25 - make_wna16_moe_quant_config,
26 - select_wna16_moe_backend,
27 -)
28 -from vllm.model_executor.layers.quantization.compressed_tensors.compressed_tensors_moe import ( # noqa E501
29 - CompressedTensorsMoEMethod,
30 -)
31 -from vllm.model_executor.layers.quantization.compressed_tensors.schemes.compressed_tensors_wNa16 import ( # noqa
32 - WNA16_SUPPORTED_TYPES_MAP,
33 - WNA16_ZP_SUPPORTED_TYPES_MAP,
34 -)
35 -from vllm.model_executor.layers.quantization.utils.marlin_utils import (
36 - get_marlin_input_dtype,
37 - marlin_make_workspace_new,
38 -)
39 -from vllm.model_executor.layers.quantization.utils.quant_utils import (
40 - QuantKey,
41 - kInt4Static32GroupScale,
42 - kInt4StaticGroupScale,
43 - kInt8StaticGroupScale,
44 -)
45 -from vllm.model_executor.utils import replace_parameter, set_weight_attrs
46 -
47 -logger = init_logger(__name__)
48 -
49 -
50 -class CompressedTensorsWNA16MarlinMoEMethod(CompressedTensorsMoEMethod):
51 - def __init__(
52 - self,
53 - weight_quant: QuantizationArgs,
54 - input_quant: QuantizationArgs | None,
55 - moe: FusedMoEConfig,
56 - layer_name: str | None = None,
57 - ):
58 - super().__init__(moe)
59 - self.weight_quant = weight_quant
60 - self.input_quant = input_quant
61 - self.symmetric = weight_quant.symmetric
62 - # Extract properties from weight_quant
63 - self.num_bits = weight_quant.num_bits
64 - self.packed_factor = 32 // weight_quant.num_bits
65 - self.strategy = weight_quant.strategy
66 - self.group_size = weight_quant.group_size
67 - self.actorder = weight_quant.actorder
68 -
69 - self.quant_type = (
70 - WNA16_SUPPORTED_TYPES_MAP[self.num_bits]
71 - if self.symmetric
72 - else WNA16_ZP_SUPPORTED_TYPES_MAP[self.num_bits]
73 - )
74 -
75 - self.marlin_input_dtype = get_marlin_input_dtype(layer_name)
76 -
77 - if self.num_bits == 4:
78 - if self.group_size == 32:
79 - scale = kInt4Static32GroupScale
80 - else:
81 - scale = kInt4StaticGroupScale
82 - elif self.num_bits == 8:
83 - scale = kInt8StaticGroupScale
84 - else:
85 - raise ValueError(
86 - "CompressedTensorsWNA16MarlinMoEMethod only supports int4 and int8 now."
87 - )
88 -
89 - weight_key = QuantKey(self.quant_type, scale, symmetric=self.symmetric)
90 -
91 - # Select WNA16 MoE backend via oracle.
92 - self.wna16_backend, self.experts_cls = select_wna16_moe_backend(
93 - config=self.moe,
94 - weight_key=weight_key,
95 - quant_config=self.weight_quant,
96 - may_have_zp=not self.symmetric,
97 - may_have_bias=False,
98 - )
99 - self.is_marlin = self.wna16_backend in [
100 - WNA16MoEBackend.MARLIN,
101 - WNA16MoEBackend.BATCHED_MARLIN,
102 - ]
103 - self.is_transposed = self.wna16_backend != WNA16MoEBackend.FLASHINFER_TRTLLM
104 -
105 - def get_weight_shape(
106 - self,
107 - weight_name: str,
108 - num_experts: int,
109 - hidden_size: int,
110 - intermediate_size_per_partition: int,
111 - num_groups_w2: int | None = None,
112 - num_groups_w13: int | None = None,
113 - ) -> tuple[int, int, int]:
114 - """
115 - Get the shape of the weight based on the weight name, number of experts
116 - hidden size, intermediate size per partition, number of groups for w2,
117 - and number of groups for w13. Pass in num_groups_w2 and num_groups_w13
118 - for weight scales/zero_points.
119 - """
120 - if weight_name in ("w13_scale", "w13_zp"):
121 - assert num_groups_w13 is not None, (
122 - "num_groups_w13 must be provided for weight scales/zero_points"
123 - )
124 - if weight_name in ("w2_scale", "w2_zp"):
125 - assert num_groups_w2 is not None, (
126 - "num_groups_w2 must be provided for weight scales/zero_points"
127 - )
128 - w13_num_shards = 2 if self.moe.is_act_and_mul else 1
129 - shape_map = {
130 - "w13_weight": {
131 - "Flashinfer": (
132 - num_experts,
133 - w13_num_shards * intermediate_size_per_partition,
134 - hidden_size // self.packed_factor,
135 - ),
136 - "Marlin": (
137 - num_experts,
138 - hidden_size // self.packed_factor,
139 - w13_num_shards * intermediate_size_per_partition,
140 - ),
141 - },
142 - "w13_scale": {
143 - "Flashinfer": (
144 - num_experts,
145 - w13_num_shards * intermediate_size_per_partition,
146 - num_groups_w13,
147 - ),
148 - "Marlin": (
149 - num_experts,
150 - num_groups_w13,
151 - w13_num_shards * intermediate_size_per_partition,
152 - ),
153 - },
154 - "w13_zp": {
155 - "Marlin": (
156 - num_experts,
157 - num_groups_w13,
158 - w13_num_shards
159 - * intermediate_size_per_partition
160 - // self.packed_factor,
161 - ),
162 - },
163 - "w2_weight": {
164 - "Flashinfer": (
165 - num_experts,
166 - hidden_size,
167 - intermediate_size_per_partition // self.packed_factor,
168 - ),
169 - "Marlin": (
170 - num_experts,
171 - intermediate_size_per_partition // self.packed_factor,
172 - hidden_size,
173 - ),
174 - },
175 - "w2_scale": {
176 - "Flashinfer": (num_experts, hidden_size, num_groups_w2),
177 - "Marlin": (num_experts, num_groups_w2, hidden_size),
178 - },
179 - "w2_zp": {
180 - "Marlin": (
181 - num_experts,
182 - num_groups_w2,
183 - hidden_size // self.packed_factor,
184 - ),
185 - },
186 - }
187 - backend_key = "Marlin" if self.is_transposed else "Flashinfer"
188 - return shape_map[weight_name][backend_key]
189 -
190 - @staticmethod
191 - def _w2_scale_sharding(
192 - actorder,
193 - group_size: int,
194 - intermediate_size_per_partition: int,
195 - intermediate_size_full: int,
196 - ) -> tuple[bool, int, bool]:
197 - """Decide how to shard w2 group scales across TP for WNA16 Marlin MoE.
198 -
199 - Only ``actorder="group"`` permutes activations by ``g_idx`` at runtime
200 - and therefore needs the full-K (unsharded) w2 scales plus ``is_k_full``.
201 - ``actorder="weight"``/``"static"`` (and ``None``) reorder weights at
202 - quantization time, so scales shard normally per TP rank.
203 - """
204 - load_full_w2 = (actorder == "group") and group_size != -1
205 - w2_scales_size = (
206 - intermediate_size_full if load_full_w2 else intermediate_size_per_partition
207 - )
208 - is_k_full = (actorder != "group") or (
209 - intermediate_size_per_partition == intermediate_size_full
210 - )
211 - return load_full_w2, w2_scales_size, is_k_full
212 -
213 - def create_weights(
214 - self,
215 - layer: torch.nn.Module,
216 - num_experts: int,
217 - hidden_size: int,
218 - intermediate_size_per_partition: int,
219 - params_dtype: torch.dtype,
220 - **extra_weight_attrs,
221 - ):
222 - intermediate_size_full = extra_weight_attrs.pop("intermediate_size_full")
223 -
224 - # Will transpose the loaded weight along the
225 - # intermediate and hidden dim sizes. Will
226 - # shard for TP along the transposed dims
227 - extra_weight_attrs.update(
228 - {"is_transposed": self.is_transposed, "quant_method": self.strategy}
229 - )
230 -
231 - w13_weight = torch.nn.Parameter(
232 - torch.empty(
233 - *self.get_weight_shape(
234 - "w13_weight",
235 - num_experts,
236 - hidden_size,
237 - intermediate_size_per_partition,
238 - ),
239 - dtype=torch.int32,
240 - ),
241 - requires_grad=False,
242 - )
243 - layer.register_parameter("w13_weight_packed", w13_weight)
244 - set_weight_attrs(w13_weight, extra_weight_attrs)
245 -
246 - w2_weight = torch.nn.Parameter(
247 - torch.empty(
248 - *self.get_weight_shape(
249 - "w2_weight",
250 - num_experts,
251 - hidden_size,
252 - intermediate_size_per_partition,
253 - ),
254 - dtype=torch.int32,
255 - ),
256 - requires_grad=False,
257 - )
258 - layer.register_parameter("w2_weight_packed", w2_weight)
259 - set_weight_attrs(w2_weight, extra_weight_attrs)
260 -
261 - load_full_w2, w2_scales_size, self.is_k_full = self._w2_scale_sharding(
262 - self.actorder,
263 - self.group_size,
264 - intermediate_size_per_partition,
265 - intermediate_size_full,
266 - )
267 -
268 - if self.strategy == "channel":
269 - num_groups_w2 = num_groups_w13 = 1
270 - self.group_size = -1
271 - else:
272 - if hidden_size % self.group_size != 0:
273 - raise ValueError(
274 - "CompressedTensors WNA16 Marlin MoE requires hidden_size "
275 - f"({hidden_size}) to be divisible by group_size "
276 - f"({self.group_size})."
277 - )
278 - if (
279 - not load_full_w2
280 - and intermediate_size_per_partition % self.group_size != 0
281 - ):
282 - raise ValueError(
283 - "CompressedTensors WNA16 Marlin MoE with static group "
284 - "scales requires the MoE intermediate size per "
285 - "tensor-parallel partition "
286 - f"({intermediate_size_per_partition}) to be divisible by "
287 - f"group_size ({self.group_size}). Scale groups would "
288 - "otherwise cross TP shard boundaries; use a compatible TP "
289 - "size or enable expert parallelism."
290 - )
291 - num_groups_w2 = w2_scales_size // self.group_size
292 - num_groups_w13 = hidden_size // self.group_size
293 -
294 - layer.num_groups_w13 = num_groups_w13
295 - layer.num_groups_w2 = num_groups_w2
296 -
297 - w13_scale = torch.nn.Parameter(
298 - torch.ones(
299 - *self.get_weight_shape(
300 - "w13_scale",
301 - num_experts,
302 - hidden_size,
303 - intermediate_size_per_partition,
304 - num_groups_w13=num_groups_w13,
305 - ),
306 - dtype=params_dtype,
307 - ),
308 - requires_grad=False,
309 - )
310 - layer.register_parameter("w13_weight_scale", w13_scale)
311 - set_weight_attrs(w13_scale, extra_weight_attrs)
312 -
313 - w2_scale = torch.nn.Parameter(
314 - torch.ones(
315 - *self.get_weight_shape(
316 - "w2_scale",
317 - num_experts,
318 - hidden_size,
319 - intermediate_size_per_partition,
320 - num_groups_w2=num_groups_w2,
321 - ),
322 - dtype=params_dtype,
323 - ),
324 - requires_grad=False,
325 - )
326 - layer.register_parameter("w2_weight_scale", w2_scale)
327 - set_weight_attrs(w2_scale, extra_weight_attrs)
328 - set_weight_attrs(w2_scale, {"load_full_w2": load_full_w2})
329 -
330 - if not self.symmetric:
331 - w13_zp = torch.nn.Parameter(
332 - torch.zeros(
333 - *self.get_weight_shape(
334 - "w13_zp",
335 - num_experts,
336 - hidden_size,
337 - intermediate_size_per_partition,
338 - num_groups_w13=num_groups_w13,
339 - ),
340 - dtype=torch.int32,
341 - ),
342 - requires_grad=False,
343 - )
344 - layer.register_parameter("w13_weight_zero_point", w13_zp)
345 - set_weight_attrs(w13_zp, extra_weight_attrs)
346 -
347 - w2_zp = torch.nn.Parameter(
348 - torch.zeros(
349 - *self.get_weight_shape(
350 - "w2_zp",
351 - num_experts,
352 - hidden_size,
353 - intermediate_size_per_partition,
354 - num_groups_w2=num_groups_w2,
355 - ),
356 - dtype=torch.int32,
357 - ),
358 - requires_grad=False,
359 - )
360 - layer.register_parameter("w2_weight_zero_point", w2_zp)
361 - set_weight_attrs(w2_zp, extra_weight_attrs)
362 -
363 - w2_weight_shape = torch.nn.Parameter(
364 - torch.empty(num_experts, 2), requires_grad=False
365 - )
366 - layer.register_parameter("w2_weight_shape", w2_weight_shape)
367 - set_weight_attrs(w2_weight_shape, extra_weight_attrs)
368 - w13_weight_shape = torch.nn.Parameter(
369 - torch.empty(num_experts, 2), requires_grad=False
370 - )
371 -
372 - layer.register_parameter("w13_weight_shape", w13_weight_shape)
373 - set_weight_attrs(w13_weight_shape, extra_weight_attrs)
374 -
375 - w13_g_idx = torch.nn.Parameter(
376 - torch.empty(
377 - num_experts,
378 - hidden_size,
379 - dtype=torch.int32,
380 - ),
381 - requires_grad=False,
382 - )
383 - layer.register_parameter("w13_weight_g_idx", w13_g_idx)
384 - set_weight_attrs(w13_g_idx, extra_weight_attrs)
385 -
386 - w2_g_idx = torch.nn.Parameter(
387 - torch.empty(
388 - num_experts,
389 - intermediate_size_per_partition,
390 - dtype=torch.int32,
391 - ),
392 - requires_grad=False,
393 - )
394 - layer.register_parameter("w2_weight_g_idx", w2_g_idx)
395 - set_weight_attrs(w2_g_idx, extra_weight_attrs)
396 -
397 - w13_g_idx_sort_indices = torch.nn.Parameter(
398 - torch.empty(
399 - num_experts,
400 - hidden_size,
401 - dtype=torch.int32,
402 - ),
403 - requires_grad=False,
404 - )
405 - layer.register_parameter("w13_g_idx_sort_indices", w13_g_idx_sort_indices)
406 - set_weight_attrs(w13_g_idx_sort_indices, extra_weight_attrs)
407 -
408 - w2_g_idx_sort_indices = torch.nn.Parameter(
409 - torch.empty(
410 - num_experts,
411 - intermediate_size_per_partition,
412 - dtype=torch.int32,
413 - ),
414 - requires_grad=False,
415 - )
416 - layer.register_parameter("w2_g_idx_sort_indices", w2_g_idx_sort_indices)
417 - set_weight_attrs(w2_g_idx_sort_indices, extra_weight_attrs)
418 -
419 - layer.a13_scale = None
420 - layer.a2_scale = None
421 -
422 - def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
423 - # Process weights using the shared oracle infrastructure
424 - converted = convert_to_wna16_moe_kernel_format(
425 - backend=self.wna16_backend,
426 - layer=layer,
427 - quant_config=self.weight_quant,
428 - input_dtype=self.marlin_input_dtype,
429 - w13=layer.w13_weight_packed,
430 - w2=layer.w2_weight_packed,
431 - w13_scale=layer.w13_weight_scale,
432 - w2_scale=layer.w2_weight_scale,
433 - w13_g_idx=layer.w13_weight_g_idx,
434 - w2_g_idx=layer.w2_weight_g_idx,
435 - w13_qzeros=getattr(layer, "w13_weight_zero_point", None),
436 - w2_qzeros=getattr(layer, "w2_weight_zero_point", None),
437 - )
438 -
439 - if converted is None:
440 - # In-place backends (e.g. Humming) are not wired through this
441 - # marlin-only method; fail clearly rather than unpacking None.
442 - raise NotImplementedError(
443 - f"{type(self).__name__} does not support the "
444 - f"{self.wna16_backend.value} MoE backend."
445 - )
446 -
447 - (
448 - w13_qweight,
449 - w2_qweight,
450 - w13_scales,
451 - w2_scales,
452 - w13_g_idx_processed,
453 - w2_g_idx_processed,
454 - w13_g_idx_sort_indices,
455 - w2_g_idx_sort_indices,
456 - w13_qzeros,
457 - w2_qzeros,
458 - w13_input_global_scale,
459 - w2_input_global_scale,
460 - _, # w13_bias
461 - _, # w2_bias
462 - ) = converted
463 -
464 - # Replace common parameters
465 - replace_parameter(layer, "w13_weight_packed", w13_qweight)
466 - replace_parameter(layer, "w2_weight_packed", w2_qweight)
467 - replace_parameter(layer, "w13_weight_scale", w13_scales)
468 - replace_parameter(layer, "w2_weight_scale", w2_scales)
469 -
470 - # CPU fused_experts_cpu requires zero points even for symmetric quant
471 - if not self.symmetric or self.wna16_backend == WNA16MoEBackend.CPU:
472 - replace_parameter(layer, "w13_weight_zero_point", w13_qzeros)
473 - replace_parameter(layer, "w2_weight_zero_point", w2_qzeros)
474 -
475 - # Marlin-specific parameters (not needed for Flashinfer)
476 - if self.is_marlin:
477 - if w13_g_idx_processed is not None:
478 - replace_parameter(layer, "w13_weight_g_idx", w13_g_idx_processed)
479 - if w2_g_idx_processed is not None:
480 - replace_parameter(layer, "w2_weight_g_idx", w2_g_idx_processed)
481 - if w13_g_idx_sort_indices is not None:
482 - replace_parameter(
483 - layer, "w13_g_idx_sort_indices", w13_g_idx_sort_indices
484 - )
485 - if w2_g_idx_sort_indices is not None:
486 - replace_parameter(layer, "w2_g_idx_sort_indices", w2_g_idx_sort_indices)
487 -
488 - # Register input global scales if present
489 - if w13_input_global_scale is not None:
490 - layer.register_parameter(
491 - "w13_input_global_scale",
492 - torch.nn.Parameter(w13_input_global_scale, requires_grad=False),
493 - )
494 - if w2_input_global_scale is not None:
495 - layer.register_parameter(
496 - "w2_input_global_scale",
497 - torch.nn.Parameter(w2_input_global_scale, requires_grad=False),
498 - )
499 -
500 - # Marlin workspace — only needed for Marlin-family backends, not emulation.
501 - if (
502 - self.experts_cls is not None
503 - and issubclass(self.experts_cls, FusedMoEExpertsModular)
504 - and self.wna16_backend != WNA16MoEBackend.EMULATION
505 - ):
506 - layer.workspace = marlin_make_workspace_new(
507 - layer.w13_weight_g_idx.device,
508 - 4,
509 - existing=getattr(layer, "workspace", None),
510 - )
511 -
512 - # Alias packed weights to w13_weight/w2_weight for the modular kernel interface
513 - layer.w13_weight = layer.w13_weight_packed
514 - layer.w2_weight = layer.w2_weight_packed
515 -
516 - assert self.experts_cls is not None
517 - self.moe_quant_config = self.get_fused_moe_quant_config(layer)
518 - assert self.moe_quant_config is not None
519 -
520 - # Add Marlin-specific arguments
521 - marlin_args: dict[str, Any] = {}
522 - if self.is_marlin:
523 - marlin_args = {
524 - "w13_g_idx": layer.w13_weight_g_idx,
525 - "w2_g_idx": layer.w2_weight_g_idx,
526 - "w13_g_idx_sort_indices": layer.w13_g_idx_sort_indices,
527 - "w2_g_idx_sort_indices": layer.w2_g_idx_sort_indices,
528 - "is_k_full": self.is_k_full,
529 - }
530 -
531 - self.moe_kernel = make_wna16_moe_kernel(
532 - moe_quant_config=self.moe_quant_config,
533 - moe_config=self.moe,
534 - experts_cls=self.experts_cls,
535 - routing_tables=layer._expert_routing_tables(),
536 - **marlin_args,
537 - )
538 -
539 - def get_fused_moe_quant_config(
540 - self, layer: torch.nn.Module
541 - ) -> FusedMoEQuantConfig | None:
542 - return make_wna16_moe_quant_config(
543 - w1_scale=layer.w13_weight_scale,
544 - w2_scale=layer.w2_weight_scale,
545 - group_size=self.group_size,
546 - num_bits=self.num_bits,
547 - w1_zp=getattr(layer, "w13_weight_zero_point", None),
548 - w2_zp=getattr(layer, "w2_weight_zero_point", None),
549 - gemm1_clamp_limit=getattr(layer, "swiglu_limit", None),
550 - gemm1_alpha=getattr(layer, "swiglu_alpha", None),
551 - gemm1_beta=getattr(layer, "swiglu_beta", None),
552 - )
553 -
554 - def apply_monolithic(
555 - self,
556 - layer: RoutedExperts,
557 - x: torch.Tensor,
558 - router_logits: torch.Tensor,
559 - input_ids: torch.Tensor | None = None,
560 - ) -> torch.Tensor:
561 - assert self.is_monolithic
562 - assert self.moe_kernel is not None
563 - return self.moe_kernel.apply_monolithic(
564 - x,
565 - layer.w13_weight,
566 - layer.w2_weight,
567 - router_logits,
568 - activation=layer.activation,
569 - global_num_experts=layer.global_num_experts,
570 - expert_map=layer.expert_map,
571 - apply_router_weight_on_input=layer.apply_router_weight_on_input,
572 - num_expert_group=layer.num_expert_group,
573 - topk_group=layer.topk_group,
574 - e_score_correction_bias=layer.e_score_correction_bias,
575 - routed_scaling_factor=layer.routed_scaling_factor,
576 - )
577 -
578 - def apply(
579 - self,
580 - layer: RoutedExperts,
581 - x: torch.Tensor,
582 - topk_weights: torch.Tensor,
583 - topk_ids: torch.Tensor,
584 - shared_experts: SharedExperts | None,
585 - shared_experts_input: torch.Tensor | None,
586 - ) -> torch.Tensor:
587 - assert not self.is_monolithic
588 - assert self.moe_kernel is not None
589 - return self.moe_kernel.apply(
590 - x,
591 - layer.w13_weight,
592 - layer.w2_weight,
593 - topk_weights,
594 - topk_ids,
595 - activation=layer.activation,
596 - global_num_experts=layer.global_num_experts,
597 - expert_map=layer.expert_map,
598 - apply_router_weight_on_input=layer.apply_router_weight_on_input,
599 - shared_experts=shared_experts,
600 - shared_experts_input=shared_experts_input,
601 - )
vllm/model_executor/layers/quantization/modelopt.py
-20
@@ -778,16 +778,6 @@ class ModelOptFp8MoEMethod(FusedMoEMethodBase):
778 "logic. This function should not be called."
779 )
780
781 - def select_gemm_impl(
782 - self,
783 - prepare_finalize: mk.FusedMoEPrepareAndFinalizeModular,
784 - layer: RoutedExperts,
785 - ) -> mk.FusedMoEExpertsModular:
786 - raise ValueError(
787 - f"{self.__class__.__name__} uses the new modular kernel initialization "
788 - "logic. This function should not be called."
789 - )
790 -
781 def create_weights(
782 self,
783 layer: RoutedExperts,
@@ -2104,16 +2094,6 @@ class ModelOptMxFp8FusedMoE(FusedMoEMethodBase):
2094 "logic. This function should not be called."
2095 )
2096
2107 - def select_gemm_impl(
2108 - self,
2109 - prepare_finalize: mk.FusedMoEPrepareAndFinalizeModular,
2110 - layer: RoutedExperts,
2111 - ) -> mk.FusedMoEExpertsModular:
2112 - raise ValueError(
2113 - f"{self.__class__.__name__} uses the new modular kernel initialization "
2114 - "logic. This function should not be called."
2115 - )
2116 -
2097 def get_fused_moe_quant_config(
2098 self, layer: RoutedExperts
2099 ) -> FusedMoEQuantConfig | None:
vllm/model_executor/layers/quantization/mxfp4.py
-20
@@ -430,16 +430,6 @@ class GptOssMxfp4MoEMethod(FusedMoEMethodBase):
430 layer=layer,
431 )
432
433 - def select_gemm_impl(
434 - self,
435 - prepare_finalize: mk.FusedMoEPrepareAndFinalize,
436 - layer: RoutedExperts,
437 - ) -> mk.FusedMoEExpertsModular:
438 - raise ValueError(
439 - f"{self.__class__.__name__} uses the new modular kernel "
440 - "initialization logic. This function should not be called."
441 - )
442 -
433 def apply(
434 self,
435 layer: RoutedExperts,
@@ -893,16 +883,6 @@ class Mxfp4MoEMethod(FusedMoEMethodBase):
883 layer=layer,
884 )
885
896 - def select_gemm_impl(
897 - self,
898 - prepare_finalize: mk.FusedMoEPrepareAndFinalize,
899 - layer: RoutedExperts,
900 - ) -> mk.FusedMoEExpertsModular:
901 - raise ValueError(
902 - f"{self.__class__.__name__} uses the new modular kernel "
903 - "initialization logic. This function should not be called."
904 - )
905 -
886 def apply(
887 self,
888 layer: RoutedExperts,
vllm/model_executor/layers/quantization/utils/marlin_utils.py
+19 -10
@@ -12,6 +12,7 @@ from vllm import _custom_ops as ops
12 from vllm.distributed.utils import verify_group_size_divides_partition
13 from vllm.logger import init_logger
14 from vllm.model_executor.layers.fused_moe import RoutedExperts
15 +from vllm.model_executor.layers.fused_moe.config import FusedMoEConfig
16 from vllm.model_executor.layers.linear import LinearBase
17 from vllm.model_executor.layers.quantization.input_quant_fp8 import QuantFP8
18 from vllm.model_executor.layers.quantization.utils.int8_utils import (
@@ -351,10 +352,12 @@ def marlin_moe_padded_intermediate(intermediate_size: int, group_size: int = -1)
352 return padded
353
354
354 -def check_moe_marlin_supports_layer(
355 - layer: RoutedExperts, group_size: int, allow_tile_padding: bool = False
355 +def check_moe_marlin_supports_config(
356 + config: FusedMoEConfig,
357 + group_size: int,
358 + allow_tile_padding: bool = False,
359 ) -> bool:
357 - """Whether the fused MoE Marlin kernel supports ``layer``.
360 + """Whether the fused MoE Marlin kernel supports ``config``.
361
362 Callers without act-order may pass ``allow_tile_padding=True``: a
363 tile-misaligned intermediate size is then zero-padded to a valid thread
@@ -364,15 +367,11 @@ def check_moe_marlin_supports_layer(
367 """
368 if current_platform.is_rocm():
369 return False
367 - hidden_size = layer.hidden_size
370 + hidden_size = config.hidden_dim
371 # The layer has not rounded intermediate_size yet; use the stable unpadded
372 # size. gate-up needs n=2*intermediate % 128, down needs k=intermediate % 64.
370 - intermediate_size_per_partition = (
371 - layer.moe_config.intermediate_size_per_partition_unpadded
372 - )
373 + intermediate_size_per_partition = config.intermediate_size_per_partition_unpadded
374 assert intermediate_size_per_partition is not None
374 - # apply_router_weight_on_input is not supported for moe marlin
375 - supports_router_weight = not layer.apply_router_weight_on_input
375
376 if allow_tile_padding:
377 supports_shape = hidden_size % 128 == 0 and (
@@ -384,7 +383,17 @@ def check_moe_marlin_supports_layer(
383 and intermediate_size_per_partition % max(64, group_size) == 0
384 )
385 supports_group_size = group_size in [-1, 32, 64, 128]
387 - return supports_shape and supports_group_size and supports_router_weight
386 + return supports_shape and supports_group_size
387 +
388 +
389 +def check_moe_marlin_supports_layer(
390 + layer: RoutedExperts,
391 + group_size: int,
392 + allow_tile_padding: bool = False,
393 +) -> bool:
394 + return check_moe_marlin_supports_config(
395 + layer.moe_config, group_size, allow_tile_padding
396 + )
397
398
399 def marlin_moe_intermediate_size(w1_packed: torch.Tensor, w2_packed: torch.Tensor):