Prhub

#43229 [CompressedTensors] FP4 Qutlass Integration

原始 PR 作者 kylesayrs 合并时间 2026-07-29 10:34 文件变更 7 提交数 40 评论 29 代码增减 +169 / -34

执行摘要

QUTLASS 内核集成 NVFP4 在线变换

原有 NVFP4 在线变换仅支持确定性 Hadamard 变换(hadacore),且依赖旧式自定义算子可能在未来 PyTorch 版本中报错。该 PR 通过 QUTLASS 内核支持一般块对角变换,覆盖更多量化配置,并修复算子兼容性问题。PR body 明确指出 'It supports any block-diagonal transform, and is not limited to deterministic hadamard transforms like hadacore'。

值得精读。该 PR 展示了如何安全包装第三方自定义算子、通过 torch.library.custom_op 拓展算子并注册假实现、以及将运行时计算融合到权重加载中。对从事量化 kernel 集成的工程师有直接的参考价值。
关注点safeFusedQuantizeNv 的设计模式;process_weights_after_loading 中的缩放因子推导;_get_flashinfer_gemm_backend 的动态后端推断。

讨论亮点
  • QUTLASS vs FlashInfer 后端选择:LopezCastroRoberto 建议使用 flashinfer_scaled_fp4_mm 替代直接调用 QUTLASS,brian-dellabetta 采纳并更新代码。后续进一步改为从 _LINEAR_BACKEND_KERNEL_MAP 动态推断后端名称,避免硬编码。
  • 自定义算子兼容性kylesayrs 注意到 fusedQuantizeNv 在 torch 2.12+ 中可能触发「输出别名输入」错误,brian-dellabetta 引入 safeFusedQuantizeNv 包装器,通过原地修改预分配张量解决。
  • 权重尺度融合kylesayrs 指出应将归一化尺度融合到权重中,避免运行时除法和额外显存占用。brian-dellabetta 实现为在 process_weights_after_loading 中调用 partition.data.div_(math.sqrt(n)),并使用 scaled_data_ptrs 集合确保每个数据指针仅缩放一次。
  • 设备能力检查mgoin 询问是否应使用 is_device_capability_family(精确匹配 100)而非 has_device_capability(>=100),最终代码保留 has 方式,因 QUTLASS 仅 Blackwell 可用但 >= 条件更宽松。
  • AI 建议错误gemini-code-assist[bot] 建议对 self.input_transform.weight 加下标索引,brian-dellabettakylesayrs 指正 weight 已是 SharedWeightParameter,可直接访问 partitions

实现拆解

  1. 方案选择与路由:修改 CompressedTensorsLinearTransformMethod.from_schemeslinear.py),当量化方案为 NVFP4、输入变换的块大小在 [16,32,64,128] 且 GPU 计算能力 >=100 时,优先选用 QutlassNvFP4LinearMethod,否则回退至 hadacore 或密集 GEMM。

  2. 权重后处理:在 linear_qutlass_nvfp4.py 中新增 process_weights_after_loading 方法,从 input_transform.weight.partitions[0] 读取 Hadamard 矩阵并存储在 layer.hadamard_matrix;计算融合缩放因子 fused_alpha = weight_global_scale / 6.0 以补偿 QUTLASS 与 compressed-tensors 的 scaling 差异;预生成 fused_global_scale 常量;并通过 _get_flashinfer_gemm_backend 根据 layer.scheme.kernel 类型动态推断 FlashInfer 后端名称(cute-dsl 等)。

  3. 前向计算apply 方法对输入 x 依次执行:fusedQuantizeNv(含 Hadamard 变换和 NVFP4 量化)、to_blocked(通过 Triton 重排缩放因子)、flashinfer_scaled_fp4_mm(调用 FlashInfer FP4 GEMM),最后通过 slice_nvfp4_output 提取有效输出。

  4. 自定义算子安全包装:在 _custom_ops.py 中新增 safeFusedQuantizeNv 自定义算子,其接收预分配的输出缓冲区并执行原地操作,避免直接返回 QUTLASS 算子输出导致的「输出不能别名输入」错误;同时注册 _fake_fused_quantize_nv 假实现以支持 torch.compile

  5. Triton 核函数自定义算子化:将 qutlass_utils.py 中的 triton_mx_block_rearrange 函数提升为 @torch.library.custom_op,并注册假实现 _triton_mx_block_rearrange_fake,确保其在 torch.compile 下正常工作。

  6. Hadamard 权重尺度融合:修改 HadamardTransformmodule.py),将归一化尺度 1/sqrt(n) 直接融合到权重矩阵中,移除运行时的 scales 字典和乘法,减少开销。

  7. 测试覆盖:在 test_compressed_tensors.py 中添加烟雾测试模型 nm-testing/Llama-3.2-1B-Instruct-quipv16-nvfp4,验证变换后 perplexity 合理。

文件 模块 状态 重要度
vllm/model_executor/layers/quantization/compressed_tensors/transform/schemes/linear_qutlass_nvfp4.py 量化层 modified 8.23
vllm/_custom_ops.py 自定义算子 modified 7.43
vllm/model_executor/layers/quantization/compressed_tensors/transform/module.py 变换模块 modified 6.8
vllm/model_executor/layers/quantization/qutlass_utils.py 量化工具 modified 6.56
vllm/model_executor/layers/quantization/compressed_tensors/transform/linear.py 变换工厂 modified 5.94
vllm/model_executor/layers/linear.py 线性层 modified 4.65
tests/quantization/test_compressed_tensors.py 集成测试 modified 3.82

关键符号

is_qutlass_fp4_scheme QutlassNvFP4LinearMethod.create_weights QutlassNvFP4LinearMethod.process_weights_after_loading QutlassNvFP4LinearMethod._get_flashinfer_gemm_backend QutlassNvFP4LinearMethod.apply safeFusedQuantizeNv _fake_fused_quantize_nv fusedQuantizeNv triton_mx_block_rearrange _triton_mx_block_rearrange_fake HadamardTransform.process_weights_after_loading HadamardTransform.forward HadamardTransform.__init__ CompressedTensorsLinearTransformMethod.from_schemes

关键源码片段

vllm/_custom_ops.py core-logic

新增 safeFusedQuantizeNv 自定义算子以解决 torch 2.12+ 兼容性,并修改 fusedQuantizeNv 以使用安全包装。

# vllm/_custom_ops.py ( 片段 )@torch.library.custom_op(
    "vllm::safeFusedQuantizeNv", mutates_args=("xh_e2m1", "xh_e4m3")
)
def safeFusedQuantizeNv(
    a: torch.Tensor,
    b: torch.Tensor,
    xh_e2m1: torch.Tensor,
    xh_e4m3: torch.Tensor,
    global_scale: torch.Tensor,
) -> None:
    """
    QUTLASS fusedQuantizeNv 的原地包装器。
    接受预分配的输出张量,避免 torch 2.12+ 中“输出不能别名输入”的错误。
    """
    torch.ops._qutlass_C.fusedQuantizeNvAbsMax(a, b, xh_e2m1, xh_e4m3, global_scale)
    return# 在 fusedQuantizeNv 中调用 safe 版本
def fusedQuantizeNv(
    a: torch.Tensor, b: torch.Tensor, global_scale: torch.Tensor
) -> tuple[torch.Tensor, torch.Tensor]:
    xh_e2m1 = torch.empty(...)
    xh_e4m3 = torch.empty(...)
    safeFusedQuantizeNv(a, b, xh_e2m1, xh_e4m3, global_scale)
    return xh_e2m1, xh_e4m3# 假实现,供 torch.compile 使用
if hasattr(torch.ops._qutlass_C, "fusedQuantizeNv"):
    @register_fake("vllm::safeFusedQuantizeNv")
    def _fake_fused_quantize_nv(
        a, b, xh_e2m1, xh_e4m3, global_scale
    ) -> None:
        return
vllm/model_executor/layers/quantization/compressed_tensors/transform/module.py refactor

HadamardTransform 重构:移除运行时 scales 字典,将归一化尺度融合到权重中,简化前向逻辑。

# vllm/model_executor/layers/quantization/compressed_tensors/transform/module.py ( 关键变更 )class HadamardTransform(torch.nn.Module):
    # 移除 scales 字典,改用 scaled_data_ptrs 追踪已缩放权重
    scaled_data_ptrs: set[int] = set()
​
    def __init__(self, transforms, layer, weight_loader, input_size_per_partition, output_partition_sizes):
        # ...
        for part_index, (scheme_name, scheme, args) in self.transforms.items():
            # 加载时使用模型默认精度,而非 scheme.precision
            self.weight.add_partition(
                part_index, data_key, size=(weight_size, weight_size),
                # dtype=scheme.precision # 移除
            )
​
    def process_weights_after_loading(self):
        """将归一化尺度融合到权重中,避免运行时除法。"""
        for part_id, partition in self.weight.partitions.items():
            self.weight.process_weights_after_loading()
            data_ptr = partition.data.data_ptr()
            # 确保每个数据指针只缩放一次
            if data_ptr not in HadamardTransform.scaled_data_ptrs:
                partition.data.div_(math.sqrt(partition.data.size(0)))
                HadamardTransform.scaled_data_ptrs.add(data_ptr)
        # 移除 scales 字典的填充
​
    def forward(self, value, part_id=0):
        if self.transforms[part_id].scheme.type == "hadamard":
            # hadacore 路径,无需手动缩放
            # ...
        else:
            # 密集路径,直接做 GEMM (已无 scale 乘法)
            weight = self.weight.partitions[part_id]
            # ...
            return dispatch_unquantized_gemm()(
                self, value.to(weight.dtype), weight, None
            ).to(value.dtype)

评论区精华

QUTLASS 方案判断条件泛化 设计

LopezCastroRoberto 建议使用 `flashinfer_scaled_fp4_mm` 替代直接调用 QUTLASS;brian-dellabetta 采纳并改为动态从 _LINEAR_BACKEND_KERNEL_MAP 推断后端。

结论:采用 flashinfer_scaled_fp4_mm + 动态后端推断,避免硬编码。 · 已解决

自定义算子输出别名问题 正确性

kylesayrs 指出 fusedQuantizeNv 在 torch 2.12+ 可能因输出别名输入触发错误;brian-dellabetta 引入 safeFusedQuantizeNv 原地包装器解决。

结论:新增 safeFusedQuantizeNv 自定义算子,使用预分配输出并标注 mutates_args。 · 已解决

权重归一化尺度融合 性能

kylesayrs 提出将运行时 scale 除融合到权重中以减少内存和计算;brian-dellabetta 在 process_weights_after_loading 中通过 data_ptr 追踪实现一次性缩放。

结论:移除 scales 字典,在权重后处理中直接 div 融入了归一化因子。 · 已解决

设备能力精确检查 正确性

mgoin 建议使用 `is_device_capability_family(100)` 替代 `has_device_capability(100)`,后者为 >=100。

结论:最终保留 `has` 实现,因 QUTLASS 需要 Blackwell (cc=100) ,但 >=100 亦可行。 · unresolved

Triton 核函数自定义算子化 设计

为使 triton_mx_block_rearrange 在 torch.compile 下正常,brian-dellabetta 将其转换为 @torch.library.custom_op 并注册假实现。

结论:成功自定义算子化,通过 register_fake 提供 fake 实现。 · 已解决

风险与影响

  1. QUTLASS 扩展依赖safeFusedQuantizeNv 依赖 torch.ops._qutlass_C.fusedQuantizeNvAbsMax,若 QUTLASS 未编译安装将导致 import 错误。当前通过 hasattr 检查有条件注册假实现,但实际调用若依赖缺失仍会崩溃。
  2. Blackwell GPU 限制:通过 has_device_capability(100) 限制,但其他 GPU 上不会启用,可能引起用户困惑:为何同一模型在不同 GPU 上行为不同。
  3. 回归 hadacore 路径:QutlassNvFP4LinearMethod 被设为默认,可能改变原有 hadacore 用户的精度/吞吐特性,需用户主动回退。
  4. 自定义算子 torch.compile 兼容:新增两个自定义算子(safeFusedQuantizeNvtriton_mx_block_rearrange)虽注册了假实现,但实际 graph capture 时仍需验证完整正确性。
  5. FlashInfer 版本同步_get_flashinfer_gemm_backend 中的 cutedsl -> cute-dsl 字符串替换依赖 FlashInfer 内部命名约定,版本升级可能失效。

用户影响:使用 compressed-tensors 导出的 NVFP4 量化模型(含任意块对角变换)将自动启用 QUTLASS 内核,预期在 Blackwell GPU 上获得更优困惑度和吞吐量。无需用户修改配置。
系统影响:引入新的 CUDA kernel 依赖(QUTLASS + FlashInfer),安装包体积增大。新增两个自定义算子,增加 torch.compile 知识库。
团队影响:为后续 MXFP4 QUTLASS 集成铺平了架构路径;HadamardTransform 的尺度融合简化了未来后端集成。

Blackwell GPU 依赖 QUTLASS 扩展要求 自定义算子兼容性

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论