Prhub

#43328 Enable B12x backend for non-gated MoEs (like Nemotron)

原始 PR 作者 askliar 合并时间 2026-07-07 03:40 文件变更 3 提交数 5 评论 9 代码增减 +225 / -40

执行摘要

为 B12x 后端增加 ReLU2 非门控 MoE 支持

为了支持 Nemotron 等使用 ReLU2 激活的非门控 MoE 模型,需要扩展 FlashInfer B12x 后端。原实现仅支持 SiLU 门控 MoE,且直接使用低层 API。本 PR 采用 FlashInfer 的 B12xMoEWrapper 统一接口,简化集成并激活了 ReLU2 支持。PR body 明确指出 'Enable B12x backend for non-gated MoEs (like Nemotron)'。

本 PR 展示了如何扩展现有 MoE kernel 以支持新的激活函数,并采用 Wrapper API 统一集成。值得关注的设计决策包括:使用 _ACTIVATION_MAP 映射激活类型、延迟创建 Wrapper、在 process_weights_after_loading 中提前计算 MMA 布局。建议阅读 flashinfer_b12x_moe.py 中的 _ensure_wrapperapply 方法。

讨论亮点

缺失 Any 导入

  • gemini-code-assist[bot] 指出代码中使用了 Any 类型标注但未导入,会导致 NameError。作者后续 commit 已添加导入。

使用 out 参数避免多余分配

  • gemini-code-assist[bot] 建议在 wrapper.run 中传入 out=output 以复用缓冲区,减少张量分配。目前代码未采用此优化。

不必要 is_act_and_mul 参数

  • mgoin 评论 make_dummy_moe_config 中新增的 is_act_and_mul 参数冗余,可直接基于 MoEActivation 判断。作者回复 "Updated, thanks!" 并移除了该参数。

a2_gscale 可能为 None 的风险

  • depthfirst-app[bot] 指出当 a2_gscaleNone 时(W4A16 检查点),apply 会传递 None 给 wrapper 而非原先的单位张量,可能导致推理错误。该问题未在 PR 中得到明确解决。

实现拆解

  1. 激活映射与验证:在 FlashInferB12xExperts 类中添加 _ACTIVATION_MAP 字典(SILU→"silu", RELU2_NO_MUL→"relu2");在 __init__ 中根据 moe_config.activation 验证并存储 _activation_str;同时存储 Wrapper 构建所需的形状参数(global_num_experts, topk, hidden_dim 等),并延迟初始化 _wrapper
  2. Wrapper 封装:新增 _ensure_wrapper 方法,在首次 apply 调用时根据保存的配置创建 B12xMoEWrapper 实例,使用 _activation_str 选择内核。
  3. apply 重写:将 apply 中原来的 flashinfer_b12x_fused_moe 调用替换为 self._wrapper.run(...);移除了对应的导入;添加断言行确保 w1_sf_mmaw2_sf_mma 等已就绪。
  4. 能力声明更新_supports_activation 现在接受 RELU2_NO_MUL_supports_no_act_and_mul 返回 True_supports_parallel_config 在启用专家并行时返回 False(因 Wrapper 尚不支持 EP)。
  5. 测试配套:在 test_flashinfer_b12x_moe.py 中添加 _process_b12x_weights 辅助函数(模拟 process_weights_after_loading)和 test_flashinfer_b12x_moe_relu2 测试用例;在 utils.pymake_dummy_moe_config 中添加 activation 参数,使测试可灵活指定激活类型。
文件 模块 状态 重要度
vllm/model_executor/layers/fused_moe/experts/flashinfer_b12x_moe.py MoE 层 modified 7.8
tests/kernels/moe/test_flashinfer_b12x_moe.py MoE 测试 modified 6.95
tests/kernels/moe/utils.py 测试工具 modified 3.92

关键符号

_ensure_wrapper apply process_weights_after_loading _process_b12x_weights test_flashinfer_b12x_moe_relu2

关键源码片段

vllm/model_executor/layers/fused_moe/experts/flashinfer_b12x_moe.py core-logic

核心实现文件:添加激活映射、延迟 wrapper 创建、切换 apply 到 B12xMoEWrapper 接口。

class FlashInferB12xExperts(mk.FusedMoEExpertsModular):
    """FlashInfer CuteDSL fused MoE expert for SM12x (SM120/SM121, ...)."""
​
    # 激活类型到内核名称的映射
    _ACTIVATION_MAP: dict[MoEActivation, str] = {
        MoEActivation.SILU: "silu",
        MoEActivation.RELU2_NO_MUL: "relu2",
    }
​
    def __init__(
        self,
        moe_config: FusedMoEConfig,
        quant_config: FusedMoEQuantConfig,
    ):
        super().__init__(moe_config=moe_config, quant_config=quant_config)
        assert quant_config.quant_dtype == "nvfp4", (
            "FlashInferB12xExperts only supports nvfp4 quantization."
        )
        self.out_dtype = moe_config.in_dtype
        self.num_local_experts = moe_config.num_local_experts
        self.ep_rank = moe_config.moe_parallel_config.ep_rank
        self._fc2_input_scale: torch.Tensor | None = None
​
        # B12xMoEWrapper 构建所需的形状参数
        self.global_num_experts = moe_config.num_experts
        self.topk = moe_config.experts_per_token
        self.hidden_dim = moe_config.hidden_dim
        self.intermediate_size_per_partition = (
            moe_config.intermediate_size_per_partition
        )
        self.max_num_tokens = moe_config.max_num_tokens
        self.local_expert_offset = self.ep_rank * self.num_local_experts
​
        # 验证并记录激活类型字符串
        activation = moe_config.activation
        if activation not in self._ACTIVATION_MAP:
            raise ValueError(
                f"FlashInferB12xExperts does not support activation {activation!r}. "
                f"Supported: {list(self._ACTIVATION_MAP.keys())}"
            )
        self._activation_str = self._ACTIVATION_MAP[activation]
​
        # 延迟初始化的 wrapper 和 MMA 布局尺度
        self._wrapper: Any | None = None
        self.w1_sf_mma: torch.Tensor | None = None
        self.w2_sf_mma: torch.Tensor | None = None
​
    def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
        # 将权重全局尺度吸收进 block scale,使 w1_alpha = 1.0
        layer.w13_weight_scale.data = (
            layer.w13_weight_scale.float()
            * layer.w13_weight_scale_2.view(-1, 1, 1)
        ).to(layer.w13_weight_scale.dtype)
        layer.w13_weight_scale_2.data.fill_(1.0)
​
        layer.w2_weight_scale.data = (
            layer.w2_weight_scale.float()
            * layer.w2_weight_scale_2.view(-1, 1, 1)
        ).to(layer.w2_weight_scale.dtype)
        layer.w2_weight_scale_2.data.fill_(1.0)
​
        # 强制 a2_gscale 为 1.0,让内核使用动态每块量化
        if self.a2_gscale is not None:
            self.a2_gscale.fill_(1.0)
tests/kernels/moe/test_flashinfer_b12x_moe.py test-coverage

新增 ReLU2 测试覆盖,添加权重处理辅助函数,重构测试设置。

# 辅助函数:模拟 process_weights_after_loading 权重处理
# 用于测试中直接调用,避免重复代码
def _process_b12x_weights(
    experts: FlashInferB12xExperts,
    w1_scale: torch.Tensor,
    w2_scale: torch.Tensor,
    w1_scale_2: torch.Tensor,
    w2_scale_2: torch.Tensor,
) -> None:
    # 创建一个 SimpleNamespace 模拟 layer 对象
    layer = SimpleNamespace(
        w13_weight_scale=w1_scale,
        w13_weight_scale_2=w1_scale_2,
        w2_weight_scale=w2_scale,
        w2_weight_scale_2=w2_scale_2,
    )
    experts.process_weights_after_loading(layer)

评论区精华

缺失 Any 导入 正确性

gemini-code-assist[bot] 指出代码中使用了 Any 类型标注但未导入,会导致 NameError。

结论:作者后续 commit 添加了 from typing import Any。 · 已解决

使用 out 参数优化性能 性能

gemini-code-assist[bot] 建议在 apply 中通过 out=output 复用缓冲区,避免额外分配。目前代码未采用此优化。

结论:未被采纳;当前实现仍创建新张量。 · unresolved

不必要 is_act_and_mul 参数 设计

mgoin 评论 make_dummy_moe_config 新加的 is_act_and_mul 参数冗余,可直接检查 MoEActivation。

结论:作者回复 "Updated, thanks!" 并移除了该参数。 · 已解决

a2_gscale 可能为 None 的风险 正确性

depthfirst-app[bot] 指出当 a2_gscale 为 None 时,apply 会传递 None 给 wrapper,可能导致错误。该问题未在 PR 中得到明确解决。

结论:未修复,存在潜在风险。 · unresolved

风险与影响

  1. 量化尺度传递风险:当 a2_gscaleNone 时,apply 直接传递 None 给 wrapper,而 wrapper 可能不处理 None,导致推理异常。depthfirst-app 已指出但未修复。
  2. 专家并行限制_supports_parallel_config 对专家并行返回 False,分布式场景中无法使用该后端。
  3. 硬件依赖:仅支持 SM120+ 架构,运行时需检查 is_device_capability_family(120),不满足时跳过。
  4. 性能风险:切换到 Wrapper API 可能引入额外的间接调用开销,但 B12xMoEWrapper 设计上应更高效。综合风险可控。

用户影响:Nemotron 等使用 ReLU2 激活的 MoE 模型现在可在 B12x 后端(SM120+)上正确运行,获得 NVFP4 量化加速。系统影响FlashInferB12xExperts 类的构造函数和行为发生变化(延迟初始化、激活映射),所有依赖该类的模块需确保兼容。团队影响:新增的测试覆盖了 ReLU2 路径,有助于未来维护。兼容性:仍要求 FlashInfer 新版本(支持 B12xMoEWrapper)。

未处理边缘情况 硬件限制 潜在量化精度风险 缺少专家并行支持

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论