176 lines
6.5 KiB
Python
176 lines
6.5 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
|
|
|
|
|
import torch
|
|
|
|
from vllm import _custom_ops as ops
|
|
from vllm.model_executor.layers.quantization.utils.quant_utils import (
|
|
pack_quantized_values_into_int32,
|
|
)
|
|
from vllm.model_executor.parameter import BasevLLMParameter, permute_param_layout_
|
|
from vllm.platforms import current_platform
|
|
from vllm.scalar_type import scalar_types
|
|
|
|
from .MPLinearKernel import MPLinearKernel, MPLinearLayerConfig
|
|
|
|
|
|
class ExllamaLinearKernel(MPLinearKernel):
|
|
SUPPORTED_QUANT_TYPES = [scalar_types.uint4b8, scalar_types.uint8b128]
|
|
# In theory supports `scalar_types.uint2b2, scalar_types.uint3b4` too but
|
|
# currently untested so not added to the list
|
|
|
|
@classmethod
|
|
def get_min_capability(cls) -> int:
|
|
return 60
|
|
|
|
@classmethod
|
|
def can_implement(cls, c: MPLinearLayerConfig) -> tuple[bool, str | None]:
|
|
if not current_platform.is_cuda_alike():
|
|
return (
|
|
False,
|
|
"Exllama is only supported on CUDA and ROCm",
|
|
)
|
|
|
|
if c.has_g_idx and c.partition_weight_shape[0] != c.full_weight_shape[0]:
|
|
return (
|
|
False,
|
|
"Act reordering currently not supported by Exllama, "
|
|
"when the input features are partitioned across "
|
|
"devices",
|
|
)
|
|
|
|
if c.partition_weight_shape[1] % (32 // c.weight_type.size_bits) != 0:
|
|
return (
|
|
False,
|
|
"Output features must be a multiple of the pack "
|
|
"factor (32 / num_bits) so that we can correctly "
|
|
"pack the zero points",
|
|
)
|
|
|
|
if c.act_type != torch.float16:
|
|
return False, "Exllama only supports float16 activations"
|
|
|
|
if c.weight_type not in cls.SUPPORTED_QUANT_TYPES:
|
|
return (
|
|
False,
|
|
f"Quant type ({c.weight_type}) not supported by "
|
|
"Exllama, supported types are: "
|
|
f"{cls.SUPPORTED_QUANT_TYPES}",
|
|
)
|
|
|
|
if c.group_size <= 0:
|
|
return (
|
|
False,
|
|
f"Group size ({c.group_size}) must be positive, "
|
|
"Exllama does not support channelwise quantization",
|
|
)
|
|
|
|
if c.full_weight_shape[0] % c.group_size != 0:
|
|
return (
|
|
False,
|
|
f"Group size ({c.group_size}) does not evenly divide"
|
|
" the number of input features "
|
|
f"({c.full_weight_shape[0]})",
|
|
)
|
|
|
|
return True, None
|
|
|
|
def process_weights_after_loading(self, layer: torch.nn.Module):
|
|
c = self.config
|
|
|
|
# For Exllama, we need to set a zero-point tensor if there is not one
|
|
if not c.zero_points:
|
|
self.w_zp_name = "qzeros"
|
|
device = getattr(layer, self.w_q_name).device
|
|
groups = c.partition_weight_shape[0] // c.group_size
|
|
out_features = c.partition_weight_shape[1]
|
|
|
|
if c.weight_type.has_bias():
|
|
# if the type has a bias we have to create a zeros tensor that
|
|
# contains the bias values repeated for each group (-1 due to
|
|
# a bug in the original GPTQ checkpoint format leading to
|
|
# exllama kernel adding 1 to the zero points during inference)
|
|
# Documentation of the bug can be found here:
|
|
# https://garden.danieldk.eu/GPTQ-Checkpoint-Format
|
|
zeros = torch.full(
|
|
(groups, out_features),
|
|
c.weight_type.bias - 1,
|
|
dtype=torch.int32,
|
|
device=device,
|
|
)
|
|
else:
|
|
raise NotImplementedError(
|
|
"A 0 zero-point is not supported by Exllama due to "
|
|
"a bug in the original GPTQ checkpoint format leading to "
|
|
"exllama kernel adding 1 to the zero points during "
|
|
"inference"
|
|
)
|
|
zeros = pack_quantized_values_into_int32(zeros, c.weight_type, packed_dim=1)
|
|
setattr(
|
|
layer, self.w_zp_name, torch.nn.Parameter(zeros, requires_grad=False)
|
|
)
|
|
|
|
if c.has_g_idx:
|
|
|
|
def transform_w_g_idx(x):
|
|
# Exllama wants the permutation array instead of the group
|
|
# indices
|
|
return torch.argsort(x).to(torch.int)
|
|
|
|
self._transform_param(layer, self.w_gidx_name, transform_w_g_idx) # type: ignore
|
|
else:
|
|
self.w_gidx_name = "g_idx"
|
|
empty_g_idx = torch.nn.Parameter(
|
|
torch.empty((0,), dtype=torch.int, device=device), requires_grad=False
|
|
)
|
|
setattr(layer, self.w_gidx_name, empty_g_idx)
|
|
|
|
def transform_w_q(x):
|
|
assert isinstance(x, BasevLLMParameter)
|
|
assert self.w_gidx_name is not None
|
|
g_idx = getattr(layer, self.w_gidx_name)
|
|
|
|
permute_param_layout_(x, input_dim=0, output_dim=1, packed_dim=0)
|
|
x_cont = x.data.contiguous()
|
|
ops.gptq_shuffle(x_cont, g_idx, c.weight_type.size_bits)
|
|
return x_cont
|
|
|
|
def transform_w_s(x):
|
|
assert isinstance(x, BasevLLMParameter)
|
|
permute_param_layout_(x, input_dim=0, output_dim=1)
|
|
x.data = x.data.contiguous()
|
|
return x.to(dtype=c.act_type)
|
|
|
|
# Repack weights and scales for Machete
|
|
self._transform_param(layer, self.w_q_name, transform_w_q)
|
|
self._transform_param(layer, self.w_s_name, transform_w_s)
|
|
|
|
def apply_weights(
|
|
self,
|
|
layer: torch.nn.Module,
|
|
x: torch.Tensor,
|
|
bias: torch.Tensor | None = None,
|
|
) -> torch.Tensor:
|
|
c = self.config
|
|
|
|
x_2d = x.reshape(-1, x.shape[-1])
|
|
out_shape = x.shape[:-1] + (c.partition_weight_shape[1],)
|
|
|
|
w_q, w_s, w_zp, w_g_idx = self._get_weight_params(layer)
|
|
|
|
# gptq_gemm supports GPTQv2 format by passing use_v2_format=True.
|
|
# However, the MPLinearLayerConfig doesn't contain format info.
|
|
# So hardcode GPTQv1 format here, to keep its behavior unchanged.
|
|
use_v2_format = False
|
|
|
|
assert w_zp is not None, "Zero points are required by Exllama"
|
|
assert w_g_idx is not None, "Group index is required by Exllama"
|
|
output = ops.gptq_gemm(
|
|
x_2d, w_q, w_zp, w_s, w_g_idx, True, use_v2_format, c.weight_type.size_bits
|
|
)
|
|
|
|
if bias is not None:
|
|
output.add_(bias)
|
|
return output.reshape(out_shape)
|