Prhub

#33205 [Kernel] Unify BaseFusedOp and MultiPlatformOp dispatch

原始 PR 作者 BBuf 合并时间 2026-08-06 08:52 文件变更 23 提交数 6 评论 3 代码增减 +1289 / -289

执行摘要

统一算子分派体系,MultiPlatformOp 并入 BaseFusedOp

RFC #29630 引入 BaseFusedOp 作为统一 sglang.kernels 命名空间下的 per-operator 多后端契约,但多平台分派、OOT 平台插件和 torch.compile enter/leave 协议仍由 sglang.srt.layers.utils.MultiPlatformOp 持有,两套抽象并存造成概念分裂。PR body 明确指出本 PR 的目标是完成 #29630 的收尾讨论以及 #26426(HAL)中“平台分派应统一进可扩展运行时基类”的设计方向,让 BaseFusedOp 成为唯一算子抽象。此外,旧实现的 torch.compile 特例依赖类名字符串匹配("FusedMoE" in cls.__name__),脆弱且曾漏掉 NPUMXFP8OnlineMoEMethod,需要替换为可覆盖的钩子。

值得精读:这是理解 sglang 算子抽象与 HAL 方向的核心 PR。重点看:分派优先级设计(fused_op.py 模块 docstring)、_defined_method MRO 判断、_torch_compile_forward 钩子替代类名匹配、MUSA 显式 opt-in 的原因分析(review 中有完整论证)。建议后续:补 AMD/XPU/NPU CI 覆盖,OOT 插件迁移窗口期后删除 alias。

讨论亮点

ErenAta16 做了独立审查并逐点验证:

  • 全量迁移核查:main 上 16 个引用 MultiPlatformOp 的文件全部被本 PR 覆盖,0 遗漏。
  • _defined_method 的 MRO 遍历停在 BaseFusedOp 是 load-bearing 逻辑:基类为每个 backend 提供 stub,hasattr 式检查会把所有 backend 报为可用;只有向下遍历 MRO 才能把“implements”变成真实谓词。
  • NPU 失败模式讨论:ErenAta16 最初担心 forward_npu 语义变化会导致 NPU 从 raise 变 silent,BBuf 以 main 分支代码证明 NPU/XPU/CPU 平台默认本就是 silent fallback;raise 的是 kernels 侧 torch_npu backend stub,改名 forward_torch_npu 后仍 raise。ErenAta16 确认撤回。
  • MUSA 链移除:BBuf 指出 srt 模块级 kernel 导入 gated on is_cuda()(如 gelu_tanh_and_mulrelu2),隐式链在 MUSA 上会 NameError 而非降级;ErenAta16 认为该论据优于他原来的“变慢”推理,且旧别名保留 forward_musa → forward_cuda 默认是对 OOT 子类的正确保护。

实现拆解

1. 重构 BaseFusedOp 为统一算子基类(python/sglang/kernels/fused_op.py

  • 从纯 ABC 改为 torch.nn.Module + ABC,调用经 nn.Module.__call__,使 forward hook 与模块遍历生效。
  • 新增 _PLATFORM_METHODS 分派表与 _platform_key() / _oot_dispatch_key() 惰性探测(lru_cache),import sglang.kernels 仍不触发平台探测。
  • BACKEND_METHODSTORCH_NPU 映射改名为 forward_torch_npu,避免与 NPU 平台方法命名冲突。
  • 分派优先级固定为:显式 backend= → 全局强制 backend(best-effort,带一次性警告)→ OOT 注册 / forward_<key> → 声明且 capability 匹配的优化后端 → 平台 forward(HIP 链 CUDA,CPU 需 AMX)→ forward_native;步骤 3-6 结果缓存在 self._forward_method

2. 迁移全部 in-repo 用户(activation.pylayernorm.pytopk.pydsa_indexer.py 等 10+ 文件)

  • 所有 class X(MultiPlatformOp) 改为 class X(BaseFusedOp)XIELUDualChunkRotaryEmbeddingIndexer 等无纯 torch 路径的类补齐 forward_native
  • TopK 与 Gemma norm 家族新增显式 forward_musa(MUSA 支持确凿的 op),保持 dispatch 不变。
  • UnquantizedFusedMoEMethod 保持 (FusedMoEMethodBase, BaseFusedOp) 多重继承,MRO 与 init 顺序不变。

3. torch.compile 协议统一(fused_op.py + tc_piecewise_cuda_graph_backend.py

  • enter_torch_compile(num_tokens) / leave_torch_compile() 移入 BaseFusedOp,保持幂等语义,退出时恢复原分派。
  • 类名匹配替换为可覆盖的 _torch_compile_forward(num_tokens) 钩子:TopKUnquantizedFusedMoEMethod 仅在 num_tokens == 1 时切 native。
  • tc_piecewise_cuda_graph_backend.py_toggle_multi_platform_ops 更名为 _toggle_fused_ops,改判 BaseFusedOp

4. 兼容层与行为收敛(multi_platform.pykernels/ops/layernorm/__init__.py

  • MultiPlatformOp 改为 BaseFusedOp 的 deprecated 子类,__init_subclass__DeprecationWarning,保留旧平台默认方法(forward_hip / forward_musaforward_cuda 等)保证 OOT 插件位级兼容。
  • register_oot_forward 单一注册表共享;forward_hpu 默认从新基类移除(旧别名保留);forward_npu(torch_npu backend 方法)重命名为 forward_torch_npu 后仍按需 raise。

5. 测试、基准与文档配套

  • 新增 test/registered/kernels/test_fused_op_dispatch.py(58 用例,mock 平台,CPU 可跑)覆盖优先级阶梯、nn.Module 契约、OOT 注册、编译协议、别名兼容。
  • 新增 test/manual/kernels/bench_fused_op_dispatch.py,实测分派开销较旧 MultiPlatformOp 仅 +38 ns/call;H200 上 Qwen2.5 --enable-torch-compile decode 212.66 vs 212.65 tok/s。
  • 更新平台接口文档、kernels/ops/layernorm/__init__.py 符号名。
文件 模块 状态 重要度
python/sglang/kernels/fused_op.py 算子基类 modified 8.84
python/sglang/srt/layers/utils/multi_platform.py 兼容层 modified 8.5
test/registered/kernels/test_fused_op_dispatch.py 分派测试 added 7.76
python/sglang/srt/layers/layernorm.py 归一化层 modified 7.37
python/sglang/srt/layers/activation.py 激活层 modified 7.03
python/sglang/srt/layers/moe/topk.py 路由算子 modified 6.91
python/sglang/srt/model_executor/runner_backend/tc_piecewise_cuda_graph_backend.py 图后端 modified 6.89
python/sglang/srt/layers/quantization/unquant.py 量化层 modified 6.37
test/manual/kernels/bench_fused_op_dispatch.py 基准测试 added 6.75

关键符号

BaseFusedOp.forward BaseFusedOp.enter_torch_compile BaseFusedOp.leave_torch_compile BaseFusedOp._torch_compile_forward BaseFusedOp.register_oot_forward _platform_key _oot_dispatch_key clear_platform_caches TopK._torch_compile_forward TopK.forward_musa UnquantizedFusedMoEMethod._torch_compile_forward MultiPlatformOp.__init_subclass__ _toggle_fused_ops

关键源码片段

python/sglang/kernels/fused_op.py core-logic

核心文件:BaseFusedOp 从纯 ABC 重构为 nn.Module,吸收 MultiPlatformOp 的全部分派职责,新增平台分派表、惰性探测、编译协议与强制后端 fallback 逻辑。

# python/sglang/kernels/fused_op.py —— 平台分派的核心配置与惰性探测
# 平台分派表:每个平台对应一组 forward 候选方法,按优先级排列。候选方法
# 只有在子类真正覆盖时才生效,否则分派自动落到 forward_native。
# 只有 HIP 保留隐式 CUDA 回退(ROCm 内核实为 hipified CUDA,sgl_kernel 同时
# 为两平台构建);MUSA 刻意不链入 forward_cuda —— srt 模块级 kernel 导入
# 由 is_cuda() 门控,在 MUSA 机器上隐式进入 forward_cuda 会因名字未导入
# 而 NameError,而不是优雅降级,因此 MUSA 需要显式 forward_musa 选择加入。
_PLATFORM_METHODS: Dict[str, Tuple[str, ...]] = {
    "cuda": ("forward_cuda",),
    "hip": ("forward_hip", "forward_cuda"), # 唯一的隐式 CUDA 链
    "musa": ("forward_musa",),
    "npu": ("forward_npu",),
    "xpu": ("forward_xpu",),
    "cpu": ("forward_cpu",),
}
​
​
@functools.lru_cache(maxsize=1)
def _platform_key() -> str:
    """进程内平台分派键;无优化平台时返回空字符串,走 native。    检查顺序与旧的 MultiPlatformOp 保持一致:CPU 仅当 AMX 可用时才算数
    (否则纯 torch 参考实现比假装有 CPU 优化路径更快)。
    """
    from sglang.srt.utils import (
        cpu_has_amx_support,
        is_cpu,
        is_cuda,
        is_hip,
        is_musa,
        is_npu,
        is_xpu,
    )
​
    if is_cuda():
        return "cuda"
    if is_hip():
        return "hip"
    if is_cpu() and cpu_has_amx_support():
        return "cpu"
    if is_npu():
        return "npu"
    if is_xpu():
        return "xpu"
    if is_musa():
        return "musa"
    return ""
​
​
@functools.lru_cache(maxsize=1)
def _oot_dispatch_key() -> Optional[str]:
    """当前 OOT 平台的分派键;in-tree 平台返回 None。"""
    from sglang.srt.platforms import current_platform
​
    if current_platform.is_out_of_tree():
        return current_platform.get_dispatch_key_name()
    return None
python/sglang/srt/layers/utils/multi_platform.py core-logic

MultiPlatformOp 从正式实现降级为 deprecated 别名,保留属性兼容与 OOT 插件默认方法,是 OOT 兼容性的关键文件。

# python/sglang/srt/layers/utils/multi_platform.py —— 兼容别名(核心片段)
# 旧类作为 BaseFusedOp 的 deprecated 子类保留:新代码必须直接继承
# BaseFusedOp,这个别名仅为 OOT 平台插件和外部用户提供迁移窗口,
# 未来版本会移除。
class MultiPlatformOp(BaseFusedOp):
    def __init_subclass__(cls, **kwargs):
        warnings.warn(
            "MultiPlatformOp is deprecated; subclass "
            "sglang.kernels.fused_op.BaseFusedOp instead (RFC #29630).",
            DeprecationWarning,
            stacklevel=2,
        )
        super().__init_subclass__(**kwargs)
​
    # 旧平台默认方法整体保留,位级兼容直接调用它们的插件子类:
    # forward_native 保持可实例化的 raise 语义,HIP/MUSA 仍链入 CUDA,
    # NPU/XPU/HPU/CPU 静默回落 native。这些方法只存在于别名上,
    # 不再污染新基类的平台分派语义。
    def forward_native(self, *args, **kwargs):
        raise NotImplementedError
​
    def forward_cuda(self, *args, **kwargs):
        raise NotImplementedError
​
    def forward_hip(self, *args, **kwargs):
        return self.forward_cuda(*args, **kwargs)
​
    def forward_musa(self, *args, **kwargs):
        return self.forward_cuda(*args, **kwargs)
​
    def forward_npu(self, *args, **kwargs):
        return self.forward_native(*args, **kwargs)
​
    def forward_xpu(self, *args, **kwargs):
        return self.forward_native(*args, **kwargs)
​
    def forward_hpu(self, *args, **kwargs):
        return self.forward_native(*args, **kwargs)
​
    def forward_cpu(self, *args, **kwargs):
        return self.forward_native(*args, **kwargs)

评论区精华

全量迁移核查:所有 MultiPlatformOp 用户是否已迁移 question

ErenAta16 独立核查 main 上 16 个引用 MultiPlatformOp 的文件,确认本 PR 全部覆盖,0 遗漏。

结论:无遗漏,迁移完整。 · 已解决

_defined_method 的 MRO 遍历是 load-bearing 逻辑 设计

ErenAta16 指出 MRO 遍历停在 BaseFusedOp 才使 available_backends() 成为真实谓词;基类 stub 存在时 hasattr 检查会误报所有 backend 可用。

结论:保留该 docstring,MRO 停止点语义不变。 · 已解决

NPU 失败模式是否从 raise 变 silent 正确性

ErenAta16 担心 forward_npu 改名改变 NPU 失败模式;BBuf 用 main 分支代码证明 NPU/XPU/CPU 平台默认本就是 silent fallback,raise 的是 torch_npu backend stub。

结论:ErenAta16 撤回,NPU 无行为变化。 · 已解决

移除 MUSA 隐式 CUDA 链的后果 设计

BBuf 指出 srt 模块级 kernel 导入 gated on is_cuda(),隐式链在 MUSA 上会 NameError 而非降级;TopK 与 Gemma norm 通过显式 forward_musa 保持 dispatch。

结论:MUSA 显式 opt-in,HIP 保留隐式链;ErenAta16 认可 NameError 论据更优。 · 已解决

全局强制后端改为 best-effort fallback 设计

SGLANG_FORCE_FUSED_OP_BACKEND 对未实现该后端的 op(如 DSA Indexer 强制 torch)从 raise 改为 fallback + 一次性警告,显式 backend= 保持 strict。

结论:采纳,数值二分开关可作用于整个模型。 · 已解决

风险与影响

  1. 多平台行为变化:MUSA 平台上未定义显式 forward_musa 的 op 从(隐式 CUDA 或 raise)变为静默 forward_native,依赖旧链的 OOT 自定义 op 可能出现数值/性能路径改变(in-repo 已确认无此问题)。
  2. 全局强制后端语义放宽:SGLANG_FORCE_FUSED_OP_BACKEND 对未实现该后端的 op 从 raise 变为 fallback + 一次性警告,依赖“强制必报错”的调试脚本需要适配(显式 backend= 仍 strict)。
  3. 命名空间导入面变化:sglang.kernels 顶层现在 import torch,任何导入该包的环境都需 torch 可用。
  4. NPU 编译路径行为变化:NPUMXFP8OnlineMoEMethod 因名字不含 FusedMoE,旧逻辑未享受 bs=1 特例,现在继承该钩子——在未实测的 NPU torch.compile 路径上属于行为修正,需要 NPU CI 覆盖。
  5. 硬件验证缺口:ROCm/HIP、Ascend NPU、XPU、MUSA 均无硬件实测,依赖 mock 测试与 AMD/NPU CI 通道。
  6. 性能风险低:dispatch 开销 +38 ns/call,相对 kernel 执行时间可忽略,Qwen2.5 实测 decode 无回归。

影响范围:所有算子层代码(activation / layernorm / topk / rotary / conv / moe)、torch.compile 与 tc_piecewise CUDA Graph 路径、OOT 平台插件生态。对开发者:新算子只需继承 BaseFusedOp,一个类同时表达 backend 与 platform 分派;MultiPlatformOp 在迁移期内仍可用但将被移除。对系统:分派逻辑集中化后,SGLANG_FORCE_FUSED_OP_BACKEND 数值二分调试可作用于整个模型(原来只覆盖 kernels/ops 实例)。对团队:消除 sgl-kernel 与 srt 层两套分派并存的历史包袱,是 HAL(#26426)落地的关键一步。

核心分派路径重构 MUSA 分派行为变化 OOT 插件依赖别名层 ROCm/NPU/XPU 无实机验证 强制后端语义放宽

关联 Issue

#26426 [RFC] Building towards a Hardware Abstraction Layer in SGLang
#29630 [RFC] Introduce a unified sglang.kernels namespace for kernel organization and dispatch

完整报告

参与讨论