from __future__ import annotations from dataclasses import dataclass from typing import TYPE_CHECKING, Callable, Optional import torch from sglang.srt.layers.moe import MoeRunner, MoeRunnerBackend, get_moe_runner_backend from sglang.srt.layers.moe.moe_runner.marlin import MarlinMoeQuantInfo from sglang.srt.layers.parameter import BasevLLMParameter, permute_param_layout_ from sglang.srt.layers.quantization.marlin_utils import ( apply_gptq_marlin_linear, check_marlin_supports_shape, marlin_is_k_full, marlin_make_empty_g_idx, marlin_make_workspace, marlin_moe_permute_scales, marlin_permute_scales, marlin_sort_g_idx, marlin_zero_points, ) from sglang.srt.layers.quantization.utils import ( get_scalar_types, replace_parameter, unpack_cols, ) if TYPE_CHECKING: from sglang.srt.layers.moe import MoeRunnerConfig from sglang.srt.layers.moe.token_dispatcher import ( CombineInput, StandardDispatchOutput, ) from sglang.srt.layers.quantization.base_config import QuantizationConfig ScalarType, _ = get_scalar_types() def _unsupported_kernel(*args, **kwargs): raise RuntimeError("GPTQ CUDA kernels are unavailable on the current platform.") gptq_gemm = _unsupported_kernel gptq_marlin_repack = _unsupported_kernel gptq_shuffle = _unsupported_kernel try: from sgl_kernel import gptq_gemm, gptq_shuffle from sglang.jit_kernel.gptq_marlin_repack import gptq_marlin_repack except Exception: pass @dataclass class MarlinLinearLayerConfig: full_weight_shape: tuple[int, int] # [in, out] partition_weight_shape: tuple[int, int] weight_type: ScalarType act_type: torch.dtype group_size: int zero_points: bool has_g_idx: bool def gptq_marlin_moe_repack( b_q_weight: torch.Tensor, perm: torch.Tensor, size_k: int, size_n: int, num_bits: int, ) -> torch.Tensor: num_experts = b_q_weight.shape[0] assert size_k % 16 == 0 output = torch.empty( (num_experts, size_k // 16, size_n * (num_bits // 2)), device=b_q_weight.device, dtype=b_q_weight.dtype, ) for e in range(num_experts): output[e] = gptq_marlin_repack(b_q_weight[e], perm[e], size_k, size_n, num_bits) return output class GPTQLinearKernel: def __init__(self, quant_config: Optional[QuantizationConfig] = None): self.quant_config = quant_config self.use_shuffle = True def process_weights_after_loading(self, layer: torch.nn.Module) -> None: # for torch.compile layer.qzeros = torch.nn.Parameter(layer.qzeros.data, requires_grad=False) layer.qweight = torch.nn.Parameter(layer.qweight.data, requires_grad=False) layer.g_idx = torch.nn.Parameter(layer.g_idx.data, requires_grad=False) layer.scales = torch.nn.Parameter(layer.scales.data, requires_grad=False) # exllama needs to shuffle the weight after the weight is loaded # here we do the shuffle on first forward pass if self.use_shuffle: if self.quant_config.desc_act: layer.g_idx.data = torch.argsort(layer.g_idx).to(torch.int) else: layer.g_idx.data = torch.empty( (0,), dtype=torch.int, device=layer.g_idx.device ) gptq_shuffle(layer.qweight, layer.g_idx, self.quant_config.weight_bits) def apply( self, layer: torch.nn.Module, x: torch.Tensor, bias: Optional[torch.Tensor] = None, ) -> torch.Tensor: out_shape = x.shape[:-1] + (layer.qweight.shape[-1],) reshaped_x = x.reshape(-1, x.shape[-1]) output = gptq_gemm( reshaped_x, layer.qweight, layer.qzeros, layer.scales, layer.g_idx, self.use_shuffle, self.quant_config.weight_bits, ) if bias is not None: output.add_(bias) return output.reshape(out_shape) class GPTQMarlinLinearKernel: def __init__(self, quant_config: Optional[QuantizationConfig] = None): self.quant_config = quant_config def process_weights_after_loading(self, layer: torch.nn.Module) -> None: device = getattr(layer, "qweight").device c = self.kernel_config check_marlin_supports_shape( c.partition_weight_shape[1], # out_features c.partition_weight_shape[0], # in_features c.full_weight_shape[0], # in_features c.group_size, ) row_parallel = c.partition_weight_shape[0] != c.full_weight_shape[0] self.is_k_full = marlin_is_k_full(c.has_g_idx, row_parallel) # Allocate marlin workspace. self.workspace = marlin_make_workspace(device) # Default names since marlin requires empty parameters for these, # TODO: remove this requirement from marlin (allow optional tensors) self.w_q_name = "qweight" self.w_s_name = "scales" self.w_zp_name = "qzeros" self.w_gidx_name = "g_idx" def _transform_param( layer: torch.nn.Module, name: Optional[str], fn: Callable ) -> None: if name is not None and getattr(layer, name, None) is not None: old_param = getattr(layer, name) new_param = fn(old_param) # replace the parameter with torch.nn.Parameter for TorchDynamo # compatibility replace_parameter( layer, name, torch.nn.Parameter(new_param.data, requires_grad=False) ) def transform_w_q(x): assert isinstance(x, BasevLLMParameter) permute_param_layout_(x, input_dim=0, output_dim=1, packed_dim=0) x.data = gptq_marlin_repack( x.data.contiguous(), perm=layer.g_idx_sort_indices, size_k=c.partition_weight_shape[0], size_n=c.partition_weight_shape[1], num_bits=c.weight_type.size_bits, ) return x def transform_w_s(x): assert isinstance(x, BasevLLMParameter) permute_param_layout_(x, input_dim=0, output_dim=1) x.data = marlin_permute_scales( x.data.contiguous(), size_k=c.partition_weight_shape[0], size_n=c.partition_weight_shape[1], group_size=c.group_size, ) return x if c.has_g_idx: g_idx, g_idx_sort_indices = marlin_sort_g_idx( getattr(layer, self.w_gidx_name) ) _transform_param(layer, self.w_gidx_name, lambda _: g_idx) layer.g_idx_sort_indices = g_idx_sort_indices else: setattr(layer, self.w_gidx_name, marlin_make_empty_g_idx(device)) layer.g_idx_sort_indices = marlin_make_empty_g_idx(device) if c.zero_points: grouped_k = ( c.partition_weight_shape[0] // c.group_size if c.group_size != -1 else 1 ) _transform_param( layer, self.w_zp_name, lambda x: marlin_zero_points( unpack_cols( x.t(), c.weight_type.size_bits, grouped_k, c.partition_weight_shape[1], ), size_k=grouped_k, size_n=c.partition_weight_shape[1], num_bits=c.weight_type.size_bits, ), ) else: setattr(layer, self.w_zp_name, marlin_make_empty_g_idx(device)) _transform_param(layer, self.w_q_name, transform_w_q) _transform_param(layer, self.w_s_name, transform_w_s) def apply( self, layer: torch.nn.Module, x: torch.Tensor, bias: Optional[torch.Tensor] = None, ) -> torch.Tensor: c = self.kernel_config def _get_weight_params( layer: torch.nn.Module, ) -> tuple[ torch.Tensor, # w_q torch.Tensor, # w_s Optional[torch.Tensor], # w_zp, Optional[torch.Tensor], # w_gidx ]: return ( getattr(layer, self.w_q_name), getattr(layer, self.w_s_name), getattr(layer, self.w_zp_name or "", None), getattr(layer, self.w_gidx_name or "", None), ) w_q, w_s, w_zp, w_gidx = _get_weight_params(layer) # `process_weights_after_loading` will ensure w_zp and w_gidx are not # None for marlin return apply_gptq_marlin_linear( input=x, weight=w_q, weight_scale=w_s, weight_zp=w_zp, # type: ignore g_idx=w_gidx, # type: ignore g_idx_sort_indices=layer.g_idx_sort_indices, workspace=self.workspace, wtype=c.weight_type, input_size_per_partition=c.partition_weight_shape[0], output_size_per_partition=c.partition_weight_shape[1], is_k_full=self.is_k_full, bias=bias, ) class GPTQMarlinMoEKernel: def __init__(self, quant_config: Optional[QuantizationConfig] = None): self.quant_config = quant_config def process_weights_after_loading(self, layer: torch.nn.Module) -> None: # Process act_order if self.quant_config.desc_act: # Get sorting based on g_idx num_experts = layer.w13_g_idx.shape[0] w13_g_idx_sort_indices = torch.empty_like(layer.w13_g_idx) w2_g_idx_sort_indices = torch.empty_like(layer.w2_g_idx) w13_sorted_g_idx = torch.empty_like(layer.w13_g_idx) w2_sorted_g_idx = torch.empty_like(layer.w2_g_idx) for e in range(num_experts): w13_g_idx_sort_indices[e] = torch.argsort(layer.w13_g_idx[e]).to( torch.int32 ) w2_g_idx_sort_indices[e] = torch.argsort(layer.w2_g_idx[e]).to( torch.int32 ) w13_sorted_g_idx[e] = layer.w13_g_idx[e][w13_g_idx_sort_indices[e]] w2_sorted_g_idx[e] = layer.w2_g_idx[e][w2_g_idx_sort_indices[e]] replace_parameter(layer, "w13_g_idx", w13_sorted_g_idx) replace_parameter(layer, "w2_g_idx", w2_sorted_g_idx) replace_parameter(layer, "w13_g_idx_sort_indices", w13_g_idx_sort_indices) replace_parameter(layer, "w2_g_idx_sort_indices", w2_g_idx_sort_indices) else: # Reset g_idx related tensors num_experts = layer.w13_g_idx.shape[0] device = layer.w13_g_idx.device layer.w13_g_idx = torch.nn.Parameter( torch.empty((num_experts, 0), dtype=torch.int32, device=device), requires_grad=False, ) layer.w2_g_idx = torch.nn.Parameter( torch.empty((num_experts, 0), dtype=torch.int32, device=device), requires_grad=False, ) layer.w13_g_idx_sort_indices = torch.nn.Parameter( torch.empty((num_experts, 0), dtype=torch.int32, device=device), requires_grad=False, ) layer.w2_g_idx_sort_indices = torch.nn.Parameter( torch.empty((num_experts, 0), dtype=torch.int32, device=device), requires_grad=False, ) # Repack weights marlin_w13_qweight = gptq_marlin_moe_repack( layer.w13_qweight, layer.w13_g_idx_sort_indices, layer.w13_qweight.shape[1] * self.quant_config.pack_factor, layer.w13_qweight.shape[2], self.quant_config.weight_bits, ) replace_parameter(layer, "w13_qweight", marlin_w13_qweight) marlin_w2_qweight = gptq_marlin_moe_repack( layer.w2_qweight, layer.w2_g_idx_sort_indices, layer.w2_qweight.shape[1] * self.quant_config.pack_factor, layer.w2_qweight.shape[2], self.quant_config.weight_bits, ) replace_parameter(layer, "w2_qweight", marlin_w2_qweight) # Repack scales marlin_w13_scales = marlin_moe_permute_scales( s=layer.w13_scales, size_k=layer.intermediate_size_per_partition, size_n=layer.w13_scales.shape[2], group_size=self.quant_config.group_size, ) replace_parameter(layer, "w13_scales", marlin_w13_scales) marlin_w2_scales = marlin_moe_permute_scales( s=layer.w2_scales, size_k=layer.w2_scales.shape[1] * ( self.quant_config.group_size if self.quant_config.group_size != -1 else self.quant_config.pack_factor ), size_n=layer.w2_scales.shape[2], group_size=self.quant_config.group_size, ) replace_parameter(layer, "w2_scales", marlin_w2_scales) def create_moe_runner( self, layer: torch.nn.Module, moe_runner_config: MoeRunnerConfig ): assert get_moe_runner_backend().is_auto() self.moe_runner_config = moe_runner_config self.runner = MoeRunner(MoeRunnerBackend.MARLIN, moe_runner_config) def apply( self, layer: torch.nn.Module, dispatch_output: StandardDispatchOutput, ) -> CombineInput: quant_info = MarlinMoeQuantInfo( w13_qweight=layer.w13_qweight, w2_qweight=layer.w2_qweight, w13_scales=layer.w13_scales, w2_scales=layer.w2_scales, w13_g_idx=layer.w13_g_idx, w2_g_idx=layer.w2_g_idx, w13_g_idx_sort_indices=layer.w13_g_idx_sort_indices, w2_g_idx_sort_indices=layer.w2_g_idx_sort_indices, weight_bits=self.quant_config.weight_bits, is_k_full=self.is_k_full, ) return self.runner.run(dispatch_output, quant_info)