Prhub

#26255 [fix] Add support for flashinfer MOE A2A to Qwen3 BF16 model path

原始 PR 作者 djns99 合并时间 2026-07-01 16:59 文件变更 3 提交数 4 评论 23 代码增减 +112 / -0

执行摘要

支持 BF16 Qwen3 FlashInfer MOE A2A 路径

BF16 + DP attention + EP MoE + FlashInfer A2A + FlashInfer MOE Cutlass后端组合当前不受支持,这导致模型崩溃。此PR旨在启用该组合并修复相关的崩溃问题(参见PR body)。

值得精读,特别是 should_skip_post_experts_all_reduce 的设计权衡和CUDA kernel中的空tokens保护模式。该PR展示了针对分布式MoE+Attention组合的典型调试模式(all-reduce重复、CUDA边界条件)。

讨论亮点

关于FlashInfer版本依赖:Fridge003询问是否依赖新版本flashinfer(如0.6.13),djns99回答不依赖,只是修复现有集成中的bug。

关于sgl-kernel拆分为独立PR:Fridge003建议将sgl-kernel的修改单独提PR并先合并再发布kernel,以便测试。djns99解释合在一起的优点:所有现有用例不受影响(无回归),用户可提前使用功能,且仅在Blackwell上运行风险低。最终无需拆分,b8zhong和Fridge003均approve。

实现拆解

  1. 跳过重复all-reduce:在 python/sglang/srt/layers/moe/utils.pyshould_skip_post_experts_all_reduce 函数中新增条件:当 get_moe_a2a_backend().is_flashinfer() 为 True 时跳过后续all-reduce,因为FlashInfer A2A dispatcher的combine已包含alltoall-reduce,避免重复计算导致BF16溢出。该设计参照TRTLLM的 not enable_alltoall 逻辑。

  2. CUDA kernel保护:在 sgl-kernel/csrc/moe/moe_topk_softmax_kernels.cutopk_softmax 函数中增加早期返回(当 num_tokens == 0 时),添加对 num_expertstopk 非正的检查,并收集launch错误,防止DP下某些rank无token时触发非法参数错误。

  3. 集成测试:新增 test/registered/moe/test_flashinfer_a2a_cutlass.py,启动Qwen3-30B-A3B模型(B200x4, EP=4, DP=4, flashinfer_cutlass后端+flashinfer A2A后端),运行GSM8K评估并验证准确率>0.90。测试因依赖sgl-kernel发布暂被禁用(disabled标志),但本地已验证。

文件 模块 状态 重要度
test/registered/moe/test_flashinfer_a2a_cutlass.py 测试 added 7.69
python/sglang/srt/layers/moe/utils.py MoE 工具 modified 5.45
sgl-kernel/csrc/moe/moe_topk_softmax_kernels.cu CUDA 内核 modified 3.32

关键符号

should_skip_post_experts_all_reduce topk_softmax test_gsm8k

关键源码片段

test/registered/moe/test_flashinfer_a2a_cutlass.py test-coverage

新增集成测试,验证 Qwen3-30B-A3B 在 B200 上使用 FlashInfer Cutlass MoE + FlashInfer A2A + DP/EP 配置的 GSM8K 准确率。测试因 sgl-kernel 依赖暂被禁用,但提供了完整的启动参数和验证逻辑。

"""Test FlashInfer Cutlass BF16 MoE + FlashInfer alltoall on B200 with DP attention.Config: Qwen3-30B-A3B, B200x4, EP=4 DP=4, flashinfer cutlass + flashinfer a2a.
"""import os
import unittest
from types import SimpleNamespaceimport torchfrom sglang.srt.utils import kill_process_tree
from sglang.test.ci.ci_register import register_cuda_ci
from sglang.test.run_eval import run_eval
from sglang.test.test_utils import (
    DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH,
    DEFAULT_URL_FOR_TEST,
    CustomTestCase,
    popen_launch_server,
)# 注册到 CI extra-b 阶段,但暂 disabled 直至 sgl-kernel 修复发布
register_cuda_ci(
    est_time=600,
    stage="extra-b",
    runner_config="4-gpu-b200",
    disabled="Waived until sgl-kernel fix is released",
)MODEL = os.environ.get("QWEN3_30B_A3B_MODEL_PATH", "Qwen/Qwen3-30B-A3B")
SKIP_TEST = torch.cuda.get_device_capability() < (10, 0) # 仅 Blackwell
SKIP_REASON = "Requires Blackwell (B200, sm_100a) or above."
​
​
@unittest.skipIf(SKIP_TEST, SKIP_REASON)
class TestFlashinferCutlassFlashinferA2A(CustomTestCase):
    """FlashInfer Cutlass BF16 MoE + FlashInfer alltoall + DP4 EP4 on B200."""
​
    @classmethod
    def setUpClass(cls):
        cls.model = MODEL
        cls.base_url = DEFAULT_URL_FOR_TEST
        cls.process = popen_launch_server(
            cls.model,
            cls.base_url,
            timeout=DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH * 3,
            other_args=[
                "--trust-remote-code",
                "--tp", "4",
                "--ep-size", "4",
                "--dp", "4",
                "--enable-dp-attention",
                "--enable-dp-lm-head",
                "--moe-runner-backend", "flashinfer_cutlass", # 关键:使用 cutlass 后端
                "--moe-a2a-backend", "flashinfer", # 关键:使用 flashinfer A2A
                "--max-prefill-tokens", "4096",
                "--disable-radix-cache",
                "--disable-flashinfer-autotune",
                "--watchdog-timeout", "900",
            ],
        )
​
    @classmethod
    def tearDownClass(cls):
        kill_process_tree(cls.process.pid)
​
    def test_gsm8k(self):
        args = SimpleNamespace(
            base_url=self.base_url,
            eval_name="gsm8k",
            num_examples=1319,
            max_tokens=10240,
            repeat=1,
            num_threads=1319,
            num_shots=8,
            temperature=0.6,
            top_p=0.95,
            top_k=20,
        )
        metrics = run_eval(args)
        print(metrics)
        self.assertGreater(metrics["score"], 0.90)
python/sglang/srt/layers/moe/utils.py core-logic

核心逻辑变更:修改 `should_skip_post_experts_all_reduce` 函数,添加对 FlashInfer A2A 后端的检查,避免重复 all-reduce 导致 BF16 溢出。这是使得 BF16 模型路径可用的关键修复。

def should_skip_post_experts_all_reduce(
    *,
    is_tp_path: bool,
    use_reduce_scatter: bool = False,
    should_allreduce_fusion: bool = False,
) -> bool:
    # ... 原有注释和条件 ...
    if should_allreduce_fusion or use_reduce_scatter:
        return True
    if should_use_dp_reduce_scatterv():
        return True
    if is_tp_path and should_use_flashinfer_cutlass_moe_fp4_allgather():
        return True
    # 新增:当使用 FlashInfer A2A 后端时,其 combine 已包含 alltoall-reduce,
    # 后续 EP/TP all-reduce 会导致双倍累加并溢出 BF16。
    if get_moe_a2a_backend().is_flashinfer():
        return True
    return False
sgl-kernel/csrc/moe/moe_topk_softmax_kernels.cu core-logic

修正 DP 下某些 rank 没有 token 时 top_k softmax 崩溃的问题:添加早期返回 num_tokens==0 的 case,并增加 TORCH_CHECK 防止非法参数。

void topk_softmax(
    // ... parameters ...
) {
    const int num_tokens = static_cast<int>(gating_output.size(0));
    const int topk = static_cast<int>(topk_weights.size(-1));    // DP 下某些 rank 可能没有 token,直接返回避免 kernel launch 失败
    if (num_tokens == 0) {
        return;
    }    TORCH_CHECK(num_experts > 0, "num_experts must be greater than 0");
    TORCH_CHECK(topk > 0, "topk must be greater than 0");    // ... 原有计算逻辑 ...    // 在函数末尾添加 launch 错误检查,确保所有 CUDA 调用成功
    auto launch_error = cudaGetLastError();
    TORCH_CHECK(launch_error == cudaSuccess, "topk_softmax launch error: ", cudaGetErrorString(launch_error));
}

评论区精华

是否依赖新版本 FlashInfer question

Fridge003 询问 utils.py 中的改动是否依赖新版本 flashinfer ( 如 0.6.13)。

结论:djns99 回答不依赖,只是修复现有集成中的 bug。 · 已解决

是否将 sgl-kernel 修改拆分为独立 PR 设计

Fridge003 建议将 sgl-kernel 的修改单独提 PR,先合并并发布新版本,然后才能启用测试。

结论:djns99 认为无需拆分:所有现有用例仍被覆盖(无回归),用户可提前使用功能,且仅在 Blackwell 上运行风险低。同意保留合并。 · 已解决

风险与影响

回归风险:utils.py中all-reduce跳过逻辑的修改可能影响其他A2A后端(如NCCL),但当前is_flashinfer()检查明确限定了范围。sgl-kernel的改动仅添加边界检查和错误收集,不会改变正常路径行为。

测试覆盖不足:新增集成测试在CI中被禁用(disabled标志),直到下一版sgl-kernel发布。在此期间,该功能路径在CI中无覆盖,可能引入未发现的回归。需确保本地验证充分。

兼容性:功能仅在Blackwell GPU(B200, sm_100a)上支持,其他GPU会跳过测试。BF16溢出修复依赖于FlashInfer A2A dispatcher的行为,若未来该行为变化需同步更新。

用户影响:允许用户在BF16 Qwen3模型上使用FlashInfer A2A + Cutlass MoE组合,提升通信效率。该配置此前完全崩溃,故为正向影响。

系统影响:增加一个集成测试,但暂时禁用,不增加CI负担。utils.py的改动影响所有使用should_skip_post_experts_all_reduce的路径,但新条件只在特定配置下生效。

团队影响:需协调sgl-kernel发布以启用测试,或后续将此PR中的kernel改动提前发布。

核心路径变更 : all-reduce 跳过逻辑 缺少测试覆盖 : 新测试被禁用 CUDA kernel 保护 : topk_softmax 边界条件 仅 Blackwell 验证

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论