main
py 339 lines 11.7 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 from __future__ import annotations
5
6 import torch
7
8 from vllm.model_executor.layers.quantization.utils.fp8_utils import (
9 _upcast_e8m0_to_fp32,
10 )
11 from vllm.model_executor.layers.quantization.utils.quant_utils import GroupShape
12 from vllm.model_executor.utils import replace_parameter
13 from vllm.platforms import current_platform
14 from vllm.utils.b12x import B12xWarmupUnit, reuse_packed_weight_storage
15 from vllm.utils.b12x import (
16 get_b12x_blockscaled as _import_b12x_blockscaled,
17 )
18 from vllm.utils.b12x import (
19 get_b12x_tensor_fp8_linear as _import_b12x_tensor_fp8,
20 )
21 from vllm.utils.torch_utils import current_stream
22
23 from .BlockScaledMMLinearKernel import (
24 Fp8BlockScaledMMLinearKernel,
25 FP8ScaledMMLinearLayerConfig,
26 )
27 from .ScaledMMLinearKernel import FP8ScaledMMLinearKernel
28
29
30 def _run_b12x_fp8_block_scaled_mm(
31 a: torch.Tensor,
32 weight: torch.Tensor,
33 a_scale: torch.Tensor,
34 weight_scale: torch.Tensor,
35 out_dtype: torch.dtype,
36 ) -> torch.Tensor:
37 blockscaled = _import_b12x_blockscaled()
38 assert blockscaled is not None
39
40 return blockscaled.mm_block_fp8(
41 a,
42 a_scale,
43 weight,
44 weight_scale,
45 out_dtype=out_dtype,
46 )
47
48
49 class B12xFp8BlockScaledMMKernel(Fp8BlockScaledMMLinearKernel):
50 """K128 block-FP8 linear through the native B12X SM120 dense GEMM."""
51
52 @classmethod
53 def is_supported(
54 cls,
55 compute_capability: int | None = None,
56 ) -> tuple[bool, str | None]:
57 del compute_capability
58 if not current_platform.is_cuda():
59 return False, "B12X FP8 kernels are only available on CUDA"
60 if not current_platform.is_device_capability_family(120):
61 return False, "B12X FP8 kernels require a Blackwell 12x device"
62 blockscaled = _import_b12x_blockscaled()
63 if blockscaled is None:
64 return False, "Install the B12X backend with `pip install vllm[b12x]`"
65 if not blockscaled.is_supported():
66 return False, "B12X regular block-FP8 GEMM is not supported"
67 return True, None
68
69 @classmethod
70 def can_implement(
71 cls,
72 config: FP8ScaledMMLinearLayerConfig,
73 ) -> tuple[bool, str | None]:
74 can_implement_base, reason = super().can_implement(config)
75 if not can_implement_base:
76 return can_implement_base, reason
77
78 if config.input_dtype not in (torch.bfloat16, torch.float16):
79 return False, "Supports only bf16/fp16 input dtype"
80 if config.input_dtype != config.out_dtype:
81 return False, "Input and output dtype must match"
82
83 act_group_shape = config.activation_quant_key.scale.group_shape
84 if act_group_shape != GroupShape(1, 128):
85 return (
86 False,
87 "Supports only dynamic per-token group activation quantization "
88 "with group_shape=(1,128)",
89 )
90 weight_group_shape = config.weight_quant_key.scale.group_shape
91 if weight_group_shape != GroupShape(128, 128):
92 return False, "Supports only 128x128 block-scaled FP8 weights"
93
94 out_features, in_features = config.weight_shape
95 if in_features <= 0 or in_features % 128 != 0:
96 return False, "Input features must be a positive multiple of 128"
97 if out_features <= 0 or out_features % 128 != 0:
98 return False, "Output features must be a positive multiple of 128"
99 return True, None
100
101 def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
102 super().process_weights_after_loading(layer)
103 params = self._get_layer_params(layer)
104 if params.weight_scale_inv is not None:
105 weight_scale = params.weight_scale_inv
106 scale_attr = params.WEIGHT_SCALE_INV
107 else:
108 weight_scale = params.weight_scale
109 scale_attr = params.WEIGHT_SCALE
110 if weight_scale is not None and weight_scale.dtype in (
111 torch.float8_e8m0fnu,
112 torch.uint8,
113 ):
114 # TODO: Remove once B12X supports 128x128 UE8M0 block scales.
115 replace_parameter(
116 layer,
117 scale_attr,
118 _upcast_e8m0_to_fp32(weight_scale).contiguous(),
119 )
120 layer.b12x_warmup_provider = self
121
122 def get_b12x_warmup_unit(
123 self,
124 layer: torch.nn.Module,
125 token_counts: tuple[int, ...],
126 output_dtype: torch.dtype,
127 ) -> B12xWarmupUnit:
128 weight = layer.weight
129 weight_scale = getattr(layer, "weight_scale_inv", None)
130 if weight_scale is None:
131 weight_scale = layer.weight_scale
132 n, k = map(int, weight.shape)
133
134 def compile() -> None:
135 for tokens in token_counts:
136 a = torch.empty((tokens, k), dtype=weight.dtype, device=weight.device)
137 a_scale = torch.empty(
138 (tokens, k // 128),
139 dtype=torch.float32,
140 device=weight.device,
141 )
142 _run_b12x_fp8_block_scaled_mm(
143 a, weight, a_scale, weight_scale, output_dtype
144 )
145
146 return B12xWarmupUnit(
147 name="block-FP8",
148 key=(
149 type(self),
150 weight.device,
151 n,
152 k,
153 weight.dtype,
154 weight_scale.dtype,
155 output_dtype,
156 ),
157 compile=compile,
158 )
159
160 def apply_block_scaled_mm(
161 self,
162 A: torch.Tensor,
163 B: torch.Tensor,
164 As: torch.Tensor,
165 Bs: torch.Tensor,
166 ) -> torch.Tensor:
167 return _run_b12x_fp8_block_scaled_mm(
168 A,
169 B,
170 As,
171 Bs,
172 self.config.out_dtype,
173 )
174
175
176 def _apply_b12x_tensor_fp8_packed_linear(
177 layer: torch.nn.Module,
178 x_q: torch.Tensor,
179 bias: torch.Tensor | None,
180 out_dtype: torch.dtype,
181 ) -> torch.Tensor:
182 packed_weight = layer.b12x_tensor_fp8_packed_weight
183
184 tensor_fp8 = _import_b12x_tensor_fp8()
185 assert tensor_fp8 is not None
186
187 input_2d = x_q.reshape(-1, x_q.shape[-1]).contiguous()
188 output_shape = [*x_q.shape[:-1], int(packed_weight.out_features)]
189 output = tensor_fp8.mm(
190 input_2d,
191 packed_weight,
192 bias=bias,
193 out_dtype=out_dtype,
194 expected_m=max(1, int(input_2d.shape[0])),
195 )
196 return output.view(*output_shape)
197
198
199 class B12xTensorFP8ScaledMMLinearKernel(FP8ScaledMMLinearKernel):
200 """Static per-tensor FP8 linear through the B12X SM12x dense GEMM."""
201
202 @classmethod
203 def is_supported(
204 cls,
205 compute_capability: int | None = None,
206 ) -> tuple[bool, str | None]:
207 del compute_capability
208 if not current_platform.is_cuda():
209 return False, "b12x tensor FP8 kernels are only available on CUDA"
210 if not current_platform.is_device_capability_family(120):
211 return False, "b12x tensor FP8 kernels require a Blackwell 12x device"
212 tensor_fp8 = _import_b12x_tensor_fp8()
213 if tensor_fp8 is None:
214 return False, "Install the B12X backend with `pip install vllm[b12x]`"
215 if not tensor_fp8.is_supported():
216 return False, "b12x.gemm.tensor_fp8_linear is not supported"
217 return True, None
218
219 @classmethod
220 def can_implement(
221 cls,
222 config: FP8ScaledMMLinearLayerConfig,
223 ) -> tuple[bool, str | None]:
224 activation_scale = config.activation_quant_key.scale
225 weight_scale = config.weight_quant_key.scale
226 if (
227 not activation_scale.static
228 or not activation_scale.group_shape.is_per_tensor()
229 ):
230 return False, "requires static per-tensor activation scales"
231 if not weight_scale.static or not weight_scale.group_shape.is_per_tensor():
232 return False, "requires static per-tensor weight scales"
233 if config.input_dtype not in (torch.bfloat16, torch.float16):
234 return False, "supports only bf16/fp16 input dtype"
235 if config.out_dtype not in (torch.bfloat16, torch.float16):
236 return False, "supports only bf16/fp16 output dtype"
237 out_features, in_features = config.weight_shape
238 if out_features <= 0 or in_features <= 0 or in_features % 32 != 0:
239 return False, "weight dimensions must be positive with K divisible by 32"
240 return True, None
241
242 def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
243 weight, weight_scale, input_scale, _ = self._get_layer_params(layer)
244 assert weight.dtype == torch.float8_e4m3fn
245 assert input_scale is not None
246 assert weight_scale.numel() == input_scale.numel() == 1
247
248 out_features, in_features = map(int, self.config.weight_shape)
249 assert tuple(weight.shape) == (in_features, out_features)
250
251 tensor_fp8 = _import_b12x_tensor_fp8()
252 assert tensor_fp8 is not None
253 output_scale = (
254 input_scale.detach().to(torch.float32).reshape(1)
255 * weight_scale.detach().to(torch.float32).reshape(1)
256 ).contiguous()
257 packed_weight = tensor_fp8.pack_weight(
258 weight.detach().T.contiguous(),
259 output_scale,
260 )
261 layer.b12x_tensor_fp8_packed_weight = reuse_packed_weight_storage(
262 getattr(layer, "b12x_tensor_fp8_packed_weight", None),
263 packed_weight,
264 )
265 weight_name, weight_scale_name, _, _ = self.layer_param_names
266 replace_parameter(layer, weight_name, weight.new_empty((0,)))
267 replace_parameter(layer, weight_scale_name, weight_scale.new_empty((0,)))
268 layer.b12x_warmup_provider = self
269
270 def get_b12x_warmup_unit(
271 self,
272 layer: torch.nn.Module,
273 token_counts: tuple[int, ...],
274 output_dtype: torch.dtype,
275 ) -> B12xWarmupUnit:
276 packed_weight = layer.b12x_tensor_fp8_packed_weight
277 device = torch.device(packed_weight.values.device)
278
279 def compile() -> None:
280 tensor_fp8 = _import_b12x_tensor_fp8()
281 assert tensor_fp8 is not None
282 tensor_fp8.prewarm(
283 packed_weight,
284 token_counts,
285 out_dtype=output_dtype,
286 stream=current_stream().cuda_stream,
287 )
288
289 return B12xWarmupUnit(
290 name="tensor FP8",
291 key=(
292 type(self),
293 device,
294 int(packed_weight.in_features),
295 int(packed_weight.padded_in_features),
296 int(packed_weight.out_features),
297 output_dtype,
298 ),
299 compile=compile,
300 )
301
302 def apply_weights(
303 self,
304 layer: torch.nn.Module,
305 x: torch.Tensor,
306 bias: torch.Tensor | None = None,
307 ) -> torch.Tensor:
308 assert isinstance(x, torch.Tensor)
309 _, _, input_scale, input_scale_ub = self._get_layer_params(layer)
310 input_2d = x.reshape(-1, x.shape[-1])
311 x_q, _ = self.quant_fp8(input_2d, input_scale, input_scale_ub)
312 out_dtype = self.config.out_dtype
313 output = _apply_b12x_tensor_fp8_packed_linear(
314 layer,
315 x_q,
316 bias,
317 out_dtype,
318 )
319 return output.view(*x.shape[:-1], output.shape[-1])
320
321 def apply_scaled_mm(
322 self,
323 *,
324 A: torch.Tensor,
325 B: torch.Tensor,
326 out_dtype: torch.dtype,
327 As: torch.Tensor,
328 Bs: torch.Tensor,
329 bias: torch.Tensor | None,
330 output_shape: list,
331 ) -> torch.Tensor:
332 del A, B, out_dtype, As, Bs, bias, output_shape
333 raise NotImplementedError("b12x tensor FP8 linear overrides apply_weights")
334
335
336 __all__ = [
337 "B12xFp8BlockScaledMMKernel",
338 "B12xTensorFP8ScaledMMLinearKernel",
339 ]