A high-throughput and memory-efficient inference and serving engine for LLMs
Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git
Synced Nov 2, 09:45 UTC
[MoE Refactor] Migrate MoeWNA16Method quantization to MK oracle (#42647)
Signed-off-by: Bill Nell <bnell@redhat.com> Co-authored-by: Claude <noreply@anthropic.com>
bnellnm committed
May 29, 2026 at 17:19 UTC
106aa92f04c0d1c3c37947fd9d4921530562a961
8 files changed
+332
-97
vllm/model_executor/layers/fused_moe/experts/triton_moe.py
+30
-20
@@ -43,7 +43,10 @@ from vllm.model_executor.layers.quantization.utils.quant_utils import (
43
kFp8Static128BlockSym,
44
kFp8StaticChannelSym,
45
kFp8StaticTensorSym,
46
+ kInt4Static,
47
+ kInt4Static32,
48
kInt8DynamicTokenSym,
49
+ kInt8Static,
50
kInt8StaticChannelSym,
51
)
52
from vllm.platforms import current_platform
@@ -459,40 +462,45 @@ class TritonExperts(LoRAExpertsMixin, mk.FusedMoEExpertsModular):
462
class TritonWNA16Experts(TritonExperts):
463
@staticmethod
464
def _supports_current_device() -> bool:
462
- raise NotImplementedError(
463
- "TritonWNA16Experts is not yet used by an Oracle. "
464
- "This method should not be called."
465
- )
465
+ return current_platform.is_cuda_alike() or current_platform.is_xpu()
466
467
@staticmethod
468
def _supports_no_act_and_mul() -> bool:
469
- raise NotImplementedError(
470
- "TritonWNA16Experts is not yet used by an Oracle. "
471
- "This method should not be called."
472
- )
469
+ return True
470
471
@staticmethod
472
def _supports_quant_scheme(
473
weight_key: QuantKey | None,
474
activation_key: QuantKey | None,
475
) -> bool:
479
- raise NotImplementedError(
480
- "TritonWNA16Experts is not yet used by an Oracle. "
481
- "This method should not be called."
482
- )
476
+ SUPPORTED_W = [
477
+ kInt4Static,
478
+ kInt8Static,
479
+ kInt4Static32,
480
+ # other group sizes?
481
+ ]
482
+ return weight_key in SUPPORTED_W
483
484
@staticmethod
485
def _supports_activation(activation: MoEActivation) -> bool:
486
- raise NotImplementedError(
487
- "TritonWNA16Experts is not yet used by an Oracle. "
488
- "This method should not be called."
489
- )
486
+ return activation in [
487
+ MoEActivation.SILU,
488
+ MoEActivation.GELU,
489
+ MoEActivation.GELU_TANH,
490
+ MoEActivation.SWIGLUOAI,
491
+ MoEActivation.SWIGLUSTEP,
492
+ MoEActivation.SILU_NO_MUL,
493
+ MoEActivation.GELU_NO_MUL,
494
+ MoEActivation.GELU_TANH_NO_MUL,
495
+ MoEActivation.RELU2_NO_MUL,
496
+ ]
497
498
@staticmethod
499
def _supports_parallel_config(moe_parallel_config: FusedMoEParallelConfig) -> bool:
493
- raise NotImplementedError(
494
- "TritonWNA16Experts is not yet used by an Oracle. "
495
- "This method should not be called."
500
+ # Why?
501
+ return not (
502
+ moe_parallel_config.use_fi_nvl_two_sided_kernels
503
+ or moe_parallel_config.use_fi_nvl_one_sided_kernels
504
)
505
506
def apply(
@@ -515,7 +523,9 @@ class TritonWNA16Experts(TritonExperts):
523
):
524
# Check constraints.
525
if self.quant_config.use_int4_w4a16:
518
- assert hidden_states.size(-1) // 2 == w1.size(2), "Hidden size mismatch"
526
+ assert hidden_states.size(-1) // 2 == w1.size(2), (
527
+ f"Hidden size mismatch {hidden_states.size(-1) // 2} == {w1.size(2)}"
528
+ )
529
else:
530
assert hidden_states.size(-1) == w1.size(2), (
531
f"Hidden size mismatch {hidden_states.size(-1)} != {w1.size(2)}"
vllm/model_executor/layers/fused_moe/fused_moe.py
+6
-1
@@ -30,6 +30,7 @@ from vllm.model_executor.layers.fused_moe.utils import (
30
)
31
from vllm.platforms import current_platform
32
from vllm.triton_utils import tl, triton
33
+from vllm.utils.math_utils import next_power_of_2
34
from vllm.utils.torch_utils import direct_register_custom_op
35
36
logger = init_logger(__name__)
@@ -1243,7 +1244,11 @@ def get_default_config(
1244
bit = 4 if dtype == "int4_w4a16" else 8
1245
use_moe_wna16_cuda = should_moe_wna16_use_cuda(M * topk, block_shape[1], E, bit)
1246
if use_moe_wna16_cuda:
1246
- config = {"BLOCK_SIZE_M": min(16, M), "SPLIT_K": 1}
1247
+ config = {
1248
+ "BLOCK_SIZE_M": min(16, next_power_of_2(M)),
1249
+ "GROUP_SIZE_M": 1,
1250
+ "SPLIT_K": 1,
1251
+ }
1252
elif M <= 20:
1253
config = {"BLOCK_SIZE_M": 16, "GROUP_SIZE_M": 1, "SPLIT_K": 1}
1254
elif M <= 40:
vllm/model_executor/layers/fused_moe/oracle/int_wna16.py
+91
-6
@@ -23,6 +23,9 @@ from vllm.model_executor.layers.fused_moe.experts.marlin_moe import (
23
MarlinExperts,
24
MarlinExpertsBase,
25
)
26
+from vllm.model_executor.layers.fused_moe.experts.triton_moe import (
27
+ TritonWNA16Experts,
28
+)
29
from vllm.model_executor.layers.fused_moe.experts.trtllm_mxint4_moe import (
30
TrtLlmMxint4ExpertsMonolithic,
31
)
@@ -31,6 +34,7 @@ from vllm.model_executor.layers.quantization.utils.marlin_utils import (
34
marlin_act_int8_process_scales,
35
marlin_moe_permute_scales,
36
marlin_permute_bias,
37
+ marlin_zero_points,
38
moe_awq_to_marlin_zero_points,
39
)
40
from vllm.model_executor.layers.quantization.utils.quant_utils import (
@@ -45,6 +49,7 @@ class WNA16MoEBackend(Enum):
49
MARLIN = "MARLIN"
50
BATCHED_MARLIN = "BATCHED_MARLIN"
51
FLASHINFER_TRTLLM = "FLASHINFER_TRTLLM"
52
+ TRITON = "TRITON"
53
XPU = "XPU"
54
55
@@ -58,6 +63,8 @@ def backend_to_kernel_cls(
63
return [BatchedMarlinExperts]
64
elif backend == WNA16MoEBackend.FLASHINFER_TRTLLM:
65
return [TrtLlmMxint4ExpertsMonolithic]
66
+ elif backend == WNA16MoEBackend.TRITON:
67
+ return [TritonWNA16Experts]
68
elif backend == WNA16MoEBackend.XPU:
69
from vllm.model_executor.layers.fused_moe.experts.xpu_moe import (
70
XPUExpertsWNA16,
@@ -68,24 +75,38 @@ def backend_to_kernel_cls(
75
raise ValueError(f"Unknown WNA16 MoE backend: {backend.value}")
76
77
71
-def _get_priority_backends() -> list[WNA16MoEBackend]:
78
+def _get_priority_backends(
79
+ may_have_zp: bool, may_have_bias: bool
80
+) -> list[WNA16MoEBackend]:
81
"""
82
Get available backends in priority order based on platform and config.
83
"""
84
if current_platform.is_xpu():
85
return [WNA16MoEBackend.XPU]
86
78
- _AVAILABLE_BACKENDS = [
79
- WNA16MoEBackend.FLASHINFER_TRTLLM,
87
+ _AVAILABLE_BACKENDS = []
88
+
89
+ if not may_have_zp and not may_have_bias:
90
+ _AVAILABLE_BACKENDS.append(WNA16MoEBackend.FLASHINFER_TRTLLM)
91
+
92
+ # Marlin supports ZP and bias
93
+ _AVAILABLE_BACKENDS += [
94
WNA16MoEBackend.MARLIN,
95
WNA16MoEBackend.BATCHED_MARLIN,
96
]
97
return _AVAILABLE_BACKENDS
98
99
+ if not may_have_bias:
100
+ _AVAILABLE_BACKENDS.append(WNA16MoEBackend.TRITON)
101
+
102
+ return _AVAILABLE_BACKENDS
103
+
104
105
def select_wna16_moe_backend(
106
config: FusedMoEConfig,
107
weight_key: QuantKey,
108
+ may_have_zp: bool,
109
+ may_have_bias: bool,
110
) -> tuple[WNA16MoEBackend, type[mk.FusedMoEExperts]]:
111
"""Select the WNA16 MoE backend.
112
@@ -136,7 +157,7 @@ def select_wna16_moe_backend(
157
raise ValueError(_make_log_unsupported(backend, reason))
158
159
# Select kernels in order of backend.
139
- AVAILABLE_BACKENDS = _get_priority_backends()
160
+ AVAILABLE_BACKENDS = _get_priority_backends(may_have_zp, may_have_bias)
161
162
for backend in AVAILABLE_BACKENDS:
163
activation_key = None # always BF16 activation for WNA16 MoE
@@ -218,6 +239,7 @@ def make_wna16_moe_kernel(
239
assert experts_cls in (
240
MarlinExperts,
241
BatchedMarlinExperts,
242
+ TritonWNA16Experts,
243
TrtLlmMxint4ExpertsMonolithic,
244
XPUExpertsWNA16,
245
)
@@ -234,6 +256,7 @@ def make_wna16_moe_kernel(
256
assert prepare_finalize is not None
257
258
logger.info_once("Using %s", prepare_finalize.__class__.__name__, scope="local")
259
+ logger.info_once("Using %s", experts_cls.__name__, scope="local")
260
261
extra_args: dict[str, Any] = {}
262
if issubclass(experts_cls, MarlinExpertsBase):
@@ -403,6 +426,8 @@ def _process_weights_marlin(
426
w2_input_global_scale: torch.Tensor | None = None
427
w13_bias_out: torch.Tensor | None = None
428
w2_bias_out: torch.Tensor | None = None
429
+ w13_qzeros_out: torch.Tensor | None = None
430
+ w2_qzeros_out: torch.Tensor | None = None
431
432
# --- FP8 weight / scale adjustment ---
433
if input_dtype == torch.float8_e4m3fn:
@@ -502,6 +527,24 @@ def _process_weights_marlin(
527
if w2_bias is not None:
528
w2_bias_out = marlin_permute_bias(w2_bias)
529
530
+ if w13_qzeros is not None:
531
+ w13_qzeros_out = marlin_zero_points(
532
+ w13_qzeros,
533
+ size_k=layer.intermediate_size_per_partition,
534
+ size_n=w13_qzeros.shape[2],
535
+ num_bits=num_bits,
536
+ is_a_8bit=is_a_8bit,
537
+ )
538
+
539
+ if w2_qzeros is not None:
540
+ w2_qzeros_out = marlin_zero_points(
541
+ w2_qzeros,
542
+ size_k=w2_qzeros.shape[1] * group_size_or_pack_factor,
543
+ size_n=w2_qzeros.shape[2],
544
+ num_bits=num_bits,
545
+ is_a_8bit=is_a_8bit,
546
+ )
547
+
548
return (
549
marlin_w13_qweight,
550
marlin_w2_qweight,
@@ -511,8 +554,8 @@ def _process_weights_marlin(
554
w2_g_idx,
555
w13_g_idx_sort_indices,
556
w2_g_idx_sort_indices,
514
- w13_qzeros,
515
- w2_qzeros,
557
+ w13_qzeros_out,
558
+ w2_qzeros_out,
559
w13_input_global_scale,
560
w2_input_global_scale,
561
w13_bias_out,
@@ -780,6 +823,9 @@ def convert_to_wna16_moe_kernel_format(
823
from vllm.model_executor.layers.quantization.awq_marlin import (
824
AWQMarlinConfig,
825
)
826
+ from vllm.model_executor.layers.quantization.moe_wna16 import (
827
+ MoeWNA16Config,
828
+ )
829
830
if isinstance(quant_config, AWQMarlinConfig):
831
if w13_qzeros is None or w2_qzeros is None:
@@ -814,11 +860,17 @@ def convert_to_wna16_moe_kernel_format(
860
pack_factor = 32 // quant_config.num_bits
861
group_size = quant_config.group_size
862
actorder = quant_config.actorder
863
+ elif isinstance(quant_config, MoeWNA16Config):
864
+ num_bits = quant_config.weight_bits
865
+ pack_factor = quant_config.bit8_pack_factor
866
+ group_size = quant_config.group_size
867
+ actorder = None
868
else:
869
raise TypeError(
870
"Marlin WNA16 MoE backend requires AutoGPTQConfig, AWQMarlinConfig or "
871
f"QuantizationArgs, got {type(quant_config).__name__}."
872
)
873
+
874
if w13_g_idx is None or w2_g_idx is None:
875
raise ValueError("GPTQ Marlin MoE requires g_idx tensors.")
876
return _process_weights_marlin(
@@ -850,6 +902,39 @@ def convert_to_wna16_moe_kernel_format(
902
w13_bias,
903
w2_bias,
904
)
905
+ elif backend == WNA16MoEBackend.TRITON:
906
+ # Convert from int32 to uint8 format for Triton kernel.
907
+ # This changes the shape from (E, N, K // 8) to (E, N, K // 2) for int4,
908
+ # which matches what the Triton kernel expects.
909
+ w13_uint8 = w13.view(torch.uint8)
910
+ w2_uint8 = w2.view(torch.uint8)
911
+ return (
912
+ w13_uint8,
913
+ w2_uint8,
914
+ w13_scale,
915
+ w2_scale,
916
+ None,
917
+ None,
918
+ None,
919
+ None,
920
+ w13_qzeros,
921
+ w2_qzeros,
922
+ None,
923
+ None,
924
+ w13_bias,
925
+ w2_bias,
926
+ )
927
+ elif backend == WNA16MoEBackend.FLASHINFER_TRTLLM:
928
+ return _process_weights_flashinfer(
929
+ w13,
930
+ w2,
931
+ w13_scale,
932
+ w2_scale,
933
+ w13_g_idx,
934
+ w2_g_idx,
935
+ w13_bias,
936
+ w2_bias,
937
+ )
938
elif backend == WNA16MoEBackend.XPU:
939
assert quant_config is not None
940
(
vllm/model_executor/layers/quantization/auto_gptq.py
+26
-38
@@ -485,6 +485,8 @@ class AutoGPTQMoEMethod(FusedMoEMethodBase):
485
self.wna16_moe_backend, self.experts_cls = select_wna16_moe_backend(
486
moe,
487
weight_key,
488
+ may_have_zp=True,
489
+ may_have_bias=True,
490
)
491
492
def create_weights(
@@ -644,12 +646,22 @@ class AutoGPTQMoEMethod(FusedMoEMethodBase):
646
layer.workspace = marlin_make_workspace_new(device, 4)
647
648
def process_weights_after_loading(self, layer: RoutedExperts) -> None:
649
+ def replace_or_register(name: str, val: torch.Tensor | None):
650
+ if val is None:
651
+ return
652
+
653
+ if hasattr(layer, name):
654
+ replace_parameter(layer, name, val)
655
+ else:
656
+ layer.register_parameter(
657
+ name, torch.nn.Parameter(val, requires_grad=False)
658
+ )
659
+
660
is_a_8bit = self.input_dtype is not None and self.input_dtype.itemsize == 1
661
649
- if is_a_8bit:
650
- assert self.quant_config.quant_type.size_bits == 8, (
651
- "W8A8-INT8 is not supported by marlin kernel."
652
- )
662
+ assert not is_a_8bit or self.quant_config.quant_type.size_bits == 8, (
663
+ "W8A8-INT8 is not supported by marlin kernel."
664
+ )
665
666
(
667
w13,
@@ -660,8 +672,8 @@ class AutoGPTQMoEMethod(FusedMoEMethodBase):
672
w2_g_idx,
673
w13_g_idx_sort_indices,
674
w2_g_idx_sort_indices,
663
- _w13_qzeros,
664
- _w2_qzeros,
675
+ w13_qzeros,
676
+ w2_qzeros,
677
w13_input_global_scale,
678
w2_input_global_scale,
679
w13_bias,
@@ -679,6 +691,8 @@ class AutoGPTQMoEMethod(FusedMoEMethodBase):
691
w2_g_idx=layer.w2_g_idx,
692
w13_bias=getattr(layer, "w13_bias", None),
693
w2_bias=getattr(layer, "w2_bias", None),
694
+ w13_qzeros=getattr(layer, "w13_qzeros", None),
695
+ w2_qzeros=getattr(layer, "w2_qzeros", None),
696
)
697
698
replace_parameter(layer, "w13_qweight", w13)
@@ -689,38 +703,12 @@ class AutoGPTQMoEMethod(FusedMoEMethodBase):
703
replace_parameter(layer, "w2_g_idx", w2_g_idx)
704
replace_parameter(layer, "w13_g_idx_sort_indices", w13_g_idx_sort_indices)
705
replace_parameter(layer, "w2_g_idx_sort_indices", w2_g_idx_sort_indices)
692
- if w13_input_global_scale is not None:
693
- if hasattr(layer, "w13_input_global_scale"):
694
- replace_parameter(
695
- layer, "w13_input_global_scale", w13_input_global_scale
696
- )
697
- else:
698
- layer.register_parameter(
699
- "w13_input_global_scale",
700
- torch.nn.Parameter(w13_input_global_scale, requires_grad=False),
701
- )
702
- if w2_input_global_scale is not None:
703
- if hasattr(layer, "w2_input_global_scale"):
704
- replace_parameter(layer, "w2_input_global_scale", w2_input_global_scale)
705
- else:
706
- layer.register_parameter(
707
- "w2_input_global_scale",
708
- torch.nn.Parameter(w2_input_global_scale, requires_grad=False),
709
- )
710
- if w13_bias is not None:
711
- if hasattr(layer, "w13_bias"):
712
- replace_parameter(layer, "w13_bias", w13_bias)
713
- else:
714
- layer.register_parameter(
715
- "w13_bias", torch.nn.Parameter(w13_bias, requires_grad=False)
716
- )
717
- if w2_bias is not None:
718
- if hasattr(layer, "w2_bias"):
719
- replace_parameter(layer, "w2_bias", w2_bias)
720
- else:
721
- layer.register_parameter(
722
- "w2_bias", torch.nn.Parameter(w2_bias, requires_grad=False)
723
- )
706
+ replace_or_register("w13_input_global_scale", w13_input_global_scale)
707
+ replace_or_register("w2_input_global_scale", w2_input_global_scale)
708
+ replace_or_register("w13_bias", w13_bias)
709
+ replace_or_register("w2_bias", w2_bias)
710
+ replace_or_register("w13_qzeros", w13_qzeros)
711
+ replace_or_register("w2_qzeros", w2_qzeros)
712
713
self._setup_kernel(layer)
714
vllm/model_executor/layers/quantization/awq_marlin.py
+2
@@ -524,6 +524,8 @@ class AWQMarlinMoEMethod(FusedMoEMethodBase):
524
self.wna16_moe_backend, self.experts_cls = select_wna16_moe_backend(
525
moe,
526
kInt4Static,
527
+ may_have_zp=self.quant_config.zero_point,
528
+ may_have_bias=True,
529
)
530
531
def create_weights(
vllm/model_executor/layers/quantization/compressed_tensors/compressed_tensors_moe/compressed_tensors_moe_wna16_marlin.py
+10
-7
@@ -88,7 +88,13 @@ class CompressedTensorsWNA16MarlinMoEMethod(CompressedTensorsMoEMethod):
88
self.wna16_backend, self.experts_cls = select_wna16_moe_backend(
89
config=self.moe,
90
weight_key=weight_key,
91
+ may_have_zp=False,
92
+ may_have_bias=False,
93
)
94
+ self.is_marlin = self.wna16_backend in [
95
+ WNA16MoEBackend.MARLIN,
96
+ WNA16MoEBackend.BATCHED_MARLIN,
97
+ ]
98
99
def get_weight_shape(
100
self,
@@ -114,7 +120,6 @@ class CompressedTensorsWNA16MarlinMoEMethod(CompressedTensorsMoEMethod):
120
"num_groups_w2 must be provided for weight scales"
121
)
122
w13_num_shards = 2 if self.moe.is_act_and_mul else 1
117
- is_flashinfer = self.wna16_backend == WNA16MoEBackend.FLASHINFER_TRTLLM
123
shape_map = {
124
"w13_weight": {
125
"Flashinfer": (
@@ -157,7 +162,7 @@ class CompressedTensorsWNA16MarlinMoEMethod(CompressedTensorsMoEMethod):
162
"Marlin": (num_experts, num_groups_w2, hidden_size),
163
},
164
}
160
- backend_key = "Flashinfer" if is_flashinfer else "Marlin"
165
+ backend_key = "Marlin" if self.is_marlin else "Flashinfer"
166
return shape_map[weight_name][backend_key]
167
168
def create_weights(
@@ -174,9 +179,8 @@ class CompressedTensorsWNA16MarlinMoEMethod(CompressedTensorsMoEMethod):
179
# Will transpose the loaded weight along the
180
# intermediate and hidden dim sizes. Will
181
# shard for TP along the transposed dims
177
- is_transposed = self.wna16_backend != WNA16MoEBackend.FLASHINFER_TRTLLM
182
extra_weight_attrs.update(
179
- {"is_transposed": is_transposed, "quant_method": self.strategy}
183
+ {"is_transposed": self.is_marlin, "quant_method": self.strategy}
184
)
185
186
w13_weight = torch.nn.Parameter(
@@ -324,7 +328,6 @@ class CompressedTensorsWNA16MarlinMoEMethod(CompressedTensorsMoEMethod):
328
329
def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
330
# Process weights using the shared oracle infrastructure
327
- is_flashinfer = self.wna16_backend == WNA16MoEBackend.FLASHINFER_TRTLLM
331
(
332
w13_qweight,
333
w2_qweight,
@@ -360,7 +363,7 @@ class CompressedTensorsWNA16MarlinMoEMethod(CompressedTensorsMoEMethod):
363
replace_parameter(layer, "w2_weight_scale", w2_scales)
364
365
# Marlin-specific parameters (not needed for Flashinfer)
363
- if not is_flashinfer:
366
+ if self.is_marlin:
367
replace_parameter(layer, "w13_weight_g_idx", w13_g_idx_processed)
368
replace_parameter(layer, "w2_weight_g_idx", w2_g_idx_processed)
369
replace_parameter(layer, "w13_g_idx_sort_indices", w13_g_idx_sort_indices)
@@ -392,7 +395,7 @@ class CompressedTensorsWNA16MarlinMoEMethod(CompressedTensorsMoEMethod):
395
396
# Add Marlin-specific arguments
397
marlin_args: dict[str, Any] = {}
395
- if not is_flashinfer:
398
+ if self.is_marlin:
399
marlin_args = {
400
"w13_g_idx": layer.w13_weight_g_idx,
401
"w2_g_idx": layer.w2_weight_g_idx,
vllm/model_executor/layers/quantization/moe_wna16.py
+150
-24
@@ -13,11 +13,15 @@ from vllm.model_executor.layers.fused_moe import (
13
RoutedExperts,
14
SharedExperts,
15
)
16
-from vllm.model_executor.layers.fused_moe.activation import MoEActivation
16
from vllm.model_executor.layers.fused_moe.config import (
17
FusedMoEQuantConfig,
19
- int4_w4a16_moe_quant_config,
20
- int8_w8a16_moe_quant_config,
18
+)
19
+from vllm.model_executor.layers.fused_moe.oracle.int_wna16 import (
20
+ WNA16MoEBackend,
21
+ convert_to_wna16_moe_kernel_format,
22
+ make_wna16_moe_kernel,
23
+ make_wna16_moe_quant_config,
24
+ select_wna16_moe_backend,
25
)
26
from vllm.model_executor.layers.fused_moe.unquantized_fused_moe_method import (
27
UnquantizedFusedMoEMethod,
@@ -31,7 +35,15 @@ from vllm.model_executor.layers.quantization.base_config import (
35
from vllm.model_executor.layers.quantization.utils.marlin_utils import (
36
check_marlin_supports_layer,
37
)
34
-from vllm.model_executor.utils import set_weight_attrs
38
+from vllm.model_executor.layers.quantization.utils.quant_utils import (
39
+ INT4_DTYPE,
40
+ INT8_DTYPE,
41
+ QuantKey,
42
+ kInt4Static32GroupScale,
43
+ kInt4StaticGroupScale,
44
+ kInt8StaticGroupScale,
45
+)
46
+from vllm.model_executor.utils import replace_parameter, set_weight_attrs
47
from vllm.platforms import current_platform
48
49
@@ -216,6 +228,33 @@ class MoeWNA16Method(FusedMoEMethodBase):
228
super().__init__(moe)
229
self.quant_config = quant_config
230
231
+ num_bits = self.quant_config.weight_bits
232
+ group_size = self.quant_config.group_size
233
+
234
+ if num_bits == 4:
235
+ quant_type = INT4_DTYPE
236
+ if group_size == 32:
237
+ scale = kInt4Static32GroupScale
238
+ else:
239
+ scale = kInt4StaticGroupScale
240
+ elif num_bits == 8:
241
+ assert group_size == -1
242
+ quant_type = INT8_DTYPE
243
+ scale = kInt8StaticGroupScale
244
+ else:
245
+ raise ValueError("MoeWNA16Method only supports int4 and int8 now.")
246
+
247
+ weight_key = QuantKey(quant_type, scale)
248
+
249
+ # Select WNA16 MoE backend via oracle.
250
+ # handle ZP?
251
+ self.wna16_backend, self.experts_cls = select_wna16_moe_backend(
252
+ config=self.moe,
253
+ weight_key=weight_key,
254
+ may_have_zp=self.quant_config.has_zp,
255
+ may_have_bias=False,
256
+ )
257
+
258
def create_weights(
259
self,
260
layer: RoutedExperts,
@@ -336,24 +375,89 @@ class MoeWNA16Method(FusedMoEMethodBase):
375
layer.register_parameter(key, param)
376
set_weight_attrs(param, extra_weight_attrs)
377
378
+ def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
379
+ has_zp = self.quant_config.has_zp
380
+ (
381
+ w13_qweight,
382
+ w2_qweight,
383
+ w13_scales,
384
+ w2_scales,
385
+ w13_g_idx_processed,
386
+ w2_g_idx_processed,
387
+ w13_g_idx_sort_indices,
388
+ w2_g_idx_sort_indices,
389
+ w13_qzeros,
390
+ w2_qzeros,
391
+ w13_input_global_scale,
392
+ w2_input_global_scale,
393
+ _, # w13_bias
394
+ _, # w2_bias
395
+ ) = convert_to_wna16_moe_kernel_format(
396
+ backend=self.wna16_backend,
397
+ layer=layer,
398
+ quant_config=self.quant_config,
399
+ input_dtype=None,
400
+ w13=layer.w13_qweight,
401
+ w2=layer.w2_qweight,
402
+ w13_scale=layer.w13_scales,
403
+ w2_scale=layer.w2_scales,
404
+ w13_g_idx=getattr(layer, "w13_g_idx", None),
405
+ w2_g_idx=getattr(layer, "w2_g_idx", None),
406
+ w13_qzeros=layer.w13_qzeros if has_zp else None,
407
+ w2_qzeros=layer.w2_qzeros if has_zp else None,
408
+ )
409
+
410
+ # Replace common parameters
411
+ replace_parameter(layer, "w13_qweight", w13_qweight)
412
+ replace_parameter(layer, "w2_qweight", w2_qweight)
413
+ replace_parameter(layer, "w13_scales", w13_scales)
414
+ replace_parameter(layer, "w2_scales", w2_scales)
415
+
416
+ if has_zp:
417
+ assert w13_qzeros is not None and w2_qzeros is not None
418
+ replace_parameter(layer, "w13_qzeros", w13_qzeros)
419
+ replace_parameter(layer, "w2_qzeros", w2_qzeros)
420
+
421
+ # Marlin-specific parameters (not needed for Flashinfer)
422
+ if self.wna16_backend != WNA16MoEBackend.FLASHINFER_TRTLLM:
423
+ replace_parameter(layer, "w13_g_idx", w13_g_idx_processed)
424
+ replace_parameter(layer, "w2_g_idx", w2_g_idx_processed)
425
+ replace_parameter(layer, "w13_g_idx_sort_indices", w13_g_idx_sort_indices)
426
+ replace_parameter(layer, "w2_g_idx_sort_indices", w2_g_idx_sort_indices)
427
+
428
+ # Register input global scales if present
429
+ if w13_input_global_scale is not None:
430
+ layer.register_parameter(
431
+ "w13_input_global_scale",
432
+ torch.nn.Parameter(w13_input_global_scale, requires_grad=False),
433
+ )
434
+ if w2_input_global_scale is not None:
435
+ layer.register_parameter(
436
+ "w2_input_global_scale",
437
+ torch.nn.Parameter(w2_input_global_scale, requires_grad=False),
438
+ )
439
+
440
+ assert self.experts_cls is not None
441
+ self.moe_quant_config = self.get_fused_moe_quant_config(layer)
442
+ assert self.moe_quant_config is not None
443
+ self.moe_kernel = make_wna16_moe_kernel(
444
+ moe_quant_config=self.moe_quant_config,
445
+ moe_config=self.moe,
446
+ experts_cls=self.experts_cls,
447
+ routing_tables=layer._expert_routing_tables(),
448
+ )
449
+
450
def get_fused_moe_quant_config(
451
self, layer: RoutedExperts
452
) -> FusedMoEQuantConfig | None:
342
- weight_bits = self.quant_config.weight_bits
453
has_zp = self.quant_config.has_zp
344
- assert weight_bits == 4 or weight_bits == 8
345
- config_builder = (
346
- int4_w4a16_moe_quant_config
347
- if weight_bits == 4
348
- else int8_w8a16_moe_quant_config
349
- )
350
-
351
- return config_builder(
454
+ return make_wna16_moe_quant_config(
455
w1_scale=layer.w13_scales,
456
w2_scale=layer.w2_scales,
457
w1_zp=layer.w13_qzeros if has_zp else None,
458
w2_zp=layer.w2_qzeros if has_zp else None,
356
- block_shape=[0, layer.group_size],
459
+ group_size=layer.group_size,
460
+ num_bits=self.quant_config.weight_bits,
461
)
462
463
def apply(
@@ -365,22 +469,44 @@ class MoeWNA16Method(FusedMoEMethodBase):
469
shared_experts: SharedExperts | None,
470
shared_experts_input: torch.Tensor | None,
471
) -> torch.Tensor:
368
- from vllm.model_executor.layers.fused_moe import fused_experts
369
-
370
- assert layer.activation == MoEActivation.SILU, (
371
- f"Only SiLU activation is supported, not {layer.activation}."
372
- )
373
-
374
- return fused_experts(
472
+ assert not self.is_monolithic
473
+ assert self.moe_kernel is not None
474
+ return self.moe_kernel.apply(
475
x,
476
layer.w13_qweight,
477
layer.w2_qweight,
378
- topk_weights=topk_weights,
379
- topk_ids=topk_ids,
478
+ topk_weights,
479
+ topk_ids,
480
+ activation=layer.activation,
481
+ global_num_experts=layer.global_num_experts,
482
+ expert_map=layer.expert_map,
483
apply_router_weight_on_input=layer.apply_router_weight_on_input,
484
+ shared_experts=shared_experts,
485
+ shared_experts_input=shared_experts_input,
486
+ )
487
+
488
+ def apply_monolithic(
489
+ self,
490
+ layer: RoutedExperts,
491
+ x: torch.Tensor,
492
+ router_logits: torch.Tensor,
493
+ input_ids: torch.Tensor | None = None,
494
+ ) -> torch.Tensor:
495
+ assert self.is_monolithic
496
+ assert self.moe_kernel is not None
497
+ return self.moe_kernel.apply_monolithic(
498
+ x,
499
+ layer.w13_weight,
500
+ layer.w2_weight,
501
+ router_logits,
502
+ activation=layer.activation,
503
global_num_experts=layer.global_num_experts,
504
expert_map=layer.expert_map,
383
- quant_config=self.moe_quant_config,
505
+ apply_router_weight_on_input=layer.apply_router_weight_on_input,
506
+ num_expert_group=layer.num_expert_group,
507
+ topk_group=layer.topk_group,
508
+ e_score_correction_bias=layer.e_score_correction_bias,
509
+ routed_scaling_factor=layer.routed_scaling_factor,
510
)
511
512
@staticmethod
vllm/model_executor/layers/quantization/utils/gptq_utils.py
+17
-1
@@ -3,7 +3,7 @@
3
from collections.abc import Mapping
4
from copy import deepcopy
5
from types import MappingProxyType
6
-from typing import TYPE_CHECKING
6
+from typing import TYPE_CHECKING, Any
7
8
import regex as re
9
import torch
@@ -69,6 +69,20 @@ def get_dynamic_override(
69
return default_value
70
71
72
+def flatten_list(lst: list[Any]) -> list[Any]:
73
+ output = []
74
+
75
+ def _flatten(lst: list[Any]):
76
+ for i in lst:
77
+ if isinstance(i, list):
78
+ _flatten(i)
79
+ else:
80
+ output.append(i)
81
+
82
+ _flatten(lst)
83
+ return output
84
+
85
+
86
def is_layer_gptq_quantized(
87
prefix: str,
88
quantized_layers: list[str],
@@ -83,6 +97,8 @@ def is_layer_gptq_quantized(
97
98
proj_name = prefix.split(".")[-1]
99
100
+ quantized_layers = flatten_list(quantized_layers)
101
+
102
# Fused layers like gate_up_proj or qkv_proj will not be fused
103
# in the safetensors checkpoint. So, we convert the name
104
# from the fused version to unfused + check to make sure that