Prhub

#43853 Feature: Enable Flashinfer non-gated MoE bf16

原始 PR 作者 amirkl94 合并时间 2026-06-17 22:32 文件变更 3 提交数 2 评论 5 代码增减 +30 / -8

执行摘要

Flashinfer 非门控 MoE bf16 支持

在 NVIDIA GB200 节点上,NeMo(Nemotron-3-Nano-30B-A3B-BF16)等非门控 MoE 模型此前无法利用 Flashinfer TRTLLM 高性能内核,只能回退到 Triton 后端。PR body 数据显示 Flashinfer 相比 Triton 带来约 15% 端到端吞吐提升(12212 vs 10468 tok/s)和更低的 TTFT(714ms vs 2960ms)。

值得精读。本 PR 展示了在统一 MoE 抽象层中新增非门控支持的典型步骤:能力声明、权重格式适配、后端编排和激活传递。设计决策(如对齐策略)经过 review 讨论,可作为类似扩展的参考。

讨论亮点

Reviewer tomeras91 提出了两个关键问题:

  1. 对非门控权重进行 128 对齐也影响了门控路径?作者 amirkl94 澄清这是预期行为,但大多数模型的 hidden_dim 已经是 128 的倍数,并将 padding 逻辑限制在非门控分支。
  2. 重复日志记录:align_moe_weights_for_fi 内部已经打印 padding 警告,不应再在调用处添加额外日志。作者同意移除重复日志。
    讨论最终全部 resolved,PR 获得 approval。

实现拆解

  1. 权重格式转换函数扩展(flashinfer_utils.py:为 convert_moe_weights_to_flashinfer_trtllm_block_layout 新增 is_gated_act_gemm 参数(默认 True),非门控时跳过 permute indices 的 offset 修正步骤,因为 w13 不需要拆分为 gate/up 两部分。
  2. 未量化 MoE 后端编排(unquantized.py:在 convert_to_unquantized_kernel_format 的 FLASHINFER_TRTLLM 分支中,识别非门控配置;当 is_act_and_mul=False 时,调用 align_moe_weights_for_fi 确保中间维度按 block_k=128 对齐(非门控 MoE 要求 strict alignment),并更新 moe_config.intermediate_size_per_partition;同时将 is_gated_act_gemm=False 传递给 convert_moe_weights_to_flashinfer_trtllm_block_layout
  3. Expert 类能力声明(trtllm_bf16_moe.py:将 _supports_no_act_and_mul() 从 False 改为 True,_supports_activation() 新增 MoEActivation.RELU2_NO_MULapply 方法中添加 assert 检查激活类型,并传递 activation_type=activation_to_flashinfer_int(activation) 给 flashinfer 内核。
  4. 对齐函数重命名(flashinfer_utils.py:将 align_fp8_moe_weights_for_fi 重命名为 align_moe_weights_for_fi,使其适用于未量化场景,并更新所有调用点(包括 FP8 量化路径)。
文件 模块 状态 重要度
vllm/model_executor/layers/quantization/utils/flashinfer_utils.py 权重工具 modified 7.12
vllm/model_executor/layers/fused_moe/experts/trtllm_bf16_moe.py MoE 内核 modified 6.2
vllm/model_executor/layers/fused_moe/oracle/unquantized.py 后端编排 modified 6.18

关键符号

convert_moe_weights_to_flashinfer_trtllm_block_layout align_moe_weights_for_fi convert_to_unquantized_kernel_format TrtLlmBf16Experts._supports_no_act_and_mul TrtLlmBf16Experts._supports_activation TrtLlmBf16Experts.apply

关键源码片段

vllm/model_executor/layers/quantization/utils/flashinfer_utils.py data-contract

核心函数 `convert_moe_weights_to_flashinfer_trtllm_block_layout` 新增 `is_gated_act_gemm` 参数,非门控时跳过 permute 偏移;`align_fp8_moe_weights_for_fi` 重命名为通用 `align_moe_weights_for_fi`。

# vllm/model_executor/layers/quantization/utils/flashinfer_utils.pydef convert_moe_weights_to_flashinfer_trtllm_block_layout(
    cache_permute_indices: dict[torch.Size, torch.Tensor],
    w13_weight: torch.Tensor,
    w2_weight: torch.Tensor,
    is_gated_act_gemm: bool = True, # 新增参数 : 非门控时跳过 offset 修正
) -> tuple[torch.Tensor, torch.Tensor]:
    # ... 省略部分代码 ...
    for i in range(num_experts):
        w13_expert_uint8 = w13_weight[i].view(torch.uint8)
        permute_indices = _maybe_get_cached_w3_w1_permute_indices(
            cache_permute_indices,
            w13_expert_uint8,
            epilogue_tile_m,
            is_gated_act_gemm=is_gated_act_gemm, # 传递参数给 flashinfer 内部
        )
        # 门控 MoE 需要调整 permute indices 以对齐 w3/w1 顺序
        if is_gated_act_gemm:
            rows = w13_expert_uint8.shape[0]
            permute_indices = (permute_indices + rows // 2) % rows
        _copy_permuted_expert_to_block_layout(
            w13_weights_shuffled_tensor[i],
            w13_expert_uint8,
            permute_indices,
        )
    # ...# 重命名 : 从 FP8 专用改为通用对齐函数,适用于未量化场景
def align_moe_weights_for_fi( # 原 align_fp8_moe_weights_for_fi
    w13: torch.Tensor,
    w2: torch.Tensor,
    is_act_and_mul: bool,
    min_alignment: int = 16,
) -> tuple[torch.Tensor, torch.Tensor, int]:
    """Pad intermediate size so FlashInfer kernels' alignment constraints hold."""
    # ... 逻辑不变,仅重命名和更新注释
vllm/model_executor/layers/fused_moe/experts/trtllm_bf16_moe.py data-contract

Expert 类声明支持非门控激活(RELU2_NO_MUL),并在 `apply` 中传递 `activation_type` 给 flashinfer 内核。

# vllm/model_executor/layers/fused_moe/experts/trtllm_bf16_moe.pyclass TrtLlmBf16Experts(mk.FusedMoEExpertsMonolithic):
    # ...
​
    @staticmethod
    def _supports_no_act_and_mul() -> bool:
        """BF16 kernels support non-gated MoE via RELU2_NO_MUL."""
        return True # 之前为 False,现在放开
​
    @staticmethod
    def _supports_activation(activation: MoEActivation) -> bool:
        """Supports SiLU (gated) and RELU^2 (non-gated) activations."""
        return activation in [MoEActivation.SILU, MoEActivation.RELU2_NO_MUL] # 新增 RELU2_NO_MUL
​
    def apply(self, ...) -> torch.Tensor:
        import flashinfer
​
        assert activation in [MoEActivation.SILU, MoEActivation.RELU2_NO_MUL] # 新增检查
​
        return flashinfer.fused_moe.trtllm_bf16_moe(
            # ... 其他参数不变
            activation_type=activation_to_flashinfer_int(activation), # 新增 : 传递激活类型给内核
        )
vllm/model_executor/layers/fused_moe/oracle/unquantized.py data-contract

在 FLASHINFER_TRTLLM 后端路径中处理非门控 MoE 的中间维度对齐,并传递 `is_gated_act_gemm` 标志。

# vllm/model_executor/layers/fused_moe/oracle/unquantized.pydef convert_to_unquantized_kernel_format(
    unquantized_backend: UnquantizedMoeBackend,
    layer: Module,
    w13_weight: torch.Tensor,
    w2_weight: torch.Tensor,
) -> tuple[torch.Tensor, torch.Tensor]:
    # ... 其他分支 ...
    elif unquantized_backend == UnquantizedMoeBackend.FLASHINFER_TRTLLM:
        is_act_and_mul = layer.moe_config.is_act_and_mul
        # 非门控 MoE 需要中间维度严格对齐到 128 (block_k)
        if not is_act_and_mul:
            w13_weight, w2_weight, padded_intermediate = align_moe_weights_for_fi(
                w13_weight, w2_weight, is_act_and_mul, min_alignment=128
            )
            # 更新配置,确保后续内核使用正确的中间大小
            layer.moe_config.intermediate_size_per_partition = padded_intermediate
​
        _cache_permute_indices: dict[torch.Size, torch.Tensor] = {}
        w13_weight, w2_weight = convert_moe_weights_to_flashinfer_trtllm_block_layout(
            _cache_permute_indices,
            w13_weight,
            w2_weight,
            is_gated_act_gemm=is_act_and_mul, # 非门控时传 False
        )
    return w13_weight.contiguous(), w2_weight.contiguous()

评论区精华

中间维度对齐是否影响门控 MoE 路径 正确性

tomeras91 质疑门控 MoE 路径也被 padding 到 128 对齐,这是否为有意的变更?

结论:作者解释说大部分模型的 hidden_dim 已经是 128 的倍数,并将 padding 逻辑限制在非门控分支,避免影响门控路径。 · 已解决

重复日志记录 style

tomeras91 指出 `align_moe_weights_for_fi` 内部已经打印 padding 警告,不应在调用处再添加额外日志。

结论:作者同意移除重复日志。 · 已解决

风险与影响

  1. 回归风险(低):门控 MoE 路径增加 is_gated_act_gemm 参数(默认 True)和中间维度对齐,但大多数模型的 hidden_dim 已经是 128 的倍数,且代码保持向后兼容。
  2. 性能风险(低):非门控路径的 padding 可能略微增加显存占用和计算量,但只在 hidden_dim 不是 128 倍数时发生,且性能提升(~15%)远大于此开销。
  3. 缺少测试覆盖:该变更未包含对应测试文件,风险中等。

对用户:NVIDIA Blackwell GPU 上的非门控 bf16 MoE 模型(如 NeMo)将自动获得 Flashinfer 加速,无需任何配置更改。
对系统:新增 activation_to_flashinfer_int 导入和 activation_type 参数传递,内核调用接口扩展。
对团队:重命名 align_fp8_moe_weights_for_fi 为通用函数,需确保 FP8 量化路径同步更新(已做),后续维护者需注意非门控 MoE 的激活对齐约束。

缺少对应测试 核心 MoE 路径变更 Blackwell GPU 专属

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论