Prhub

#52112 [Bugfix][ROCm] Fix a few int4/int8 quantization errors

原始 PR 作者 qli88 合并时间 2026-08-18 16:31 文件变更 4 提交数 3 评论 5 代码增减 +69 / -7

执行摘要

修复 ROCm 平台 int4/int8 量化 MoE 回归,扩 TRITON 后端能力

PR #44120 将 MoeWNA16Method 迁移到新的 MK oracle 方案后,在 ROCm 平台引入了若干量化回归:一是 compressed-tensors 路径对非对称量化直接断言拒绝,导致 MiniMax-M3-AWQ-INT4 这类带 zero point 的 AWQ 模型无法走 TRITON MoE 后端;二是 int8 量化被错误地限定为 group_size==-1(无 group 量化),QuantTrio/Qwen3-235B-A22B-GPTQ-Int8 这类带 group size 的 GPTQ-Int8 模型直接断言失败;三是 TRITON MoE 后端不支持 MiniMax-M3 所需的 SWIGLUOAI_UNINTERLEAVE 激活。PR body 明确列出两个目标模型的 serve 测试命令并验证输出正确。

值得精读,尤其推荐关注 int_wna16.py 中 ZP 布局转换的逐步推导注释——这是理解 fused_moe_kernel_gptq_awq 数据布局的绝佳材料。适合对 MoE 量化、TP 切分与 Triton kernel 数据契约感兴趣的读者。建议后续补充针对 ZP 布局转换和 packed_factor 校验的单元测试,并考虑在文档中明确非对称 WNA16 的 TP 约束。

讨论亮点

本 PR 没有实质性的 review 技术讨论,代码评审流程简洁:

  • claude[bot] 因 PR 来自 fork 自动跳过审查,提示维护者可用 @claude review 触发一次性审查。
  • 维护者 tjtanaa 直接批准(APPROVED)并两次触发 Buildkite CI(#84194、#84345),说明改动在维护者视角风险可控、验证充分。
  • 关联 Issue #44120 的讨论集中在迁移动机(将 MoeWNA16Method 迁移到 MK oracle 方案)与自动 GPTQ 权重转换修复,本 PR 属于该迁移的后续回归修复,未再展开设计争论。

实现拆解

本 PR 分四步修复回归,全部落在 vllm/model_executor/layers 下的量化与 MoE 路径:

  1. 放开对称量化限制并增加 packed_factor 校验quantization/compressed_tensors/compressed_tensors_moe/compressed_tensors_moe_wna16.py
    - 删除 assert self.symmetric,使非对称(asymmetric)WNA16 可以进入非 Marlin 后端(含 TRITON)分支。
    - 在 create_weights 中新增非对称量化下的整除性校验:2 * intermediate_size_per_partitionhidden_size 必须能被 packed_factor(int4 时为 8)整除,因为 int4 的 zero point 以 int32 打包存储,TP 切分时每个 rank 必须持有整数个 int32 元素,否则 ZP 会被从字节中间截断导致解包错误。
    - 调整 process_weights_after_loading 中 ZP 替换条件:EMULATION 后端会把 ZP 烘焙进反量化后的 bf16 权重,返回的 w13_qzeros/w2_qzerosNone,因此需显式排除 EMULATION,避免无谓的断言失败。

  2. 新增 TRITON 后端 ZP 布局转换fused_moe/oracle/int_wna16.py
    - 在 convert_to_wna16_moe_kernel_format 的 TRITON 分支中,对来自 compressed-tensors/auto_gptq 的 K-first int32 零点张量做布局变换:(E, K//gs, N//8) int32 依次经 transpose、view(uint8)、reshape、permute 转为 (E, N//2, K//gs) uint8,使每字节携带 2 个 int4 ZP,匹配 fused_moe_kernel_gptq_awq 的索引约定 (offs_bn // 2) * stride_bzn + offs_k_group * stride_bzk

  3. 扩展 TRITON WNA16 专家后端的能力声明fused_moe/experts/triton_moe.py
    - 导入 kInt4StaticAsymkInt4Static32Asym 并加入 TritonWNA16Experts._supports_quant_scheme 的支持列表,使 oracle 能对该后端放行非对称 int4 方案。
    - 将 MoEActivation.SWIGLUOAI_UNINTERLEAVE 加入 _supports_activation,并同步修正注释,说明该激活经由 apply_moe_activation() 路由到 torch.ops._C.silu_and_mul_with_clamp

  4. 移除 int8 的 group_size 限制quantization/moe_wna16.py
    - 删除 assert group_size == -1,使 int8 量化支持 group_size != -1(即 group-wise int8 量化,如 Qwen3-235B-A22B-GPTQ-Int8),为这类模型构建出正确的 QuantKey(kInt8StaticGroupScale, ...)

  5. 测试与配套:PR 未新增自动化测试,仅以两个真实模型(MiniMax-M3-AWQ-INT4、Qwen3-235B-A22B-GPTQ-Int8)的端到端 serve 命令作为测试计划;CI 由 tjtanaa 触发两次并通过。

文件 模块 状态 重要度
vllm/model_executor/layers/fused_moe/oracle/int_wna16.py 量化编排 modified 6.83
vllm/model_executor/layers/quantization/compressed_tensors/compressed_tensors_moe/compressed_tensors_moe_wna16.py 量化层 modified 6.94
vllm/model_executor/layers/fused_moe/experts/triton_moe.py MoE 后端 modified 5.75
vllm/model_executor/layers/quantization/moe_wna16.py 量化层 modified 4.79

关键符号

convert_to_wna16_moe_kernel_format CompressedTensorsMoeWNA16Method.create_weights CompressedTensorsMoeWNA16Method.process_weights_after_loading TritonWNA16Experts._supports_quant_scheme TritonWNA16Experts._supports_activation MoeWNA16Method.__init__

关键源码片段

vllm/model_executor/layers/fused_moe/oracle/int_wna16.py data-contract

核心算法改动:为 TRITON WNA16 后端补齐 compressed-tensors 来源 zero point 的布局转换(K-first int32 → N-first uint8),这是解锁非对称 AWQ/GPTQ 模型的关键,转换链路含详细的逐步推导注释。

# 文件:vllm/model_executor/layers/fused_moe/oracle/int_wna16.py
# 位置:convert_to_wna16_moe_kernel_format() 的 TRITON 分支内
#
# compressed-tensors checkpoints 的 zero point 是 K-first int32,每个元素
# 打包 8 个 int4 ZP,形状为 (E, K//gs, N//8);而 fused_moe_kernel_gptq_awq
# 期望 N-first uint8、每字节携带 2 个 int4 ZP,形状为 (E, N//2, K//gs),
# 索引方式为 (offs_bn // 2) * stride_bzn + offs_k_group * stride_bzk。
if w13_qzeros is not None:
    E13, Kg13, Np13 = w13_qzeros.shape
    # 转换链路(每步都依赖 view/reshape 的内存连续性):
    # (E, K//gs, N//8) int32
    # → transpose(1,2) → (E, N//8, K//gs) int32
    # → view(uint8) → (E, N//8, K//gs*4) 每个 int32 拆成 4 字节
    # → reshape(..., 4) → (E, N//8, K//gs, 4) 隔离出字节下标
    # → permute(0,1,3,2) → (E, N//8, 4, K//gs) 字节下标挪到 K 之前
    # → reshape → (E, N//2, K//gs) 得到 kernel 期望布局
    # 转换后元素 [e, offs_bn//2, k_group] 即为同时持有输出通道
    # offs_bn 与 offs_bn+1 两个 int4 ZP 的 uint8 字节。
    w13_qzeros = (
        w13_qzeros.transpose(1, 2)
        .contiguous()
        .view(torch.uint8)
        .reshape(E13, Np13, Kg13, 4)
        .permute(0, 1, 3, 2)
        .reshape(E13, Np13 * 4, Kg13)
        .contiguous()
    )
if w2_qzeros is not None:
    E2, Kg2, Np2 = w2_qzeros.shape
    # 与 w13 完全相同的转换逻辑,作用于 MoE 的 w2 权重(输出投影)。
    w2_qzeros = (
        w2_qzeros.transpose(1, 2)
        .contiguous()
        .view(torch.uint8)
        .reshape(E2, Np2, Kg2, 4)
        .permute(0, 1, 3, 2)
        .reshape(E2, Np2 * 4, Kg2)
        .contiguous()
    )
vllm/model_executor/layers/quantization/compressed_tensors/compressed_tensors_moe/compressed_tensors_moe_wna16.py data-contract

移除对称量化断言并增加 packed_factor 整除性校验,同时修正 EMULATION 后端的 ZP 处理;这是让非对称 WNA16 能进入 TRITON 后端的入口级变更。

# 文件:vllm/model_executor/layers/quantization/compressed_tensors/compressed_tensors_moe/compressed_tensors_moe_wna16.py
# 位置:CompressedTensorsMoeWNA16Method.create_weights() 计算 num_groups 之后
if not self.symmetric:
    # 非对称 int4 量化下,8 个 int4 zero point 打包进 1 个 int32 元素;
    # TP 切分时每个 rank 必须拿到整数个 int32,否则 packed ZP 会被从
    # int32 中间截断,解包结果错乱,因此提前做整除性校验并给出明确报错。
    w13_n = 2 * intermediate_size_per_partition # gate+up 输出通道数
    if w13_n % self.packed_factor != 0:
        raise ValueError(
            f"CompressedTensors WNA16 MoE: gate+up output channels per "
            f"TP rank (2 * intermediate_size_per_partition = {w13_n}) "
            f"must be divisible by packed_factor ({self.packed_factor}). "
            f"Use a TP size where 2 * intermediate_size is divisible by "
            f"{self.packed_factor}."
        )
    if hidden_size % self.packed_factor != 0:
        raise ValueError(
            f"CompressedTensors WNA16 MoE: hidden_size ({hidden_size}) "
            f"must be divisible by packed_factor ({self.packed_factor}) "
            f"for correct ZP unpacking."
        )# 位置:process_weights_after_loading() 中替换权重参数的阶段
# CPU fused_experts_cpu 即使对称量化也需要 zero point;而 EMULATION
# 后端在 convert 阶段就把 ZP 烘焙进反量化后的 bf16 权重,返回的
# w13_qzeros/w2_qzeros 为 None,因此必须显式排除,避免误断言。
if (
    not self.symmetric or self.wna16_backend == WNA16MoEBackend.CPU
) and self.wna16_backend != WNA16MoEBackend.EMULATION:
    assert w13_qzeros is not None and w2_qzeros is not None
    replace_parameter(layer, "w13_weight_zero_point", w13_qzeros)
    replace_parameter(layer, "w2_weight_zero_point", w2_qzeros)

评论区精华

CI 触发与运行状态 other

维护者 tjtanaa 两次通过 `/ci run` 触发 Buildkite CI(#84194、#84345),github-actions 机器人同步反馈授权信息和 CI 可用状态。

结论:两次 CI 均被触发,PR 最终由 tjtanaa 批准合并,未在评论中暴露失败信息。 · 已解决

fork PR 的自动化审查策略 other

claude[bot] 指出该 PR 来自 fork,自动化 review 默认关闭,维护者可通过 `@claude review` 手动触发一次性审查。

结论:未触发额外审查,由人类维护者直接批准。 · 已解决

风险与影响

  1. ZP 布局转换正确性风险fused_moe/oracle/int_wna16.py):新增的 transpose/view/reshape/permute 链依赖对 fused_moe_kernel_gptq_awq 索引语义的精确理解,一旦 compressed-tensors 侧的打包约定或 kernel 侧索引方式变化,容易静默产出错误结果;本 PR 未附带单元测试覆盖此转换,回归风险较高。
  2. TP 配置兼容性compressed_tensors_moe_wna16.py):新增的 packed_factor 整除性校验会在某些 TP 规模下直接抛 ValueError 拒绝启动。这是显式失败而非静默错误,比原先更安全,但可能让部分既有非对称模型配置从“可运行但结果错误”变成“无法启动”,需在文档中说明 TP 选择约束。
  3. EMULATION 分支的 ZP 缺失compressed_tensors_moe_wna16.py):EMULATION 后端不再断言 ZP 非 None,若未来 EMULATION 实现改为要求在权重中保留 ZP,则该分支会静默丢掉 ZP 信息。
  4. int8 group-wise 支持面扩大moe_wna16.py):移除 group_size == -1 断言后,kInt8StaticGroupScale 会覆盖所有 group size,但 kernel 是否完整支持任意 group size(如非 128 的 group)未在本 PR 中验证,存在潜在未覆盖路径。
  5. 缺少自动化测试:两个手工 serve 命令依赖特定模型权重(体积大),难以进入常规 CI,后续重构容易再次打破这些路径。
  • 用户影响:ROCm 平台上 int4/int8 量化 MoE 模型(尤其 AWQ-INT4 与 GPTQ-Int8 带 group 或 zero point 的模型)从“无法启动/断言失败”恢复为可用,直接解锁 MiniMax-M3 与 Qwen3-235B 等模型在 AMD GPU 上的推理。
  • 系统影响:改动触及 MoeWNA16Method 公共选择路径与 TRITON MoE 专家后端的方案声明,属于核心量化数据契约变更;对 NVIDIA 平台影响有限(TRITON 后端跨 CUDA/ROCm 共用,但本 PR 主要解锁 ROCm 场景)。
  • 团队影响:为后续在 TRITON MoE 后端支持非对称 WNA16 提供了参考实现(ZP 布局转换),可复用于其他模型接入;同时暴露了 #44120 迁移缺乏回归测试覆盖的问题。
缺少自动化测试覆盖 ZP 布局转换依赖 kernel 索引语义 新增 TP 配置拒绝逻辑 int8 group 支持面未验证

关联 Issue

#44120 [MoE Refactor] Migrate MoeWNA16Method quantization method over to using the new MK oracle scheme.

完整报告

参与讨论