main
py 201 lines 6.08 KB
Raw
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 collections.abc import Sequence
6 from dataclasses import dataclass
7 from typing import Generic, TypeVar
8
9 import torch
10
11 from vllm.model_executor.layers.fusion.quant_activation import (
12 QuantizedActivation,
13 as_quantized_activation,
14 )
15 from vllm.model_executor.layers.quantization.input_quant_fp8 import QuantFP8
16 from vllm.model_executor.layers.quantization.utils.quant_utils import (
17 QuantKey,
18 )
19 from vllm.platforms import current_platform
20
21 from ..base import MMLinearLayerConfig
22
23
24 @dataclass
25 class Int8ScaledMMLinearLayerConfig(MMLinearLayerConfig):
26 # TODO: Change to QuantKey like FP8ScaledMMLinearLayerConfig
27 is_static_input_scheme: bool
28 is_channelwise: bool
29 input_symmetric: bool
30
31
32 @dataclass
33 class FP8ScaledMMLinearLayerConfig(MMLinearLayerConfig):
34 weight_quant_key: QuantKey
35 activation_quant_key: QuantKey
36 weight_shape: tuple[int, int]
37 input_dtype: torch.dtype
38 out_dtype: torch.dtype
39
40
41 _FP8ParamsT = tuple[
42 torch.Tensor, # weight
43 torch.Tensor, # weight_scale
44 torch.Tensor | None, # input_scale,
45 torch.Tensor | None, # input_scale_ub,
46 ]
47 _Int8ParamsT = tuple[
48 torch.Tensor, # weight
49 torch.Tensor, # weight_scale
50 torch.Tensor | None, # input_scale,
51 torch.Tensor | None, # input_zp
52 torch.Tensor | None, # azp_adj
53 ]
54
55 _ParamsT = TypeVar("_ParamsT", _Int8ParamsT, _FP8ParamsT)
56 _ConfigT = TypeVar("_ConfigT", bound=MMLinearLayerConfig)
57
58
59 class ScaledMMLinearKernel(Generic[_ConfigT, _ParamsT], ABC):
60 @classmethod
61 @abstractmethod
62 def is_supported(
63 cls, compute_capability: int | None = None
64 ) -> tuple[bool, str | None]:
65 raise NotImplementedError
66
67 @classmethod
68 @abstractmethod
69 def can_implement(cls, c: _ConfigT) -> tuple[bool, str | None]:
70 raise NotImplementedError
71
72 def __init__(self, c: _ConfigT, layer_param_names: Sequence[str]) -> None:
73 assert self.can_implement(c)[0]
74 assert self.is_supported()[0]
75 self.config = c
76 self.layer_param_names = layer_param_names
77
78 def input_quant_key(self) -> QuantKey | None:
79 """The activation quant key this kernel can consume pre-quantized.
80
81 Manual fusion uses this to decide whether to hoist activation
82 quantization out of apply_weights into an upstream fused kernel.
83 Return None when the kernel needs in-kernel quantization (custom
84 padding or swizzling, dynamic scales, etc.). Kernels that return a
85 key must consume the activation via as_quantized_activation.
86 """
87 return None
88
89 @abstractmethod
90 def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
91 raise NotImplementedError
92
93 @abstractmethod
94 def apply_weights(
95 self,
96 layer: torch.nn.Module,
97 x: torch.Tensor,
98 bias: torch.Tensor | None = None,
99 ) -> torch.Tensor:
100 raise NotImplementedError
101
102 # return a covariant type in the subclass
103 @abstractmethod
104 def _get_layer_params(self, layer) -> _ParamsT:
105 raise NotImplementedError
106
107
108 class FP8ScaledMMLinearKernel(
109 ScaledMMLinearKernel[FP8ScaledMMLinearLayerConfig, _FP8ParamsT], ABC
110 ):
111 def __init__(
112 self, c: FP8ScaledMMLinearLayerConfig, layer_param_names: Sequence[str]
113 ) -> None:
114 act_scale_descriptor = c.activation_quant_key.scale
115 self.quant_fp8 = QuantFP8(
116 static=act_scale_descriptor.static,
117 group_shape=act_scale_descriptor.group_shape,
118 num_token_padding=self.get_output_padding(),
119 )
120 self.fp8_dtype = current_platform.fp8_dtype()
121 super().__init__(c, layer_param_names)
122
123 def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
124 pass
125
126 def _get_layer_params(self, layer) -> _FP8ParamsT:
127 w, w_s, x_s, x_s_ub = self.layer_param_names
128 return (
129 getattr(layer, w),
130 getattr(layer, w_s),
131 getattr(layer, x_s, None),
132 getattr(layer, x_s_ub, None),
133 )
134
135 def apply_weights(
136 self,
137 layer: torch.nn.Module,
138 x: torch.Tensor | QuantizedActivation,
139 bias: torch.Tensor | None = None,
140 ) -> torch.Tensor:
141 fp8_dtype = self.fp8_dtype
142 maybe_out_dtype = self.config.out_dtype
143 w, w_s, x_s, x_s_ub = self._get_layer_params(layer)
144
145 qa = as_quantized_activation(x, self.input_quant_key())
146 if qa is not None:
147 x_data, x_s = qa.data, qa.scale
148 orig_shape, orig_dtype = qa.orig_shape, qa.orig_dtype
149 assert x_data.dtype == fp8_dtype
150 else:
151 assert isinstance(x, torch.Tensor)
152 x_data = x
153 orig_shape, orig_dtype = x.shape, x.dtype
154
155 x_2d = x_data.view(-1, x_data.shape[-1])
156 output_shape = [*orig_shape[:-1], w.shape[1]]
157 out_dtype = orig_dtype if maybe_out_dtype is None else maybe_out_dtype
158
159 x_2d_q = x_2d
160 if qa is None:
161 x_2d_q, x_s = self.quant_fp8(x_2d, x_s, x_s_ub)
162 return self.apply_scaled_mm(
163 A=x_2d_q,
164 B=w,
165 out_dtype=out_dtype,
166 As=x_s,
167 Bs=w_s,
168 bias=bias,
169 output_shape=output_shape,
170 )
171
172 @abstractmethod
173 def apply_scaled_mm(
174 self,
175 *,
176 A: torch.Tensor,
177 B: torch.Tensor,
178 out_dtype: torch.dtype,
179 As: torch.Tensor,
180 Bs: torch.Tensor,
181 bias: torch.Tensor | None,
182 output_shape: list,
183 ) -> torch.Tensor:
184 raise NotImplementedError
185
186 def get_output_padding(self) -> int | None:
187 return None
188
189
190 class Int8ScaledMMLinearKernel(
191 ScaledMMLinearKernel[Int8ScaledMMLinearLayerConfig, _Int8ParamsT], ABC
192 ):
193 def _get_layer_params(self, layer) -> _Int8ParamsT:
194 w_q, w_s, i_s, i_zp, azp_adj = self.layer_param_names
195 return (
196 getattr(layer, w_q),
197 getattr(layer, w_s),
198 getattr(layer, i_s, None),
199 getattr(layer, i_zp, None),
200 getattr(layer, azp_adj, None),
201 )