[Quantization][Autoround][XPU] Add W4A16(moe) / MXFP4(linear/moe) Support (#47124)
Signed-off-by: lkk12014402 <kaokao.lv@intel.com> Signed-off-by: lkk <33276950+lkk12014402@users.noreply.github.com> Signed-off-by: Kunshang Ji <kunshang.ji@intel.com> Co-authored-by: Kunshang Ji <kunshang.ji@intel.com>
lkk committed
Jul 30, 2026 at 09:52 UTC
e5f48dfda2b53090f539a2b3988e075e8ac0eba8
9 files changed
+816
-48
tests/quantization/test_auto_round.py
+391
-11
@@ -8,16 +8,20 @@ Validating the configuration and printing results for manual checking.
8
Run `pytest tests/quantization/test_auto_round.py`.
9
"""
10
11
+from typing import Any, cast
12
+
13
import pytest
14
import torch
15
16
from vllm.model_executor.layers.fused_moe import RoutedExperts
17
+from vllm.model_executor.layers.fused_moe.oracle.mxfp4 import Mxfp4MoeBackend
18
from vllm.model_executor.layers.linear import LinearBase, UnquantizedLinearMethod
19
from vllm.model_executor.layers.quantization.auto_gptq import AutoGPTQConfig
20
from vllm.model_executor.layers.quantization.inc import INCConfig
21
from vllm.model_executor.layers.quantization.inc.config_parser import INCLayerConfig
22
from vllm.model_executor.layers.quantization.inc.inc_linear import INCLinearMethod
23
from vllm.model_executor.layers.quantization.inc.schemes import (
24
+ INCMxfp4Scheme,
25
INCMxfp8Scheme,
26
INCWna16Scheme,
27
resolve_scheme,
@@ -71,7 +75,36 @@ MODELS = [
75
),
76
]
77
74
-MODEL_RUNNER_KWARGS = {
78
+QWEN3_AUTOROUND_MODELS = [
79
+ pytest.param(
80
+ "INCModel/Qwen3-1.7B-AutoRound-MXFP4-W4A4",
81
+ marks=pytest.mark.skipif(
82
+ not (current_platform.is_cuda() or current_platform.is_xpu()),
83
+ reason="Qwen3-1.7B MXFP4 AutoRound model requires CUDA/XPU.",
84
+ ),
85
+ id="auto_round:mxfp4:qwen3-1p7b",
86
+ ),
87
+ pytest.param(
88
+ "INCModel/Qwen3-30B-A3B-12L-W4A16-test",
89
+ marks=pytest.mark.skipif(
90
+ not (current_platform.is_cuda() or current_platform.is_xpu()),
91
+ reason="Qwen3-30B-A3B W4A16 AutoRound model requires CUDA/XPU.",
92
+ ),
93
+ id="auto_round:w4a16:qwen3-30b-a3b",
94
+ ),
95
+ pytest.param(
96
+ "INCModel/Qwen3-30B-A3B-12L-MXFP4-test",
97
+ marks=pytest.mark.skipif(
98
+ not (current_platform.is_cuda() or current_platform.is_xpu()),
99
+ reason="Qwen3-30B-A3B MXFP4 AutoRound model requires CUDA/XPU.",
100
+ ),
101
+ id="auto_round:mxfp4:qwen3-30b-a3b",
102
+ ),
103
+]
104
+
105
+MODEL_RUNNER_KWARGS: dict[str, dict[str, Any]] = {
106
+ "INCModel/Qwen3-1.7B-AutoRound-MXFP4-W4A4": {"enforce_eager": True},
107
+ "INCModel/Qwen3-30B-A3B-12L-MXFP4-test": {"enforce_eager": True},
108
"Intel/Qwen3-8B-w2g64-for-ut": {
109
"block_size": 64,
110
"gpu_memory_utilization": 0.8,
@@ -93,7 +126,7 @@ MODEL_RUNNER_KWARGS = {
126
),
127
reason="Only supports CPU/XPU/CUDA backend.",
128
)
96
-@pytest.mark.parametrize("model", MODELS)
129
+@pytest.mark.parametrize("model", MODELS + QWEN3_AUTOROUND_MODELS)
130
def test_auto_round_model(vllm_runner, model):
131
with vllm_runner(model, **MODEL_RUNNER_KWARGS.get(model, {})) as llm:
132
output = llm.generate_greedy(["The capital of France is"], max_tokens=8)
@@ -144,6 +177,65 @@ def make_layer_config(**overrides) -> INCLayerConfig:
177
return INCLayerConfig(**kwargs)
178
179
180
+def make_qwen3_autoround_config(kind: str) -> INCConfig:
181
+ configs = {
182
+ "qwen3_1p7b_mxfp4": {
183
+ "quant_method": "auto-round",
184
+ "bits": 4,
185
+ "group_size": 32,
186
+ "sym": True,
187
+ "packing_format": "auto_round:llm_compressor",
188
+ "data_type": "mx_fp",
189
+ "extra_config": {
190
+ "model.layers.0.self_attn.q_proj": {
191
+ "bits": 16,
192
+ "data_type": "float",
193
+ },
194
+ },
195
+ },
196
+ "qwen3_30b_a3b_w4a16": {
197
+ "quant_method": "auto-round",
198
+ "bits": 4,
199
+ "group_size": 32,
200
+ "sym": True,
201
+ "packing_format": "auto_round:auto_gptq",
202
+ "data_type": "int",
203
+ "extra_config": {
204
+ "model.layers.0.mlp.gate": {
205
+ "bits": 16,
206
+ "data_type": "float",
207
+ },
208
+ },
209
+ },
210
+ "qwen3_30b_a3b_mxfp4": {
211
+ "quant_method": "auto-round",
212
+ "bits": 4,
213
+ "group_size": 32,
214
+ "sym": True,
215
+ "packing_format": "auto_round:llm_compressor",
216
+ "data_type": "mx_fp",
217
+ "act_bits": 4,
218
+ "act_group_size": 32,
219
+ "act_data_type": "mx_fp",
220
+ "extra_config": {
221
+ "model.layers.0.mlp.gate": {
222
+ "bits": 16,
223
+ "data_type": "float",
224
+ },
225
+ "model.layers.0.self_attn.q_proj": {
226
+ "bits": 16,
227
+ "data_type": "float",
228
+ },
229
+ },
230
+ },
231
+ }
232
+ try:
233
+ config = configs[kind]
234
+ except KeyError as err:
235
+ raise AssertionError(f"unknown qwen3 autoround config: {kind}") from err
236
+ return INCConfig.from_config(config)
237
+
238
+
239
def test_inc_config_parser_exact_match() -> None:
240
config = make_config(
241
extra_config={
@@ -374,6 +466,301 @@ def test_inc_resolve_scheme_selects_wna16() -> None:
466
assert isinstance(scheme, INCWna16Scheme)
467
468
469
+def test_inc_config_accepts_mxfp_family_llm_compressor() -> None:
470
+ config = INCConfig.from_config(
471
+ {
472
+ "quant_method": "auto-round",
473
+ "bits": 4,
474
+ "group_size": 32,
475
+ "sym": True,
476
+ "packing_format": "auto_round:llm_compressor",
477
+ "data_type": "mx_fp4e2m1",
478
+ }
479
+ )
480
+
481
+ layer_config = config.config_parser.resolve(
482
+ DummyLayer(), "model.layers.0.mlp.down_proj"
483
+ )
484
+
485
+ assert config.sym is True
486
+ assert layer_config.is_mxfp4 is True
487
+ assert isinstance(resolve_scheme(layer_config), INCMxfp4Scheme)
488
+
489
+
490
+def test_qwen3_1p7b_mxfp4_autoround_uses_mxfp4_linear_scheme(
491
+ monkeypatch,
492
+) -> None:
493
+ class DummyKernel:
494
+ pass
495
+
496
+ monkeypatch.setattr(
497
+ "vllm.model_executor.layers.quantization.inc.schemes."
498
+ "inc_mxfp4_linear.init_mxfp4_linear_kernel",
499
+ lambda: DummyKernel(),
500
+ )
501
+
502
+ from vllm.model_executor.layers.quantization.inc.schemes.inc_mxfp4_linear import ( # noqa: E501
503
+ INCMxfp4LinearMethod,
504
+ )
505
+
506
+ config = make_qwen3_autoround_config("qwen3_1p7b_mxfp4")
507
+
508
+ assert (
509
+ INCConfig.override_quantization_method(
510
+ {"quant_method": "auto-round"}, user_quant=None
511
+ )
512
+ == "inc"
513
+ )
514
+ ignored_method = config.get_quant_method(
515
+ object.__new__(LinearBase), "model.layers.0.self_attn.q_proj"
516
+ )
517
+ layer_config = config.config_parser.resolve(
518
+ DummyLayer(), "model.layers.0.mlp.gate_proj"
519
+ )
520
+ method = INCMxfp4Scheme().get_linear_method(
521
+ config,
522
+ object.__new__(LinearBase),
523
+ "model.layers.0.mlp.gate_proj",
524
+ layer_config,
525
+ )
526
+
527
+ assert isinstance(ignored_method, UnquantizedLinearMethod)
528
+ assert layer_config.bits == 4
529
+ assert layer_config.group_size == 32
530
+ assert layer_config.is_mxfp4 is True
531
+ assert isinstance(resolve_scheme(layer_config), INCMxfp4Scheme)
532
+ assert isinstance(method, INCLinearMethod)
533
+ assert isinstance(method.scheme, INCMxfp4LinearMethod)
534
+ assert isinstance(method.scheme.kernel, DummyKernel)
535
+
536
+
537
+def test_qwen3_30b_a3b_w4a16_autoround_routes_to_gptq_moe(
538
+ monkeypatch,
539
+) -> None:
540
+ captured = {}
541
+ expected_method = object()
542
+
543
+ class DummyMoeConfig:
544
+ pass
545
+
546
+ def fake_resolve_gptq_moe(layer, layer_config):
547
+ captured["layer"] = layer
548
+ captured["layer_config"] = layer_config
549
+ return expected_method
550
+
551
+ monkeypatch.setattr(
552
+ "vllm.model_executor.layers.quantization.inc.schemes."
553
+ "inc_wna16_scheme._resolve_gptq_moe",
554
+ fake_resolve_gptq_moe,
555
+ )
556
+
557
+ config = make_qwen3_autoround_config("qwen3_30b_a3b_w4a16")
558
+ layer = object.__new__(RoutedExperts)
559
+ layer.moe_config = DummyMoeConfig()
560
+
561
+ method = config.get_quant_method(layer, "model.layers.0.mlp")
562
+
563
+ assert method is expected_method
564
+ assert captured["layer"] is layer
565
+ assert captured["layer_config"].bits == 4
566
+ assert captured["layer_config"].group_size == 32
567
+ assert captured["layer_config"].is_gptq is True
568
+ assert captured["layer_config"].is_wna16_int is True
569
+
570
+
571
+def test_qwen3_30b_a3b_mxfp4_autoround_routes_to_mxfp4_moe(
572
+ monkeypatch,
573
+) -> None:
574
+ class DummyMoeConfig:
575
+ pass
576
+
577
+ class DummyMxfp4MoEMethod:
578
+ def __init__(self, moe_config) -> None:
579
+ self.moe_config = moe_config
580
+
581
+ monkeypatch.setattr(
582
+ "vllm.model_executor.layers.quantization.inc.schemes.inc_mxfp4_moe.INCMxfp4MoEMethod",
583
+ DummyMxfp4MoEMethod,
584
+ )
585
+
586
+ config = make_qwen3_autoround_config("qwen3_30b_a3b_mxfp4")
587
+ layer = object.__new__(RoutedExperts)
588
+ layer.moe_config = DummyMoeConfig()
589
+
590
+ ignored_method = config.get_quant_method(
591
+ object.__new__(LinearBase), "model.layers.0.self_attn.q_proj"
592
+ )
593
+ method = config.get_quant_method(layer, "model.layers.0.mlp")
594
+ layer_config = config.config_parser.resolve(DummyLayer(), "model.layers.0.mlp")
595
+
596
+ assert isinstance(ignored_method, UnquantizedLinearMethod)
597
+ assert layer_config.bits == 4
598
+ assert layer_config.group_size == 32
599
+ assert layer_config.is_mxfp4 is True
600
+ assert isinstance(resolve_scheme(layer_config), INCMxfp4Scheme)
601
+ assert isinstance(method, DummyMxfp4MoEMethod)
602
+ assert method.moe_config is layer.moe_config
603
+
604
+
605
+def test_inc_mxfp4_linear_method_registers_and_processes_weights(
606
+ monkeypatch,
607
+) -> None:
608
+ captured = {}
609
+
610
+ class DummyKernel:
611
+ def process_weights_after_loading(self, layer) -> None:
612
+ captured["processed_layer"] = layer
613
+
614
+ monkeypatch.setattr(
615
+ "vllm.model_executor.layers.quantization.inc.schemes."
616
+ "inc_mxfp4_linear.init_mxfp4_linear_kernel",
617
+ lambda: DummyKernel(),
618
+ )
619
+ monkeypatch.setattr(
620
+ "vllm.model_executor.parameter.get_tensor_model_parallel_rank",
621
+ lambda: 0,
622
+ )
623
+ monkeypatch.setattr(
624
+ "vllm.model_executor.parameter.get_tensor_model_parallel_world_size",
625
+ lambda: 1,
626
+ )
627
+
628
+ from vllm.model_executor.layers.quantization.inc.schemes.inc_mxfp4_linear import ( # noqa: E501
629
+ INCMxfp4LinearMethod,
630
+ )
631
+
632
+ layer = torch.nn.Module()
633
+ method = INCMxfp4LinearMethod(
634
+ make_layer_config(group_size=32, data_type="mx_fp4e2m1")
635
+ )
636
+
637
+ method.create_weights(
638
+ layer,
639
+ input_size_per_partition=64,
640
+ output_partition_sizes=[16, 32],
641
+ input_size=64,
642
+ output_size=48,
643
+ params_dtype=torch.bfloat16,
644
+ )
645
+
646
+ assert layer.weight_packed.shape == (48, 32)
647
+ assert layer.weight_packed.dtype is torch.uint8
648
+ assert layer.weight_scale.shape == (48, 2)
649
+ assert layer.weight_scale.dtype is torch.uint8
650
+ assert layer.logical_widths == [16, 32]
651
+ assert layer.input_size_per_partition == 64
652
+ assert layer.output_size_per_partition == 48
653
+
654
+ packed_data = layer.weight_packed.data
655
+ method.process_weights_after_loading(layer)
656
+
657
+ assert layer.weight.data.data_ptr() == packed_data.data_ptr()
658
+ assert not hasattr(layer, "weight_packed")
659
+ assert captured["processed_layer"] is layer
660
+
661
+
662
+def test_inc_mxfp4_moe_method_registers_weights_and_builds_kernel(
663
+ monkeypatch,
664
+) -> None:
665
+ captured = {}
666
+ expected_quant_config = object()
667
+ expected_kernel = object()
668
+ expected_experts_cls = object()
669
+
670
+ monkeypatch.setattr(
671
+ "vllm.model_executor.layers.quantization.inc.schemes.inc_mxfp4_moe."
672
+ "CutlassExpertsMxfp4._supports_current_device",
673
+ lambda: False,
674
+ )
675
+ monkeypatch.setattr(current_platform, "is_xpu", lambda: True)
676
+ monkeypatch.setattr(
677
+ "vllm.model_executor.layers.quantization.inc.schemes.inc_mxfp4_moe."
678
+ "select_mxfp4_moe_backend",
679
+ lambda moe: (Mxfp4MoeBackend.XPU, expected_experts_cls),
680
+ )
681
+ monkeypatch.setattr(
682
+ "vllm.model_executor.layers.quantization.inc.schemes.inc_mxfp4_moe."
683
+ "make_mxfp4_moe_quant_config",
684
+ lambda **kwargs: captured.update({"quant_config_kwargs": kwargs})
685
+ or expected_quant_config,
686
+ )
687
+ monkeypatch.setattr(
688
+ "vllm.model_executor.layers.quantization.inc.schemes.inc_mxfp4_moe."
689
+ "make_mxfp4_moe_kernel",
690
+ lambda **kwargs: captured.update({"kernel_kwargs": kwargs}) or expected_kernel,
691
+ )
692
+
693
+ from vllm.model_executor.layers.quantization.inc.schemes.inc_mxfp4_moe import (
694
+ INCMxfp4MoEMethod,
695
+ )
696
+
697
+ method = INCMxfp4MoEMethod(moe=cast(Any, "moe-config"))
698
+ layer = torch.nn.Module()
699
+ layer._expert_routing_tables = lambda: "routing-tables"
700
+
701
+ method.create_weights(
702
+ layer,
703
+ num_experts=2,
704
+ hidden_size=64,
705
+ intermediate_size_per_partition=32,
706
+ params_dtype=torch.bfloat16,
707
+ )
708
+
709
+ assert method.experts_cls is expected_experts_cls
710
+ assert layer.w13_weight_packed.shape == (2, 64, 32)
711
+ assert layer.w2_weight_packed.shape == (2, 64, 16)
712
+ assert layer.w13_weight_scale.shape == (2, 64, 2)
713
+ assert layer.w2_weight_scale.shape == (2, 64, 1)
714
+
715
+ w13_packed_data = layer.w13_weight_packed.data
716
+ w2_packed_data = layer.w2_weight_packed.data
717
+ method.process_weights_after_loading(layer)
718
+
719
+ assert layer.w13_weight.data.data_ptr() == w13_packed_data.data_ptr()
720
+ assert layer.w2_weight.data.data_ptr() == w2_packed_data.data_ptr()
721
+ assert not hasattr(layer, "w13_weight_packed")
722
+ assert not hasattr(layer, "w2_weight_packed")
723
+ assert captured["quant_config_kwargs"]["w1_scale"] is layer.w13_weight_scale
724
+ assert captured["quant_config_kwargs"]["w2_scale"] is layer.w2_weight_scale
725
+ assert captured["kernel_kwargs"]["moe_quant_config"] is expected_quant_config
726
+ assert captured["kernel_kwargs"]["moe_config"] == "moe-config"
727
+ assert captured["kernel_kwargs"]["experts_cls"] is expected_experts_cls
728
+ assert captured["kernel_kwargs"]["routing_tables"] == "routing-tables"
729
+ assert method.moe_kernel is expected_kernel
730
+
731
+
732
+def test_wna16_xpu_moe_routes_to_gptq_moe(monkeypatch) -> None:
733
+ captured = {}
734
+ expected_method = object()
735
+
736
+ class DummyMoeConfig:
737
+ pass
738
+
739
+ monkeypatch.setattr(current_platform, "is_xpu", lambda: True)
740
+ monkeypatch.setattr(current_platform, "is_cpu", lambda: False)
741
+ monkeypatch.setattr(
742
+ "vllm.model_executor.layers.quantization.inc.schemes."
743
+ "inc_wna16_scheme._resolve_gptq_moe",
744
+ lambda layer, layer_config: captured.update(
745
+ {"layer": layer, "layer_config": layer_config}
746
+ )
747
+ or expected_method,
748
+ )
749
+
750
+ layer = object.__new__(RoutedExperts)
751
+ layer.moe_config = DummyMoeConfig()
752
+ method = INCWna16Scheme().get_moe_method(
753
+ make_config(),
754
+ layer,
755
+ "model.layers.0.mlp",
756
+ make_layer_config(group_size=32),
757
+ )
758
+
759
+ assert method is expected_method
760
+ assert captured["layer"] is layer
761
+ assert captured["layer_config"].is_gptq is True
762
+
763
+
764
def test_inc_resolve_scheme_selects_mxfp8() -> None:
765
layer_config = INCLayerConfig(
766
bits=8,
@@ -878,7 +1265,8 @@ def test_resolve_gptq_moe_falls_back_to_moe_wna16(monkeypatch) -> None:
1265
captured["moe"] = moe
1266
1267
monkeypatch.setattr(
881
- "vllm.model_executor.layers.quantization.utils.marlin_utils.check_marlin_supported",
1268
+ "vllm.model_executor.layers.quantization.utils.marlin_utils."
1269
+ "check_moe_marlin_supports_layer",
1270
lambda *args, **kwargs: False,
1271
)
1272
monkeypatch.setattr(
@@ -927,10 +1315,6 @@ def test_resolve_gptq_moe_uses_auto_gptq_when_supported(monkeypatch) -> None:
1315
captured["cfg"] = cfg
1316
captured["moe"] = moe
1317
930
- monkeypatch.setattr(
931
- "vllm.model_executor.layers.quantization.utils.marlin_utils.check_marlin_supported",
932
- lambda *args, **kwargs: True,
933
- )
1318
monkeypatch.setattr(
1319
"vllm.model_executor.layers.quantization.utils.marlin_utils."
1320
"check_moe_marlin_supports_layer",
@@ -964,10 +1348,6 @@ def test_resolve_awq_moe_uses_marlin_when_supported(monkeypatch) -> None:
1348
captured["cfg"] = cfg
1349
captured["moe"] = moe
1350
967
- monkeypatch.setattr(
968
- "vllm.model_executor.layers.quantization.utils.marlin_utils.check_marlin_supported",
969
- lambda *args, **kwargs: True,
970
- )
1351
monkeypatch.setattr(
1352
"vllm.model_executor.layers.quantization.utils.marlin_utils.check_moe_marlin_supports_layer",
1353
lambda *args, **kwargs: True,
vllm/model_executor/layers/quantization/inc/config_parser.py
+2
-2
@@ -38,11 +38,11 @@ class INCLayerConfig:
38
39
@property
40
def is_mxfp4(self) -> bool:
41
- return self.data_type == "mx_fp" and self.bits == 4
41
+ return "mx_fp" in self.data_type and self.bits == 4
42
43
@property
44
def is_mxfp8(self) -> bool:
45
- return self.data_type == "mx_fp" and self.bits == 8
45
+ return "mx_fp" in self.data_type and self.bits == 8
46
47
48
class INCConfigParser:
vllm/model_executor/layers/quantization/inc/inc.py
+19
-8
@@ -72,7 +72,10 @@ class INCConfig(QuantizationConfig):
72
f"Unsupported weight_bits: {weight_bits}, "
73
f"currently only support {self.SUPPORTED_BITS}."
74
)
75
- if data_type not in self.SUPPORTED_DTYPES:
75
+ # auto-round mxfp data_type is e.g. "mx_fp4" / "mx_fp4e2m1"; match the
76
+ # "mx_fp" family by substring like auto_round.compressors.is_mx_fp.
77
+ is_mxfp = "mx_fp" in data_type
78
+ if data_type not in self.SUPPORTED_DTYPES and not is_mxfp:
79
raise ValueError(
80
f"Unsupported data_type: {data_type}, "
81
f"currently only support {self.SUPPORTED_DTYPES}."
@@ -111,11 +114,19 @@ class INCConfig(QuantizationConfig):
114
f"group_size={self.group_size}, sym={self.sym})"
115
)
116
117
+ @property
118
+ def is_mxfp(self) -> bool:
119
+ # auto-round mxfp data_type is the "mx_fp" family (e.g. "mx_fp",
120
+ # "mx_fp4e2m1"); match by substring like auto_round.compressors.is_mx_fp.
121
+ return "mx_fp" in self.data_type
122
+
123
+ @property
124
+ def is_mxfp8(self) -> bool:
125
+ # MXFP4 and MXFP8 share data_type "mx_fp" and differ only by bit width.
126
+ return self.is_mxfp and self.weight_bits == self.MXFP8_BITS
127
+
128
def _validate_supported_quantization(self) -> None:
115
- if self.data_type == self.MXFP8_DATA_TYPE:
116
- assert self.weight_bits == self.MXFP8_BITS, (
117
- f"INC MXFP8 only supports bits=8, but found bits={self.weight_bits}."
118
- )
129
+ if self.is_mxfp8:
130
assert self.group_size == self.MXFP8_GROUP_SIZE, (
131
"INC MXFP8 only supports group_size=32, "
132
f"but found group_size={self.group_size}."
@@ -130,14 +141,14 @@ class INCConfig(QuantizationConfig):
141
"INC MXFP8 only supports backend='auto', "
142
f"but found backend={self.backend!r}."
143
)
133
- elif self.packing_format == self.MXFP8_PACKING_FORMAT:
144
+ elif self.packing_format == self.MXFP8_PACKING_FORMAT and not self.is_mxfp:
145
raise ValueError(
146
f"packing_format={self.MXFP8_PACKING_FORMAT!r} requires "
136
- f"data_type={self.MXFP8_DATA_TYPE!r}."
147
+ f"an {self.MXFP8_DATA_TYPE!r} data_type."
148
)
149
150
def _validate_raw_config(self, config: dict[str, Any]) -> None:
140
- if self.data_type != self.MXFP8_DATA_TYPE:
151
+ if not self.is_mxfp8:
152
return
153
154
expected_fields = {
vllm/model_executor/layers/quantization/inc/schemes/__init__.py
+2
@@ -2,6 +2,7 @@
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4
from .factory import resolve_scheme
5
+from .inc_mxfp4_scheme import INCMxfp4Scheme
6
from .inc_mxfp8_scheme import INCMxfp8Scheme
7
from .inc_scheme import INCLinearScheme, INCScheme
8
from .inc_wna16_scheme import INCWna16Scheme
@@ -11,5 +12,6 @@ __all__ = [
12
"INCLinearScheme",
13
"INCMxfp8Scheme",
14
"INCWna16Scheme",
15
+ "INCMxfp4Scheme",
16
"resolve_scheme",
17
]
vllm/model_executor/layers/quantization/inc/schemes/factory.py
+2
@@ -9,12 +9,14 @@ if TYPE_CHECKING:
9
10
11
def resolve_scheme(layer_config: "INCLayerConfig") -> "INCScheme":
12
+ from .inc_mxfp4_scheme import INCMxfp4Scheme
13
from .inc_mxfp8_scheme import INCMxfp8Scheme
14
from .inc_wna16_scheme import INCWna16Scheme
15
16
scheme_list: list[type[INCScheme]] = [
17
INCMxfp8Scheme,
18
INCWna16Scheme,
19
+ INCMxfp4Scheme,
20
]
21
22
for scheme_cls in scheme_list:
vllm/model_executor/layers/quantization/inc/schemes/inc_mxfp4_linear.py
new
+91
@@ -0,0 +1,91 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+
4
+from typing import TYPE_CHECKING, Any
5
+
6
+import torch
7
+from torch.nn.parameter import Parameter
8
+
9
+from vllm.model_executor.kernels.linear import init_mxfp4_linear_kernel
10
+from vllm.model_executor.parameter import (
11
+ GroupQuantScaleParameter,
12
+ ModelWeightParameter,
13
+)
14
+
15
+from .inc_scheme import INCLinearScheme
16
+
17
+if TYPE_CHECKING:
18
+ from ..config_parser import INCLayerConfig
19
+
20
+
21
+class INCMxfp4LinearMethod(INCLinearScheme):
22
+ """MXFP4 (W4A4) linear method for AutoRound checkpoints.
23
+
24
+ E2M1 weights packed two per byte with per-group E8M0 scales
25
+ (group_size=32, no global scale). The platform kernel is selected by
26
+ ``init_mxfp4_linear_kernel`` (FlashInfer / Marlin on CUDA, ``fp4_gemm``
27
+ on XPU).
28
+ """
29
+
30
+ def __init__(self, layer_config: "INCLayerConfig") -> None:
31
+ self.group_size = layer_config.group_size or 32
32
+ self.kernel = init_mxfp4_linear_kernel()
33
+
34
+ @classmethod
35
+ def get_min_capability(cls) -> int:
36
+ return 80
37
+
38
+ def create_weights(
39
+ self,
40
+ layer: torch.nn.Module,
41
+ input_size_per_partition: int,
42
+ output_partition_sizes: list[int],
43
+ input_size: int,
44
+ output_size: int,
45
+ params_dtype: torch.dtype,
46
+ **extra_weight_attrs: Any,
47
+ ) -> None:
48
+ del input_size, output_size
49
+ output_size_per_partition = sum(output_partition_sizes)
50
+ layer.logical_widths = output_partition_sizes
51
+ layer.input_size_per_partition = input_size_per_partition
52
+ layer.output_size_per_partition = output_size_per_partition
53
+ layer.params_dtype = params_dtype
54
+ weight_loader = extra_weight_attrs.get("weight_loader")
55
+
56
+ weight = ModelWeightParameter(
57
+ data=torch.empty(
58
+ output_size_per_partition,
59
+ input_size_per_partition // 2,
60
+ dtype=torch.uint8,
61
+ ),
62
+ input_dim=1,
63
+ output_dim=0,
64
+ weight_loader=weight_loader,
65
+ )
66
+ layer.register_parameter("weight_packed", weight)
67
+
68
+ weight_scale = GroupQuantScaleParameter(
69
+ data=torch.empty(
70
+ output_size_per_partition,
71
+ input_size_per_partition // self.group_size,
72
+ dtype=torch.uint8,
73
+ ),
74
+ input_dim=1,
75
+ output_dim=0,
76
+ weight_loader=weight_loader,
77
+ )
78
+ layer.register_parameter("weight_scale", weight_scale)
79
+
80
+ def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
81
+ layer.weight = Parameter(layer.weight_packed.data, requires_grad=False)
82
+ del layer.weight_packed
83
+ self.kernel.process_weights_after_loading(layer)
84
+
85
+ def apply_weights(
86
+ self,
87
+ layer: torch.nn.Module,
88
+ x: torch.Tensor,
89
+ bias: torch.Tensor | None = None,
90
+ ) -> torch.Tensor:
91
+ return self.kernel.apply_weights(layer, x, bias)
vllm/model_executor/layers/quantization/inc/schemes/inc_mxfp4_moe.py
new
+242
@@ -0,0 +1,242 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+
4
+import torch
5
+
6
+import vllm.model_executor.layers.fused_moe.modular_kernel as mk
7
+from vllm.logger import init_logger
8
+from vllm.model_executor.layers.fused_moe import (
9
+ FusedMoeWeightScaleSupported,
10
+ RoutedExperts,
11
+ SharedExperts,
12
+)
13
+from vllm.model_executor.layers.fused_moe.config import (
14
+ FusedMoEQuantConfig,
15
+ mxfp4_moe_quant_config,
16
+)
17
+from vllm.model_executor.layers.fused_moe.experts.cutlass_moe import (
18
+ CutlassExpertsMxfp4,
19
+)
20
+from vllm.model_executor.layers.fused_moe.experts.marlin_moe import (
21
+ MarlinExperts,
22
+)
23
+from vllm.model_executor.layers.fused_moe.fused_moe_method_base import (
24
+ FusedMoEMethodBase,
25
+)
26
+from vllm.model_executor.layers.fused_moe.oracle.mxfp4 import (
27
+ Mxfp4MoeBackend,
28
+ make_mxfp4_moe_kernel,
29
+ make_mxfp4_moe_quant_config,
30
+ select_mxfp4_moe_backend,
31
+)
32
+from vllm.model_executor.layers.quantization.utils.marlin_utils_fp4 import (
33
+ prepare_moe_fp4_layer_for_marlin,
34
+)
35
+from vllm.model_executor.utils import set_weight_attrs
36
+from vllm.platforms import current_platform
37
+
38
+logger = init_logger(__name__)
39
+
40
+
41
+class INCMxfp4MoEMethod(FusedMoEMethodBase):
42
+ """W4A4 MXFP4 group MoE for AutoRound ``auto_round:llm_compressor`` exports.
43
+
44
+ Registers the packed MXFP4 layout (uint8 ``weight_packed`` + uint8 E8M0
45
+ ``weight_scale``, ``group_size=32``) and dispatches the fused MoE to the
46
+ best backend for the current device: CUTLASS (true W4A4 on supported
47
+ GPUs), the native XPU kernel, or Marlin weight-only as a fallback. The
48
+ per-expert ``gate_proj`` / ``up_proj`` / ``down_proj`` tensors are folded
49
+ into the stacked ``w13`` / ``w2`` parameters by ``make_expert_params_mapping``.
50
+ """
51
+
52
+ def __init__(self, moe) -> None:
53
+ super().__init__(moe)
54
+ self.group_size = 32
55
+ # Backend selection must stay consistent with the weight preparation in
56
+ # process_weights_after_loading / get_fused_moe_quant_config, which only
57
+ # implement three layouts: CUTLASS swizzle (true W4A4), the native XPU
58
+ # kernel (packed passthrough), and Marlin weight-only. XPU dispatch is
59
+ # deferred to the shared oracle; every other non-CUTLASS device falls
60
+ # back to Marlin (mirroring CompressedTensorsW4A4Mxfp4MoEMethod).
61
+ self.use_cutlass_mxfp4 = CutlassExpertsMxfp4._supports_current_device()
62
+ self.mxfp4_backend = Mxfp4MoeBackend.MARLIN
63
+ self.experts_cls: type[mk.FusedMoEExperts] | None = None
64
+ if self.use_cutlass_mxfp4:
65
+ self.experts_cls = CutlassExpertsMxfp4
66
+ logger.info_once("Using CutlassExpertsMxfp4 for AutoRound MXFP4 MoE")
67
+ elif current_platform.is_xpu():
68
+ self.mxfp4_backend, self.experts_cls = select_mxfp4_moe_backend(moe)
69
+ else:
70
+ self.experts_cls = MarlinExperts
71
+ logger.info_once(
72
+ "Using MarlinExperts (weight-only FP4) for AutoRound MXFP4 MoE"
73
+ )
74
+
75
+ def create_weights(
76
+ self,
77
+ layer: torch.nn.Module,
78
+ num_experts: int,
79
+ hidden_size: int,
80
+ intermediate_size_per_partition: int,
81
+ params_dtype: torch.dtype,
82
+ **extra_weight_attrs,
83
+ ) -> None:
84
+ layer.num_experts = num_experts
85
+ layer.params_dtype = params_dtype
86
+
87
+ # gate + up fused on the output dim; two FP4 packed per input byte.
88
+ w13_weight = torch.nn.Parameter(
89
+ torch.empty(
90
+ num_experts,
91
+ 2 * intermediate_size_per_partition,
92
+ hidden_size // 2,
93
+ dtype=torch.uint8,
94
+ ),
95
+ requires_grad=False,
96
+ )
97
+ layer.register_parameter("w13_weight_packed", w13_weight)
98
+ set_weight_attrs(w13_weight, extra_weight_attrs)
99
+
100
+ w2_weight = torch.nn.Parameter(
101
+ torch.empty(
102
+ num_experts,
103
+ hidden_size,
104
+ intermediate_size_per_partition // 2,
105
+ dtype=torch.uint8,
106
+ ),
107
+ requires_grad=False,
108
+ )
109
+ layer.register_parameter("w2_weight_packed", w2_weight)
110
+ set_weight_attrs(w2_weight, extra_weight_attrs)
111
+
112
+ # Per-group E8M0 block scales (group_size=32), stored as uint8.
113
+ w13_weight_scale = torch.nn.Parameter(
114
+ torch.empty(
115
+ num_experts,
116
+ 2 * intermediate_size_per_partition,
117
+ hidden_size // self.group_size,
118
+ dtype=torch.uint8,
119
+ ),
120
+ requires_grad=False,
121
+ )
122
+ layer.register_parameter("w13_weight_scale", w13_weight_scale)
123
+ extra_weight_attrs.update(
124
+ {"quant_method": FusedMoeWeightScaleSupported.GROUP.value}
125
+ )
126
+ set_weight_attrs(w13_weight_scale, extra_weight_attrs)
127
+
128
+ w2_weight_scale = torch.nn.Parameter(
129
+ torch.empty(
130
+ num_experts,
131
+ hidden_size,
132
+ intermediate_size_per_partition // self.group_size,
133
+ dtype=torch.uint8,
134
+ ),
135
+ requires_grad=False,
136
+ )
137
+ layer.register_parameter("w2_weight_scale", w2_weight_scale)
138
+ set_weight_attrs(w2_weight_scale, extra_weight_attrs)
139
+
140
+ def get_fused_moe_quant_config(
141
+ self, layer: torch.nn.Module
142
+ ) -> FusedMoEQuantConfig | None:
143
+ if self.use_cutlass_mxfp4:
144
+ # W4A4: both weights and activations quantized to MXFP4.
145
+ return mxfp4_moe_quant_config(
146
+ w1_scale=layer.w13_weight_scale,
147
+ w2_scale=layer.w2_weight_scale,
148
+ )
149
+ # Weight-only (Marlin) or native XPU kernel.
150
+ return make_mxfp4_moe_quant_config(
151
+ mxfp4_backend=self.mxfp4_backend,
152
+ w1_scale=layer.w13_weight_scale,
153
+ w2_scale=layer.w2_weight_scale,
154
+ )
155
+
156
+ def process_weights_after_loading(self, layer: RoutedExperts) -> None:
157
+ layer.w13_weight = torch.nn.Parameter(
158
+ layer.w13_weight_packed.data, requires_grad=False
159
+ )
160
+ delattr(layer, "w13_weight_packed")
161
+ layer.w2_weight = torch.nn.Parameter(
162
+ layer.w2_weight_packed.data, requires_grad=False
163
+ )
164
+ delattr(layer, "w2_weight_packed")
165
+
166
+ if self.use_cutlass_mxfp4:
167
+ # Swizzle weight scales from flat checkpoint layout [E, N, K//32]
168
+ # to the CUTLASS tiled layout.
169
+ from vllm.model_executor.layers.fused_moe.experts.cutlass_moe import (
170
+ swizzle_mxfp4_scales,
171
+ )
172
+
173
+ E = layer.w13_weight_scale.shape[0]
174
+ w13_N = layer.w13_weight_scale.shape[1]
175
+ w13_scale_K = layer.w13_weight_scale.shape[2]
176
+ w13_K = w13_scale_K * 32
177
+
178
+ w2_M = layer.w2_weight_scale.shape[1]
179
+ w2_scale_N = layer.w2_weight_scale.shape[2]
180
+ w2_N = w2_scale_N * 32
181
+
182
+ swizzled_w13 = []
183
+ swizzled_w2 = []
184
+ for e_idx in range(E):
185
+ s13 = layer.w13_weight_scale[e_idx]
186
+ sw13 = swizzle_mxfp4_scales(s13, w13_N, w13_K)
187
+ swizzled_w13.append(sw13.reshape(w13_N, w13_scale_K))
188
+ s2 = layer.w2_weight_scale[e_idx]
189
+ sw2 = swizzle_mxfp4_scales(s2, w2_M, w2_N)
190
+ swizzled_w2.append(sw2.reshape(w2_M, w2_scale_N))
191
+ layer.w13_weight_scale = torch.nn.Parameter(
192
+ torch.stack(swizzled_w13), requires_grad=False
193
+ )
194
+ layer.w2_weight_scale = torch.nn.Parameter(
195
+ torch.stack(swizzled_w2), requires_grad=False
196
+ )
197
+ elif current_platform.is_xpu():
198
+ # The XPU fused-MoE kernel consumes the packed layout directly; no
199
+ # swizzle / repack / transpose is required.
200
+ pass
201
+ else:
202
+ logger.warning_once(
203
+ "This device lacks native FP4 compute; using weight-only FP4 "
204
+ "via the Marlin kernel, which may reduce performance for "
205
+ "compute-heavy workloads."
206
+ )
207
+ prepare_moe_fp4_layer_for_marlin(layer)
208
+
209
+ self.moe_quant_config = self.get_fused_moe_quant_config(layer)
210
+ if self.moe_quant_config is not None:
211
+ assert self.experts_cls is not None
212
+ self.moe_kernel = make_mxfp4_moe_kernel(
213
+ moe_quant_config=self.moe_quant_config,
214
+ moe_config=self.moe,
215
+ experts_cls=self.experts_cls,
216
+ mxfp4_backend=self.mxfp4_backend,
217
+ routing_tables=layer._expert_routing_tables(),
218
+ )
219
+
220
+ def apply(
221
+ self,
222
+ layer: RoutedExperts,
223
+ x: torch.Tensor,
224
+ topk_weights: torch.Tensor,
225
+ topk_ids: torch.Tensor,
226
+ shared_experts: SharedExperts | None,
227
+ shared_experts_input: torch.Tensor | None,
228
+ ) -> torch.Tensor:
229
+ assert self.moe_kernel is not None
230
+ return self.moe_kernel.apply(
231
+ x,
232
+ layer.w13_weight,
233
+ layer.w2_weight,
234
+ topk_weights,
235
+ topk_ids,
236
+ activation=layer.activation,
237
+ global_num_experts=layer.global_num_experts,
238
+ expert_map=layer.expert_map,
239
+ apply_router_weight_on_input=layer.apply_router_weight_on_input,
240
+ shared_experts=shared_experts,
241
+ shared_experts_input=shared_experts_input,
242
+ )
vllm/model_executor/layers/quantization/inc/schemes/inc_mxfp4_scheme.py
new
+54
@@ -0,0 +1,54 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+
4
+from typing import TYPE_CHECKING
5
+
6
+from vllm.logger import init_logger
7
+
8
+from ..inc_linear import INCLinearMethod
9
+from .inc_scheme import INCScheme
10
+
11
+if TYPE_CHECKING:
12
+ import torch
13
+
14
+ from ..config_parser import INCLayerConfig
15
+ from ..inc import INCConfig
16
+
17
+logger = init_logger(__name__)
18
+
19
+
20
+class INCMxfp4Scheme(INCScheme):
21
+ """MXFP4 (W4A4) scheme for AutoRound checkpoints.
22
+
23
+ Dispatches to :class:`INCMxfp4LinearMethod` for linear layers and
24
+ :class:`INCMxfp4MoEMethod` for fused MoE layers; see those classes for the
25
+ per-module weight layout and kernel-selection details.
26
+ """
27
+
28
+ @staticmethod
29
+ def can_handle(layer_config: "INCLayerConfig") -> bool:
30
+ return layer_config.is_mxfp4
31
+
32
+ def get_linear_method(
33
+ self,
34
+ config: "INCConfig",
35
+ layer: "torch.nn.Module",
36
+ prefix: str,
37
+ layer_config: "INCLayerConfig",
38
+ ):
39
+ del config, layer, prefix
40
+ from .inc_mxfp4_linear import INCMxfp4LinearMethod
41
+
42
+ return INCLinearMethod(INCMxfp4LinearMethod(layer_config))
43
+
44
+ def get_moe_method(
45
+ self,
46
+ config: "INCConfig",
47
+ layer: "torch.nn.Module",
48
+ prefix: str,
49
+ layer_config: "INCLayerConfig",
50
+ ):
51
+ del config, prefix, layer_config
52
+ from .inc_mxfp4_moe import INCMxfp4MoEMethod
53
+
54
+ return INCMxfp4MoEMethod(layer.moe_config)
vllm/model_executor/layers/quantization/inc/schemes/inc_wna16_scheme.py
+13
-27
@@ -7,7 +7,6 @@ from vllm.logger import init_logger
7
from vllm.model_executor.layers.quantization.auto_awq import AutoAWQConfig
8
from vllm.model_executor.layers.quantization.auto_gptq import AutoGPTQConfig
9
from vllm.platforms import current_platform
10
-from vllm.scalar_type import scalar_types
10
11
from ..inc_linear import INCLinearMethod
12
from .inc_scheme import INCScheme
@@ -97,8 +96,8 @@ class INCWna16Scheme(INCScheme):
96
layer_config: "INCLayerConfig",
97
):
98
del config, prefix
100
- # XPU and CPU do not support MoE quantization yet
101
- if current_platform.is_xpu() or current_platform.is_cpu():
99
+ # CPU does not support quantized MoE yet.
100
+ if current_platform.is_cpu():
101
from vllm.model_executor.layers.fused_moe import (
102
UnquantizedFusedMoEMethod,
103
)
@@ -120,21 +119,17 @@ def _resolve_gptq_moe(layer: "torch.nn.Module", layer_config: "INCLayerConfig"):
119
MoeWNA16Method,
120
)
121
from vllm.model_executor.layers.quantization.utils.marlin_utils import (
123
- check_marlin_supported,
122
check_moe_marlin_supports_layer,
123
)
124
127
- gptq_type_map = {
128
- (4, True): scalar_types.uint4b8,
129
- (8, True): scalar_types.uint8b128,
130
- }
131
- use_marlin = (layer_config.bits, layer_config.sym) in gptq_type_map
132
- if use_marlin:
133
- use_marlin = check_marlin_supported(
134
- gptq_type_map[(layer_config.bits, layer_config.sym)],
135
- layer_config.group_size,
136
- has_zp=not layer_config.sym,
137
- ) and check_moe_marlin_supports_layer(layer, layer_config.group_size)
125
+ # AutoGPTQMoEMethod selects its fused-MoE backend through the WNA16 oracle
126
+ # (Marlin on CUDA, XPUExpertsWNA16 on XPU). Gate only on the layer-shape
127
+ # check like compressed-tensors does; the capability-based
128
+ # check_marlin_supported is skipped so the XPU path is reachable.
129
+ use_marlin = (layer_config.bits, layer_config.sym) in {
130
+ (4, True),
131
+ (8, True),
132
+ } and check_moe_marlin_supports_layer(layer, layer_config.group_size)
133
134
if use_marlin:
135
return AutoGPTQMoEMethod(
@@ -169,21 +164,12 @@ def _resolve_awq_moe(layer: "torch.nn.Module", layer_config: "INCLayerConfig"):
164
MoeWNA16Method,
165
)
166
from vllm.model_executor.layers.quantization.utils.marlin_utils import (
172
- check_marlin_supported,
167
check_moe_marlin_supports_layer,
168
)
169
176
- awq_type_map = {
177
- 4: scalar_types.uint4,
178
- 8: scalar_types.uint8,
179
- }
180
- use_marlin = layer_config.bits in awq_type_map
181
- if use_marlin:
182
- use_marlin = check_marlin_supported(
183
- awq_type_map[layer_config.bits],
184
- layer_config.group_size,
185
- not layer_config.sym,
186
- ) and check_moe_marlin_supports_layer(layer, layer_config.group_size)
170
+ use_marlin = layer_config.bits in (4, 8) and check_moe_marlin_supports_layer(
171
+ layer, layer_config.group_size
172
+ )
173
174
if use_marlin:
175
return AutoAWQMoEMethod(