Spaces:
Running on Zero
Running on Zero
| from __future__ import annotations | |
| import itertools | |
| import math | |
| from contextlib import nullcontext | |
| from typing import Any | |
| import torch | |
| import torch.nn as nn | |
| from ...utils import is_accelerate_available, is_kernels_available | |
| if is_accelerate_available(): | |
| from accelerate import init_empty_weights | |
| _HF_KERNEL_REPO = "rootonchair/nunchaku-lite-kernels" | |
| _HF_KERNEL_VERSION = 2 | |
| if is_kernels_available(): | |
| from kernels import get_kernel | |
| ops = get_kernel(_HF_KERNEL_REPO, version=_HF_KERNEL_VERSION, trust_remote_code=True).ops | |
| else: | |
| raise ImportError( | |
| "Loading Nunchaku checkpoints requires the Hugging Face `kernels` package. " | |
| "Install it with `pip install kernels`." | |
| ) | |
| def _gemm_w4a4( | |
| act: torch.Tensor, | |
| wgt: torch.Tensor, | |
| out: torch.Tensor, | |
| ascales: torch.Tensor, | |
| wscales: torch.Tensor, | |
| lora_act_in: torch.Tensor, | |
| lora_up: torch.Tensor, | |
| bias: torch.Tensor | None, | |
| act_unsigned: bool, | |
| lora_scales: list[float], | |
| nvfp4: bool, | |
| alpha: torch.Tensor | None, | |
| wcscales: torch.Tensor | None, | |
| ) -> None: | |
| ops.gemm_w4a4( | |
| act, | |
| wgt, | |
| out, | |
| None, | |
| ascales, | |
| wscales, | |
| None, | |
| None, | |
| lora_act_in, | |
| lora_up, | |
| None, | |
| None, | |
| None, | |
| None, | |
| None, | |
| bias, | |
| None, | |
| None, | |
| None, | |
| act_unsigned, | |
| lora_scales, | |
| False, | |
| nvfp4, | |
| alpha, | |
| wcscales, | |
| None, | |
| None, | |
| None, | |
| 0, | |
| ) | |
| def replace_with_nunchaku_linear( | |
| model: nn.Module, quantization_config: dict[str, Any], compute_dtype: torch.dtype | |
| ) -> int: | |
| num_replaced = 0 | |
| svdq_config = quantization_config.get("svdq_w4a4") | |
| awq_config = quantization_config.get("awq_w4a16") | |
| if svdq_config is not None: | |
| num_replaced += _replace_quantize_targets(model, "svdq_w4a4", svdq_config, compute_dtype) | |
| if awq_config is not None: | |
| num_replaced += _replace_quantize_targets(model, "awq_w4a16", awq_config, compute_dtype) | |
| if num_replaced == 0: | |
| raise ValueError( | |
| "Nunchaku compact quantization config must include `svdq_w4a4.targets` or `awq_w4a16.targets`." | |
| ) | |
| return num_replaced | |
| class SVDQW4A4Linear(nn.Module): | |
| def __init__( | |
| self, | |
| in_features: int, | |
| out_features: int, | |
| rank: int = 32, | |
| bias: bool = True, | |
| precision: str = "int4", | |
| group_size: int = 64, | |
| torch_dtype: torch.dtype = torch.bfloat16, | |
| device: str | torch.device | None = None, | |
| act_unsigned: bool = False, | |
| ): | |
| super().__init__() | |
| self.in_features = in_features | |
| self.out_features = out_features | |
| self.rank = rank | |
| self.precision = precision | |
| self.group_size = group_size | |
| self.torch_dtype = torch_dtype | |
| self.act_unsigned = act_unsigned | |
| self.qweight = nn.Parameter( | |
| torch.empty(out_features, in_features // 2, dtype=torch.int8, device=device), requires_grad=False | |
| ) | |
| self.bias = ( | |
| nn.Parameter(torch.empty(out_features, dtype=torch_dtype, device=device), requires_grad=False) | |
| if bias | |
| else None | |
| ) | |
| self.wscales = nn.Parameter( | |
| torch.empty( | |
| in_features // group_size, | |
| out_features, | |
| dtype=torch_dtype if precision == "int4" else torch.float8_e4m3fn, | |
| device=device, | |
| ), | |
| requires_grad=False, | |
| ) | |
| self.smooth_factor = nn.Parameter( | |
| torch.empty(in_features, dtype=torch_dtype, device=device), requires_grad=False | |
| ) | |
| self.proj_down = nn.Parameter( | |
| torch.empty(in_features, rank, dtype=torch_dtype, device=device), requires_grad=False | |
| ) | |
| self.proj_up = nn.Parameter( | |
| torch.empty(out_features, rank, dtype=torch_dtype, device=device), requires_grad=False | |
| ) | |
| if precision == "nvfp4": | |
| self.wcscales = nn.Parameter( | |
| torch.ones(out_features, dtype=torch_dtype, device=device), requires_grad=False | |
| ) | |
| self.wtscale = nn.Parameter(torch.ones(1, dtype=torch_dtype, device=device), requires_grad=False) | |
| else: | |
| self.wcscales = None | |
| self.wtscale = None | |
| def forward(self, x: torch.Tensor, output: torch.Tensor | None = None) -> torch.Tensor: | |
| original_shape = x.shape | |
| channels = x.shape[-1] | |
| x = x.reshape(-1, channels) | |
| rows = x.shape[0] | |
| if output is None: | |
| output = torch.empty(rows, self.out_features, dtype=self.torch_dtype, device=x.device) | |
| pad_size = 256 | |
| batch_size_pad = math.ceil(x.shape[0] / pad_size) * pad_size | |
| quantized_x = torch.empty(batch_size_pad, channels // 2, dtype=torch.uint8, device=x.device) | |
| if self.precision == "nvfp4": | |
| ascales = torch.empty(channels // 16, batch_size_pad, dtype=torch.float8_e4m3fn, device=x.device) | |
| else: | |
| ascales = torch.empty(channels // 64, batch_size_pad, dtype=x.dtype, device=x.device) | |
| lora_act = torch.empty(batch_size_pad, self.rank, dtype=torch.float32, device=x.device) | |
| ops.quantize_w4a4_act_fuse_lora( | |
| x, | |
| quantized_x, | |
| ascales, | |
| self.proj_down, | |
| lora_act, | |
| self.smooth_factor, | |
| False, | |
| self.precision == "nvfp4", | |
| ) | |
| lora_scales = [1.0] * math.ceil(self.rank / 16) | |
| _gemm_w4a4( | |
| quantized_x, | |
| self.qweight, | |
| output, | |
| ascales, | |
| self.wscales, | |
| lora_act, | |
| self.proj_up, | |
| self.bias, | |
| self.act_unsigned, | |
| lora_scales, | |
| self.precision == "nvfp4", | |
| self.wtscale, | |
| self.wcscales, | |
| ) | |
| return output.reshape(*original_shape[:-1], self.out_features) | |
| class AWQW4A16Linear(nn.Module): | |
| def __init__( | |
| self, | |
| in_features: int, | |
| out_features: int, | |
| bias: bool = True, | |
| group_size: int = 64, | |
| torch_dtype: torch.dtype = torch.bfloat16, | |
| device: str | torch.device | None = None, | |
| ): | |
| super().__init__() | |
| self.in_features = in_features | |
| self.out_features = out_features | |
| self.group_size = group_size | |
| self.qweight = nn.Parameter( | |
| torch.empty(out_features // 4, in_features // 2, dtype=torch.int32, device=device), requires_grad=False | |
| ) | |
| self.bias = ( | |
| nn.Parameter(torch.empty(out_features, dtype=torch_dtype, device=device), requires_grad=False) | |
| if bias | |
| else None | |
| ) | |
| self.wscales = nn.Parameter( | |
| torch.empty(in_features // group_size, out_features, dtype=torch_dtype, device=device), requires_grad=False | |
| ) | |
| self.wzeros = nn.Parameter( | |
| torch.empty(in_features // group_size, out_features, dtype=torch_dtype, device=device), requires_grad=False | |
| ) | |
| def forward(self, x: torch.Tensor) -> torch.Tensor: | |
| if x.shape[-1] != self.in_features: | |
| raise ValueError( | |
| f"AWQW4A16Linear expected input last dimension {self.in_features}, got shape {tuple(x.shape)}." | |
| ) | |
| output_shape = (*x.shape[:-1], self.out_features) | |
| x_flat = x.reshape(-1, self.in_features).contiguous() | |
| if x_flat.shape[0] == 0: | |
| output = x.new_empty(output_shape) | |
| elif self._use_gemm(x_flat.shape[0]): | |
| output = ops.awq_gemm_w4a16_g64_int32(x_flat, self.qweight, self.wscales, self.wzeros).reshape( | |
| output_shape | |
| ) | |
| else: | |
| output = self._forward_gemv_chunks(x_flat, ops.gemv_awq).reshape(output_shape) | |
| if self.bias is not None: | |
| output = output + self.bias.view([1] * (output.ndim - 1) + [-1]) | |
| return output | |
| def _use_gemm(self, rows: int) -> bool: | |
| return rows >= 16 and self.in_features % 64 == 0 and self.out_features % 128 == 0 | |
| def _forward_gemv_chunks(self, x_flat: torch.Tensor, gemv) -> torch.Tensor: | |
| outputs = [] | |
| for start in range(0, x_flat.shape[0], 8): | |
| chunk = x_flat[start : start + 8] | |
| outputs.append( | |
| gemv( | |
| chunk, | |
| self.qweight, | |
| self.wscales, | |
| self.wzeros, | |
| chunk.shape[0], | |
| self.out_features, | |
| self.in_features, | |
| 64, | |
| ) | |
| ) | |
| return torch.cat(outputs, dim=0) | |
| def _replace_quantize_targets(model: nn.Module, op: str, raw: Any, compute_dtype: torch.dtype) -> int: | |
| precision = raw["precision"] | |
| group_size = raw["group_size"] | |
| targets = raw["targets"] | |
| rank = raw["rank"] if op == "svdq_w4a4" else 0 | |
| for target in targets: | |
| try: | |
| module = model.get_submodule(target) | |
| except AttributeError as exc: | |
| raise ValueError(f"Nunchaku target {target!r} does not exist in the model.") from exc | |
| in_features = getattr(module, "in_features", None) | |
| out_features = getattr(module, "out_features", None) | |
| bias = getattr(module, "bias", None) | |
| if not isinstance(in_features, int) or not isinstance(out_features, int): | |
| raise TypeError(f"Nunchaku target {target!r} must expose integer in_features/out_features.") | |
| ctx = init_empty_weights if is_accelerate_available() else nullcontext | |
| with ctx(): | |
| if op == "svdq_w4a4": | |
| replacement = SVDQW4A4Linear( | |
| in_features, | |
| out_features, | |
| rank=rank, | |
| bias=bias is not None, | |
| precision=precision, | |
| group_size=group_size, | |
| torch_dtype=compute_dtype, | |
| ) | |
| elif op == "awq_w4a16": | |
| replacement = AWQW4A16Linear( | |
| in_features, | |
| out_features, | |
| bias=bias is not None, | |
| group_size=group_size, | |
| torch_dtype=compute_dtype, | |
| ) | |
| _set_submodule(model, target, replacement) | |
| return len(targets) | |
| def _set_submodule(model: nn.Module, path: str, module: nn.Module) -> None: | |
| parent_path, _, child_name = path.rpartition(".") | |
| parent = model.get_submodule(parent_path) if parent_path else model | |
| if child_name.isdigit() and isinstance(parent, (nn.Sequential, nn.ModuleList)): | |
| parent[int(child_name)] = module | |
| else: | |
| setattr(parent, child_name, module) | |
| def check_strict_state_dict_match(model: nn.Module, state_dict: dict[str, Any]) -> None: | |
| expected_keys = {n for n, _ in itertools.chain(model.named_parameters(), model.named_buffers())} | |
| loaded_keys = set(state_dict.keys()) | |
| missing_keys = sorted(expected_keys - loaded_keys) | |
| unexpected_keys = sorted(loaded_keys - expected_keys) | |
| if missing_keys or unexpected_keys: | |
| message = "Nunchaku checkpoint keys must exactly match the patched model state dict." | |
| if missing_keys: | |
| message += f" Missing keys: {missing_keys[:10]}" | |
| if len(missing_keys) > 10: | |
| message += f" and {len(missing_keys) - 10} more" | |
| message += "." | |
| if unexpected_keys: | |
| message += f" Unexpected keys: {unexpected_keys[:10]}" | |
| if len(unexpected_keys) > 10: | |
| message += f" and {len(unexpected_keys) - 10} more" | |
| message += "." | |
| raise ValueError(message) | |