[Feature][NPU]: add runtime support for GPTQ-quantized MoE models (#16364)

Co-authored-by: GuoYechang <52730608+GuoYechang@users.noreply.github.com>
Co-authored-by: root <root@localhost.localdomain>
This commit is contained in:
YeChang Guo
2026-03-04 21:02:19 +08:00
committed by GitHub
parent c2b66d320d
commit 6910c1b281
4 changed files with 370 additions and 5 deletions

View File

@@ -64,6 +64,8 @@ WEIGHT_LOADER_V2_SUPPORTED = [
"GPTQMarlin24LinearMethod",
"TPUInt8LinearMethod",
"GPTQLinearMethod",
"GPTQLinearAscendMethod",
"GPTQMoEAscendMethod",
"FBGEMMFp8LinearMethod",
"GPTQLinearAscendMethod",
"ModelOptFp8LinearMethod",

View File

@@ -7,6 +7,9 @@ from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Union
import torch
from sglang.srt.hardware_backend.npu.quantization.fused_moe_method_npu import (
npu_fused_experts,
)
from sglang.srt.layers.moe import (
MoeRunner,
MoeRunnerBackend,
@@ -174,8 +177,8 @@ class GPTQConfig(QuantizationConfig):
f"group_size={self.group_size}, "
f"desc_act={self.desc_act}),"
f"lm_head_quantized={self.lm_head_quantized}), "
f"dynamic={self.dynamic}",
f"checkpoint_format={self.checkpoint_format})",
f"dynamic={self.dynamic},"
f"checkpoint_format={self.checkpoint_format})"
)
def get_scaled_act_names(self) -> List[str]:
@@ -236,11 +239,10 @@ class GPTQConfig(QuantizationConfig):
from sglang.srt.layers.moe.fused_moe_triton import FusedMoE
if _is_npu:
if isinstance(layer, FusedMoE):
return GPTQMoEAscendMethod(self)
if isinstance(layer, LinearBase):
return GPTQLinearAscendMethod(self)
elif isinstance(layer, FusedMoE):
# TODO: support GPTQ quantization MoE on npu.
raise NotImplementedError("GPTQ Method does not support MoE yet.")
return None
if isinstance(layer, FusedMoE):
@@ -597,6 +599,290 @@ class GPTQLinearMethod(LinearMethodBase):
return output.reshape(out_shape)
class GPTQMoEAscendMethod(FusedMoEMethodBase):
def __init__(self, quant_config: GPTQConfig):
super().__init__()
self.quant_config = quant_config
self.use_v2_format = quant_config.checkpoint_format == "gptq_v2"
self.moe_runner_config: Optional[MoeRunnerConfig] = None
def create_weights(
self,
layer: torch.nn.Module,
num_experts: int,
hidden_size: int,
intermediate_size_per_partition: int,
params_dtype: torch.dtype,
**extra_weight_attrs,
):
from sglang.srt.layers.moe.fused_moe_triton import FusedMoeWeightScaleSupported
pack_factor = self.quant_config.pack_factor
num_groups_w13 = hidden_size // self.quant_config.group_size
num_groups_w2 = intermediate_size_per_partition // self.quant_config.group_size
extra_weight_attrs.update(
{
"is_transposed": True,
"quant_method": FusedMoeWeightScaleSupported.GROUP.value,
}
)
w13_qweight = torch.nn.Parameter(
torch.empty(
num_experts,
hidden_size // pack_factor,
2 * intermediate_size_per_partition,
dtype=torch.int32,
),
requires_grad=False,
)
layer.register_parameter("w13_qweight", w13_qweight)
set_weight_attrs(w13_qweight, extra_weight_attrs)
w2_qweight = torch.nn.Parameter(
torch.empty(
num_experts,
intermediate_size_per_partition // pack_factor,
hidden_size,
dtype=torch.int32,
),
requires_grad=False,
)
layer.register_parameter("w2_qweight", w2_qweight)
set_weight_attrs(w2_qweight, extra_weight_attrs)
w13_scales = torch.nn.Parameter(
torch.empty(
num_experts,
num_groups_w13,
2 * intermediate_size_per_partition,
dtype=params_dtype,
),
requires_grad=False,
)
layer.register_parameter("w13_scales", w13_scales)
set_weight_attrs(w13_scales, extra_weight_attrs)
w2_scales = torch.nn.Parameter(
torch.empty(
num_experts,
num_groups_w2,
hidden_size,
dtype=params_dtype,
),
requires_grad=False,
)
layer.register_parameter("w2_scales", w2_scales)
set_weight_attrs(w2_scales, extra_weight_attrs)
w13_qzeros = torch.nn.Parameter(
torch.empty(
num_experts,
num_groups_w13,
2 * intermediate_size_per_partition // pack_factor,
dtype=torch.int32,
),
requires_grad=False,
)
layer.register_parameter("w13_qzeros", w13_qzeros)
set_weight_attrs(w13_qzeros, extra_weight_attrs)
w2_qzeros = torch.nn.Parameter(
torch.empty(
num_experts,
num_groups_w2,
hidden_size // pack_factor,
dtype=torch.int32,
),
requires_grad=False,
)
layer.register_parameter("w2_qzeros", w2_qzeros)
set_weight_attrs(w2_qzeros, extra_weight_attrs)
def create_moe_runner(
self,
layer: torch.nn.Module,
moe_runner_config: MoeRunnerConfig,
**extra_weight_attrs,
):
self.moe_runner_config = moe_runner_config
def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
w13_qzeros_2d = layer.w13_qzeros.data.contiguous().reshape(
-1, layer.w13_qzeros.shape[-1]
)
layer.w13_qzeros = torch.nn.Parameter(
unpack_from_int32(
w13_qzeros_2d,
self.quant_config.weight_bits,
packed_dim=1,
)
.reshape(layer.w13_qzeros.shape[0], layer.w13_qzeros.shape[1], -1)
.to(layer.w13_scales.dtype),
requires_grad=False,
)
if not self.use_v2_format:
layer.w13_qzeros += 1
w2_qzeros_2d = layer.w2_qzeros.data.contiguous().reshape(
-1, layer.w2_qzeros.shape[-1]
)
layer.w2_qzeros = torch.nn.Parameter(
unpack_from_int32(
w2_qzeros_2d,
self.quant_config.weight_bits,
packed_dim=1,
)
.reshape(layer.w2_qzeros.shape[0], layer.w2_qzeros.shape[1], -1)
.to(layer.w2_scales.dtype),
requires_grad=False,
)
if not self.use_v2_format:
layer.w2_qzeros += 1
w13_qweight_2d = (
layer.w13_qweight.data.transpose(-1, -2)
.contiguous()
.reshape(-1, layer.w13_qweight.shape[-2])
)
w13_qweight_tmp = unpack_from_int32(
w13_qweight_2d, self.quant_config.weight_bits, packed_dim=1
)
if self.quant_config.weight_bits == 4:
group_size = self.quant_config.group_size
scale_expanded = layer.w13_scales.data.repeat_interleave(group_size, dim=1)
neg_mask = scale_expanded < 0
if neg_mask.any():
neg_mask = neg_mask.transpose(-1, -2)
neg_mask = neg_mask.contiguous().reshape(w13_qweight_tmp.shape)
w13_qweight_tmp[neg_mask] = -w13_qweight_tmp[neg_mask]
if w13_qweight_tmp.max() > 7:
w13_qweight_tmp.clamp_(max=7)
layer.w13_scales.data.abs_()
layer.w13_qweight = torch.nn.Parameter(
torch_npu.npu_convert_weight_to_int4pack(
w13_qweight_tmp.reshape(
layer.w13_qweight.shape[0], layer.w13_qweight.shape[2], -1
)
.transpose(-1, -2)
.contiguous()
.reshape(-1, layer.w13_qweight.shape[2])
.to(torch.int32)
)
.reshape(layer.w13_qweight.shape[0], layer.w13_qweight.shape[1] * 8, -1)
.contiguous(),
requires_grad=False,
)
# use int8 to store weight by default
else:
layer.w13_qweight = torch.nn.Parameter(
w13_qweight_tmp.reshape(
layer.w13_qweight.shape[0], layer.w13_qweight.shape[2], -1
)
.transpose(-1, -2)
.contiguous(),
requires_grad=False,
)
w2_qweight_2d = (
layer.w2_qweight.data.transpose(-1, -2)
.contiguous()
.reshape(-1, layer.w2_qweight.shape[-2])
)
w2_qweight_tmp = unpack_from_int32(
w2_qweight_2d, self.quant_config.weight_bits, packed_dim=1
)
if self.quant_config.weight_bits == 4:
group_size = self.quant_config.group_size
scale_expanded = layer.w2_scales.data.repeat_interleave(group_size, dim=1)
neg_mask = scale_expanded < 0
if neg_mask.any():
neg_mask = neg_mask.transpose(-1, -2)
neg_mask = neg_mask.contiguous().reshape(w2_qweight_tmp.shape)
w2_qweight_tmp[neg_mask] = -w2_qweight_tmp[neg_mask]
if w2_qweight_tmp.max() > 7:
w2_qweight_tmp.clamp_(max=7)
layer.w2_scales.data.abs_()
layer.w2_qweight = torch.nn.Parameter(
torch_npu.npu_convert_weight_to_int4pack(
w2_qweight_tmp.reshape(
layer.w2_qweight.shape[0], layer.w2_qweight.shape[2], -1
)
.transpose(-1, -2)
.contiguous()
.reshape(-1, layer.w2_qweight.shape[2])
.to(torch.int32)
)
.reshape(layer.w2_qweight.shape[0], layer.w2_qweight.shape[1] * 8, -1)
.contiguous(),
requires_grad=False,
)
# use int8 to store weight by default
else:
layer.w2_qweight = torch.nn.Parameter(
w2_qweight_tmp.reshape(
layer.w2_qweight.shape[0], layer.w2_qweight.shape[2], -1
)
.transpose(-1, -2)
.contiguous(),
requires_grad=False,
)
def apply(
self,
layer: torch.nn.Module,
dispatch_output: StandardDispatchOutput,
) -> torch.Tensor:
from sglang.srt.layers.moe.token_dispatcher import StandardCombineInput
assert (
self.moe_runner_config is not None
), "moe_runner_config is not set. Did you forget to call create_weights/create_moe_runner?"
assert self.moe_runner_config.activation in ("silu", "swiglu"), (
f"Only SiLU/Swiglu activation is supported, "
f"got {self.moe_runner_config.activation!r}."
)
x = dispatch_output.hidden_states
topk_output = dispatch_output.topk_output
topk_weights, topk_ids, _ = topk_output
topk_ids = topk_ids.to(torch.int32)
topk_weights = topk_weights.to(x.dtype)
output = npu_fused_experts(
hidden_states=x,
w13=layer.w13_qweight,
w13_scale=layer.w13_scales,
w13_offset=layer.w13_qzeros,
w2=layer.w2_qweight,
w2_scale=layer.w2_scales,
w2_offset=layer.w2_qzeros,
topk_weights=topk_weights,
topk_ids=topk_ids,
top_k=topk_ids.shape[1],
use_wna16=True,
)
return StandardCombineInput(hidden_states=output)
class GPTQMarlinLinearMethod(LinearMethodBase):
"""Linear method for GPTQ Marlin.