main
py 152 lines 5.16 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 """Contract tests for the QuantizedActivation linear-kernel integration."""
4
5 import pytest
6 import torch
7
8 from vllm.model_executor.kernels.linear import (
9 _POSSIBLE_FP8_BLOCK_KERNELS,
10 _POSSIBLE_FP8_KERNELS,
11 _POSSIBLE_INT8_KERNELS,
12 _POSSIBLE_MXFP8_KERNELS,
13 _POSSIBLE_NVFP4_KERNELS,
14 )
15 from vllm.model_executor.kernels.linear.mxfp8.flashinfer import (
16 FlashInferCutedslMxfp8LinearKernel,
17 FlashInferCutlassMxfp8LinearKernel,
18 )
19 from vllm.model_executor.kernels.linear.nvfp4.base import (
20 NvFp4LinearKernel,
21 NvFp4LinearLayerConfig,
22 )
23 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 )
35 from vllm.model_executor.kernels.linear.scaled_mm.flashinfer import (
36 FlashInferFP8ScaledMMLinearKernel,
37 )
38 from vllm.model_executor.kernels.linear.scaled_mm.pytorch import (
39 PerTensorTorchFP8ScaledMMLinearKernel,
40 )
41 from vllm.model_executor.kernels.linear.scaled_mm.ScaledMMLinearKernel import (
42 FP8ScaledMMLinearLayerConfig,
43 Int8ScaledMMLinearKernel,
44 Int8ScaledMMLinearLayerConfig,
45 )
46 from vllm.model_executor.layers.fusion.quant_activation import (
47 QuantizedActivation,
48 as_quantized_activation,
49 expose_input_quant_key,
50 get_input_quant_key,
51 )
52 from vllm.model_executor.layers.quantization.utils.quant_utils import (
53 kFp8StaticTensorSym,
54 kNvfp4Dynamic,
55 )
56 from vllm.platforms import current_platform
57
58 # The only backends that consume a pre-quantized activation.
59 SUPPORTING = {
60 CutlassFP8ScaledMMLinearKernel,
61 FlashInferFP8ScaledMMLinearKernel,
62 FlashInferCutlassNvFp4LinearKernel,
63 PerTensorTorchFP8ScaledMMLinearKernel,
64 AiterHipbMMPerTokenFp8ScaledMMLinearKernel,
65 AiterPreshuffledPerTokenFp8ScaledMMLinearKernel,
66 AiterPerTokenFp8ScaledMMLinearKernel,
67 FlashInferCutedslMxfp8LinearKernel,
68 FlashInferCutlassMxfp8LinearKernel,
69 }
70
71
72 def _all_kernel_classes() -> list[type]:
73 seen: dict[type, None] = {}
74 for registry in (
75 _POSSIBLE_FP8_KERNELS,
76 _POSSIBLE_FP8_BLOCK_KERNELS,
77 _POSSIBLE_INT8_KERNELS,
78 _POSSIBLE_NVFP4_KERNELS,
79 _POSSIBLE_MXFP8_KERNELS,
80 ):
81 for kernels in registry.values():
82 for cls in kernels:
83 seen.setdefault(cls, None)
84 return list(seen)
85
86
87 def _probe(cls: type):
88 """A bare kernel instance with a plausible config, so input_quant_key()
89 can be queried without the hardware-gated constructor."""
90 obj = cls.__new__(cls) # type: ignore[call-overload]
91 if issubclass(cls, NvFp4LinearKernel):
92 obj.config = NvFp4LinearLayerConfig()
93 elif issubclass(cls, Int8ScaledMMLinearKernel):
94 obj.config = Int8ScaledMMLinearLayerConfig(
95 is_static_input_scheme=True, is_channelwise=False, input_symmetric=True
96 )
97 else:
98 obj.config = FP8ScaledMMLinearLayerConfig(
99 weight_quant_key=kFp8StaticTensorSym,
100 activation_quant_key=kFp8StaticTensorSym,
101 weight_shape=(16, 16),
102 input_dtype=torch.bfloat16,
103 out_dtype=torch.bfloat16,
104 )
105 return obj
106
107
108 def _resolved_apply_weights(cls: type):
109 for base in cls.__mro__:
110 if "apply_weights" in base.__dict__:
111 return base.__dict__["apply_weights"]
112 raise AssertionError(f"{cls.__name__} has no apply_weights in its MRO")
113
114
115 def test_only_known_backends_support_prequantized_input():
116 declarers = {c for c in _all_kernel_classes() if _probe(c).input_quant_key()}
117 assert declarers == SUPPORTING
118
119
120 def test_supporting_backend_declares_consume_via_helper():
121 for cls in SUPPORTING:
122 fn = _resolved_apply_weights(cls)
123 assert "as_quantized_activation" in fn.__code__.co_names, cls.__name__
124
125
126 def test_bridge_marks_supporting_and_skips_others():
127 supported = _probe(FlashInferCutlassNvFp4LinearKernel)
128 layer = torch.nn.Module()
129 expose_input_quant_key(layer, supported)
130 assert get_input_quant_key(layer) == kNvfp4Dynamic
131
132 unsupported = _probe(FlashInferTrtllmNvFp4LinearKernel)
133 assert unsupported.input_quant_key() is None
134 layer = torch.nn.Module()
135 expose_input_quant_key(layer, unsupported)
136 assert get_input_quant_key(layer) is None
137
138
139 def test_as_quantized_activation_validates_key():
140 qa = QuantizedActivation(
141 data=torch.zeros(2, 4, dtype=current_platform.fp8_dtype()),
142 scale=torch.tensor(1.0),
143 orig_dtype=torch.bfloat16,
144 orig_shape=torch.Size([2, 4]),
145 quant_key=kFp8StaticTensorSym,
146 )
147 with pytest.raises(AssertionError):
148 as_quantized_activation(qa, kNvfp4Dynamic)
149 with pytest.raises(AssertionError):
150 as_quantized_activation(qa, None)
151 assert as_quantized_activation(torch.zeros(2, 4), kFp8StaticTensorSym) is None
152 assert as_quantized_activation(qa, kFp8StaticTensorSym) is qa