Prhub

#39801 [ROCm][CI] Add missing quantization methods and fix online quant test failures

原始 PR 作者 AndreasKaratzas 合并时间 2026-04-28 04:08 文件变更 9 提交数 6 评论 6 代码增减 +50 / -23

执行摘要

补充 ROCm 量化注册并修复测试

PR body指出:Several quantization methods were failing on ROCm because they weren't registered in the platform's supported_quantization list, even though the underlying kernels already support them (either natively or through emulation fallbacks). 同时测试中峰值内存统计被is_cuda()门控从而在ROCm上未触发,且内存阈值硬编码为CUDA的格式,导致test_online_quant_peak_mem失败。

该PR已合并,值得所有维护ROCm后端的工程师精读。核心学习点包括:(1)平台抽象层如何通过supported_quantization列表控制量化方法可见性;(2)测试中通过is_cuda_alike()而非is_cuda()实现多平台兼容的模式;(3)get_current_memory_usage应使用max_memory_allocated而非total-free以确保准确性。对于关注Quark量化或MXFP4 MoE的开发者,quark_moe.py中的仿真条件设计值得参考。

讨论亮点

代码审查中有一重要讨论:

  • gfx950的FP8格式:gshtras指出gfx950 uses fp8_e4m3fn, not fp8_e4m3fnuz,且两种格式占用空间相同,若内存有差异应查找真正原因,而非简单调整阈值。
  • 作者回应:AndreasKaratzas承认差异源于ROCm与CUDA的内存计算方式不同,在将get_current_memory_usage实现对齐后内存增量消失,不再需要调整阈值。
    • 该讨论促成了get_current_memory_usage方法的改进,间接提高了内存统计的准确性。

实现拆解

实现拆解分为6步:

  1. 注册缺失量化方法到平台支持列表vllm/platforms/rocm.py
    RocmPlatform.supported_quantization中添加fp8_per_tensorfp8_per_blockonlinemxfp8modeloptmodelopt_mxfp8modelopt_mixedgpt_oss_mxfp4(原有顺序调整)。这些方法已有对应的ROCmFP8ScaledMMLinearKernelEmulationMxfp8LinearKernel(#39205)等kernel。

  2. 修复峰值内存日志与计算方式vllm/platforms/rocm.pyvllm/model_executor/model_loader/base_loader.py
    base_loader.py中将is_cuda()条件改为is_cuda_alike()使日志在ROCm上输出;在rocm.py中将get_current_memory_usagetotal - free改为torch.cuda.max_memory_allocated(device)(带前缀empty_cache()清零)、保持与CUDA一致的内存统计语义。

  3. 修复native路径的bias遗漏vllm/model_executor/layers/quantization/quark/schemes/quark_ocp_mx.py
    apply_weightsgemm_with_dynamic_quant分支缺少bias加法,现在在外部补加y = y + bias if bias is not None,使native路径行为与emulate路径的F.linear一致。

  4. 严格化MoE仿真条件vllm/model_executor/layers/quantization/quark/quark_moe.py
    将原先not self.ocp_mx_scheme.startswith('w_mxfp4')替换为self.ocp_mx_scheme not in ('w_mxfp4',)常量,明确只有纯w4方案允许跳过仿真,混合方案(如w_mxfp4_a_mxfp6_*)强制走仿真避免不支持的kernel dispatch。

  5. 处理1-D scaling張量兼容性vllm/model_executor/kernels/linear/scaled_mm/pytorch.py
    TorchFP8ScaledMMLinearKernel.apply_scaled_mm中对BsAs增加维度检测:若为一维则reshape为(1, n)或(-1, 1),满足torch._scaled_mm的rowwise契约。

  6. 修复测试中CPU LAPACK依赖tests/quantization/test_turboquant.py
    generate_rotation_matrix将QR分解移至GPU执行,因为ROCm PyTorch wheel不带CPU LAPACK。

配套测试调整:tests/quantization/utils.py中的内存阈值改为平台无关;tests/quantization/test_configs.pytests/quantization/test_mixed_precision.pyis_cuda()改为is_cuda_alike()

文件 模块 状态 重要度
vllm/platforms/rocm.py 平台层 modified 5.95
vllm/model_executor/layers/quantization/quark/schemes/quark_ocp_mx.py 量化层 modified 6.35
vllm/model_executor/kernels/linear/scaled_mm/pytorch.py 线性层 modified 5.72
vllm/model_executor/model_loader/base_loader.py 模型加载 modified 5.1
tests/quantization/test_turboquant.py 测试 modified 4.23
tests/quantization/utils.py 测试工具 modified 4.07
tests/quantization/test_configs.py 测试 modified 3.59
tests/quantization/test_mixed_precision.py 测试 modified 3.25
vllm/model_executor/layers/quantization/quark/quark_moe.py 量化层 modified 6.22

关键符号

apply_weights __init__ apply_scaled_mm get_current_memory_usage verify_quantization generate_rotation_matrix

关键源码片段

vllm/platforms/rocm.py core-logic

核心变更:添加 8 种量化方法到 supported_quantization 列表,并重构 get_current_memory_usage 内存计算方法,直接影响 ROCm 上所有量化方法的可用性和峰值内存日志的正确性。

class RocmPlatform(Platform):
    # ... ( 其他成员 )
    supported_quantization: list[str] = [
        "awq",
        "awq_marlin",
        "gptq",
        "gptq_marlin",
        "fp8",
        "compressed-tensors",
        "fbgemm_fp8",
        "gguf",
        "quark",
        "mxfp4",
        "mxfp8", # 新增:MXFP8 量化
        "torchao",
        "bitsandbytes",
        "modelopt", # 新增:ModelOpt 基础框架
        "modelopt_fp4",
        "modelopt_mxfp8", # 新增:ModelOpt MXFP8
        "modelopt_mixed", # 新增:ModelOpt 混合精度
        "fp8_per_tensor", # 新增:每张量 FP8 缩放
        "fp8_per_block", # 新增:每块 FP8 缩放
        "online", # 新增:在线量化
        "gpt_oss_mxfp4",
    ]
​
    @classmethod
    def get_current_memory_usage(
        cls, device: torch.types.Device | None = None
    ) -> float:
        # 先清空缓存再重置峰值计数器,确保统计的是模型加载后的峰值
        torch.cuda.empty_cache()
        torch.cuda.reset_peak_memory_stats(device)
        # 使用 max_memory_allocated 返回峰值分配内存,
        # 而非 total - free(后者受 HIP 运行时等后台开销影响)
        return torch.cuda.max_memory_allocated(device)
vllm/model_executor/layers/quantization/quark/schemes/quark_ocp_mx.py data-contract

修复 native 路径下 gemm_with_dynamic_quant 缺失 bias 的问题,确保与 emulate 路径行为一致,影响所有使用 OCP_MX 方案的线性层(如 qkv_proj)。

def apply_weights(
    self,
    layer: torch.nn.Module,
    x: torch.Tensor,
    bias: torch.Tensor | None = None,
) -> torch.Tensor:
    if self.emulate:
        # 仿真路径:先反量化权重、量化再反量化激活,然后使用 F.linear
        dq_w = self.dequant_func(layer.weight, layer.weight_scale, x.dtype)
        qdq_x = self.quant_dequant_func(x)
        return F.linear(qdq_x, dq_w, bias)
    # native 路径:调用自定义 GEMM 算子(无 bias 参数)
    y = torch.ops.vllm.gemm_with_dynamic_quant(
        x,
        layer.weight,
        layer.weight_scale,
        self.rocm_use_aiter_fp4_asm_gemm,
        self.out_dtype,
    )
    # gemm_with_dynamic_quant 不携带 bias,手动补加使其与
    # F.linear 行为一致(例如 qkv_proj 使用 qkv_bias=True 时)
    if bias is not None:
        y = y + bias
    return y
vllm/model_executor/layers/quantization/quark/quark_moe.py data-contract

修复 MoE 仿真条件,避免不支持的 OCP MX 方案错误地跳过仿真导致 kernel dispatch 失败。

# 在 QuarkOCP_MX_MoEMethod.__init__ 中
# 定义当前被 AITER 原生支持的 OCP MX 方案元组
# TODO(aiter): 待 rocm_aiter_fused_experts 扩展后更新此列表
_AITER_NATIVE_OCP_MX_SCHEMES = ("w_mxfp4",)self.emulate = (
    not current_platform.supports_mx()
    or self.ocp_mx_scheme not in _AITER_NATIVE_OCP_MX_SCHEMES
) and (
    self.mxfp4_backend is Mxfp4MoeBackend.NONE or not self.use_rocm_aiter_moe
)
# 原先使用 `not self.ocp_mx_scheme.startswith("w_mxfp4")`,
# 会错误地将 w_mxfp4_a_mxfp6_* 等混合方案也判为 native,
# 但 AITER kernel 不支持这些混合方案,会导致 QuantMethod.NO 错误。
# 改用显式元组后只有纯 w4a16 方案能走 native,其余走仿真。

评论区精华

gfx950 FP8 格式及内存阈值讨论 正确性

gshtras 评论指出 gfx950 使用 fp8_e4m3fn 而非 fp8_e4m3fnuz,且两种格式占用空间相同,内存差异应查找真正原因。AndreasKaratzas 回应差异源于内存计算方式不同,在修复 get_current_memory_usage 后内存增量消失,不再需要调整阈值。

结论:确认内存差异源于计算方式而非 FP8 格式,通过统一内存计算方法解决。 · 已解决

风险与影响

  1. 新增量化方法未经验证:虽然注册了列表,但部分方法(如modelopt_mxfp8)可能仅在仿真模式下可用,性能或数值精度未在PR中充分验证,可能出现推理结果错误。
  2. bias添加可能影响eager模式quark_ocp_mx.py的bias加法在已有联合bias的模型中工作正常,但若gemm_with_dynamic_quant内部已经处理bias则会导致重复添加。从当前逻辑看gemm_with_dynamic_quant无bias参数,因此是安全的,但需注意未来kernel变更。
  3. 内存计算改变对其他平台的影响get_current_memory_usage改为max_memory_allocated对CUDA平台同样生效,若CUDA那边之前依赖total - free的语义(例如计算峰值)可能发生变化,但新定义更符合“峰值分配”的意图。
  4. 仿真MoE性能回退quark_moe.py的仿真条件严格化后,w_mxfp4以外的MX方案(如w_mxfp4_a_mxfp6_*)将走仿真,在ROCm上性能可能下降超过90%(如Rohan138在评论中担心的),这是故意牺牲性能换取正确性。
  • 用户影响:ROCm平台用户现在可以使用包括fp8_per_tensormodelopt系列在内的多种量化方法,提升了推理精度和部署灵活性。test_online_quant_peak_mem在ROCm上通过,增强了CI可靠性。
  • 系统影响:无外部API或配置变更。
  • 团队影响:加强了ROCm平台与CUDA平台在量化支持上的对齐,后续新增量化方法时需同时更新平台列表。内存统计方式的统一减少了平台差异。
核心路径变更 仿真路径性能回退 测试阈值调整

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论