main
py 551 lines 18 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 from abc import ABC, abstractmethod
4 from typing import Any
5
6 import torch
7 from torch._higher_order_ops import auto_functionalized
8 from torch._ops import OpOverload
9
10 from vllm._aiter_ops import rocm_aiter_ops
11 from vllm.config import get_current_vllm_config
12 from vllm.model_executor.layers.activation import SiluAndMul
13 from vllm.model_executor.layers.layernorm import RMSNormGated
14 from vllm.model_executor.layers.quantization.input_quant_fp8 import QuantFP8
15 from vllm.model_executor.layers.quantization.utils.quant_utils import (
16 GroupShape,
17 QuantKey,
18 _normalize_quant_group_shape,
19 kFp8Dynamic64Sym,
20 kFp8Dynamic128Sym,
21 kFp8DynamicTensorSym,
22 kFp8DynamicTokenSym,
23 kFp8StaticTensorSym,
24 kNvfp4Dynamic,
25 )
26 from vllm.model_executor.layers.rotary_embedding import RotaryEmbedding
27 from vllm.model_executor.layers.rotary_embedding.deepseek_scaling_rope import (
28 DeepseekScalingRotaryEmbedding,
29 )
30 from vllm.platforms import current_platform
31
32 ROTARY_OP = torch.ops._C.rotary_embedding.default
33 FLASHINFER_ROTARY_OP = torch.ops.vllm.flashinfer_rotary_embedding.default
34 MROPE_OP = torch.ops.vllm.mrope.default
35
36 QUANT_OPS: dict[QuantKey, OpOverload] = {
37 kFp8StaticTensorSym: torch.ops._C.static_scaled_fp8_quant.default, # noqa: E501
38 kFp8DynamicTensorSym: torch.ops._C.dynamic_scaled_fp8_quant.default, # noqa: E501
39 kFp8DynamicTokenSym: torch.ops._C.dynamic_per_token_scaled_fp8_quant.default, # noqa: E501
40 }
41
42 if hasattr(torch.ops._C, "per_token_group_fp8_quant"):
43 QUANT_OPS[kFp8Dynamic128Sym] = torch.ops._C.per_token_group_fp8_quant.default # noqa: E501
44 QUANT_OPS[kFp8Dynamic64Sym] = torch.ops._C.per_token_group_fp8_quant.default # noqa: E501
45
46 if current_platform.is_cuda() and hasattr(torch.ops._C, "scaled_fp4_quant"):
47 QUANT_OPS[kNvfp4Dynamic] = torch.ops._C.scaled_fp4_quant.out # noqa: E501
48
49
50 SILU_MUL_OP = torch.ops._C.silu_and_mul.default
51
52
53 class MatcherCustomOp(ABC):
54 def __init__(self, enabled: bool) -> None:
55 config = get_current_vllm_config()
56 self.model_dtype = config.model_config.dtype if config.model_config else None
57 self.device = config.device_config.device if config.device_config else None
58
59 self.enabled = enabled
60 self.forward = self.forward_custom if enabled else self.forward_native
61
62 @abstractmethod
63 def forward_custom(self, *args: Any, **kwargs: Any) -> Any:
64 pass
65
66 @abstractmethod
67 def forward_native(self, *args: Any, **kwargs: Any) -> Any:
68 pass
69
70 def __call__(self, *args: Any, **kwargs: Any) -> Any:
71 return self.forward(*args, **kwargs)
72
73 def empty(self, *args: Any, **kwargs: Any) -> torch.Tensor:
74 return torch.empty(*args, dtype=self.model_dtype, device=self.device, **kwargs)
75
76 def empty_int64(self, *args: Any, **kwargs: Any) -> torch.Tensor:
77 return torch.empty(*args, dtype=torch.int64, device=self.device, **kwargs)
78
79 def empty_f32(self, *args: Any, **kwargs: Any) -> torch.Tensor:
80 return torch.empty(*args, dtype=torch.float32, device=self.device, **kwargs)
81
82 def inputs(self) -> list[torch.Tensor]:
83 """Utility for inputs to the pattern."""
84 raise NotImplementedError
85
86
87 class MatcherRotaryEmbedding(MatcherCustomOp):
88 def __init__(
89 self,
90 is_neox: bool,
91 head_size: int,
92 num_heads: int,
93 num_kv_heads: int,
94 use_flashinfer: bool = False,
95 match_rocm_aiter: bool | None = None,
96 enabled: bool | None = None,
97 ) -> None:
98 if enabled is None:
99 enabled = RotaryEmbedding.enabled()
100 if match_rocm_aiter is None:
101 match_rocm_aiter = rocm_aiter_ops.is_triton_rotary_embed_enabled()
102
103 super().__init__(enabled)
104 self.is_neox = is_neox
105 self.head_size = head_size
106 self.num_heads = num_heads
107 self.num_kv_heads = num_kv_heads
108 self.q_size = self.num_heads * self.head_size
109 self.kv_size = self.num_kv_heads * self.head_size
110 self.rotary_dim = head_size
111 if use_flashinfer:
112 self.rotary_op = FLASHINFER_ROTARY_OP
113 elif match_rocm_aiter:
114 self.rotary_op = rocm_aiter_ops.get_triton_rotary_embedding_op()
115 else:
116 self.rotary_op = ROTARY_OP
117
118 def inputs(self) -> list[torch.Tensor]:
119 positions = self.empty_int64(5)
120 query = self.empty(5, self.q_size)
121 key = self.empty(5, self.kv_size)
122 cos_sin_cache = self.empty(4096, self.rotary_dim)
123 return [positions, query, key, cos_sin_cache]
124
125 def forward_custom(
126 self,
127 positions: torch.Tensor,
128 query: torch.Tensor,
129 key: torch.Tensor | None,
130 cos_sin_cache: torch.Tensor,
131 ) -> tuple[torch.Tensor, torch.Tensor | None]:
132 result = auto_functionalized(
133 self.rotary_op,
134 positions=positions,
135 query=query,
136 key=key,
137 head_size=self.head_size,
138 cos_sin_cache=cos_sin_cache,
139 is_neox=self.is_neox,
140 )
141 query_out = result[1]
142 key_out = result[2] if len(result) > 2 else None
143 return query_out, key_out
144
145 def forward_native(
146 self,
147 positions: torch.Tensor,
148 query: torch.Tensor,
149 key: torch.Tensor | None,
150 cos_sin_cache: torch.Tensor,
151 ) -> tuple[torch.Tensor, torch.Tensor | None]:
152 result: tuple[torch.Tensor, torch.Tensor | None] = (
153 RotaryEmbedding.forward_static(
154 positions,
155 query,
156 key,
157 self.head_size,
158 self.rotary_dim,
159 cos_sin_cache,
160 self.is_neox,
161 )
162 )
163 return result
164
165
166 class MatcherMRotaryEmbedding(MatcherCustomOp):
167 def __init__(
168 self,
169 is_neox: bool,
170 head_size: int,
171 rotary_dim: int,
172 num_heads: int,
173 num_kv_heads: int,
174 mrope_section: tuple[int, int, int],
175 mrope_interleaved: bool,
176 enabled: bool | None = None,
177 ) -> None:
178 if enabled is None:
179 enabled = RotaryEmbedding.enabled()
180
181 super().__init__(enabled)
182 self.is_neox = is_neox
183 self.head_size = head_size
184 self.rotary_dim = rotary_dim
185 self.num_heads = num_heads
186 self.num_kv_heads = num_kv_heads
187 self.q_size = self.num_heads * self.head_size
188 self.kv_size = self.num_kv_heads * self.head_size
189 self.mrope_section = mrope_section
190 self.mrope_interleaved = mrope_interleaved
191
192 def inputs(self) -> list[torch.Tensor]:
193 positions = self.empty_int64(3, 5)
194 query = self.empty(5, self.q_size)
195 key = self.empty(5, self.kv_size)
196 cos_sin_cache = self.empty(4096, self.rotary_dim)
197 return [positions, query, key, cos_sin_cache]
198
199 def forward_custom(
200 self,
201 positions: torch.Tensor,
202 query: torch.Tensor,
203 key: torch.Tensor,
204 cos_sin_cache: torch.Tensor,
205 ) -> tuple[torch.Tensor, torch.Tensor]:
206 result = auto_functionalized(
207 MROPE_OP,
208 positions=positions,
209 query=query,
210 key=key,
211 cos_sin_cache=cos_sin_cache,
212 head_size=self.head_size,
213 rotary_dim=self.rotary_dim,
214 mrope_section_t=self.mrope_section[0],
215 mrope_section_h=self.mrope_section[1],
216 mrope_section_w=self.mrope_section[2],
217 mrope_interleaved=self.mrope_interleaved,
218 is_neox_style=self.is_neox,
219 )
220 return result[1], result[2]
221
222 def forward_native(
223 self,
224 positions: torch.Tensor,
225 query: torch.Tensor,
226 key: torch.Tensor,
227 cos_sin_cache: torch.Tensor,
228 ) -> tuple[torch.Tensor, torch.Tensor]:
229 raise NotImplementedError(
230 "MRoPE fusion requires the rotary_embedding custom op"
231 )
232
233
234 class MatcherRMSNormGated(MatcherCustomOp):
235 """Matches RMSNormGated with norm_before_gate=True and group_size=None."""
236
237 def __init__(
238 self,
239 epsilon: float,
240 enabled: bool | None = None,
241 norm_before_gate: bool = True,
242 group_size: int | None = None,
243 ) -> None:
244 if enabled is None:
245 enabled = RMSNormGated.enabled()
246
247 super().__init__(enabled)
248 self.epsilon = epsilon
249 self.norm_before_gate = norm_before_gate
250 self.group_size = group_size
251
252 def inputs(self) -> list[torch.Tensor]:
253 x = self.empty(5, 16)
254 z = self.empty(5, 16)
255 weight = self.empty(16)
256 return [x, z, weight]
257
258 def forward_custom(
259 self,
260 x: torch.Tensor,
261 z: torch.Tensor,
262 weight: torch.Tensor,
263 ) -> torch.Tensor:
264 from vllm.third_party.flash_linear_attention.ops.layernorm_guard import (
265 rmsnorm_fn,
266 )
267
268 return rmsnorm_fn(
269 x,
270 weight,
271 bias=None,
272 z=z,
273 eps=self.epsilon,
274 group_size=self.group_size,
275 norm_before_gate=self.norm_before_gate,
276 )
277
278 def forward_native(
279 self,
280 x: torch.Tensor,
281 z: torch.Tensor,
282 weight: torch.Tensor,
283 ) -> torch.Tensor:
284 return RMSNormGated.forward_static(
285 x,
286 z,
287 weight,
288 self.epsilon,
289 self.model_dtype,
290 group_size=self.group_size,
291 norm_before_gate=self.norm_before_gate,
292 )
293
294
295 class MatcherDeepseekScalingRotaryEmbedding(MatcherCustomOp):
296 def __init__(
297 self,
298 is_neox: bool,
299 head_size: int,
300 num_heads: int,
301 num_kv_heads: int,
302 use_flashinfer: bool = False,
303 enabled: bool | None = None,
304 ) -> None:
305 if enabled is None:
306 enabled = DeepseekScalingRotaryEmbedding.enabled()
307
308 super().__init__(enabled)
309 self.is_neox = is_neox
310 self.head_size = head_size
311 self.num_heads = num_heads
312 self.num_kv_heads = num_kv_heads
313 self.q_size = self.num_heads * self.head_size
314 self.kv_size = self.num_kv_heads * self.head_size
315 self.rotary_dim = head_size
316 self.use_flashinfer = use_flashinfer
317
318 def inputs(self) -> list[torch.Tensor]:
319 positions = self.empty_int64(5)
320 query = self.empty(5, self.num_heads, self.head_size)
321 key = self.empty(5, self.num_kv_heads, self.head_size)
322 cos_sin_cache = self.empty(4096, self.rotary_dim)
323 return [positions, query, key, cos_sin_cache]
324
325 def forward_custom(
326 self,
327 positions: torch.Tensor,
328 query: torch.Tensor,
329 key: torch.Tensor | None,
330 cos_sin_cache: torch.Tensor,
331 ) -> tuple[torch.Tensor, torch.Tensor | None]:
332 if self.use_flashinfer:
333 torch.ops.vllm.flashinfer_rotary_embedding(
334 positions,
335 query,
336 key,
337 self.head_size,
338 cos_sin_cache,
339 self.is_neox,
340 )
341 return query, key
342 result: tuple[torch.Tensor, torch.Tensor | None] = (
343 DeepseekScalingRotaryEmbedding.forward_static(
344 positions,
345 query,
346 key,
347 self.head_size,
348 self.rotary_dim,
349 cos_sin_cache,
350 self.is_neox,
351 )
352 )
353 return result
354
355 def forward_native(
356 self,
357 positions: torch.Tensor,
358 query: torch.Tensor,
359 key: torch.Tensor | None,
360 cos_sin_cache: torch.Tensor,
361 ) -> tuple[torch.Tensor, torch.Tensor | None]:
362 result: tuple[torch.Tensor, torch.Tensor | None] = (
363 DeepseekScalingRotaryEmbedding.forward_static(
364 positions,
365 query,
366 key,
367 self.head_size,
368 self.rotary_dim,
369 cos_sin_cache,
370 self.is_neox,
371 )
372 )
373 return result
374
375
376 class MatcherQuantFP8(MatcherCustomOp):
377 def __init__(
378 self,
379 quant_key: QuantKey,
380 enabled: bool | None = None,
381 has_col_major_scales: bool = False,
382 is_e8m0: bool = False,
383 match_rocm_aiter: bool = False,
384 is_tma_aligned: bool = False,
385 ) -> None:
386 if enabled is None:
387 enabled = QuantFP8.enabled()
388
389 super().__init__(enabled)
390 self.quant_key = quant_key
391 self.has_col_major_scales = has_col_major_scales
392 self.is_e8m0 = is_e8m0
393 self.match_rocm_aiter = match_rocm_aiter
394 self.is_tma_aligned = is_tma_aligned
395
396 if match_rocm_aiter:
397 assert not quant_key.scale.group_shape.is_per_tensor(), (
398 "ROCm aiter fusion pass does not support per tensor quantization"
399 )
400 if quant_key.scale.group_shape.is_per_token():
401 self.QUANT_OP = rocm_aiter_ops.get_per_token_quant_op()
402 else:
403 assert quant_key.scale.group_shape.col == 128, (
404 "ROCm aiter fusion pass currently supports "
405 "quantization operation with group_size 128"
406 )
407 self.QUANT_OP = rocm_aiter_ops.get_group_quant_op()
408
409 else:
410 assert quant_key in QUANT_OPS, (
411 f"unsupported quantization scheme {quant_key}"
412 )
413 self.QUANT_OP = QUANT_OPS[quant_key]
414
415 assert quant_key.dtype == current_platform.fp8_dtype(), (
416 "Only QuantFP8 supported by"
417 )
418 assert quant_key.scale2 is None
419
420 self.quant_fp8 = QuantFP8(
421 quant_key.scale.static,
422 quant_key.scale.group_shape,
423 column_major_scales=has_col_major_scales,
424 use_ue8m0=is_e8m0,
425 tma_aligned_scales=self.is_tma_aligned,
426 compile_native=False,
427 )
428
429 def forward_rocm_aiter(
430 self,
431 input: torch.Tensor,
432 scale: torch.Tensor | None = None,
433 ) -> tuple[torch.Tensor, torch.Tensor]:
434 quant_key_group_shape = self.quant_key.scale.group_shape
435 if quant_key_group_shape == GroupShape.PER_TOKEN:
436 return self.QUANT_OP( # type: ignore[no-any-return]
437 x=input,
438 quant_dtype=self.quant_key.dtype,
439 scale=scale,
440 )
441 else:
442 return self.QUANT_OP(input, quant_key_group_shape.col) # type: ignore[no-any-return]
443
444 def forward_custom(
445 self,
446 input: torch.Tensor,
447 scale: torch.Tensor | None = None,
448 ) -> tuple[torch.Tensor, torch.Tensor]:
449 if self.match_rocm_aiter:
450 return self.forward_rocm_aiter(input, scale)
451
452 result = torch.empty(
453 input.shape, device=input.device, dtype=self.quant_key.dtype
454 )
455
456 if self.quant_key.scale.group_shape.is_per_group():
457 # for tma_aligned, the scale must be passed to forward_custom
458 # tma_aligned fusion then matches by custom op arguments
459 if not self.is_tma_aligned:
460 assert scale is None
461 scale = self.make_scale(input, transposed=self.has_col_major_scales)
462
463 finfo = torch.finfo(self.quant_key.dtype)
464 fp8_min = finfo.min
465 fp8_max = finfo.max
466
467 _, result, scale = auto_functionalized(
468 self.QUANT_OP,
469 input=input,
470 output_q=result,
471 output_s=scale,
472 group_size=self.quant_key.scale.group_shape[1],
473 eps=1e-10,
474 fp8_min=fp8_min,
475 fp8_max=fp8_max,
476 scale_ue8m0=self.is_e8m0,
477 dummy_is_scale_transposed=self.has_col_major_scales,
478 dummy_is_tma_aligned=self.is_tma_aligned,
479 )
480 return result, scale
481
482 if self.quant_key.scale.static:
483 assert scale is not None
484 _, result = auto_functionalized(
485 self.QUANT_OP, result=result, input=input, scale=scale
486 )
487 return result, scale
488 else:
489 assert scale is None
490 scale = self.make_scale(input)
491 _, result, scale = auto_functionalized(
492 self.QUANT_OP, result=result, input=input, scale=scale, scale_ub=None
493 )
494 return result, scale
495
496 def forward_native(
497 self,
498 input: torch.Tensor,
499 scale: torch.Tensor | None = None,
500 ) -> tuple[torch.Tensor, torch.Tensor]:
501 return self.quant_fp8(input, scale) # type: ignore[no-any-return]
502
503 def make_scale(self, input: torch.Tensor, transposed: bool = False) -> torch.Tensor:
504 normalized_group_shape = _normalize_quant_group_shape(
505 input, self.quant_key.scale.group_shape
506 )
507 scale_shape = (
508 input.shape[0] // normalized_group_shape[0],
509 input.shape[1] // normalized_group_shape[1],
510 )
511 if transposed:
512 scale_shape = tuple(reversed(scale_shape))
513 return torch.empty(
514 scale_shape, device=input.device, dtype=torch.float32
515 ).permute(-1, -2)
516
517 return torch.empty(scale_shape, device=input.device, dtype=torch.float32)
518
519 def inputs(self) -> list[torch.Tensor]:
520 input = self.empty(5, 16)
521 if self.quant_key.scale.static:
522 return [input, self.empty_f32(1, 1)]
523
524 return [input]
525
526
527 class MatcherSiluAndMul(MatcherCustomOp):
528 def __init__(self, enabled: bool | None = None) -> None:
529 if enabled is None:
530 enabled = SiluAndMul.enabled()
531 super().__init__(enabled)
532
533 def inputs(self) -> list[torch.Tensor]:
534 input = self.empty(5, 4)
535 return [input]
536
537 def forward_custom(
538 self,
539 x: torch.Tensor,
540 ) -> torch.Tensor:
541 d = x.shape[-1] // 2
542 output_shape = x.shape[:-1] + (d,)
543 out = torch.empty(output_shape, dtype=x.dtype, device=x.device)
544 result = auto_functionalized(SILU_MUL_OP, result=out, input=x)
545 return result[1]
546
547 def forward_native(
548 self,
549 x: torch.Tensor,
550 ) -> torch.Tensor:
551 return SiluAndMul.forward_native(x)