[ROCm] Expose kFp8DynamicTokenSym on AITER PTPC linears (#54248)

Signed-off-by: Rebecca Lee <rebecca.lee@amd.com> Co-authored-by: Cursor <cursoragent@cursor.com>

Rebecca Lee committed Sep 16, 2026 at 09:48 UTC 6ca2b23e22aab8534e00a85e8ec5222508a6ecf3
5 files changed +104 -4
tests/fusion/test_quant_activation_contract.py
+8
@@ -24,6 +24,11 @@ from vllm.model_executor.kernels.linear.nvfp4.flashinfer import (
24 FlashInferCutlassNvFp4LinearKernel,
25 FlashInferTrtllmNvFp4LinearKernel,
26 )
27 +from vllm.model_executor.kernels.linear.scaled_mm.aiter import (
28 + AiterHipbMMPerTokenFp8ScaledMMLinearKernel,
29 + AiterPerTokenFp8ScaledMMLinearKernel,
30 + AiterPreshuffledPerTokenFp8ScaledMMLinearKernel,
31 +)
32 from vllm.model_executor.kernels.linear.scaled_mm.cutlass import (
33 CutlassFP8ScaledMMLinearKernel,
34 )
@@ -56,6 +61,9 @@ SUPPORTING = {
61 FlashInferFP8ScaledMMLinearKernel,
62 FlashInferCutlassNvFp4LinearKernel,
63 PerTensorTorchFP8ScaledMMLinearKernel,
64 + AiterHipbMMPerTokenFp8ScaledMMLinearKernel,
65 + AiterPreshuffledPerTokenFp8ScaledMMLinearKernel,
66 + AiterPerTokenFp8ScaledMMLinearKernel,
67 FlashInferCutedslMxfp8LinearKernel,
68 FlashInferCutlassMxfp8LinearKernel,
69 }
tests/quantization/test_quark.py
+64 -1
@@ -21,9 +21,18 @@ from packaging import version
21
22 from tests.quantization.utils import load_model_without_vllm_runner
23 from vllm._aiter_ops import is_aiter_found_and_supported, rocm_aiter_ops
24 -from vllm.config import set_current_vllm_config
24 +from vllm.config import VllmConfig, set_current_vllm_config
25 from vllm.config.cache import CacheConfig
26 from vllm.forward_context import set_forward_context
27 +from vllm.model_executor import parameter
28 +from vllm.model_executor.kernels.linear.scaled_mm.aiter import (
29 + AiterHipbMMPerTokenFp8ScaledMMLinearKernel,
30 + AiterPerTokenFp8ScaledMMLinearKernel,
31 + AiterPreshuffledPerTokenFp8ScaledMMLinearKernel,
32 +)
33 +from vllm.model_executor.kernels.linear.scaled_mm.ScaledMMLinearKernel import (
34 + FP8ScaledMMLinearLayerConfig,
35 +)
36 from vllm.model_executor.layers.attention import Attention
37 from vllm.model_executor.layers.fused_moe import (
38 FusedMoeWeightScaleSupported,
@@ -113,6 +122,12 @@ QUARK_MXFP4_AVAILABLE = find_spec("quark") is not None and version.parse(
122
123 AITER_AVAILABLE = is_aiter_found_and_supported()
124
125 +AITER_PTPC_KERNELS = (
126 + AiterHipbMMPerTokenFp8ScaledMMLinearKernel,
127 + AiterPreshuffledPerTokenFp8ScaledMMLinearKernel,
128 + AiterPerTokenFp8ScaledMMLinearKernel,
129 +)
130 +
131 DEVICE_TYPE = current_platform.device_type
132
133
@@ -1095,6 +1110,54 @@ def test_quark_w8a8_fp8_moe_per_block_rejects_misaligned_partition():
1110 )
1111
1112
1113 +def test_quark_fp8_ptpc_exposes_kernel_input_quant_key(monkeypatch):
1114 + """QuarkW8A8Fp8 must advertise the key its kernel consumes pre-quantized.
1115 +
1116 + Only kernel selection runs, no GEMM. The shape matters: AITER PTPC
1117 + kernels decline untuned shapes, leaving a torch kernel that has no key.
1118 + """
1119 + # Llama-3.1-70B qkv_proj at TP1 (aiter-tuned N, K on gfx950).
1120 + N, K = 10240, 8192
1121 + dtype = torch.bfloat16
1122 + kernel_config = FP8ScaledMMLinearLayerConfig(
1123 + weight_quant_key=kFp8StaticChannelSym,
1124 + activation_quant_key=kFp8DynamicTokenSym,
1125 + weight_shape=(N, K),
1126 + input_dtype=dtype,
1127 + out_dtype=dtype,
1128 + )
1129 + if not any(
1130 + cls.is_supported()[0] and cls.can_implement(kernel_config)[0]
1131 + for cls in AITER_PTPC_KERNELS
1132 + ):
1133 + pytest.skip("no AITER PTPC kernel is usable for this shape and environment")
1134 +
1135 + monkeypatch.setattr(parameter, "get_tensor_model_parallel_rank", lambda: 0)
1136 + monkeypatch.setattr(parameter, "get_tensor_model_parallel_world_size", lambda: 1)
1137 +
1138 + scheme = QuarkW8A8Fp8.__new__(QuarkW8A8Fp8)
1139 + scheme.weight_qscheme = "per_channel"
1140 + scheme.is_static_input_scheme = False
1141 + scheme.input_qscheme = "per_channel"
1142 + scheme.activation_quant_key = kFp8DynamicTokenSym
1143 + scheme.weight_quant_key = kFp8StaticChannelSym
1144 + scheme.out_dtype = dtype
1145 + scheme.input_dtype = dtype
1146 +
1147 + layer = torch.nn.Module()
1148 + with set_current_vllm_config(VllmConfig()):
1149 + scheme.create_weights(
1150 + layer,
1151 + output_partition_sizes=[N],
1152 + input_size_per_partition=K,
1153 + params_dtype=dtype,
1154 + weight_loader=lambda *args, **kwargs: None,
1155 + )
1156 +
1157 + assert isinstance(scheme.fp8_linear, AITER_PTPC_KERNELS)
1158 + assert layer.input_quant_key == kFp8DynamicTokenSym
1159 +
1160 +
1161 @pytest.mark.parametrize("kv_cache_dtype", ["auto", "fp8"])
1162 def test_quark_fp8_w_per_tensor_a_per_tensor(
1163 kv_cache_dtype: str, monkeypatch, dist_init, workspace_init
vllm/model_executor/kernels/linear/scaled_mm/aiter.py
+16
@@ -16,6 +16,8 @@ from vllm.model_executor.layers.quantization.utils.fp8_utils import (
16 )
17 from vllm.model_executor.layers.quantization.utils.quant_utils import (
18 GroupShape,
19 + QuantKey,
20 + kFp8DynamicTokenSym,
21 )
22 from vllm.model_executor.utils import replace_parameter
23 from vllm.platforms import current_platform
@@ -150,6 +152,12 @@ class AiterPreshuffledPerTokenFp8ScaledMMLinearKernel(FP8ScaledMMLinearKernel):
152 return False, "requires aiter library to be installed."
153 return True, None
154
155 + def input_quant_key(self) -> QuantKey | None:
156 + # Does not call get_output_padding() - torch fallbacks
157 + # resolve padding from compilation_config, which is unset in
158 + # profile_run. This kernel does not pad activations.
159 + return kFp8DynamicTokenSym
160 +
161 @classmethod
162 def can_implement(cls, c: FP8ScaledMMLinearLayerConfig) -> tuple[bool, str | None]:
163 is_ptpc = (
@@ -242,6 +250,11 @@ class AiterHipbMMPerTokenFp8ScaledMMLinearKernel(FP8ScaledMMLinearKernel):
250
251 return True, None
252
253 + def input_quant_key(self) -> QuantKey | None:
254 + # Same per-token FP8 consume path as
255 + # AiterPreshuffledPerTokenFp8ScaledMMLinearKernel.
256 + return kFp8DynamicTokenSym
257 +
258 @classmethod
259 def can_implement(cls, c: FP8ScaledMMLinearLayerConfig) -> tuple[bool, str | None]:
260 is_ptpc = (
@@ -319,6 +332,9 @@ class AiterPerTokenFp8ScaledMMLinearKernel(FP8ScaledMMLinearKernel):
332 compute_capability
333 )
334
335 + def input_quant_key(self) -> QuantKey | None:
336 + return kFp8DynamicTokenSym
337 +
338 @classmethod
339 def can_implement(cls, c: FP8ScaledMMLinearLayerConfig) -> tuple[bool, str | None]:
340 is_ptpc = (
vllm/model_executor/layers/quantization/online/fp8.py
+9 -2
@@ -27,6 +27,10 @@ from vllm.model_executor.layers.fused_moe.config import FusedMoEConfig
27 from vllm.model_executor.layers.fused_moe.oracle.fp8 import (
28 select_fp8_moe_backend,
29 )
30 +from vllm.model_executor.layers.fusion.quant_activation import (
31 + QuantizedActivation,
32 + expose_input_quant_key,
33 +)
34 from vllm.model_executor.layers.linear import (
35 LinearMethodBase,
36 )
@@ -405,6 +409,7 @@ class Fp8PtpcOnlineLinearMethod(OnlineLinearBase):
409 replace_parameter(layer, "weight_scale", weight_scale)
410
411 self.fp8_linear.process_weights_after_loading(layer)
412 + expose_input_quant_key(layer, self.fp8_linear)
413
414 layer._already_called_process_weights_after_loading = True
415
@@ -415,8 +420,10 @@ class Fp8PtpcOnlineLinearMethod(OnlineLinearBase):
420 bias: torch.Tensor | None = None,
421 ) -> torch.Tensor:
422 # if batch invariant mode is enabled dequant
418 - if envs.VLLM_BATCH_INVARIANT and not isinstance(
419 - self.fp8_linear, CutlassFP8ScaledMMLinearKernel
423 + if (
424 + envs.VLLM_BATCH_INVARIANT
425 + and not isinstance(self.fp8_linear, CutlassFP8ScaledMMLinearKernel)
426 + and not isinstance(x, QuantizedActivation)
427 ):
428 weight_dequant = (
429 layer.weight.to(x.dtype) * layer.weight_scale.to(x.dtype).t()
vllm/model_executor/layers/quantization/quark/schemes/quark_w8a8_fp8.py
+7 -1
@@ -9,6 +9,10 @@ from torch.nn import Parameter
9 from vllm.config import get_current_vllm_config
10 from vllm.logger import init_logger
11 from vllm.model_executor.kernels.linear import init_fp8_linear_kernel
12 +from vllm.model_executor.layers.fusion.quant_activation import (
13 + QuantizedActivation,
14 + expose_input_quant_key,
15 +)
16 from vllm.model_executor.layers.quantization.quark.schemes import QuarkScheme
17 from vllm.model_executor.layers.quantization.utils.fp8_utils import (
18 create_fp8_scale_parameter,
@@ -195,10 +199,12 @@ class QuarkW8A8Fp8(QuarkScheme):
199 module_name=self.__class__.__name__,
200 )
201
202 + expose_input_quant_key(layer, self.fp8_linear)
203 +
204 def apply_weights(
205 self,
206 layer: torch.nn.Module,
201 - x: torch.Tensor,
207 + x: torch.Tensor | QuantizedActivation,
208 bias: torch.Tensor | None = None,
209 ) -> torch.Tensor:
210 return self.fp8_linear.apply_weights(layer, x, bias)