Prhub

#36559 [Kernel] Add swap AB optimization to fused_moe_kernel

原始 PR 作者 xyang16 合并时间 2026-06-25 09:44 文件变更 2 提交数 3 评论 4 代码增减 +59 / -14

执行摘要

fused_moe_kernel 添加 swap AB 优化

PR body 指出“add swap AB optimization to fused_moe_kernel to make better use of WGMMA”,并引用类似 PR #20396。对于小 BLOCK_SIZE_M 情况(BLOCK_SIZE_M < 64),通过交换矩阵乘法顺序以提升 WGMMA 利用率。

该 PR 是值得精读的内核级优化,展示了如何通过调整矩阵乘法顺序利用 WGMMA 指令。建议关注 enable_swap_ab 的硬件条件,待 Blackwell 等新架构验证后应放宽限制。

讨论亮点

review 中主要讨论了 enable_swap_ab 中硬件能力检查的范围。

  • gemini-code-assist[bot] 建议将 current_platform.is_device_capability(90) 改为 current_platform.has_device_capability(90),以兼容 Blackwell(compute capability 10.0)等未来架构。
  • mgoin 询问“Is there a reason to restrict this to SM90? Should this potentially be >=90?”。
    根据 PR 最终状态,代码仍使用了 is_device_capability(90) 而非 has_device_capability(90),表明作者和 reviewer 可能认为该优化当前仅在 SM90 上验证有效,或出于保守考虑暂时限制。

实现拆解

  1. 新增 enable_swap_ab 条件函数(vllm/model_executor/layers/fused_moe/utils.py
    使用 @functools.lru_cache 缓存结果,当 current_platform.is_device_capability(90)BLOCK_SIZE_M < 64BLOCK_SIZE_N >= 64 时返回 True,否则返回 False。该函数被 fused_moe.py 导入并用于构造 SWAP_AB 编译时常量。

  2. 修改 fused_moe_kernel 函数签名(vllm/model_executor/layers/fused_moe/fused_moe.py
    新增 SWAP_AB: tl.constexpr 参数,用于在编译时决定是否采用交换后的矩阵乘法顺序。

  3. 调整内核内部计算逻辑

    • 指针计算:当 SWAP_AB=True 时,a_ptrsb_ptrs 的索引顺序交换,确保按 b[N,K] @ a[K,M] 寻址。
    • 累加器形状:累加器从 [BLOCK_SIZE_M, BLOCK_SIZE_N] 改为 [BLOCK_SIZE_N, BLOCK_SIZE_M]
    • mask 逻辑a_maskb_mask 的维度相应调整,例如 a_mask = (offs_k[:, None] < K - k * BLOCK_SIZE_K) & token_mask[None, :]
    • 量化支持:在 use_fp8_w8a8use_int8_w8a8 路径中,缩放因子的乘法顺序相应调整。
  4. 调用侧传递 SWAP_AB
    fused_moe.py 中调用内核的位置,通过 enable_swap_ab(BLOCK_SIZE_M, BLOCK_SIZE_N) 计算出 SWAP_AB 值并传入内核。

  5. 测试
    仅运行了现有单元测试 tests/kernels/moe/test_moe.py 确保通过,未新增专门测试。

文件 模块 状态 重要度
vllm/model_executor/layers/fused_moe/fused_moe.py MoE 内核 modified 7.31
vllm/model_executor/layers/fused_moe/utils.py MoE 工具函数 modified 6.35

关键符号

fused_moe_kernel enable_swap_ab

关键源码片段

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

核心内核函数 `fused_moe_kernel` 的指针计算、累加器形状、mask、量化累加等均根据 `SWAP_AB` 常量做分支处理。

# vllm/model_executor/layers/fused_moe/fused_moe.py
# (partial: 展示内核中 SWAP_AB 相关的核心分支 )@triton.jit
def fused_moe_kernel(
    ...
    SWAP_AB: tl.constexpr, # 新增:是否交换 A/B 的乘法顺序
):
    # -----------------------------------------------------------
    # 指针计算根据 SWAP_AB 分支
    if SWAP_AB:
        # 目标 : b[N,K] @ a[K,M]
        # a_ptrs 按 K 连续、M 跨步;b_ptrs 按 N 连续、K 跨步
        a_ptrs = a_ptr + (
            offs_k[:, None] * stride_ak +
            offs_token[None, :] // top_k * stride_am
        )
        b_ptrs = (
            b_ptr +
            off_experts * stride_be +
            (offs_bn[:, None] * stride_bn + offs_k[None, :] * stride_bk)
        )
    else:
        # 原始 : a[M,K] @ b[K,N]
        a_ptrs = a_ptr + (
            offs_token[:, None] // top_k * stride_am +
            offs_k[None, :] * stride_ak
        )
        b_ptrs = (
            b_ptr +
            off_experts * stride_be +
            (offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn)
        )
​
    # 累加器形状也根据 SWAP_AB 调整
    if SWAP_AB:
        accumulator = tl.zeros((BLOCK_SIZE_N, BLOCK_SIZE_M), dtype=tl.float32)
    else:
        accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
​
    for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
        if SWAP_AB:
            a_mask = (offs_k[:, None] < K - k * BLOCK_SIZE_K) & token_mask[None, :]
            b_mask = offs_k[None, :] < K - k * BLOCK_SIZE_K
        else:
            a_mask = token_mask[:, None] & (offs_k[None, :] < K - k * BLOCK_SIZE_K)
            b_mask = offs_k[:, None] < K - k * BLOCK_SIZE_K
        # ... load a, b ...
        if use_fp8_w8a8:
            if SWAP_AB:
                accumulator += tl.dot(b, a) * b_scale[:, None] * a_scale[None, :]
            else:
                accumulator += tl.dot(a, b) * a_scale[:, None] * b_scale[None, :]
        # ...
​
    # 输出存储前可能需要转置回原始形状?
    # 注释:尾部输出未展示,但 accumulator 形状与 SWAP_AB 一致

评论区精华

硬件能力检查的范围:应使用 is_device_capability(90) 还是 has_device_capability(90) 设计

gemini-code-assist[bot] 建议将 is_device_capability(90) 改为 has_device_capability(90) 以兼容未来架构(如 Blackwell)。mgoin 也询问是否应放宽至 >=90。

结论:最终代码保留了 is_device_capability(90),表明当前仅验证了 SM90,保守起见未开放到未来架构。 · 已解决

风险与影响

  1. 回归风险:修改了 fused_moe_kernel 核心路径,可能影响非 SM90 或大 BLOCK_SIZE_M 场景的正确性。目前仅通过现有单元测试,覆盖有限。
  2. 性能退化风险SWAP_AB 条件基于 BLOCK_SIZE_M < 64BLOCK_SIZE_N >= 64,若实际运行时条件误判,可能导致性能下降。microbench 显示 BLOCK_SIZE_M=64 时无退化,但其他参数组合未验证。
  3. 兼容性风险:当前仅支持 SM90,未来硬件如 Blackwell 需要手动修改代码才能启用。
  4. 量化路径差异:在 use_int8_w8a16use_int8_w8a8 等量化路径中,累加逻辑有分支,可能存在未覆盖的溢出或精度问题。
  • 用户:使用 Hopper 架构(如 H100/H200)且 MoE 层 BLOCK_SIZE_M < 64 的模型将获得 2%~4% 端到端吞吐提升,无用户配置变更。
  • 系统:无外部 API 或配置变化,仅内部内核优化。
  • 团队:代码可读性略有下降(多了一组分支),但通过 SWAP_AB 常量封装,维护成本可控。
  • 影响程度:中低,仅特定 GPU 架构和模型规模受惠。
核心路径变更 缺少测试覆盖 仅 SM90 验证 量化路径分支

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论