Prhub

#51038 [Bugfix][Quantization] Fix MXFP4 conversion for FlashInfer CUTLASS

原始 PR 作者 lucifer1004 合并时间 2026-08-06 14:19 文件变更 2 提交数 2 评论 0 代码增减 +137 / -2

执行摘要

修复 FlashInfer CUTLASS 后端 MXFP4 权重转换缺失

PR body 明确指出:"The MXFP4 backend selector can choose FlashInfer CUTLASS for standard checkpoints, but convert_weight_to_mxfp4_moe_kernel_format has no corresponding branch and raises ValueError after model loading." 即后端选择器已经允许选中 FlashInfer CUTLASS,但权重转换入口缺少该分支,导致标准 MXFP4 checkpoint 在模型加载阶段直接失败。

值得精读。核心价值在于展示了多后端 MoE 量化权重转换的扩展模式:统一入口、按枚举分支布局交换与交错、用 monkeypatch 记录底层 interleave 调用来做纯 CPU 回归测试。对后续接入新后端或处理其他量化格式的开发者有直接参考意义。

讨论亮点

该 PR 来自 fork,claude[bot] 提示自动 review 被禁用,维护者可手动触发一次性审查;zyongye 直接 APPROVED。未产生人工讨论或 code review 评论,设计与实现靠 PR body 中的测试计划和 CI 保障。

实现拆解

  1. 定位入口vllm/model_executor/layers/fused_moe/oracle/mxfp4.pyconvert_weight_to_mxfp4_moe_kernel_format 是所有 MXFP4 MoE 后端的权重格式统一转换入口,原先只覆盖 DeepGEMM、Humming、Triton、AITER、XPU 与 emulation,新增 FLASHINFER_CUTLASS_MXFP4_BF16FLASHINFER_CUTLASS_MXFP4_MXFP8 分支。
  2. 布局交换:标准 checkpoint 的 fused gate/up 权重存储为 [w1; w3],而 FlashInfer CUTLASS 内核按 [w3; w1] 消费。新增分支用 flashinfer_utils.swap_w13_to_w31 对 w13 权重和 scale 做交换,bias 则通过 torch.chunktorch.cat 交换两半并统一转为 bfloat16,w2 bias 也转为 bfloat16
  3. 后端差异化交错:MXFP8 激活变体调用 flashinfer.block_scale_interleave 对 scale 做 block 级交错;BF16 激活变体调用 flashinfer.fused_moe.interleave_moe_weights_for_sm90_mixed_gemminterleave_moe_scales_for_sm90_mixed_gemm,对权重和 scale 都做 SM90 mixed-gemm 布局交错。
  4. 配套测试tests/kernels/moe/test_ocp_mx_moe.py 新增参数化测试 test_convert_standard_mxfp4_weights_for_flashinfer_cutlass,用 monkeypatch 替换 flashinfer 的 interleave 函数来记录调用,验证 [w3; w1] 交换结果、bias 的 BF16 转换,以及 MXFP8 变体不触发权重交错、BF16 变体触发两次权重交错。
  5. 错误信息与文档同步:更新 docstring 与最终 ValueError 的提示文本,将 FlashInfer CUTLASS 列入支持列表。
文件 模块 状态 重要度
vllm/model_executor/layers/fused_moe/oracle/mxfp4.py 量化转换 modified 7.18
tests/kernels/moe/test_ocp_mx_moe.py 回归测试 modified 6.21

关键符号

convert_weight_to_mxfp4_moe_kernel_format swap_w13_to_w31 test_convert_standard_mxfp4_weights_for_flashinfer_cutlass record_weight_interleave record_scale_interleave

关键源码片段

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

核心源码文件,新增 FlashInfer CUTLASS 两个变体的权重转换分支,决定模型加载后的权重布局与交错格式。

# vllm/model_executor/layers/fused_moe/oracle/mxfp4.py
# 新增分支:FlashInfer CUTLASS 两个变体
# 标准 checkpoint 存为 [w1; w3],FlashInfer CUTLASS 需要 [w3; w1],
# 权重、scale、bias 必须保持顺序一致后再做后端专属的交错。
elif mxfp4_backend in (
    Mxfp4MoeBackend.FLASHINFER_CUTLASS_MXFP4_BF16,
    Mxfp4MoeBackend.FLASHINFER_CUTLASS_MXFP4_MXFP8,
):
    # 先把 w13 权重与 scale 从 [w1; w3] 交换为 [w3; w1]
    w13_weight = swap_w13_to_w31(w13_weight.data)
    w13_weight_scale = swap_w13_to_w31(w13_weight_scale.data)
    if w13_bias is not None:
        # bias 同样按两半交换,并统一转成 BF16(内核消费格式)
        b1, b3 = torch.chunk(w13_bias.data, 2, dim=-1)
        w13_bias = torch.cat([b3, b1], dim=-1).to(torch.bfloat16)
    if w2_bias is not None:
        w2_bias = w2_bias.data.to(torch.bfloat16)
​
    if mxfp4_backend == Mxfp4MoeBackend.FLASHINFER_CUTLASS_MXFP4_MXFP8:
        # 激活也用 MXFP8 时,只需对 block scale 做 interleave
        from flashinfer import block_scale_interleave
​
        w13_scale_shape = w13_weight_scale.shape
        w13_weight_scale = block_scale_interleave(
            w13_weight_scale.view(torch.uint8)
        ).reshape(w13_scale_shape)
​
        w2_scale_shape = w2_weight_scale.shape
        w2_weight_scale = block_scale_interleave(
            w2_weight_scale.data.view(torch.uint8)
        ).reshape(w2_scale_shape)
    else:
        # 激活为 BF16 时,权重与 scale 都要做 SM90 mixed-gemm 交错
        from flashinfer.fused_moe import (
            interleave_moe_scales_for_sm90_mixed_gemm,
            interleave_moe_weights_for_sm90_mixed_gemm,
        )
​
        w13_weight = interleave_moe_weights_for_sm90_mixed_gemm(
            w13_weight.contiguous(), "fp4"
        )
        w2_weight = interleave_moe_weights_for_sm90_mixed_gemm(
            w2_weight.data.contiguous(), "fp4"
        )
        w13_weight_scale = interleave_moe_scales_for_sm90_mixed_gemm(
            w13_weight_scale.to(torch.uint8)
        )
        w2_weight_scale = interleave_moe_scales_for_sm90_mixed_gemm(
            w2_weight_scale.data.to(torch.uint8)
        )
​
    return (
        w13_weight,
        w2_weight,
        w13_weight_scale,
        w2_weight_scale,
        w13_bias,
        w2_bias,
    )
tests/kernels/moe/test_ocp_mx_moe.py test-coverage

新增参数化回归测试,覆盖 MXFP8 与 BF16 两个变体,用 monkeypatch 验证布局交换和 interleave 调用行为。

# tests/kernels/moe/test_ocp_mx_moe.py
# 参数化两个后端变体:MXFP8 激活不做权重交错,BF16 激活需要交错
@pytest.mark.parametrize(
    ("backend_name", "interleaves_weights"),
    [
        ("FLASHINFER_CUTLASS_MXFP4_MXFP8", False),
        ("FLASHINFER_CUTLASS_MXFP4_BF16", True),
    ],
)
@pytest.mark.skipif(not has_flashinfer(), reason="flashinfer is required")
def test_convert_standard_mxfp4_weights_for_flashinfer_cutlass(
    monkeypatch, backend_name, interleaves_weights
):
    # 用简单的整数张量做布局验证,形状为 [1, 4, 4] 等
    w13 = torch.arange(16, dtype=torch.uint8).reshape(1, 4, 4)
    w2 = torch.arange(8, dtype=torch.uint8).reshape(1, 4, 2)
    w13_scale = torch.arange(8, dtype=torch.uint8).reshape(1, 4, 2)
    w2_scale = torch.arange(4, dtype=torch.uint8).reshape(1, 4, 1)
    w13_bias = torch.arange(4, dtype=torch.float32).reshape(1, 4)
    w2_bias = torch.arange(4, dtype=torch.float32).reshape(1, 4)
    interleaved_weights = []
    interleaved_scales = []
​
    # monkeypatch 替换 flashinfer 的 interleave 函数,记录调用,
    # 避免在 CPU 测试中真正执行 CUTLASS 内核
    def record_weight_interleave(weight, quant_type):
        assert quant_type == "fp4"
        interleaved_weights.append(weight.clone())
        return weight
​
    def record_scale_interleave(scale):
        interleaved_scales.append(scale.clone())
        return scale
​
    monkeypatch.setattr("flashinfer.block_scale_interleave", record_scale_interleave)
    monkeypatch.setattr(
        "flashinfer.fused_moe.interleave_moe_weights_for_sm90_mixed_gemm",
        record_weight_interleave,
    )
    monkeypatch.setattr(
        "flashinfer.fused_moe.interleave_moe_scales_for_sm90_mixed_gemm",
        record_scale_interleave,
    )
    converted = convert_weight_to_mxfp4_moe_kernel_format(
        getattr(Mxfp4MoeBackend, backend_name),
        types.SimpleNamespace(),
        w13,
        w2,
        w13_scale,
        w2_scale,
        w13_bias,
        w2_bias,
    )
​
    # 核心断言:前两半与后两半交换,即 [w1; w3] -> [w3; w1]
    expected_w13 = torch.cat([w13[:, 2:], w13[:, :2]], dim=1)
    expected_w13_scale = torch.cat([w13_scale[:, 2:], w13_scale[:, :2]], dim=1)
    torch.testing.assert_close(converted[0], expected_w13)
    torch.testing.assert_close(converted[1], w2)
    torch.testing.assert_close(converted[2], expected_w13_scale)
    torch.testing.assert_close(converted[3], w2_scale)
    expected_w13_bias = torch.cat([w13_bias[:, 2:], w13_bias[:, :2]], dim=1).to(
        torch.bfloat16
    )
    torch.testing.assert_close(converted[4], expected_w13_bias)
    torch.testing.assert_close(converted[5], w2_bias.to(torch.bfloat16))
​
    # BF16 变体应触发权重交错,MXFP8 变体不应触发
    if interleaves_weights:
        assert len(interleaved_weights) == 2
        torch.testing.assert_close(interleaved_weights[0], expected_w13)
        torch.testing.assert_close(interleaved_weights[1], w2)
    else:
        assert not interleaved_weights
    assert len(interleaved_scales) == 2
    torch.testing.assert_close(interleaved_scales[0], expected_w13_scale)
    torch.testing.assert_close(interleaved_scales[1], w2_scale)

评论区精华

Fork PR 自动 review 被禁用 other

claude[bot] 提示该 PR 来自 fork,自动 review 被禁用,维护者可评论 @claude review 触发一次性审查。除该 bot 消息外没有人工评论。

结论:zyongye 直接 APPROVED,未产生实质技术讨论。 · closed

风险与影响

  1. 强依赖 flashinfer 私有接口block_scale_interleaveinterleave_moe_weights_for_sm90_mixed_gemminterleave_moe_scales_for_sm90_mixed_gemm 均为 flashinfer 内部 API,版本升级可能破坏布局假设,测试通过 monkeypatch 校验调用,无法验证真实内核的数值一致性。
  2. bias 精度降级:w13 bias 与 w2 bias 被转换为 bfloat16,若原 checkpoint 为 FP32 bias,会有精度损失;该转换与 FlashInfer CUTLASS 内核的消费格式强绑定,但测试未做端到端精度对比。
  3. 布局假设风险:交换逻辑假设标准 checkpoint 一定按 [w1; w3] 组织,若个别模型使用不同顺序(如 51125 中非门控 MoE 的 w13 特例),会静默产生错误结果;目前没有对模型源头的校验。

该 PR 修复了标准 MXFP4 checkpoint 在 FlashInfer CUTLASS 后端下无法加载的问题,直接影响 DeepSeek V4 等采用 MXFP4 权重的 MoE 模型在 NVIDIA SM90/SM100 GPU 上的可用性。改动局限在 oracle/mxfp4.py 的一个转换函数,未影响其他后端分支,对 XPU、ROCm、TRTLLM 等既有路径无回归面。团队后续在引入新的 FlashInfer CUTLASS 内核选项时无需改动此入口。

依赖 flashinfer 私有接口 缺少端到端精度验证 bias 精度降级为 BF16

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论