Prhub

#26402 [5/N] Quantization Refactor: GPTQ schemes and kernel split

原始 PR 作者 Alisehen 合并时间 2026-05-28 22:15 文件变更 12 提交数 8 评论 9 代码增减 +2068 / -1574

执行摘要

GPTQ 量化重构:按 scheme/kernel 拆分为独立模块

PR描述指出遵循AWQ已使用的scheme/kernel split模式,使GPTQ代码结构更清晰,便于扩展新硬件后端和新量化方案。同时移除散布的平台判断逻辑,集中管理,降低耦合。

值得精读,尤其是scheme/kernel分离的设计模式,以及如何通过工厂方法统一不同后端的量化逻辑。对于从事量化或硬件抽象层的工程师,这是一个很好的参考案例。PR讨论中关于移除is_xxx检查的要点也体现了架构整洁性追求。

讨论亮点

review中ping1jing2指出重构后不应保留is_xxx平台检查,Alisehen同意并移除了散布的is_cuda/is_npu检查,将平台选择集中到量化注册表中。这一讨论体现了对架构清晰度的追求。

实现拆解

  1. 创建gptq包与schemes子包:新建python/sglang/srt/layers/quantization/gptq/目录(含__init__.pygptq.py)及schemes/子目录,将原gptq.py中的配置、线性方案、Marlin方案、MoE方案拆解到独立文件。

  2. 拆分线性层方案gptq_linear.py中定义GPTQLinearScheme(GPU)和GPTQAscendLinearScheme(NPU),通过create_weightsprocess_weights_after_loadingapply_weights统一接口,内部调用各自的kernel实现。

  3. 拆分Marlin与MoE方案gptq_marlin.py定义GPTQMarlinLinearSchemegptq_moe.py定义GPTQMarlinMoESchemeGPTQMoEAscendScheme,将Marlin相关的repack和permute逻辑独立。

  4. 迁移kernel实现到硬件后端:GPU的GPTQLinearKernelGPTQMarlinLinearKernel等移入hardware_backend/gpu/quantization/gptq_kernels.py;NPU的GPTQLinearAscendKernelGPTQMoEAscendKernel等移入hardware_backend/npu/quantization/gptq_kernels.py。保留gptq_marlin_moe_repack工具函数。

  5. 更新注册与删除旧文件:修改layers/quantization/__init__.py以指向新包,调整auto_round.py配置引用。删除原gptq.py(1571行),并通过get_linear_quant_method确保embedding层不被错误分配线性量化方法。

文件 模块 状态 重要度
python/sglang/srt/layers/quantization/gptq.py 量化核心 removed 9.25
python/sglang/srt/layers/quantization/gptq/gptq.py GPTQ 配置 added 9.25
python/sglang/srt/hardware_backend/gpu/quantization/gptq_kernels.py GPU 内核 added 9.25
python/sglang/srt/hardware_backend/npu/quantization/gptq_kernels.py NPU 内核 added 9.25
python/sglang/srt/layers/quantization/gptq/schemes/gptq_moe.py MoE 方案 added 9.25
python/sglang/srt/layers/quantization/gptq/schemes/gptq_linear.py 线性方案 added 9.08
python/sglang/srt/layers/quantization/gptq/schemes/gptq_marlin.py Marlin 方案 added 9.03
python/sglang/srt/layers/quantization/gptq/schemes/gptq_scheme.py 方案基类 added 8.55
python/sglang/srt/layers/quantization/gptq/__init__.py 包初始化 added 6.92
python/sglang/srt/layers/quantization/gptq/schemes/__init__.py 方案初始化 added 6.44
python/sglang/srt/layers/quantization/__init__.py 量化入口 modified 6.27
python/sglang/srt/layers/quantization/auto_round.py AutoRound modified 5.11

关键符号

check_marlin_format gptq_marlin_moe_repack MarlinLinearLayerConfig GPTQConfig GPTQLinearKernel GPTQMarlinLinearKernel GPTQLinearAscendKernel GPTQMoEAscendKernel unpack_from_int32 GPTQLinearScheme GPTQAscendLinearScheme GPTQMarlinLinearScheme GPTQMarlinMoEScheme GPTQMoEAscendScheme GPTQLinearSchemeBase GPTQMoESchemeBase

关键源码片段

python/sglang/srt/hardware_backend/npu/quantization/gptq_kernels.py core-logic

NPU 上 GPTQ kernel 的独立实现,包括 GPTQLinearAscendKernel 和 GPTQMoEAscendKernel

from __future__ import annotationsfrom typing import TYPE_CHECKING, Optionalimport torch
import torch_npu# NPU fused experts 实现
from sglang.srt.hardware_backend.npu.quantization.fused_moe_method_npu import (
    npu_fused_experts,
)def unpack_from_int32(weight: torch.Tensor, num_bits: int, packed_dim: int = 1) -> torch.Tensor:
    """
    将int32格式的量化权重解包回原始位数。
    支持沿dim=0或dim=1解包,返回int8类型(以零点为中心)。
    """
    assert weight.dtype == torch.int32
    pack_factor = 32 // num_bits
    mask = (1 << num_bits) - 1
​
    if packed_dim == 1:
        unpacked_weight = torch.zeros(
            (weight.shape[0], weight.shape[1] * pack_factor),
            device=weight.device, dtype=torch.int32,
        )
        for i in range(pack_factor):
            unpacked_weight[:, i::pack_factor] = (weight >> (num_bits * i)) & mask
    else:
        unpacked_weight = torch.zeros(
            (weight.shape[0] * pack_factor, weight.shape[1]),
            device=weight.device, dtype=torch.int32,
        )
        for i in range(pack_factor):
            unpacked_weight[i::pack_factor, :] = (weight >> (num_bits * i)) & mask
    offset = pow(2, num_bits) // 2
    unpacked_weight = (unpacked_weight - offset).to(torch.int8)
    return unpacked_weightclass GPTQLinearAscendKernel:
    """NPU上的GPTQ线性层内核,使用torch_npu定制算子。"""
    def __init__(self, quant_config: Optional["QuantizationConfig"] = None):
        self.quant_config = quant_config
        self.use_v2_format = quant_config.checkpoint_format == "gptq_v2"
​
    def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
        # 解包 qzeros(沿 packed_dim=1)并调整数据类型
        layer.qzeros = torch.nn.Parameter(
            unpack_from_int32(
                layer.qzeros.data.contiguous(),
                self.quant_config.weight_bits,
                packed_dim=1,
            ).to(layer.scales.dtype),
            requires_grad=False,
        )
        if not self.use_v2_format:
            layer.qzeros += 1 # GPTQ v1 需要偏移
​
        qweight_tmp = unpack_from_int32(
            layer.qweight.data.contiguous(), self.quant_config.weight_bits, packed_dim=0
        )
        if self.quant_config.weight_bits != 4:
            # 非 4bit 时直接存储 int8
            layer.qweight = torch.nn.Parameter(qweight_tmp, requires_grad=False)
            return
​
        # 4bit 时使用 NPU 特定 int4pack 格式以节省内存
        layer.qweight = torch.nn.Parameter(
            torch_npu.npu_convert_weight_to_int4pack(qweight_tmp.to(torch.int32)),
            requires_grad=False,
        )
​
    def apply(self, layer, x, bias=None):
        qweight = layer.qweight
        scales = layer.scales
        qzeros = layer.qzeros
        reshaped_x = x.reshape(-1, x.shape[-1])
​
        if bias is not None and bias.dtype == torch.bfloat16:
            bias = bias.float()
​
        out_shape = x.shape[:-1] + (qweight.shape[-1] * 8,)
        out = torch_npu.npu_weight_quant_batchmatmul(
            reshaped_x, qweight,
            antiquant_scale=scales,
            antiquant_offset=qzeros,
            antiquant_group_size=self.quant_config.group_size,
            bias=bias,
        )
        return out.reshape(out_shape)

评论区精华

去除 `is_xxx` 平台检查 设计

ping1jing2 评论 : 'i think we shouldn't have `is_xxx` after refactoring, please do correct me if i am wrong';Alisehen 回复 : 'Agreed. I removed the scattered `is_xxx` checks from GPTQ. The only remaining platform selection is centralized in the quantization registry to map NPU to GPTQAscendConfig.'

结论:同意移除散布的平台检查,集中到注册表 · 已解决

风险与影响

  • 回归风险:重构涉及大量文件移动和拆分,可能引入新bug,特别是Marlin repack和MoE权重加载路径。CI已通过GPU和AMD测试,但NPU路径仅编译检查,缺少端到端推理验证。
  • 性能影响:重构未改变计算逻辑,但间接层可能引入微小开销(如额外的方法调用)。理论上不影响性能。
  • 可维护性:增加类层次结构和模块数量,新开发者需要时间熟悉。但采用与AWQ一致的设计模式,降低了长期认知负载。
  • 测试覆盖:未新增单元测试,依赖现有的集成测试和手动验证。对于这种大规模重构,风险较高。
  • 用户:无功能变化,GPTQ量化模型加载和推理行为一致。--quantization gptq_marlin等选项继续生效。
  • 系统:不影响其他量化方法(AWQ、FP8等)。
  • 团队:新架构更易扩展新硬件后端(如XPU、CPU),降低后续添加新量化方案的代码冲突。但程序员需要适应新的文件布局。
核心路径变更 缺少测试覆盖 多层继承复杂度

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论