Prhub

#27720 [DeepSeek V3] Defer moe finalize and fused it with main stream add

原始 PR 作者 kpham-sgl 合并时间 2026-06-13 10:00 文件变更 7 提交数 15 评论 16 代码增减 +743 / -36

执行摘要

熔合 MoE finalize 与 shared add,提升 DeepSeek V3 推理性能

Optimizing Kimi K2.5 NVFP4。通过推迟 MoE finalize 并与 shared output 加法融合,减少核启动开销和内存带宽,实测获得 1-2% TPOT 加速(参见 PR body speed tests)。

值得精读。该 PR 展示了如何通过推迟并融合算子来减少核启动开销,是典型的 LLM 推理优化模式。重点关注其使用 ContextVar 控制 deferred finalize 的设计,以及新 CUDA 核与 TVM JIT 的集成方式。后续可跟踪其正确性验证进展。

讨论亮点

Review 中主要讨论:

  • 默认启用与否:Qiaolin-Yu 询问是否可默认启用,kpham-sgl 回应因正确性考量(FIXME 注释)暂时默认为 False,并已创建 tracking issue。
  • 命名空间错误:b8zhong 指出 CUDA 核中命名空间 tokenspeed:: 应为 sglang::,kpham-sgl 确认修复。
  • 封装条件标志:Fridge003 建议将 deferred_finalize 的条件检查封装到全局标志,单 kpham-sgl 解释部分条件(如 topk_output.format)无法在 init 时确定,且当前实现安全支持 cudagraph 捕获。

实现拆解

  1. 引入 deferred finalize 基础设施:在 flashinfer_trtllm.py 中添加 FlashInferTrtllmDeferredFinalizeOutput 数据类和 flashinfer_trtllm_deferred_finalize_context 上下文管理器,通过 ContextVar 控制是否启用延迟 finalize。修改 fused_experts_none_to_flashinfer_trtllm_fp4 函数:当上下文启用且 topk 格式为 BYPASSED 时,跳过立即分配 output buffer 和 finalize,直接返回原始 gemm2 输出;否则保持原逻辑。
  2. 新增 JIT 融合核:创建 moe_finalize_fuse_shared.py 和对应的 CUDA 核 moe_finalize_fuse_shared.cu,该核一次完成 routed expert 输出的加权求和(finalize)与 shared output 加法,支持可选 PDL(Persistent Data Layout)优化。核的基于 flashinfer 的 finalizeKernel 修改,增加了共享输出残差加法。
  3. 调整 MoE 层执行入口:在 fused_moe_triton/layer.py 中添加 supports_deferred_finalize 属性(通过环境变量和 runner 后端判断)和 forward_deferred_finalize 方法,该方法在 dispatch 和 run_moe_core 过程中启用 deferred finalize 上下文,使 run_moe_core 跳过 finalize 直接返回 CombineInput。
  4. 修改模型前向:在 deepseek_v2.pyforward_normal_dual_stream 中,当满足条件(shared_output 存在、非 TP1 共享专家、topk 格式为 BYPASSED、支持 deferred finalize)时,调用 forward_deferred_finalize,然后在主流上调用 finalize_flashinfer_trtllm_deferred_output 完成 finalize+shared add 融合,替代原有的 maybe_fuse_routed_scale_and_shared_add。同时交换主/备流顺序:main stream 执行 routed experts,alt stream 执行 shared experts。
  5. 环境开关:在 environ.py 中添加 SGLANG_ENABLE_MOE_DEFERRED_FINALIZE 环境变量,默认 False,作为安全开关待正确性验证后默认启用。
文件 模块 状态 重要度
python/sglang/srt/layers/moe/moe_runner/flashinfer_trtllm.py MoE runner modified 8.39
python/sglang/jit_kernel/moe_finalize_fuse_shared.py JIT 核 added 7.94
python/sglang/jit_kernel/csrc/moe/moe_finalize_fuse_shared.cu CUDA 核 added 6.31
python/sglang/srt/models/deepseek_v2.py 模型前向 modified 6.99
python/sglang/srt/layers/moe/fused_moe_triton/layer.py MoE 层 modified 6.94
python/sglang/srt/environ.py 环境配置 modified 4.75
python/sglang/jit_kernel/csrc/moe/tvm_ffi_utils.h JIT 工具 added 7.1

关键符号

flashinfer_trtllm_deferred_finalize_context finalize_flashinfer_trtllm_deferred_output moe_finalize_fuse_shared forward_deferred_finalize forward_normal_dual_stream

关键源码片段

python/sglang/srt/layers/moe/moe_runner/flashinfer_trtllm.py core-logic

核心变更文件:添加 deferred finalize 数据类、上下文管理器和 finalize 函数,修改 fused_experts_none_to_flashinfer_trtllm_fp4 以支持延迟 finalize 路径。

# python/sglang/srt/layers/moe/moe_runner/flashinfer_trtllm.py# 引入 ContextVar 控制是否启用延迟 finalize
import contextvars
from contextlib import contextmanager
from dataclasses import dataclass
from typing import Generator
import torch# 延迟 finalize 是否激活的线程局部变量
_deferred_finalize_enabled: contextvars.ContextVar[bool] = contextvars.ContextVar(
    "flashinfer_trtllm_deferred_finalize_enabled", default=False
)
​
​
@dataclass
class FlashInferTrtllmDeferredFinalizeOutput:
    """
    存储延迟 finalize 所需的中间结果。
    gemm2_out: shape (num_tokens * topk, hidden_dim)
    expert_weights: shape (num_tokens, topk)
    expanded_idx_to_permuted_idx: shape (num_tokens * topk)
    top_k: int
    """
    gemm2_out: torch.Tensor
    expert_weights: torch.Tensor
    expanded_idx_to_permuted_idx: torch.Tensor
    top_k: int
​
​
@contextmanager
def flashinfer_trtllm_deferred_finalize_context(
    enabled: bool = True,
) -> Generator[None, None, None]:
    """
    上下文管理器,在 with 块内启用延迟 finalize。
    使用 ContextVar 确保多线程/协程安全。
    """
    token = _deferred_finalize_enabled.set(enabled)
    try:
        yield
    finally:
        _deferred_finalize_enabled.reset(token)
​
​
def finalize_flashinfer_trtllm_deferred_output(
    deferred_output: FlashInferTrtllmDeferredFinalizeOutput,
    shared_output: torch.Tensor,
) -> torch.Tensor:
    """
    在 main stream 上调用融合核,完成 finalize + shared add。
    引入 JIT 编译的 moe_finalize_fuse_shared 核。
    """
    from sglang.jit_kernel.moe_finalize_fuse_shared import moe_finalize_fuse_shared
    from sglang.jit_kernel.utils import is_arch_support_pdl
​
    return moe_finalize_fuse_shared(
        deferred_output.gemm2_out,
        deferred_output.expanded_idx_to_permuted_idx,
        deferred_output.expert_weights,
        shared_output,
        deferred_output.top_k,
        enable_pdl=is_arch_support_pdl(),
    )

fused_experts_none_to_flashinfer_trtllm_fp4 中插入了 defer 分支:

    # ... 之前逻辑 ...
    # 检查是否启用延迟 finalize
    defer_finalize = (
        _deferred_finalize_enabled.get()
        and not use_routed_topk
        and TopKOutputChecker.format_is_bypassed(topk_output)
    )
​
    symm_output = None
    if not defer_finalize:
        # 原逻辑:分配 output buffer 并立即 finalize
        num_tokens = hs_fp4.shape[0]
        hidden_size = (
            hs_fp4.shape[-1] * 2 if hs_fp4.dtype == torch.uint8 else hs_fp4.shape[-1]
        )
        _provided = _moe_output_buf.get()
        # ... 复用或新建 buffer ...
    # 若 defer_finalize 为 True,则跳过 output buffer 分配,gemm2 保持原始输出
    # 最终结果通过 finalize_flashinfer_trtllm_deferred_output 在主 stream 计算

python/sglang/jit_kernel/moe_finalize_fuse_shared.py core-logic

新增 JIT 融合核的 Python 包装,提供 moe_finalize_fuse_shared 函数,负责校验输入并调用 CUDA 核。

# python/sglang/jit_kernel/moe_finalize_fuse_shared.pyfrom __future__ import annotations
from typing import Optionalimport torch
from sglang.jit_kernel.utils import cache_once, load_jit
​
​
@cache_once
def _jit_module():
    """
    加载并缓存 JIT 编译的 CUDA 模块。
    依赖 cuda_files 中的 .cu 文件及 cutlass。
    """
    return load_jit(
        "moe_finalize_fuse_shared",
        cuda_files=["moe/moe_finalize_fuse_shared.cu"],
        extra_dependencies=["cutlass"],
        header_only=False,
    )
​
​
def moe_finalize_fuse_shared(
    gemm2_out: torch.Tensor,
    expanded_idx_to_permuted_idx: torch.Tensor,
    expert_weights: torch.Tensor,
    shared_output: Optional[torch.Tensor], # None 表示没有 shared expert
    top_k: int,
    enable_pdl: bool = False,
) -> torch.Tensor:
    """
    融合核:对每个 token t,计算
        out[t] = Σ_k expert_weights[t,k] * gemm2_out[permuted_idx(t,k)]
                 + shared_output[t]  (if not None)
    输入校验确保类型和维度正确。
    """
    assert gemm2_out.dtype == torch.bfloat16
    assert expert_weights.dtype in (torch.float32, torch.bfloat16)
    assert expanded_idx_to_permuted_idx.dtype == torch.int32
    assert gemm2_out.dim() == 2
    assert expert_weights.dim() == 2
​
    num_tokens, top_k_check = expert_weights.shape
    assert top_k_check == top_k
    hidden_dim = gemm2_out.shape[1]
​
    if shared_output is not None:
        assert shared_output.dtype == torch.bfloat16
        assert shared_output.dim() == 2
        assert shared_output.shape[0] == num_tokens
        hidden_dim = shared_output.shape[1]
        assert hidden_dim <= gemm2_out.shape[1]
​
    out = torch.empty(
        num_tokens, hidden_dim, dtype=torch.bfloat16, device=gemm2_out.device
    )
    if shared_output is None:
        shared_output = gemm2_out.new_empty((0, 0), dtype=torch.bfloat16)
​
    # 调用 JIT 编译的 CUDA 核
    _jit_module().moe_finalize_fuse_shared(
        out,
        gemm2_out,
        expanded_idx_to_permuted_idx,
        expert_weights,
        shared_output,
        int(top_k),
        bool(enable_pdl),
    )
    return out

评论区精华

是否默认启用 deferred finalize 设计

Qiaolin-Yu 建议默认启用,认为性能总是更好。kpham-sgl 回应因正确性顾虑(FIXME 注释)暂默认为 False,并已创建 tracking issue。

结论:保持默认关闭,待正确性验证后考虑开启。 · 已解决

CUDA 核命名空间错误 style

b8zhong 指出 CUDA 核中使用了 `tokenspeed::` 命名空间(从 flashinfer fork 的残留),应为 `sglang::`。kpham-sgl 确认修复。

结论:已修复,使用 `sglang::` 命名空间。 · 已解决

将条件检查封装到全局标志 设计

Fridge003 建议将 `deferred_finalize` 的条件检查封装到全局标志(如 utils.py 中的函数)。kpham-sgl 解释部分条件(如 `topk_output.format`)无法在 init 时确定,且当前实现安全支持 cudagraph 捕获。

结论:维持现有内联条件判断。 · 已解决

风险与影响

  1. 正确性风险:新路径仅在特定条件下生效(BYPASSED topk 格式、非 TP1 shared expert),若条件判断有误可能导致输出错误。目前环境变量默认关闭,待进一步测试。
  2. 兼容性风险:目前仅支持 flashinfer TRTLLM 后端 + NVFP4 量化方法,其他后端(如原生 Triton)不受影响。
  3. 性能风险:新 CUDA 核尚未在更广泛的模型和 GPU 架构上验证,可能存在非预期性能回退。
  4. 回归风险:若未来默认开启,需确保覆盖所有 MoE 层执行路径;目前仅依赖现有 DeepSeek 端到端测试,缺少针对性单元测试。

影响范围:限于 DeepSeek V3/R1 系列模型在使用 flashinfer TRTLLM 后端 + NVFP4 量化的解码阶段。影响程度:TPOT 提升 1-2%,单层耗时减少约 2us。对非 flashinfer TRTLLM 后端或无 NVFP4 的模型无影响。开发团队需关注后续正确性验证和默认开启计划。

新 JIT 融合核尚未广泛验证 正确性开关默认关闭 仅限 flashinfer TRTLLM+NVFP4 路径

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论