main
py 98 lines 3.44 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 """Zentorch dynamic-symmetric W8A8 int8 linear kernel for AMD Zen CPUs.
4
5 Selected by ``choose_scaled_mm_linear_kernel`` ahead of the generic
6 oneDNN-backed ``CPUInt8ScaledMMLinearKernel``. When ``is_supported`` or
7 ``can_implement`` rejects a layer, the selector falls through to the next
8 kernel in ``_POSSIBLE_INT8_KERNELS[PlatformEnum.CPU]``.
9 """
10
11 import torch
12
13 from vllm.logger import init_logger
14 from vllm.model_executor.kernels.linear.zentorch_utils import has_zentorch_op
15 from vllm.model_executor.layers.quantization.utils import replace_parameter
16 from vllm.platforms import current_platform
17
18 from .ScaledMMLinearKernel import (
19 Int8ScaledMMLinearKernel,
20 Int8ScaledMMLinearLayerConfig,
21 )
22
23 logger = init_logger(__name__)
24
25
26 class ZentorchInt8ScaledMMLinearKernel(Int8ScaledMMLinearKernel):
27 @classmethod
28 def is_supported(
29 cls, compute_capability: int | None = None
30 ) -> tuple[bool, str | None]:
31 if not current_platform.is_cpu():
32 return False, "requires CPU."
33 if not current_platform.is_zen_cpu():
34 return False, "requires AMD Zen CPU."
35 if not has_zentorch_op(["zentorch_dynamic_qlinear"]):
36 return (
37 False,
38 "torch.ops.zentorch.zentorch_dynamic_qlinear is not registered.",
39 )
40 return True, None
41
42 @classmethod
43 def can_implement(cls, c: Int8ScaledMMLinearLayerConfig) -> tuple[bool, str | None]:
44 if c.is_static_input_scheme:
45 return False, "requires dynamic activation quantization."
46 if not c.input_symmetric:
47 return False, "requires symmetric activation quantization."
48 if not c.is_channelwise:
49 return False, "requires per-channel weight quantization."
50 return True, None
51
52 def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
53 """Prepare weights for ``zentorch_dynamic_qlinear``.
54
55 Keeps weight in [N, K] layout (int8, contiguous) and converts the
56 per-channel weight scale to bf16 with shape ``(N,)``.
57 """
58 w_q_name, w_s_name, _, _, _ = self.layer_param_names
59 weight = getattr(layer, w_q_name)
60 n = weight.shape[0]
61 replace_parameter(
62 layer,
63 w_q_name,
64 torch.nn.Parameter(weight.data.contiguous(), requires_grad=False),
65 )
66
67 weight_scale = getattr(layer, w_s_name)
68 ws = weight_scale.data
69 if ws.dim() == 2 and ws.shape[-1] == 1:
70 ws = ws.squeeze(-1)
71 ws = ws.to(torch.bfloat16).contiguous()
72 assert ws.shape == (n,), (
73 f"[zen_cpu] expected weight scale shape ({n},), got {tuple(ws.shape)}"
74 )
75
76 replace_parameter(
77 layer,
78 w_s_name,
79 torch.nn.Parameter(ws, requires_grad=False),
80 )
81 logger.info_once(
82 "[zen_cpu] Using zentorch_dynamic_qlinear for W8A8 (dynamic-symmetric)"
83 )
84
85 def apply_weights(
86 self,
87 layer: torch.nn.Module,
88 x: torch.Tensor,
89 bias: torch.Tensor | None = None,
90 ) -> torch.Tensor:
91 w_q_name, w_s_name, _, _, _ = self.layer_param_names
92 return torch.ops.zentorch.zentorch_dynamic_qlinear(
93 x,
94 getattr(layer, w_q_name),
95 getattr(layer, w_s_name),
96 bias,
97 zentorch_op_name="zentorch::zentorch_dynamic_qlinear",
98 )