[ROCm][Quark][6/N] Use MXFP4 linear kernel abstraction for `aiter` backend (#49348)
Signed-off-by: Felix Marty <Felix.Marty@amd.com> Co-authored-by: Andreas Karatzas <akaratza@amd.com>
fxmarty-amd committed
Jul 28, 2026 at 04:35 UTC
7aea73d83d6064449ff8147899de161e6eb68a20
6 files changed
+370
-166
tests/evals/gsm8k/configs/Qwen3-1.7B-MXFP4.yaml
new
+5
@@ -0,0 +1,5 @@
1
+model_name: "amd-quark/Qwen3-1.7B-MXFP4"
2
+accuracy_threshold: 0.27
3
+num_questions: 1319
4
+num_fewshot: 5
5
+server_args: "--enforce-eager --max-model-len 4096"
tests/kernels/quantization/test_mxfp4_kernel_selection.py
new
+130
@@ -0,0 +1,130 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+"""Tests for MXFP4 linear kernel selection logic (CPU-only)
4
+
5
+Run `pytest tests/kernels/quantization/test_mxfp4_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
+ AiterMxfp4LinearKernel,
15
+ MxFp4LinearKernel,
16
+ MxFp4LinearLayerConfig,
17
+ init_mxfp4_linear_kernel,
18
+ register_linear_kernel,
19
+)
20
+from vllm.platforms import PlatformEnum
21
+
22
+pytestmark = pytest.mark.cpu_test
23
+
24
+
25
+def test_can_implement_is_abstract():
26
+ """Test that can_implement()/is_supported() are properly defined."""
27
+ assert hasattr(MxFp4LinearKernel, "can_implement")
28
+ assert hasattr(MxFp4LinearKernel, "is_supported")
29
+
30
+
31
+def test_aiter_kernel_is_supported_requires_native_mx_support():
32
+ """AiterMxfp4LinearKernel must not be selected on platforms without
33
+ native MX compute, even if AITER itself is importable."""
34
+ with patch(
35
+ "vllm.model_executor.kernels.linear.mxfp4.aiter.current_platform.supports_mx",
36
+ return_value=False,
37
+ ):
38
+ is_supported, reason = AiterMxfp4LinearKernel.is_supported()
39
+ assert not is_supported
40
+ assert reason
41
+
42
+
43
+class OOTMxFp4LinearKernel(MxFp4LinearKernel):
44
+ @classmethod
45
+ def is_supported(
46
+ cls, compute_capability: int | None = None
47
+ ) -> tuple[bool, str | None]:
48
+ return True, None
49
+
50
+ @classmethod
51
+ def can_implement(cls, config: MxFp4LinearLayerConfig) -> tuple[bool, str | None]:
52
+ return True, None
53
+
54
+ def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
55
+ pass
56
+
57
+ def apply_weights(
58
+ self,
59
+ layer: torch.nn.Module,
60
+ x: torch.Tensor,
61
+ bias: torch.Tensor | None = None,
62
+ ) -> torch.Tensor:
63
+ pass
64
+
65
+
66
+@patch("vllm.model_executor.kernels.linear.current_platform")
67
+def test_init_mxfp4_linear_kernel_dispatches_to_registered_kernel(platform_mock):
68
+ """init_mxfp4_linear_kernel should select a registered kernel that
69
+ reports itself as supported, and construct it with a fresh config."""
70
+ platform_mock._enum = PlatformEnum.OOT
71
+ register_linear_kernel(OOTMxFp4LinearKernel, PlatformEnum.OOT, "mxfp4")
72
+
73
+ kernel = init_mxfp4_linear_kernel()
74
+
75
+ assert isinstance(kernel, OOTMxFp4LinearKernel)
76
+ assert kernel.config == MxFp4LinearLayerConfig()
77
+
78
+
79
+class UnsupportedMxFp4LinearKernel(MxFp4LinearKernel):
80
+ @classmethod
81
+ def is_supported(
82
+ cls, compute_capability: int | None = None
83
+ ) -> tuple[bool, str | None]:
84
+ return False, "never supported"
85
+
86
+ @classmethod
87
+ def can_implement(cls, config: MxFp4LinearLayerConfig) -> tuple[bool, str | None]:
88
+ return True, None
89
+
90
+ def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
91
+ pass
92
+
93
+ def apply_weights(
94
+ self,
95
+ layer: torch.nn.Module,
96
+ x: torch.Tensor,
97
+ bias: torch.Tensor | None = None,
98
+ ) -> torch.Tensor:
99
+ pass
100
+
101
+
102
+@patch("vllm.model_executor.kernels.linear.current_platform")
103
+def test_init_mxfp4_linear_kernel_raises_when_no_kernel_matches(platform_mock):
104
+ platform_mock._enum = PlatformEnum.UNSPECIFIED
105
+ register_linear_kernel(
106
+ UnsupportedMxFp4LinearKernel, PlatformEnum.UNSPECIFIED, "mxfp4"
107
+ )
108
+
109
+ with pytest.raises(ValueError, match="Failed to find a kernel"):
110
+ 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/quantization/test_quark.py
+42
@@ -17,6 +17,7 @@ import pytest
17
import torch
18
from packaging import version
19
20
+from vllm._aiter_ops import is_aiter_found_and_supported
21
from vllm.model_executor.layers.quantization.quark.quark import ( # noqa: E501
22
QuarkLinearMethod,
23
QuarkW8A8Fp8,
@@ -26,6 +27,9 @@ from vllm.model_executor.layers.quantization.quark.quark_moe import ( # noqa: E
27
QuarkW4A8Fp8MoEMethod,
28
QuarkW8A8Int8MoEMethod,
29
)
30
+from vllm.model_executor.layers.quantization.utils.mxfp4_utils import (
31
+ quant_dequant_mxfp4,
32
+)
33
from vllm.model_executor.layers.quantization.utils.quant_utils import (
34
is_layer_skipped,
35
)
@@ -52,6 +56,8 @@ QUARK_MXFP4_AVAILABLE = find_spec("quark") is not None and version.parse(
56
importlib.metadata.version("amd-quark")
57
) >= version.parse(QUARK_MXFP4_MIN_VERSION)
58
59
+AITER_AVAILABLE = is_aiter_found_and_supported()
60
+
61
DEVICE_TYPE = current_platform.device_type
62
63
if QUARK_MXFP4_AVAILABLE:
@@ -487,6 +493,42 @@ def test_mxfp4_dequant_kernel_match_quark(
493
assert torch.equal(out_hip, out_torch)
494
495
496
+@pytest.mark.skipif(
497
+ not QUARK_MXFP4_AVAILABLE,
498
+ reason=f"amd-quark>={QUARK_MXFP4_MIN_VERSION} is not available",
499
+)
500
+@pytest.mark.skipif(
501
+ not AITER_AVAILABLE,
502
+ reason="AITER is not found or not supported on the current platform",
503
+)
504
+@pytest.mark.parametrize("float_dtype", [torch.bfloat16, torch.float16])
505
+@pytest.mark.parametrize("scalings", [[2.3, 0.03, 7.3, 0.1, 0.004, 17.3, 1e4, 1e-4]])
506
+def test_mxfp4_dynamic_quant_match_quark(
507
+ float_dtype: torch.dtype, scalings: list[float]
508
+):
509
+ """`AiterMxfp4LinearKernel` quantizes weights dynamically through AITER's
510
+ `dynamic_mxfp4_quant`, while the emulation path quantizes/dequantizes
511
+ through Quark's `qdq_mxfp4`. Check that both agree on the same input.
512
+ """
513
+ from aiter.ops.triton.quant import dynamic_mxfp4_quant
514
+
515
+ torch.manual_seed(0)
516
+
517
+ hidden_size = 32 * 64
518
+ inp = (torch.rand(48, hidden_size, dtype=float_dtype, device=DEVICE_TYPE) - 0.5) * 2
519
+ for i in range(hidden_size // 32):
520
+ inp[:, i * 32 : (i + 1) * 32] = (
521
+ inp[:, i * 32 : (i + 1) * 32] * scalings[i % len(scalings)]
522
+ )
523
+
524
+ x_q, x_s = dynamic_mxfp4_quant(inp)
525
+ out_dynamic_quant = dq_mxfp4_torch(x_q, x_s, float_dtype)
526
+
527
+ out_quark_qdq = quant_dequant_mxfp4(inp)
528
+
529
+ assert torch.equal(out_dynamic_quant, out_quark_qdq)
530
+
531
+
532
# Unit tests for ``is_layer_skipped`` fused-name handling.
533
534
FUSED_MAPPING = {
vllm/model_executor/kernels/linear/__init__.py
+8
@@ -74,6 +74,9 @@ from vllm.model_executor.kernels.linear.mxfp4 import (
74
MxFp4LinearKernel,
75
MxFp4LinearLayerConfig,
76
)
77
+from vllm.model_executor.kernels.linear.mxfp4.aiter import (
78
+ AiterMxfp4LinearKernel,
79
+)
80
from vllm.model_executor.kernels.linear.mxfp4.flashinfer import (
81
FlashInferMxFp4LinearKernel,
82
)
@@ -274,6 +277,7 @@ _LINEAR_BACKEND_KERNEL_MAP: dict[str, set[type]] = {
277
AiterFp8BlockScaledMMKernel,
278
AiterPerTokenFp8ScaledMMLinearKernel,
279
AiterPreshuffledPerTokenFp8ScaledMMLinearKernel,
280
+ AiterMxfp4LinearKernel,
281
},
282
"machete": {
283
MacheteLinearKernel,
@@ -469,6 +473,9 @@ _POSSIBLE_MXFP4_KERNELS: dict[PlatformEnum, list[type[MxFp4LinearKernel]]] = {
473
MarlinMxFp4LinearKernel,
474
HummingMxFp4LinearKernel,
475
],
476
+ PlatformEnum.ROCM: [
477
+ AiterMxfp4LinearKernel,
478
+ ],
479
PlatformEnum.XPU: [
480
XPUMxFp4LinearKernel,
481
],
@@ -1079,6 +1086,7 @@ __all__ = [
1086
"init_mxfp4_linear_kernel",
1087
"MxFp4LinearKernel",
1088
"MxFp4LinearLayerConfig",
1089
+ "AiterMxfp4LinearKernel",
1090
"FlashInferMxFp4LinearKernel",
1091
"MarlinMxFp4LinearKernel",
1092
"FlashInferCutedslMxfp8LinearKernel",
vllm/model_executor/kernels/linear/mxfp4/aiter.py
new
+177
@@ -0,0 +1,177 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+
4
+import torch
5
+from torch.nn.parameter import Parameter
6
+
7
+from vllm._aiter_ops import is_aiter_found_and_supported, rocm_aiter_ops
8
+from vllm.platforms import current_platform
9
+
10
+from .base import MxFp4LinearKernel, MxFp4LinearLayerConfig
11
+
12
+# NOTE: Do not import aiter at module scope. Importing aiter eagerly initializes HIP
13
+# which can force the engine core to spawn instead of fork.
14
+# is_aiter_found_and_supported() checks platform + arch + library availability via
15
+# find_spec/amdsmi, so it stays HIP-free.
16
+# Actual aiter imports are deferred to the functions/methods that need them,
17
+# where HIP initialization is expected.
18
+if is_aiter_found_and_supported():
19
+ from vllm.utils.torch_utils import direct_register_custom_op
20
+
21
+ def gemm_with_dynamic_quant(
22
+ x: torch.Tensor,
23
+ weight: torch.Tensor,
24
+ weight_scale: torch.Tensor,
25
+ rocm_use_aiter_fp4_asm_gemm: bool = False,
26
+ out_dtype: torch.dtype | None = torch.bfloat16,
27
+ x_scales: torch.Tensor | None = None,
28
+ ) -> torch.Tensor:
29
+ from aiter.ops.triton.gemm_afp4wfp4 import (
30
+ gemm_afp4wfp4,
31
+ gemm_afp4wfp4_preshuffled_weight_scales,
32
+ )
33
+ from aiter.ops.triton.quant import dynamic_mxfp4_quant
34
+
35
+ if rocm_use_aiter_fp4_asm_gemm:
36
+ from aiter import gemm_a4w4, per_1x32_f4_quant_hip
37
+
38
+ M = x.shape[0]
39
+ N = weight.shape[0]
40
+ K = weight.shape[1]
41
+ if rocm_use_aiter_fp4_asm_gemm:
42
+ if M <= 64 and rocm_aiter_ops.is_triton_gemm_afp4wfp4_presh_ws_tuned(N, K):
43
+ if x_scales is None:
44
+ # use hip quant kernel for performance
45
+ if M >= 32:
46
+ x_q, x_s = per_1x32_f4_quant_hip(x, shuffle=True)
47
+ else:
48
+ x_q, x_s = per_1x32_f4_quant_hip(x, shuffle=False)
49
+ else:
50
+ x_q = x
51
+ x_s = x_scales
52
+
53
+ if M >= 32:
54
+ x_s = x_s.view(torch.uint8).view(x_s.shape[0] // 32, -1)
55
+ else:
56
+ x_s = x_s[:M, ...].view(torch.uint8)
57
+
58
+ y = torch.empty(M, N, device=x_q.device, dtype=out_dtype)
59
+ gemm_afp4wfp4_preshuffled_weight_scales(
60
+ x_q.view(torch.uint8),
61
+ weight.view(torch.uint8).view(weight.shape[0] // 16, -1),
62
+ x_s,
63
+ weight_scale.view(torch.uint8).view(
64
+ weight_scale.shape[0] // 32, -1
65
+ ),
66
+ out_dtype,
67
+ y,
68
+ )
69
+ else:
70
+ if x_scales is None:
71
+ # use hip quant kernel for performance
72
+ x_q, x_s = per_1x32_f4_quant_hip(x, shuffle=True)
73
+ else:
74
+ x_q = x
75
+ x_s = x_scales
76
+
77
+ y = gemm_a4w4(
78
+ x_q,
79
+ weight.view(x_q.dtype),
80
+ x_s,
81
+ weight_scale.view(x_s.dtype),
82
+ dtype=out_dtype,
83
+ bpreshuffle=True,
84
+ )
85
+ return y[:M]
86
+ else:
87
+ if x_scales is None:
88
+ x_q, x_s = dynamic_mxfp4_quant(x)
89
+ else:
90
+ x_q = x
91
+ x_s = x_scales
92
+ y = torch.empty(
93
+ x_q.shape[0], weight.shape[0], device=x_q.device, dtype=out_dtype
94
+ )
95
+
96
+ gemm_afp4wfp4(x_q, weight, x_s, weight_scale.T, out_dtype, y)
97
+ return y
98
+
99
+ def gemm_with_dynamic_quant_fake(
100
+ x: torch.Tensor,
101
+ weight: torch.Tensor,
102
+ weight_scale: torch.Tensor,
103
+ x_scales: torch.Tensor = None,
104
+ rocm_use_aiter_fp4_asm_gemm: bool = False,
105
+ out_dtype: torch.dtype | None = torch.bfloat16,
106
+ ) -> torch.Tensor:
107
+ return torch.empty(
108
+ (*x.shape[:-1], weight.shape[0]), dtype=out_dtype, device=x.device
109
+ )
110
+
111
+ direct_register_custom_op(
112
+ op_name="gemm_with_dynamic_quant",
113
+ op_func=gemm_with_dynamic_quant,
114
+ mutates_args=[],
115
+ fake_impl=gemm_with_dynamic_quant_fake,
116
+ dispatch_key=current_platform.dispatch_key,
117
+ )
118
+
119
+
120
+class AiterMxfp4LinearKernel(MxFp4LinearKernel):
121
+ """AITER-based native MXFP4 GEMM kernel for ROCm."""
122
+
123
+ def __init__(self, config: MxFp4LinearLayerConfig) -> None:
124
+ super().__init__(config)
125
+ self.use_asm_gemm = rocm_aiter_ops.is_asm_fp4_gemm_dynamic_quant_enabled()
126
+ self.out_dtype = torch.get_default_dtype()
127
+
128
+ @classmethod
129
+ def is_supported(
130
+ cls, compute_capability: int | None = None
131
+ ) -> tuple[bool, str | None]:
132
+ if not current_platform.supports_mx():
133
+ return False, "current platform does not support native MXFP4 computation"
134
+ if is_aiter_found_and_supported():
135
+ return True, None
136
+ return False, "AITER not found or not supported on the current platform"
137
+
138
+ @classmethod
139
+ def can_implement(cls, c: MxFp4LinearLayerConfig) -> tuple[bool, str | None]:
140
+ return True, None
141
+
142
+ def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
143
+ if self.use_asm_gemm:
144
+ from aiter.ops.shuffle import shuffle_weight
145
+
146
+ weight_scale = layer.weight_scale.data
147
+ sm, sn = weight_scale.shape
148
+ weight_scale = weight_scale.view(sm // 32, 2, 16, sn // 8, 2, 4, 1)
149
+ weight_scale = weight_scale.permute(0, 3, 5, 2, 4, 1, 6).contiguous()
150
+ weight_scale = weight_scale.view(sm, sn)
151
+ layer.weight_scale = Parameter(weight_scale, requires_grad=False)
152
+
153
+ layer.weight = Parameter(
154
+ shuffle_weight(layer.weight.data, layout=(16, 16)),
155
+ requires_grad=False,
156
+ )
157
+ else:
158
+ layer.weight_scale = Parameter(
159
+ layer.weight_scale.data.T.contiguous(), requires_grad=False
160
+ )
161
+
162
+ def apply_weights(
163
+ self,
164
+ layer: torch.nn.Module,
165
+ x: torch.Tensor,
166
+ bias: torch.Tensor | None = None,
167
+ ) -> torch.Tensor:
168
+ y = torch.ops.vllm.gemm_with_dynamic_quant(
169
+ x,
170
+ layer.weight,
171
+ layer.weight_scale,
172
+ self.use_asm_gemm,
173
+ self.out_dtype,
174
+ )
175
+ if bias is not None:
176
+ y = y + bias
177
+ return y
vllm/model_executor/layers/quantization/quark/schemes/quark_ocp_mx.py
+8
-166
@@ -9,8 +9,8 @@ from typing import Any
9
import torch
10
import torch.nn.functional as F
11
12
-from vllm._aiter_ops import is_aiter_found_and_supported, rocm_aiter_ops
12
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,
@@ -36,120 +36,6 @@ from .quark_scheme import QuarkScheme
36
logger = init_logger(__name__)
37
38
39
-# NOTE: Do not import aiter at module scope. Importing aiter eagerly initializes HIP
40
-# which can force the engine core to spawn instead of fork.
41
-# is_aiter_found_and_supported() checks platform + arch + library availability via
42
-# find_spec/amdsmi, so it stays HIP-free.
43
-# Actual aiter imports are deferred to the functions/methods that need them,
44
-# where HIP initialization is expected.
45
-if is_aiter_found_and_supported():
46
- from vllm.utils.torch_utils import direct_register_custom_op
47
-
48
- def gemm_with_dynamic_quant(
49
- x: torch.Tensor,
50
- weight: torch.Tensor,
51
- weight_scale: torch.Tensor,
52
- rocm_use_aiter_fp4_asm_gemm: bool = False,
53
- out_dtype: torch.dtype | None = torch.bfloat16,
54
- x_scales: torch.Tensor | None = None,
55
- ) -> torch.Tensor:
56
- from aiter.ops.triton.gemm_afp4wfp4 import (
57
- gemm_afp4wfp4,
58
- gemm_afp4wfp4_preshuffled_weight_scales,
59
- )
60
- from aiter.ops.triton.quant import dynamic_mxfp4_quant
61
-
62
- if rocm_use_aiter_fp4_asm_gemm:
63
- from aiter import gemm_a4w4, per_1x32_f4_quant_hip
64
-
65
- M = x.shape[0]
66
- N = weight.shape[0]
67
- K = weight.shape[1]
68
- if rocm_use_aiter_fp4_asm_gemm:
69
- if M <= 64 and rocm_aiter_ops.is_triton_gemm_afp4wfp4_presh_ws_tuned(N, K):
70
- if x_scales is None:
71
- # use hip quant kernel for performance
72
- if M >= 32:
73
- x_q, x_s = per_1x32_f4_quant_hip(x, shuffle=True)
74
- else:
75
- x_q, x_s = per_1x32_f4_quant_hip(x, shuffle=False)
76
- else:
77
- x_q = x
78
- x_s = x_scales
79
-
80
- if M >= 32:
81
- x_s = x_s.view(torch.uint8).view(x_s.shape[0] // 32, -1)
82
- else:
83
- x_s = x_s[:M, ...].view(torch.uint8)
84
-
85
- y = torch.empty(M, N, device=x_q.device, dtype=out_dtype)
86
- gemm_afp4wfp4_preshuffled_weight_scales(
87
- x_q.view(torch.uint8),
88
- weight.view(torch.uint8).view(weight.shape[0] // 16, -1),
89
- x_s,
90
- weight_scale.view(torch.uint8).view(
91
- weight_scale.shape[0] // 32, -1
92
- ),
93
- out_dtype,
94
- y,
95
- )
96
- else:
97
- if x_scales is None:
98
- # use hip quant kernel for performance
99
- x_q, x_s = per_1x32_f4_quant_hip(x, shuffle=True)
100
- else:
101
- x_q = x
102
- x_s = x_scales
103
-
104
- y = gemm_a4w4(
105
- x_q,
106
- weight.view(x_q.dtype),
107
- x_s,
108
- weight_scale.view(x_s.dtype),
109
- dtype=out_dtype,
110
- bpreshuffle=True,
111
- )
112
- return y[:M]
113
- else:
114
- if x_scales is None:
115
- x_q, x_s = dynamic_mxfp4_quant(x)
116
- else:
117
- x_q = x
118
- x_s = x_scales
119
- y = torch.empty(
120
- x_q.shape[0], weight.shape[0], device=x_q.device, dtype=out_dtype
121
- )
122
-
123
- gemm_afp4wfp4(x_q, weight, x_s, weight_scale.T, out_dtype, y)
124
- return y
125
-
126
- def gemm_with_dynamic_quant_fake(
127
- x: torch.Tensor,
128
- weight: torch.Tensor,
129
- weight_scale: torch.Tensor,
130
- x_scales: torch.Tensor = None,
131
- rocm_use_aiter_fp4_asm_gemm: bool = False,
132
- out_dtype: torch.dtype | None = torch.bfloat16,
133
- ) -> torch.Tensor:
134
- return torch.empty(
135
- (*x.shape[:-1], weight.shape[0]), dtype=out_dtype, device=x.device
136
- )
137
-
138
- direct_register_custom_op(
139
- op_name="gemm_with_dynamic_quant",
140
- op_func=gemm_with_dynamic_quant,
141
- mutates_args=[],
142
- fake_impl=gemm_with_dynamic_quant_fake,
143
- dispatch_key=current_platform.dispatch_key,
144
- )
145
-elif current_platform.is_rocm():
146
- logger.warning(
147
- "AITER is not found or not supported on the current platform, "
148
- "QuarkOCP_MX will fall back to emulation."
149
- "Native MXFP4/MXFP6 acceleration will not be available."
150
- )
151
-
152
-
39
class QuarkOCP_MX(QuarkScheme):
40
def __init__(
41
self,
@@ -157,8 +43,6 @@ class QuarkOCP_MX(QuarkScheme):
43
input_quant_spec: dict[str, Any] | None,
44
dynamic_mxfp4_quant: bool = False,
45
):
160
- self.out_dtype = torch.get_default_dtype()
161
- self.qscheme = "per_group"
46
self.weight_quant_spec = weight_quant_spec
47
self.input_quant_spec = input_quant_spec
48
self.dynamic_mxfp4_quant = dynamic_mxfp4_quant
@@ -211,17 +95,10 @@ class QuarkOCP_MX(QuarkScheme):
95
self.input_dtype != "mxfp4" or self.weight_dtype != "mxfp4"
96
)
97
214
- self.rocm_use_aiter_fp4_asm_gemm = (
215
- rocm_aiter_ops.is_asm_fp4_gemm_dynamic_quant_enabled()
216
- )
217
-
218
- if not self.emulate and not is_aiter_found_and_supported():
219
- # Currently need AITER kernels if not emulating
220
- raise NotImplementedError(
221
- f"{self.__class__.__name__} requires AITER to be installed "
222
- "for non-emulation mode! Please refer to "
223
- "https://github.com/ROCm/aiter for installation details."
224
- )
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
103
if not current_platform.supports_mx():
104
logger.warning_once(
@@ -268,7 +145,7 @@ class QuarkOCP_MX(QuarkScheme):
145
from aiter.ops.triton.quant import dynamic_mxfp4_quant
146
147
w_q, w_s = dynamic_mxfp4_quant(layer.weight)
271
- layer.weight_scale = torch.nn.Parameter(w_s.T.contiguous(), requires_grad=False)
148
+ layer.weight_scale = torch.nn.Parameter(w_s, requires_grad=False)
149
layer.weight = torch.nn.Parameter(w_q, requires_grad=False)
150
151
def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
@@ -284,31 +161,7 @@ class QuarkOCP_MX(QuarkScheme):
161
else:
162
if self.dynamic_mxfp4_quant:
163
self.process_dynamic_mxfp4_weights_after_loading(layer)
287
- elif self.rocm_use_aiter_fp4_asm_gemm:
288
- from aiter.ops.shuffle import shuffle_weight
289
-
290
- # shuffle weight scale
291
- weight_scale_shuffle = layer.weight_scale.data
292
- sm, sn = weight_scale_shuffle.shape
293
- weight_scale_shuffle = weight_scale_shuffle.view(
294
- sm // 32, 2, 16, sn // 8, 2, 4, 1
295
- )
296
- weight_scale_shuffle = weight_scale_shuffle.permute(
297
- 0, 3, 5, 2, 4, 1, 6
298
- ).contiguous()
299
- weight_scale_shuffle = weight_scale_shuffle.view(sm, sn)
300
- layer.weight_scale = torch.nn.Parameter(
301
- weight_scale_shuffle, requires_grad=False
302
- )
303
-
304
- # shuffle weight
305
- weight_shuffle = layer.weight.data
306
- weight_shuffle = shuffle_weight(weight_shuffle, layout=(16, 16))
307
- layer.weight = torch.nn.Parameter(weight_shuffle, requires_grad=False)
308
- else:
309
- layer.weight_scale = torch.nn.Parameter(
310
- layer.weight_scale.data.T.contiguous(), requires_grad=False
311
- )
164
+ self.ocp_mx_linear.process_weights_after_loading(layer)
165
166
def create_weights(
167
self,
@@ -375,15 +228,4 @@ class QuarkOCP_MX(QuarkScheme):
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)
378
- y = torch.ops.vllm.gemm_with_dynamic_quant(
379
- x,
380
- layer.weight,
381
- layer.weight_scale,
382
- self.rocm_use_aiter_fp4_asm_gemm,
383
- self.out_dtype,
384
- )
385
- # gemm_with_dynamic_quant has no bias argument; add it here so the
386
- # native path matches F.linear (e.g. qkv_proj with qkv_bias=True).
387
- if bias is not None:
388
- y = y + bias
389
- return y
231
+ return self.ocp_mx_linear.apply_weights(layer, x, bias)