Prhub

#49580 Integrate CuTeDSL MoE for ReLU2 NVFP4

原始 PR 作者 danielafrimi 合并时间 2026-07-29 10:46 文件变更 3 提交数 2 评论 1 代码增减 +245 / -9

执行摘要

CuTeDSL MoE 后端支持 ReLU2 NVFP4 激活

根据 PR body,GSM8K passed at 0.9431 vs 0.9300 for Super NVFP4 model,说明集成 CuTeDSL MoE 并支持 ReLU2 激活可显著提升模型精度。A2A 基准测试显示高并发下吞吐量略优于 trtllm 后端。

推荐阅读。设计上根据激活类型条件处理权重布局是一个值得记录的模式;PR 配合清晰的注释和测试,方便后续扩展其他激活函数。

讨论亮点

作者 danielafrimi 在审查评论中解释了 prepare_nvfp4_moe_layer_for_flashinfer_cutedsl 中添加条件的原因:"Gated MoE has gate/up halves that need swapping/interleaving; ReLU2 non-gated only has the up projection, so doing that interleave would corrupt the layout"。这一设计确保了非门控激活时权重布局的正确性。

实现拆解

  1. prepare_nvfp4_moe_layer_for_flashinfer_cutedslflashinfer_fp4_moe.py)中,增加 if layer.activation.is_gated 判断:仅当激活为门控(如 SiLU)时才执行 w13 权重的交换和交织,非门控激活(如 ReLU2)跳过此步骤,避免破坏权重布局。
  2. FlashInferCuteDSLExperts 类(flashinfer_cutedsl_moe.py)中,_supports_activation 扩展支持 MoEActivation.RELU2_NO_MUL_supports_no_act_and_mul 改为返回 Trueapply 方法新增 activation_type 参数,通过 activation_to_flashinfer_int 转换后传递给底层 flashinfer_cute_dsl_fused_moe_nvfp4
  3. 新增测试文件 test_flashinfer_cutedsl_nvfp4_moe.py,包含 NVFP4 量化和反量化辅助函数,以及参数化测试 test_flashinfer_cutedsl_fp4_moe_relu2_no_mul,验证从 FP4 量化、权重准备到 MoE 前向的整体流程与 torch_moe 参考实现的一致性。
文件 模块 状态 重要度
tests/kernels/moe/test_flashinfer_cutedsl_nvfp4_moe.py MoE 测试 added 7.22
vllm/model_executor/layers/quantization/utils/flashinfer_fp4_moe.py 量化 modified 6.56
vllm/model_executor/layers/fused_moe/experts/flashinfer_cutedsl_moe.py MoE 专家 modified 6.05

关键符号

prepare_nvfp4_moe_layer_for_flashinfer_cutedsl _supports_no_act_and_mul _supports_activation apply _quantize_nvfp4_linear _dequantize_nvfp4_linear test_flashinfer_cutedsl_fp4_moe_relu2_no_mul

关键源码片段

tests/kernels/moe/test_flashinfer_cutedsl_nvfp4_moe.py test-coverage

新增测试,覆盖 RELU2_NO_MOL 激活与 NVFP4 量化的组合验证,确保 CuTeDSL MoE 后端正确定性。

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
"""Tests for FlashInfer CuTeDSL NVFP4 MoE."""from vllm.model_executor.layers.fused_moe.experts.flashinfer_cutedsl_moe import (
    FlashInferCuteDSLExperts,
)
​
​
def _quantize_nvfp4_linear(
    weight: torch.Tensor,
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
    # 对每个专家权重进行 FP4 量化
    weights_q = []
    scales = []
    global_scales = []
    for expert_weight in weight:
        # 计算 global scale,使得量化后最大绝对值适配 FP4 范围
        global_scale = (
            FLOAT8_E4M3_MAX * FLOAT4_E2M1_MAX / expert_weight.abs().max()
        ).to(torch.float32)
        weight_q, scale = ops.scaled_fp4_quant(
            expert_weight,
            global_scale,
            is_sf_swizzled_layout=False,
        )
        weights_q.append(weight_q)
        scales.append(scale)
        global_scales.append(global_scale)
    return torch.stack(weights_q), torch.stack(scales), torch.stack(global_scales)
​
​
def _dequantize_nvfp4_linear(
    tensor_fp4: torch.Tensor,
    tensor_sf: torch.Tensor,
    global_scale: torch.Tensor,
    dtype: torch.dtype,
) -> torch.Tensor:
    # 将 FP4 权重反量化回指定 dtype,用于与参考实现对比
    assert tensor_fp4.dtype == torch.uint8
    m, packed_k = tensor_fp4.shape
    k = packed_k * 2
    tensor_f32 = break_fp4_bytes(tensor_fp4, torch.float32)
    tensor_f32 = tensor_f32.reshape(m, k // 16, 16)
    tensor_sf = tensor_sf.view(torch.float8_e4m3fn).to(torch.float32)
    tensor_sf = tensor_sf[:, : k // 16] / global_scale
    return (tensor_f32 * tensor_sf.unsqueeze(-1)).reshape(m, k).to(dtype)
​
​
@pytest.mark.parametrize("m,n,k,e,topk", [(16, 128, 512, 4, 2)])
@pytest.mark.parametrize("dtype", [torch.bfloat16])
@torch.inference_mode()
def test_flashinfer_cutedsl_fp4_moe_relu2_no_mul(
    m: int, n: int, k: int, e: int, topk: int, dtype: torch.dtype, workspace_init,
):
    # 创建随机输入和权重
    hidden_states = torch.randn((m, k), device="cuda", dtype=dtype) / 10
    w1 = torch.randn((e, n, k), device="cuda", dtype=dtype) / 15
    w2 = torch.randn((e, k, n), device="cuda", dtype=dtype) / 15
    # 量化权重为 NVFP4 格式
    w1_q, w1_scale, w1_global_scale = _quantize_nvfp4_linear(w1)
    w2_q, w2_scale, w2_global_scale = _quantize_nvfp4_linear(w2)
    # 路由计算(topk 权重和索引)
    score = torch.randn((m, e), device="cuda", dtype=dtype)
    topk_weights, topk_ids, _ = fused_topk(hidden_states, score, topk, renormalize=False)
    # 使用非门控激活 (RELU2_NO_MUL) 配置 fake_layer
    activation = MoEActivation.RELU2_NO_MUL
    fake_layer = SimpleNamespace(activation=activation)
    # 准备 CuTeDSL 后端所需的权重格式
    (w13_cutedsl, w13_scale_cutedsl, w13_alpha, a1_scale,
     w2_cutedsl, w2_scale_cutedsl, w2_alpha, a2_scale) = (
        prepare_nvfp4_moe_layer_for_flashinfer_cutedsl(
            layer=fake_layer,
            w13=w1_q, w13_scale=w1_scale, w13_scale_2=(1.0 / w1_global_scale),
            a13_scale=a1_scale,
            w2=w2_q, w2_scale=w2_scale, w2_scale_2=(1.0 / w2_global_scale),
            a2_scale=a2_scale,
        )
    )
    # 后续步骤:实例化 expert 并调用 apply,然后与 torch_moe 对比(省略)
vllm/model_executor/layers/quantization/utils/flashinfer_fp4_moe.py data-contract

核心数据准备函数,新增条件判断区分门控 / 非门控激活,直接影响权重布局正确性。

def prepare_nvfp4_moe_layer_for_flashinfer_cutedsl(
    layer: "RoutedExperts",
    w13: torch.Tensor,
    w13_scale: torch.Tensor,
    w13_scale_2: torch.Tensor,
    a13_scale: torch.Tensor,
    w2: torch.Tensor,
    w2_scale: torch.Tensor,
    w2_scale_2: torch.Tensor,
    a2_scale: torch.Tensor,
) -> tuple[torch.Tensor, ...]:
    """
    准备 CuTeDSL 包装器所需的 NVFP4 权重。
    对于门控激活(如 SiLU),需要交换并交织 up/gate 行;
    对于非门控激活(如 ReLU2),保持原始行顺序不变。
    """
    from flashinfer.cute_dsl.utils import convert_sf_to_mma_layout
​
    # Global scaling factors (same as other FlashInfer backends).
    num_experts = w13.shape[0]
    a13_scale = a13_scale.max().to(torch.float32).repeat(num_experts)
    a2_scale = a2_scale.max().to(torch.float32).repeat(num_experts)
​
    # 仅在门控激活时进行行交换和交织操作
    if layer.activation.is_gated:
        half = w13.shape[1] // 2
        w13 = torch.cat([w13[:, half:], w13[:, :half]], dim=1)
        w13_scale = torch.cat([w13_scale[:, half:], w13_scale[:, :half]], dim=1)
        # 交织 up/gate 行 (group_size=64)
        w13 = interleave_linear_and_gate(w13, group_size=64, dim=1)
        w13_scale = interleave_linear_and_gate(w13_scale, group_size=64, dim=1)
​
    # 后续 scale 转换对两种激活通用
    w13_scale = swizzle_blockscale(w13_scale)
    E, M_padded, K_sf_padded = w13_scale.shape
    w13_scale_flat = w13_scale.reshape(E * M_padded, K_sf_padded)
    w13_scale = convert_sf_to_mma_layout(
        w13_scale_flat,
        m=M_padded,
        k=K_sf_padded * 16,
        num_groups=E,
        sf_vec_size=16,
    )
    # ... 类似处理 w2_scale,最后返回 8 个 tensor
vllm/model_executor/layers/fused_moe/experts/flashinfer_cutedsl_moe.py core-logic

专家层实现,明确支持 RELU2_NO_MOL 激活并传递激活类型给底层内核。

from vllm.model_executor.layers.quantization.utils.flashinfer_utils import (
    activation_to_flashinfer_int,
)class FlashInferCuteDSLExperts(mk.FusedMoEExpertsModular):
    # ...
​
    @staticmethod
    def _supports_no_act_and_mul() -> bool:
        # 非门控激活(如 ReLU2)不需要 act_and_mul,声明支持
        return True
​
    @staticmethod
    def _supports_activation(activation: MoEActivation) -> bool:
        # 支持 SiLU(门控)和 RELU2_NO_MUL(非门控)
        return activation in (MoEActivation.SILU, MoEActivation.RELU2_NO_MUL)
​
    def apply(
        self,
        output: torch.Tensor,
        # ... 其他参数
        activation: MoEActivation,
    ):
        # ... 原有逻辑
        # 将激活类型转换为 FlashInfer 内部整数标识,传递给底层 kernel
        flashinfer_cute_dsl_fused_moe_nvfp4(
            # ... 原有参数
            activation_type=activation_to_flashinfer_int(activation),
        )

评论区精华

权重准备中条件判断的必要性 正确性

作者 danielafrimi 在代码行注释中说明:门控 MoE 需要交换 / 交织 gate/up 两半部分,而 ReLU2 非门控只有 up 投影,执行交织会破坏布局。

结论:通过添加 `if layer.activation.is_gated` 条件分支解决,非门控激活跳过交换 / 交织步骤。 · 已解决

风险与影响

改动集中在 CuTeDSL MoE 后端,新增的条件判断与激活类型耦合。未来引入其他非门控激活需类似处理。测试覆盖 RELU2_NO_MUL 一种情况,足够但非穷尽。底层 FlashInfer 函数需支持 activation_type 参数,若依赖升级不兼容可能引发问题。

使用 --moe-backend flashinfer_cutedsl 且模型含 ReLU2 激活(如 Nemotron Super 系列)时,可获得精度提升(GSM8K +1.4%)。高并发下吞吐量略有提升,TTFT 在 256 并发下改善。构建依赖需要 FlashInfer CuTeDSL NVFP4 组件(SM100+)。

依赖底层 FlashInfer 函数签名 非门控激活扩展需额外测试

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论