[ROCm][Quark][7/N] Use MXFP4 linear kernel abstraction for `emulation` backend (#48949)
Signed-off-by: Felix Marty <Felix.Marty@amd.com>
fxmarty-amd committed
Jul 31, 2026 at 19:07 UTC
e67a2e0a56b7ba535bae8bfd98c1c4e4fa742e63
17 files changed
+825
-91
csrc/core/scalar_type.hpp
+3
@@ -327,6 +327,8 @@ static inline constexpr auto kFE2M1f =
327
ScalarType::float_(2, 1, true, ScalarType::NAN_NONE);
328
static inline constexpr auto kFE3M2f =
329
ScalarType::float_(3, 2, true, ScalarType::NAN_NONE);
330
+static inline constexpr auto kFE2M3f =
331
+ ScalarType::float_(2, 3, true, ScalarType::NAN_NONE);
332
static inline constexpr auto kFE4M3fn =
333
ScalarType::float_(4, 3, true, ScalarType::NAN_EXTD_RANGE_MAX_MIN);
334
static inline constexpr auto kFE8M0fnu =
@@ -346,6 +348,7 @@ static inline constexpr auto kUint8b128 = kU8B128;
348
349
static inline constexpr auto kFloat4_e2m1f = kFE2M1f;
350
static inline constexpr auto kFloat6_e3m2f = kFE3M2f;
351
+static inline constexpr auto kFloat6_e2m3f = kFE2M3f;
352
static inline constexpr auto kFloat8_e4m3fn = kFE4M3fn;
353
static inline constexpr auto kFloat8_e5m2 = kFE5M2;
354
static inline constexpr auto kFloat16_e8m7 = kFE8M7;
tests/kernels/quantization/test_mxfp4_kernel_selection.py
+118
-22
@@ -12,15 +12,40 @@ import torch
12
13
from vllm.model_executor.kernels.linear import (
14
AiterMxfp4LinearKernel,
15
+ EmulationMxfp4LinearKernel,
16
+ FlashInferMxFp4LinearKernel,
17
+ HummingMxFp4LinearKernel,
18
+ MarlinMxFp4LinearKernel,
19
MxFp4LinearKernel,
20
MxFp4LinearLayerConfig,
21
+ XPUMxFp4LinearKernel,
22
init_mxfp4_linear_kernel,
23
register_linear_kernel,
24
)
25
+from vllm.model_executor.layers.quantization.utils.mxfp4_utils import (
26
+ quant_dequant_mxfp4,
27
+)
28
+from vllm.model_executor.layers.quantization.utils.quant_utils import (
29
+ kMxfp4Dynamic,
30
+ kMxfp6E2M3Dynamic,
31
+ kMxfp6E3M2Dynamic,
32
+)
33
from vllm.platforms import PlatformEnum
34
35
pytestmark = pytest.mark.cpu_test
36
37
+# Kernels that quantize activations themselves (true W4A4): they require an
38
+# explicit MXFP4-dynamic activation key.
39
+_TRUE_W4A4_KERNELS = [
40
+ FlashInferMxFp4LinearKernel,
41
+ XPUMxFp4LinearKernel,
42
+ AiterMxfp4LinearKernel,
43
+]
44
+
45
+# Weight-only (A16) kernels: they never quantize activations. They still accept
46
+# MXFP4 activation keys as an intentional compatibility fallback.
47
+_WEIGHT_ONLY_KERNELS = [MarlinMxFp4LinearKernel, HummingMxFp4LinearKernel]
48
+
49
50
def test_can_implement_is_abstract():
51
"""Test that can_implement()/is_supported() are properly defined."""
@@ -28,6 +53,97 @@ def test_can_implement_is_abstract():
53
assert hasattr(MxFp4LinearKernel, "is_supported")
54
55
56
+@pytest.mark.parametrize("kernel_cls", _TRUE_W4A4_KERNELS)
57
+def test_true_w4a4_kernels_accept_dynamic_mxfp4_activation(kernel_cls):
58
+ config = MxFp4LinearLayerConfig(activation_quant_key=kMxfp4Dynamic)
59
+ can_implement, reason = kernel_cls.can_implement(config)
60
+ assert can_implement, reason
61
+
62
+
63
+@pytest.mark.parametrize("kernel_cls", _TRUE_W4A4_KERNELS)
64
+def test_true_w4a4_kernels_reject_unset_activation(kernel_cls):
65
+ """None means weight-only/unquantized activations, not dynamic MXFP4."""
66
+ config = MxFp4LinearLayerConfig()
67
+ can_implement, reason = kernel_cls.can_implement(config)
68
+ assert not can_implement
69
+ assert reason
70
+
71
+
72
+@pytest.mark.parametrize("kernel_cls", _TRUE_W4A4_KERNELS)
73
+def test_true_w4a4_kernels_reject_explicit_non_mxfp4_activation(kernel_cls):
74
+ """FlashInfer/XPU/Aiter quantize activations to MXFP4 internally, so an
75
+ explicit request for a different activation format must be rejected."""
76
+ config = MxFp4LinearLayerConfig(activation_quant_key=kMxfp6E3M2Dynamic)
77
+ can_implement, reason = kernel_cls.can_implement(config)
78
+ assert not can_implement
79
+ assert reason
80
+
81
+
82
+@pytest.mark.parametrize("kernel_cls", _WEIGHT_ONLY_KERNELS)
83
+@pytest.mark.parametrize("activation_quant_key", [None, kMxfp4Dynamic])
84
+def test_weight_only_kernels_accept_unquantized_or_mxfp4_activation(
85
+ kernel_cls, activation_quant_key
86
+):
87
+ """Marlin/Humming never quantize activations, so an unset activation key,
88
+ or one that already describes MXFP4-shaped data, is tolerated. When an
89
+ activation key is explicitly set, a warning must be logged noting that it
90
+ is ignored, since these kernels are weight-only (A16)."""
91
+ config = MxFp4LinearLayerConfig(activation_quant_key=activation_quant_key)
92
+ with patch(f"{kernel_cls.__module__}.logger.warning_once") as warning_once:
93
+ can_implement, reason = kernel_cls.can_implement(config)
94
+ assert can_implement, reason
95
+
96
+ if activation_quant_key is None:
97
+ warning_once.assert_not_called()
98
+ else:
99
+ warning_once.assert_called_once()
100
+ message = warning_once.call_args.args[0]
101
+ assert "the requested activation quantization" in message
102
+ assert "is ignored" in message
103
+
104
+
105
+@pytest.mark.parametrize("kernel_cls", _WEIGHT_ONLY_KERNELS)
106
+def test_weight_only_kernels_reject_non_mxfp4_activation(kernel_cls):
107
+ config = MxFp4LinearLayerConfig(activation_quant_key=kMxfp6E3M2Dynamic)
108
+ can_implement, reason = kernel_cls.can_implement(config)
109
+ assert not can_implement
110
+ assert reason
111
+
112
+
113
+@pytest.mark.parametrize(
114
+ "activation_quant_key",
115
+ [None, kMxfp4Dynamic, kMxfp6E3M2Dynamic, kMxfp6E2M3Dynamic],
116
+)
117
+def test_emulation_kernel_accepts_any_config(activation_quant_key):
118
+ """EmulationMxfp4LinearKernel is the universal fallback: it must accept
119
+ every supported activation format."""
120
+ config = MxFp4LinearLayerConfig(activation_quant_key=activation_quant_key)
121
+ with patch(
122
+ "vllm.model_executor.kernels.linear._get_linear_backend",
123
+ return_value="emulation",
124
+ ):
125
+ can_implement, reason = EmulationMxfp4LinearKernel.can_implement(config)
126
+ assert can_implement, reason
127
+
128
+
129
+def test_emulation_kernel_derives_quant_dequant_func_from_config():
130
+ """quant_dequant_func must be derived purely from the config's activation
131
+ QuantKey, not set externally."""
132
+ with patch(
133
+ "vllm.model_executor.kernels.linear._get_linear_backend",
134
+ return_value="emulation",
135
+ ):
136
+ weight_only_config = MxFp4LinearLayerConfig()
137
+ kernel = EmulationMxfp4LinearKernel(weight_only_config)
138
+ x = torch.randn(4)
139
+ # identity for weight-only
140
+ assert torch.equal(kernel.quant_dequant_func(x), x)
141
+
142
+ w4a4_config = MxFp4LinearLayerConfig(activation_quant_key=kMxfp4Dynamic)
143
+ kernel = EmulationMxfp4LinearKernel(w4a4_config)
144
+ assert kernel.quant_dequant_func is quant_dequant_mxfp4
145
+
146
+
147
def test_aiter_kernel_is_supported_requires_native_mx_support():
148
"""AiterMxfp4LinearKernel must not be selected on platforms without
149
native MX compute, even if AITER itself is importable."""
@@ -70,10 +186,10 @@ def test_init_mxfp4_linear_kernel_dispatches_to_registered_kernel(platform_mock)
186
platform_mock._enum = PlatformEnum.OOT
187
register_linear_kernel(OOTMxFp4LinearKernel, PlatformEnum.OOT, "mxfp4")
188
73
- kernel = init_mxfp4_linear_kernel()
189
+ kernel = init_mxfp4_linear_kernel(activation_quant_key=kMxfp4Dynamic)
190
191
assert isinstance(kernel, OOTMxFp4LinearKernel)
76
- assert kernel.config == MxFp4LinearLayerConfig()
192
+ assert kernel.config == MxFp4LinearLayerConfig(activation_quant_key=kMxfp4Dynamic)
193
194
195
class UnsupportedMxFp4LinearKernel(MxFp4LinearKernel):
@@ -108,23 +224,3 @@ def test_init_mxfp4_linear_kernel_raises_when_no_kernel_matches(platform_mock):
224
225
with pytest.raises(ValueError, match="Failed to find a kernel"):
226
init_mxfp4_linear_kernel()
111
-
112
-
113
-@patch("vllm.model_executor.kernels.linear.mxfp4.aiter.is_aiter_found_and_supported")
114
-@patch("vllm.model_executor.kernels.linear.mxfp4.aiter.current_platform")
115
-@patch("vllm.model_executor.kernels.linear.current_platform")
116
-def test_init_mxfp4_linear_kernel_raises_on_rocm_without_aiter(
117
- linear_platform_mock, aiter_platform_mock, is_aiter_found_and_supported_mock
118
-):
119
- """On ROCm, the only registered MXFP4 linear kernel is AITER-based.
120
- If AITER is not found/supported, no kernel should be selected."""
121
- linear_platform_mock._enum = PlatformEnum.ROCM
122
- aiter_platform_mock.supports_mx.return_value = True
123
- is_aiter_found_and_supported_mock.return_value = False
124
-
125
- with pytest.raises(
126
- ValueError,
127
- match="(?s)Failed to find a kernel.*"
128
- "AITER not found or not supported on the current platform",
129
- ):
130
- init_mxfp4_linear_kernel()
tests/kernels/quantization/test_mxfp6_kernel_selection.py
new
+150
@@ -0,0 +1,150 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+"""Tests for MXFP6 linear kernel selection logic (CPU-only)
4
+
5
+Run `pytest tests/kernels/quantization/test_mxfp6_kernel_selection.py`.
6
+"""
7
+
8
+from unittest.mock import patch
9
+
10
+import pytest
11
+import torch
12
+
13
+from vllm.model_executor.kernels.linear import (
14
+ EmulationMxfp6LinearKernel,
15
+ MxFp6LinearKernel,
16
+ MxFp6LinearLayerConfig,
17
+ init_mxfp6_linear_kernel,
18
+ register_linear_kernel,
19
+)
20
+from vllm.model_executor.layers.quantization.utils.quant_utils import (
21
+ kMxfp4Dynamic,
22
+ kMxfp4Static,
23
+ kMxfp6E2M3Dynamic,
24
+ kMxfp6E2M3Static,
25
+ kMxfp6E3M2Dynamic,
26
+ kMxfp6E3M2Static,
27
+)
28
+from vllm.platforms import PlatformEnum
29
+
30
+pytestmark = pytest.mark.cpu_test
31
+
32
+# The only implementation available at the moment is software emulation.
33
+_WEIGHT_QUANT_KEYS = [kMxfp6E3M2Static, kMxfp6E2M3Static]
34
+
35
+
36
+def test_can_implement_is_abstract():
37
+ """Test that can_implement()/is_supported() are properly defined."""
38
+ assert hasattr(MxFp6LinearKernel, "can_implement")
39
+ assert hasattr(MxFp6LinearKernel, "is_supported")
40
+
41
+
42
+def test_emulation_kernel_rejects_non_mxfp6_weights():
43
+ """EmulationMxfp6LinearKernel must not implement a non-MXFP6 weight
44
+ format."""
45
+ config = MxFp6LinearLayerConfig(weight_quant_key=kMxfp4Static)
46
+ can_implement, reason = EmulationMxfp6LinearKernel.can_implement(config)
47
+ assert not can_implement
48
+ assert reason
49
+
50
+
51
+@pytest.mark.parametrize("weight_quant_key", _WEIGHT_QUANT_KEYS)
52
+@pytest.mark.parametrize(
53
+ "activation_quant_key",
54
+ [None, kMxfp4Dynamic, kMxfp6E3M2Dynamic, kMxfp6E2M3Dynamic],
55
+)
56
+def test_emulation_kernel_accepts_any_supported_config(
57
+ weight_quant_key, activation_quant_key
58
+):
59
+ """EmulationMxfp6LinearKernel is the only backend today: it must accept
60
+ every supported weight/activation format combination."""
61
+ config = MxFp6LinearLayerConfig(
62
+ weight_quant_key=weight_quant_key, activation_quant_key=activation_quant_key
63
+ )
64
+ can_implement, reason = EmulationMxfp6LinearKernel.can_implement(config)
65
+ assert can_implement, reason
66
+
67
+
68
+@pytest.mark.parametrize("weight_quant_key", _WEIGHT_QUANT_KEYS)
69
+def test_emulation_kernel_rejects_non_mxfp4_or_mxfp6_activation(weight_quant_key):
70
+ config = MxFp6LinearLayerConfig(
71
+ weight_quant_key=weight_quant_key, activation_quant_key=kMxfp4Static
72
+ )
73
+ can_implement, reason = EmulationMxfp6LinearKernel.can_implement(config)
74
+ assert not can_implement
75
+ assert reason
76
+
77
+
78
+class OOTMxFp6LinearKernel(MxFp6LinearKernel):
79
+ @classmethod
80
+ def is_supported(
81
+ cls, compute_capability: int | None = None
82
+ ) -> tuple[bool, str | None]:
83
+ return True, None
84
+
85
+ @classmethod
86
+ def can_implement(cls, config: MxFp6LinearLayerConfig) -> tuple[bool, str | None]:
87
+ return True, None
88
+
89
+ def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
90
+ pass
91
+
92
+ def apply_weights(
93
+ self,
94
+ layer: torch.nn.Module,
95
+ x: torch.Tensor,
96
+ bias: torch.Tensor | None = None,
97
+ ) -> torch.Tensor:
98
+ pass
99
+
100
+
101
+@patch("vllm.model_executor.kernels.linear.current_platform")
102
+def test_init_mxfp6_linear_kernel_dispatches_to_registered_kernel(platform_mock):
103
+ """init_mxfp6_linear_kernel should select a registered kernel that
104
+ reports itself as supported/able to implement the given config, and
105
+ construct it with that exact config."""
106
+ platform_mock._enum = PlatformEnum.OOT
107
+ register_linear_kernel(OOTMxFp6LinearKernel, PlatformEnum.OOT, "mxfp6")
108
+
109
+ kernel = init_mxfp6_linear_kernel(
110
+ weight_quant_key=kMxfp6E3M2Static, activation_quant_key=kMxfp6E3M2Dynamic
111
+ )
112
+
113
+ assert isinstance(kernel, OOTMxFp6LinearKernel)
114
+ assert kernel.config == MxFp6LinearLayerConfig(
115
+ weight_quant_key=kMxfp6E3M2Static, activation_quant_key=kMxfp6E3M2Dynamic
116
+ )
117
+
118
+
119
+class UnsupportedMxFp6LinearKernel(MxFp6LinearKernel):
120
+ @classmethod
121
+ def is_supported(
122
+ cls, compute_capability: int | None = None
123
+ ) -> tuple[bool, str | None]:
124
+ return False, "never supported"
125
+
126
+ @classmethod
127
+ def can_implement(cls, config: MxFp6LinearLayerConfig) -> tuple[bool, str | None]:
128
+ return True, None
129
+
130
+ def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
131
+ pass
132
+
133
+ def apply_weights(
134
+ self,
135
+ layer: torch.nn.Module,
136
+ x: torch.Tensor,
137
+ bias: torch.Tensor | None = None,
138
+ ) -> torch.Tensor:
139
+ pass
140
+
141
+
142
+@patch("vllm.model_executor.kernels.linear.current_platform")
143
+def test_init_mxfp6_linear_kernel_raises_when_no_kernel_matches(platform_mock):
144
+ platform_mock._enum = PlatformEnum.UNSPECIFIED
145
+ register_linear_kernel(
146
+ UnsupportedMxFp6LinearKernel, PlatformEnum.UNSPECIFIED, "mxfp6"
147
+ )
148
+
149
+ with pytest.raises(ValueError, match="Failed to find a kernel"):
150
+ init_mxfp6_linear_kernel(weight_quant_key=kMxfp6E3M2Static)
vllm/model_executor/kernels/linear/__init__.py
+98
-2
@@ -77,6 +77,9 @@ from vllm.model_executor.kernels.linear.mxfp4 import (
77
from vllm.model_executor.kernels.linear.mxfp4.aiter import (
78
AiterMxfp4LinearKernel,
79
)
80
+from vllm.model_executor.kernels.linear.mxfp4.emulation import (
81
+ EmulationMxfp4LinearKernel,
82
+)
83
from vllm.model_executor.kernels.linear.mxfp4.flashinfer import (
84
FlashInferMxFp4LinearKernel,
85
)
@@ -89,6 +92,13 @@ from vllm.model_executor.kernels.linear.mxfp4.marlin import (
92
from vllm.model_executor.kernels.linear.mxfp4.xpu import (
93
XPUMxFp4LinearKernel,
94
)
95
+from vllm.model_executor.kernels.linear.mxfp6 import (
96
+ MxFp6LinearKernel,
97
+ MxFp6LinearLayerConfig,
98
+)
99
+from vllm.model_executor.kernels.linear.mxfp6.emulation import (
100
+ EmulationMxfp6LinearKernel,
101
+)
102
from vllm.model_executor.kernels.linear.mxfp8 import (
103
Mxfp8LinearKernel,
104
Mxfp8LinearLayerConfig,
@@ -294,6 +304,8 @@ _LINEAR_BACKEND_KERNEL_MAP: dict[str, set[type]] = {
304
"emulation": {
305
EmulationMxfp8LinearKernel,
306
EmulationNvFp4LinearKernel,
307
+ EmulationMxfp6LinearKernel,
308
+ EmulationMxfp4LinearKernel,
309
},
310
"xpu": {
311
XPUW8A8FP8LinearKernel,
@@ -467,14 +479,25 @@ _POSSIBLE_NVFP4_KERNELS: dict[PlatformEnum, list[type[NvFp4LinearKernel]]] = {
479
],
480
}
481
482
+_POSSIBLE_MXFP6_KERNELS: dict[PlatformEnum, list[type[MxFp6LinearKernel]]] = {
483
+ PlatformEnum.CUDA: [
484
+ EmulationMxfp6LinearKernel,
485
+ ],
486
+ PlatformEnum.ROCM: [
487
+ EmulationMxfp6LinearKernel,
488
+ ],
489
+}
490
+
491
_POSSIBLE_MXFP4_KERNELS: dict[PlatformEnum, list[type[MxFp4LinearKernel]]] = {
492
PlatformEnum.CUDA: [
493
FlashInferMxFp4LinearKernel,
494
MarlinMxFp4LinearKernel,
495
HummingMxFp4LinearKernel,
496
+ EmulationMxfp4LinearKernel,
497
],
498
PlatformEnum.ROCM: [
499
AiterMxfp4LinearKernel,
500
+ EmulationMxfp4LinearKernel,
501
],
502
PlatformEnum.XPU: [
503
XPUMxFp4LinearKernel,
@@ -812,9 +835,15 @@ def init_mxfp8_linear_kernel() -> Mxfp8LinearKernel:
835
)
836
837
815
-def init_mxfp4_linear_kernel() -> MxFp4LinearKernel:
838
+def init_mxfp4_linear_kernel(
839
+ activation_quant_key: QuantKey | None = None,
840
+) -> MxFp4LinearKernel:
841
"""Select and instantiate the best MXFP4 linear kernel for the
842
current platform."""
843
+ config = MxFp4LinearLayerConfig(
844
+ activation_quant_key=activation_quant_key,
845
+ )
846
+
847
linear_backend = _get_linear_backend()
848
849
platform = current_platform._enum
@@ -843,8 +872,13 @@ def init_mxfp4_linear_kernel() -> MxFp4LinearKernel:
872
failure_reasons.append(f"{kernel_cls.__name__}: {reason}")
873
continue
874
875
+ can_implement, reason = kernel_cls.can_implement(config)
876
+ if not can_implement:
877
+ failure_reasons.append(f"{kernel_cls.__name__}: {reason}")
878
+ continue
879
+
880
logger.info_once("Using %s for MXFP4 GEMM", kernel_cls.__name__)
847
- return kernel_cls(MxFp4LinearLayerConfig())
881
+ return kernel_cls(config)
882
883
raise ValueError(
884
"Failed to find a kernel that can implement the "
@@ -852,6 +886,59 @@ def init_mxfp4_linear_kernel() -> MxFp4LinearKernel:
886
)
887
888
889
+def init_mxfp6_linear_kernel(
890
+ weight_quant_key: QuantKey,
891
+ activation_quant_key: QuantKey | None = None,
892
+) -> MxFp6LinearKernel:
893
+ """Select and instantiate the best MXFP6 linear kernel for the
894
+ current platform."""
895
+ config = MxFp6LinearLayerConfig(
896
+ weight_quant_key=weight_quant_key,
897
+ activation_quant_key=activation_quant_key,
898
+ )
899
+
900
+ linear_backend = _get_linear_backend()
901
+
902
+ platform = current_platform._enum
903
+ possible = list(_POSSIBLE_MXFP6_KERNELS.get(platform, []))
904
+
905
+ # Apply --linear-backend filtering when set.
906
+ if linear_backend != "auto":
907
+ filtered = _filter_kernels_by_backend(linear_backend, possible)
908
+ if not filtered:
909
+ raise ValueError(
910
+ f"--linear-backend={linear_backend} was requested but no "
911
+ f"'{linear_backend}' kernel exists for MXFP6 layers."
912
+ )
913
+ possible = filtered
914
+
915
+ failure_reasons = []
916
+ for kernel_cls in possible:
917
+ if kernel_cls.__name__ in envs.VLLM_DISABLED_KERNELS:
918
+ failure_reasons.append(
919
+ f" {kernel_cls.__name__} disabled by environment variable"
920
+ )
921
+ continue
922
+
923
+ is_supported, reason = kernel_cls.is_supported()
924
+ if not is_supported:
925
+ failure_reasons.append(f"{kernel_cls.__name__}: {reason}")
926
+ continue
927
+
928
+ can_implement, reason = kernel_cls.can_implement(config)
929
+ if not can_implement:
930
+ failure_reasons.append(f"{kernel_cls.__name__}: {reason}")
931
+ continue
932
+
933
+ logger.info_once("Using %s for MXFP6 GEMM", kernel_cls.__name__)
934
+ return kernel_cls(config)
935
+
936
+ raise ValueError(
937
+ "Failed to find a kernel that can implement the "
938
+ "MXFP6 linear layer. Reasons: \n" + "\n".join(failure_reasons)
939
+ )
940
+
941
+
942
def init_wfp8_a16_linear_kernel(
943
weight_quant_key: QuantKey,
944
activation_quant_key: QuantKey,
@@ -1037,6 +1124,10 @@ def register_linear_kernel(
1124
if platform not in _POSSIBLE_MXFP4_KERNELS:
1125
_POSSIBLE_MXFP4_KERNELS[platform] = []
1126
_POSSIBLE_MXFP4_KERNELS[platform].append(kernel_class)
1127
+ elif kernel_type == "mxfp6":
1128
+ if platform not in _POSSIBLE_MXFP6_KERNELS:
1129
+ _POSSIBLE_MXFP6_KERNELS[platform] = []
1130
+ _POSSIBLE_MXFP6_KERNELS[platform].append(kernel_class)
1131
else:
1132
raise ValueError(f"Unrecognized kernel type: {kernel_type}")
1133
@@ -1091,7 +1182,12 @@ __all__ = [
1182
"init_mxfp4_linear_kernel",
1183
"MxFp4LinearKernel",
1184
"MxFp4LinearLayerConfig",
1185
+ "MxFp6LinearKernel",
1186
+ "MxFp6LinearLayerConfig",
1187
+ "init_mxfp6_linear_kernel",
1188
+ "EmulationMxfp6LinearKernel",
1189
"AiterMxfp4LinearKernel",
1190
+ "EmulationMxfp4LinearKernel",
1191
"FlashInferMxFp4LinearKernel",
1192
"MarlinMxFp4LinearKernel",
1193
"FlashInferCutedslMxfp8LinearKernel",
vllm/model_executor/kernels/linear/mxfp4/aiter.py
+30
-1
@@ -4,11 +4,18 @@
4
import torch
5
from torch.nn.parameter import Parameter
6
7
+import vllm.envs as envs
8
from vllm._aiter_ops import is_aiter_found_and_supported, rocm_aiter_ops
9
+from vllm.logger import init_logger
10
+from vllm.model_executor.layers.quantization.utils.quant_utils import (
11
+ kMxfp4Dynamic,
12
+)
13
from vllm.platforms import current_platform
14
15
from .base import MxFp4LinearKernel, MxFp4LinearLayerConfig
16
17
+logger = init_logger(__name__)
18
+
19
# NOTE: Do not import aiter at module scope. Importing aiter eagerly initializes HIP
20
# which can force the engine core to spawn instead of fork.
21
# is_aiter_found_and_supported() checks platform + arch + library availability via
@@ -131,12 +138,34 @@ class AiterMxfp4LinearKernel(MxFp4LinearKernel):
138
) -> tuple[bool, str | None]:
139
if not current_platform.supports_mx():
140
return False, "current platform does not support native MXFP4 computation"
141
+
142
+ from vllm._aiter_ops import is_aiter_found_and_supported
143
+ from vllm.model_executor.kernels.linear import _get_linear_backend
144
+
145
+ linear_backend = _get_linear_backend()
146
+
147
+ if (
148
+ current_platform.is_rocm()
149
+ and current_platform.supports_mx()
150
+ and "AiterMxfp4LinearKernel" not in envs.VLLM_DISABLED_KERNELS
151
+ and linear_backend == "auto"
152
+ and not is_aiter_found_and_supported()
153
+ ):
154
+ logger.warning_once(
155
+ "This platform supports native MXFP4 W4A4 MOE "
156
+ "computation via AITER MOE backend, but AITER is not "
157
+ "found or not supported. Consider installing AITER: "
158
+ "https://github.com/ROCm/aiter."
159
+ )
160
+
161
if is_aiter_found_and_supported():
162
return True, None
163
return False, "AITER not found or not supported on the current platform"
164
165
@classmethod
139
- def can_implement(cls, c: MxFp4LinearLayerConfig) -> tuple[bool, str | None]:
166
+ def can_implement(cls, config: MxFp4LinearLayerConfig) -> tuple[bool, str | None]:
167
+ if config.activation_quant_key != kMxfp4Dynamic:
168
+ return False, "only supports MXFP4 dynamic activation"
169
return True, None
170
171
def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
vllm/model_executor/kernels/linear/mxfp4/base.py
+7
-1
@@ -6,6 +6,8 @@ from dataclasses import dataclass
6
7
import torch
8
9
+from vllm.model_executor.layers.quantization.utils.quant_utils import QuantKey
10
+
11
12
@dataclass
13
class MxFp4LinearLayerConfig:
@@ -13,9 +15,13 @@ class MxFp4LinearLayerConfig:
15
16
All MXFP4 layers share the same structure: packed uint8 weights (2 FP4 values per
17
byte) and per-block weight scales (group size 32).
18
+
19
+ Attributes:
20
+ activation_quant_key: Identifies the activation quantization format,
21
+ or `None` when activations must not be quantized.
22
"""
23
18
- pass
24
+ activation_quant_key: QuantKey | None = None
25
26
27
class MxFp4LinearKernel(ABC):
vllm/model_executor/kernels/linear/mxfp4/emulation.py
new
+106
@@ -0,0 +1,106 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+
4
+from collections.abc import Callable
5
+from functools import partial
6
+
7
+import torch
8
+import torch.nn.functional as F
9
+from torch.nn.parameter import Parameter
10
+
11
+from vllm.logger import init_logger
12
+from vllm.model_executor.layers.quantization.utils.mxfp4_utils import (
13
+ dequant_mxfp4,
14
+ quant_dequant_mxfp4,
15
+)
16
+from vllm.model_executor.layers.quantization.utils.mxfp6_utils import (
17
+ quant_dequant_mxfp6,
18
+)
19
+from vllm.model_executor.layers.quantization.utils.quant_utils import (
20
+ QuantKey,
21
+ kMxfp4Dynamic,
22
+ kMxfp6E2M3Dynamic,
23
+ kMxfp6E3M2Dynamic,
24
+)
25
+from vllm.platforms import current_platform
26
+
27
+from .base import MxFp4LinearKernel, MxFp4LinearLayerConfig
28
+
29
+logger = init_logger(__name__)
30
+
31
+_ACTIVATION_QUANT_DEQUANT_FUNCS: dict[
32
+ QuantKey, Callable[[torch.Tensor], torch.Tensor]
33
+] = {
34
+ kMxfp4Dynamic: quant_dequant_mxfp4,
35
+ kMxfp6E3M2Dynamic: partial(quant_dequant_mxfp6, quant_dtype="fp6_e3m2"),
36
+ kMxfp6E2M3Dynamic: partial(quant_dequant_mxfp6, quant_dtype="fp6_e2m3"),
37
+}
38
+
39
+
40
+class EmulationMxfp4LinearKernel(MxFp4LinearKernel):
41
+ """Software emulation fallback for OCP MXFP4/MXFP6 (dequant + F.linear)."""
42
+
43
+ def __init__(self, config: MxFp4LinearLayerConfig) -> None:
44
+ super().__init__(config)
45
+ if config.activation_quant_key is None:
46
+ # no input Q/DQ for weight-only
47
+ self.quant_dequant_func: Callable[[torch.Tensor], torch.Tensor] = (
48
+ lambda x: x
49
+ )
50
+ else:
51
+ self.quant_dequant_func = _ACTIVATION_QUANT_DEQUANT_FUNCS[
52
+ config.activation_quant_key
53
+ ]
54
+
55
+ @classmethod
56
+ def is_supported(
57
+ cls, compute_capability: int | None = None
58
+ ) -> tuple[bool, str | None]:
59
+ return True, None
60
+
61
+ @classmethod
62
+ def can_implement(cls, config: MxFp4LinearLayerConfig) -> tuple[bool, str | None]:
63
+ if config.activation_quant_key not in (
64
+ None,
65
+ kMxfp4Dynamic,
66
+ kMxfp6E3M2Dynamic,
67
+ kMxfp6E2M3Dynamic,
68
+ ):
69
+ return False, "only supports MXFP4 or MXFP6 or unquantized activations"
70
+
71
+ if (
72
+ current_platform.is_rocm()
73
+ and current_platform.supports_mx()
74
+ and config.activation_quant_key != kMxfp4Dynamic
75
+ ):
76
+ logger.warning_once(
77
+ "The current platform supports native MXFP4/MXFP6 computation, "
78
+ f"but kernels for activation_quant_key={config.activation_quant_key} "
79
+ f"are not yet integrated in vLLM. Using EmulationMxfp4LinearKernel, "
80
+ "with simulated weight dequantization and activation "
81
+ "QDQ (quantize and dequantize), with the linear "
82
+ "layers computed in high precision."
83
+ )
84
+
85
+ if not current_platform.supports_mx():
86
+ logger.warning_once(
87
+ "The current platform does not support native MXFP4 "
88
+ "computation. Using EmulationMxfp4LinearKernel, with simulated weight "
89
+ "dequantization and activation QDQ (quantize and dequantize), with "
90
+ "the linear layers computed in high precision."
91
+ )
92
+
93
+ return True, None
94
+
95
+ def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
96
+ layer.weight_scale = Parameter(layer.weight_scale.data, requires_grad=False)
97
+
98
+ def apply_weights(
99
+ self,
100
+ layer: torch.nn.Module,
101
+ x: torch.Tensor,
102
+ bias: torch.Tensor | None = None,
103
+ ) -> torch.Tensor:
104
+ dq_w = dequant_mxfp4(layer.weight, layer.weight_scale, x.dtype)
105
+ qdq_x = self.quant_dequant_func(x)
106
+ return F.linear(qdq_x, dq_w, bias)
vllm/model_executor/kernels/linear/mxfp4/flashinfer.py
+5
@@ -7,6 +7,9 @@ from torch.nn.parameter import Parameter
7
from vllm.model_executor.layers.fused_moe.experts.cutlass_moe import (
8
swizzle_mxfp4_scales,
9
)
10
+from vllm.model_executor.layers.quantization.utils.quant_utils import (
11
+ kMxfp4Dynamic,
12
+)
13
from vllm.platforms import current_platform
14
from vllm.utils.flashinfer import has_flashinfer_cutedsl
15
@@ -28,6 +31,8 @@ class FlashInferMxFp4LinearKernel(MxFp4LinearKernel):
31
32
@classmethod
33
def can_implement(cls, config: MxFp4LinearLayerConfig) -> tuple[bool, str | None]:
34
+ if config.activation_quant_key != kMxfp4Dynamic:
35
+ return False, "only supports MXFP4 dynamic activation"
36
return True, None
37
38
def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
vllm/model_executor/kernels/linear/mxfp4/humming.py
+13
-1
@@ -3,14 +3,18 @@
3
4
import torch
5
6
+from vllm.logger import init_logger
7
from vllm.model_executor.layers.quantization.utils.humming_utils import (
8
convert_linear_layer_to_humming_standard,
9
prepare_humming_layer,
10
)
11
+from vllm.model_executor.layers.quantization.utils.quant_utils import kMxfp4Dynamic
12
from vllm.platforms import current_platform
13
14
from .base import MxFp4LinearKernel, MxFp4LinearLayerConfig
15
16
+logger = init_logger(__name__)
17
+
18
19
class HummingMxFp4LinearKernel(MxFp4LinearKernel):
20
"""Humming GEMM Kernel for MXFP4."""
@@ -28,7 +32,15 @@ class HummingMxFp4LinearKernel(MxFp4LinearKernel):
32
return True, None
33
34
@classmethod
31
- def can_implement(cls, c: MxFp4LinearLayerConfig) -> tuple[bool, str | None]:
35
+ def can_implement(cls, config: MxFp4LinearLayerConfig) -> tuple[bool, str | None]:
36
+ if config.activation_quant_key not in (None, kMxfp4Dynamic):
37
+ return False, "only supports MXFP4 dynamic or unquantized activations"
38
+ if config.activation_quant_key is not None:
39
+ logger.warning_once(
40
+ "HummingMxFp4LinearKernel is a weight-only (A16) kernel; "
41
+ "the requested activation quantization (%s) is ignored.",
42
+ config.activation_quant_key,
43
+ )
44
return True, None
45
46
def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
vllm/model_executor/kernels/linear/mxfp4/marlin.py
+14
-1
@@ -3,8 +3,13 @@
3
4
import torch
5
6
+from vllm.logger import init_logger
7
+from vllm.model_executor.layers.quantization.utils.quant_utils import kMxfp4Dynamic
8
+
9
from .base import MxFp4LinearKernel, MxFp4LinearLayerConfig
10
11
+logger = init_logger(__name__)
12
+
13
14
class MarlinMxFp4LinearKernel(MxFp4LinearKernel):
15
@classmethod
@@ -20,7 +25,15 @@ class MarlinMxFp4LinearKernel(MxFp4LinearKernel):
25
return False, "Marlin FP4 not available"
26
27
@classmethod
23
- def can_implement(cls, c: MxFp4LinearLayerConfig) -> tuple[bool, str | None]:
28
+ def can_implement(cls, config: MxFp4LinearLayerConfig) -> tuple[bool, str | None]:
29
+ if config.activation_quant_key not in (None, kMxfp4Dynamic):
30
+ return False, "only supports MXFP4 dynamic or unquantized activations"
31
+ if config.activation_quant_key is not None:
32
+ logger.warning_once(
33
+ "MarlinMxFp4LinearKernel is a weight-only (A16) kernel; "
34
+ "the requested activation quantization (%s) is ignored.",
35
+ config.activation_quant_key,
36
+ )
37
return True, None
38
39
def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
vllm/model_executor/kernels/linear/mxfp4/xpu.py
+6
-1
@@ -6,6 +6,9 @@ import torch
6
from vllm.model_executor.layers.quantization.utils.mxfp4_utils import (
7
xpu_mxfp4_quantize as quant_mxfp4,
8
)
9
+from vllm.model_executor.layers.quantization.utils.quant_utils import (
10
+ kMxfp4Dynamic,
11
+)
12
from vllm.model_executor.utils import replace_parameter
13
from vllm.platforms import current_platform
14
@@ -24,7 +27,9 @@ class XPUMxFp4LinearKernel(MxFp4LinearKernel):
27
return True, None
28
29
@classmethod
27
- def can_implement(cls, c: MxFp4LinearLayerConfig) -> tuple[bool, str | None]:
30
+ def can_implement(cls, config: MxFp4LinearLayerConfig) -> tuple[bool, str | None]:
31
+ if config.activation_quant_key != kMxfp4Dynamic:
32
+ return False, "only supports MXFP4 dynamic activation"
33
return True, None
34
35
def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
vllm/model_executor/kernels/linear/mxfp6/__init__.py
new
+12
@@ -0,0 +1,12 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+
4
+from vllm.model_executor.kernels.linear.mxfp6.base import (
5
+ MxFp6LinearKernel,
6
+ MxFp6LinearLayerConfig,
7
+)
8
+
9
+__all__ = [
10
+ "MxFp6LinearKernel",
11
+ "MxFp6LinearLayerConfig",
12
+]
vllm/model_executor/kernels/linear/mxfp6/base.py
new
+76
@@ -0,0 +1,76 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+
4
+from abc import ABC, abstractmethod
5
+from dataclasses import dataclass
6
+
7
+import torch
8
+
9
+from vllm.model_executor.layers.quantization.utils.quant_utils import QuantKey
10
+
11
+
12
+@dataclass
13
+class MxFp6LinearLayerConfig:
14
+ """Configuration for an MXFP6 linear layer.
15
+
16
+ All MXFP6 layers share the same structure: packed uint8 weights (2 FP4 values per
17
+ byte) and per-block weight scales (group size 32).
18
+
19
+ Attributes:
20
+ weight_quant_key: Identifies the weight quantization format. Can be
21
+ kMxfp6E2M3Static or kMxfp6E3M2Static.
22
+ activation_quant_key: Identifies the activation quantization format,
23
+ or `None` when activations must not be quantized.
24
+ """
25
+
26
+ weight_quant_key: QuantKey
27
+ activation_quant_key: QuantKey | None = None
28
+
29
+
30
+class MxFp6LinearKernel(ABC):
31
+ """Base class for MXFP6 quantized linear kernels.
32
+
33
+ Each subclass implements a specific GEMM backend (CUTLASS, Marlin, etc).
34
+ The kernel selection mechanism iterates over registered subclasses in
35
+ priority order,calling ``is_supported`` and ``can_implement`` to find the best
36
+ match for the current hardware.
37
+ """
38
+
39
+ def __init__(self, config: MxFp6LinearLayerConfig) -> None:
40
+ assert self.can_implement(config)[0]
41
+ assert self.is_supported()[0]
42
+ self.config = config
43
+
44
+ @classmethod
45
+ @abstractmethod
46
+ def is_supported(
47
+ cls, compute_capability: int | None = None
48
+ ) -> tuple[bool, str | None]:
49
+ """Return whether this kernel can run on the current platform."""
50
+ raise NotImplementedError
51
+
52
+ @classmethod
53
+ @abstractmethod
54
+ def can_implement(cls, config: MxFp6LinearLayerConfig) -> tuple[bool, str | None]:
55
+ """Return whether this kernel can handle *config*."""
56
+ raise NotImplementedError
57
+
58
+ @abstractmethod
59
+ def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
60
+ """Transform weights into the format required by this kernel.
61
+
62
+ Called once after checkpoint weights have been loaded onto the
63
+ device. Implementations should repack / swizzle / pad weights
64
+ and scales in-place on *layer*.
65
+ """
66
+ raise NotImplementedError
67
+
68
+ @abstractmethod
69
+ def apply_weights(
70
+ self,
71
+ layer: torch.nn.Module,
72
+ x: torch.Tensor,
73
+ bias: torch.Tensor | None = None,
74
+ ) -> torch.Tensor:
75
+ """Run the quantized GEMM."""
76
+ raise NotImplementedError
vllm/model_executor/kernels/linear/mxfp6/emulation.py
new
+93
@@ -0,0 +1,93 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+
4
+from collections.abc import Callable
5
+from functools import partial
6
+
7
+import torch
8
+import torch.nn.functional as F
9
+from torch.nn.parameter import Parameter
10
+
11
+from vllm.model_executor.layers.quantization.utils.mxfp4_utils import (
12
+ quant_dequant_mxfp4,
13
+)
14
+from vllm.model_executor.layers.quantization.utils.mxfp6_utils import (
15
+ dequant_mxfp6,
16
+ quant_dequant_mxfp6,
17
+)
18
+from vllm.model_executor.layers.quantization.utils.quant_utils import (
19
+ QuantKey,
20
+ kMxfp4Dynamic,
21
+ kMxfp6E2M3Dynamic,
22
+ kMxfp6E2M3Static,
23
+ kMxfp6E3M2Dynamic,
24
+ kMxfp6E3M2Static,
25
+)
26
+
27
+from .base import MxFp6LinearKernel, MxFp6LinearLayerConfig
28
+
29
+_WEIGHT_DEQUANT_FUNCS: dict[QuantKey, Callable[..., torch.Tensor]] = {
30
+ kMxfp6E3M2Static: partial(dequant_mxfp6, quant_dtype="fp6_e3m2"),
31
+ kMxfp6E2M3Static: partial(dequant_mxfp6, quant_dtype="fp6_e2m3"),
32
+}
33
+
34
+_ACTIVATION_QUANT_DEQUANT_FUNCS: dict[
35
+ QuantKey, Callable[[torch.Tensor], torch.Tensor]
36
+] = {
37
+ kMxfp4Dynamic: quant_dequant_mxfp4,
38
+ kMxfp6E3M2Dynamic: partial(quant_dequant_mxfp6, quant_dtype="fp6_e3m2"),
39
+ kMxfp6E2M3Dynamic: partial(quant_dequant_mxfp6, quant_dtype="fp6_e2m3"),
40
+}
41
+
42
+
43
+class EmulationMxfp6LinearKernel(MxFp6LinearKernel):
44
+ """Software emulation fallback for OCP MXFP4/MXFP6 (dequant + F.linear)."""
45
+
46
+ def __init__(self, config: MxFp6LinearLayerConfig) -> None:
47
+ super().__init__(config)
48
+ self.dequant_func = _WEIGHT_DEQUANT_FUNCS[config.weight_quant_key]
49
+ if config.activation_quant_key is None:
50
+ # no input Q/DQ for weight-only
51
+ self.quant_dequant_func: Callable[[torch.Tensor], torch.Tensor] = (
52
+ lambda x: x
53
+ )
54
+ else:
55
+ self.quant_dequant_func = _ACTIVATION_QUANT_DEQUANT_FUNCS[
56
+ config.activation_quant_key
57
+ ]
58
+
59
+ @classmethod
60
+ def is_supported(
61
+ cls, compute_capability: int | None = None
62
+ ) -> tuple[bool, str | None]:
63
+ return True, None
64
+
65
+ @classmethod
66
+ def can_implement(cls, config: MxFp6LinearLayerConfig) -> tuple[bool, str | None]:
67
+ if config.weight_quant_key not in (
68
+ kMxfp6E2M3Static,
69
+ kMxfp6E3M2Static,
70
+ ):
71
+ return False, "only supports MXFP6 weights"
72
+ if config.activation_quant_key not in (
73
+ None,
74
+ kMxfp4Dynamic,
75
+ kMxfp6E3M2Dynamic,
76
+ kMxfp6E2M3Dynamic,
77
+ ):
78
+ return False, "only supports MXFP4 or MXFP6 or unquantized activations"
79
+
80
+ return True, None
81
+
82
+ def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
83
+ layer.weight_scale = Parameter(layer.weight_scale.data, requires_grad=False)
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
+ dq_w = self.dequant_func(layer.weight, layer.weight_scale, x.dtype)
92
+ qdq_x = self.quant_dequant_func(x)
93
+ return F.linear(qdq_x, dq_w, bias)
vllm/model_executor/layers/quantization/compressed_tensors/schemes/compressed_tensors_w4a4_mxfp4.py
+6
-1
@@ -9,6 +9,9 @@ from vllm.model_executor.kernels.linear import init_mxfp4_linear_kernel
9
from vllm.model_executor.layers.quantization.compressed_tensors.schemes import (
10
CompressedTensorsScheme,
11
)
12
+from vllm.model_executor.layers.quantization.utils.quant_utils import (
13
+ kMxfp4Dynamic,
14
+)
15
from vllm.model_executor.parameter import (
16
GroupQuantScaleParameter,
17
ModelWeightParameter,
@@ -35,7 +38,9 @@ class CompressedTensorsW4A4Mxfp4(CompressedTensorsScheme):
38
39
def __init__(self):
40
self.group_size = 32
38
- self.kernel = init_mxfp4_linear_kernel()
41
+ self.kernel = init_mxfp4_linear_kernel(
42
+ activation_quant_key=kMxfp4Dynamic,
43
+ )
44
45
@classmethod
46
def get_min_capability(cls) -> int:
vllm/model_executor/layers/quantization/quark/schemes/quark_ocp_mx.py
+56
-59
@@ -3,25 +3,28 @@
3
4
from collections.abc import Callable
5
from fractions import Fraction
6
-from functools import partial
6
from typing import Any
7
8
import torch
10
-import torch.nn.functional as F
9
10
from vllm.logger import init_logger
13
-from vllm.model_executor.kernels.linear import init_mxfp4_linear_kernel
14
-from vllm.model_executor.layers.quantization.utils.mxfp4_utils import (
15
- dequant_mxfp4,
16
- quant_dequant_mxfp4,
17
-)
18
-from vllm.model_executor.layers.quantization.utils.mxfp6_utils import (
19
- dequant_mxfp6,
20
- quant_dequant_mxfp6,
11
+from vllm.model_executor.kernels.linear import (
12
+ MxFp4LinearKernel,
13
+ MxFp6LinearKernel,
14
+ init_mxfp4_linear_kernel,
15
+ init_mxfp6_linear_kernel,
16
)
17
from vllm.model_executor.layers.quantization.utils.ocp_mx_utils import (
18
OCP_MX_BLOCK_SIZE,
24
- OCP_MX_Scheme,
19
+)
20
+from vllm.model_executor.layers.quantization.utils.quant_utils import (
21
+ QuantKey,
22
+ kMxfp4Dynamic,
23
+ kMxfp4Static,
24
+ kMxfp6E2M3Dynamic,
25
+ kMxfp6E2M3Static,
26
+ kMxfp6E3M2Dynamic,
27
+ kMxfp6E3M2Static,
28
)
29
from vllm.model_executor.parameter import (
30
GroupQuantScaleParameter,
@@ -35,8 +38,22 @@ from .quark_scheme import QuarkScheme
38
39
logger = init_logger(__name__)
40
41
+_WEIGHT_QUANT_KEY_MAP: dict[str, QuantKey] = {
42
+ "mxfp4": kMxfp4Static,
43
+ "mxfp6_e3m2": kMxfp6E3M2Static,
44
+ "mxfp6_e2m3": kMxfp6E2M3Static,
45
+}
46
+
47
+_ACTIVATION_QUANT_KEY_MAP: dict[str, QuantKey] = {
48
+ "mxfp4": kMxfp4Dynamic,
49
+ "mxfp6_e3m2": kMxfp6E3M2Dynamic,
50
+ "mxfp6_e2m3": kMxfp6E2M3Dynamic,
51
+}
52
+
53
54
class QuarkOCP_MX(QuarkScheme):
55
+ ocp_mx_linear: MxFp6LinearKernel | MxFp4LinearKernel
56
+
57
def __init__(
58
self,
59
weight_quant_spec: dict[str, Any],
@@ -49,35 +66,26 @@ class QuarkOCP_MX(QuarkScheme):
66
self.weight_dtype = weight_quant_spec["dtype"].replace("fp", "mxfp")
67
self.input_dtype: str | None = None
68
if input_quant_spec is not None:
52
- input_quant = input_quant_spec["dtype"]
53
- if input_quant == "fp8_e4m3":
54
- self.input_dtype = "fp8"
55
- else:
56
- self.input_dtype = input_quant.replace("fp", "mxfp")
57
-
58
- self.ocp_mx_scheme = OCP_MX_Scheme.from_quant_dtype(
59
- self.input_dtype, self.weight_dtype
69
+ self.input_dtype = input_quant_spec["dtype"].replace("fp", "mxfp")
70
+
71
+ if self.input_dtype not in [None, *_ACTIVATION_QUANT_KEY_MAP]:
72
+ raise ValueError(
73
+ f"Unsupported input_dtype={self.input_dtype} for QuarkOCP_MX. "
74
+ f"Supported activation dtypes are {_ACTIVATION_QUANT_KEY_MAP.keys()}, "
75
+ "or None for weight-only quantization."
76
+ )
77
+
78
+ self.weight_quant_key = _WEIGHT_QUANT_KEY_MAP[self.weight_dtype]
79
+ self.activation_quant_key = (
80
+ _ACTIVATION_QUANT_KEY_MAP[self.input_dtype]
81
+ if self.input_dtype is not None
82
+ else None
83
)
84
85
if self.weight_dtype == "mxfp4":
86
self.packed_factor: int | Fraction = 2
64
- self.dequant_func = dequant_mxfp4
87
else:
88
self.packed_factor = Fraction(numerator=8, denominator=6)
67
- self.dequant_func = partial(
68
- dequant_mxfp6, quant_dtype=self.weight_dtype.replace("mx", "")
69
- )
70
-
71
- if self.input_dtype is None:
72
- self.quant_dequant_func: Callable[[torch.Tensor], torch.Tensor] = (
73
- lambda x: x
74
- ) # no input Q/DQ for weight-only
75
- elif self.input_dtype == "mxfp4":
76
- self.quant_dequant_func = quant_dequant_mxfp4
77
- else:
78
- self.quant_dequant_func = partial(
79
- quant_dequant_mxfp6, quant_dtype=self.input_dtype.replace("mx", "")
80
- )
89
90
if input_quant_spec is None:
91
self.static_input_scales = False
@@ -90,16 +98,6 @@ class QuarkOCP_MX(QuarkScheme):
98
"implemented. Please open an issue."
99
)
100
93
- # TODO: integrate (or test) mixed-precision kernel.
94
- self.emulate = not current_platform.supports_mx() or (
95
- self.input_dtype != "mxfp4" or self.weight_dtype != "mxfp4"
96
- )
97
-
98
- # TODO: Move emulation code path as a kernel, and always
99
- # use init_mxfp4_linear_kernel.
100
- if not self.emulate:
101
- self.ocp_mx_linear = init_mxfp4_linear_kernel()
102
-
101
if not current_platform.supports_mx():
102
logger.warning_once(
103
"The current platform does not support native MXFP4/MXFP6 "
@@ -151,17 +149,10 @@ class QuarkOCP_MX(QuarkScheme):
149
def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
150
layer.weight = torch.nn.Parameter(layer.weight.data, requires_grad=False)
151
154
- if self.emulate:
155
- if self.dynamic_mxfp4_quant:
156
- self.process_dynamic_mxfp4_weights_after_loading(layer)
157
- else:
158
- layer.weight_scale = torch.nn.Parameter(
159
- layer.weight_scale.data, requires_grad=False
160
- )
161
- else:
162
- if self.dynamic_mxfp4_quant:
163
- self.process_dynamic_mxfp4_weights_after_loading(layer)
164
- self.ocp_mx_linear.process_weights_after_loading(layer)
152
+ if self.dynamic_mxfp4_quant:
153
+ self.process_dynamic_mxfp4_weights_after_loading(layer)
154
+
155
+ self.ocp_mx_linear.process_weights_after_loading(layer)
156
157
def create_weights(
158
self,
@@ -218,14 +209,20 @@ class QuarkOCP_MX(QuarkScheme):
209
)
210
layer.register_parameter("weight_scale", weight_scale)
211
212
+ if self.weight_quant_key == kMxfp4Static:
213
+ self.ocp_mx_linear = init_mxfp4_linear_kernel(
214
+ activation_quant_key=self.activation_quant_key,
215
+ )
216
+ elif self.weight_quant_key in [kMxfp6E2M3Static, kMxfp6E3M2Static]:
217
+ self.ocp_mx_linear = init_mxfp6_linear_kernel(
218
+ weight_quant_key=self.weight_quant_key,
219
+ activation_quant_key=self.activation_quant_key,
220
+ )
221
+
222
def apply_weights(
223
self,
224
layer: torch.nn.Module,
225
x: torch.Tensor,
226
bias: torch.Tensor | None = None,
227
) -> torch.Tensor:
227
- if self.emulate:
228
- dq_w = self.dequant_func(layer.weight, layer.weight_scale, x.dtype)
229
- qdq_x = self.quant_dequant_func(x)
230
- return F.linear(qdq_x, dq_w, bias)
228
return self.ocp_mx_linear.apply_weights(layer, x, bias)
vllm/model_executor/layers/quantization/utils/quant_utils.py
+32
-2
@@ -90,6 +90,7 @@ class ScaleDesc:
90
GroupShape.PER_CHANNEL: "per_channel",
91
}
92
group_shape = d.get(self.group_shape, str(self.group_shape))
93
+
94
return (
95
f"{fx.graph.dtype_abbrs[self.dtype]},"
96
f"{'static' if self.static else 'dynamic'},{group_shape}"
@@ -106,15 +107,24 @@ class QuantKey:
107
symmetric: symmetric if True, asymmetric if False
108
"""
109
109
- dtype: torch.dtype
110
+ # TODO: QuantKey.dtype is assumed to be `torch.dtype` in matcher_utils.py,
111
+ # but #37990 introduced e.g. `kInt4Static` that uses a `ScalarType` dtype,
112
+ # same for kMxfp6 that does not have a native torch representation.
113
+ # Logical dtype and storage (torch) dtype should be separated (see #48949).
114
+ dtype: torch.dtype | ScalarType
115
scale: ScaleDesc
116
scale2: ScaleDesc | None = None
117
symmetric: bool = True
118
119
def __str__(self):
120
scale2_str = f"scale2({self.scale2})," if self.scale2 else ""
121
+ dtype_description = (
122
+ fx.graph.dtype_abbrs[self.dtype]
123
+ if isinstance(self.dtype, torch.dtype)
124
+ else self.dtype
125
+ )
126
return (
117
- f"QuantKey({fx.graph.dtype_abbrs[self.dtype]},"
127
+ f"QuantKey({dtype_description},"
128
f"scale({self.scale}),{scale2_str}"
129
f"{'a' if not self.symmetric else ''}symmetric)"
130
)
@@ -172,6 +182,26 @@ kMxfp8Dynamic = QuantKey(FP8_DTYPE, scale=kMxfp8DynamicGroupScale, symmetric=Tru
182
kMxfp4StaticGroupScale = ScaleDesc(MXFP_SCALE_DTYPE, True, GroupShape(1, 32))
183
kMxfp4Static = QuantKey(FP4_DTYPE, scale=kMxfp4StaticGroupScale, symmetric=True)
184
185
+kMxfp6E3M2StaticGroupScale = ScaleDesc(MXFP_SCALE_DTYPE, True, GroupShape(1, 32))
186
+kMxfp6E3M2Static = QuantKey(
187
+ scalar_types.float6_e3m2f, scale=kMxfp6E3M2StaticGroupScale, symmetric=True
188
+)
189
+
190
+kMxfp6E3M2DynamicGroupScale = ScaleDesc(MXFP_SCALE_DTYPE, False, GroupShape(1, 32))
191
+kMxfp6E3M2Dynamic = QuantKey(
192
+ scalar_types.float6_e3m2f, scale=kMxfp6E3M2DynamicGroupScale, symmetric=True
193
+)
194
+
195
+kMxfp6E2M3StaticGroupScale = ScaleDesc(MXFP_SCALE_DTYPE, True, GroupShape(1, 32))
196
+kMxfp6E2M3Static = QuantKey(
197
+ scalar_types.float6_e2m3f, scale=kMxfp6E2M3StaticGroupScale, symmetric=True
198
+)
199
+
200
+kMxfp6E2M3DynamicGroupScale = ScaleDesc(MXFP_SCALE_DTYPE, False, GroupShape(1, 32))
201
+kMxfp6E2M3Dynamic = QuantKey(
202
+ scalar_types.float6_e2m3f, scale=kMxfp6E2M3DynamicGroupScale, symmetric=True
203
+)
204
+
205
# TODO: convert this to use SCALAR_TYPE. This is not right.
206
kInt4StaticGroupScale = ScaleDesc(torch.float16, True, GroupShape(1, -1))
207
kInt4Static = QuantKey(INT4_DTYPE, scale=kInt4StaticGroupScale, symmetric=True)