Prhub

#33997 Bump FlashInfer to 0.6.17 and remove Kimi K3 workarounds

原始 PR 作者 mmangkad 合并时间 2026-08-12 17:17 文件变更 19 提交数 14 评论 11 代码增减 +84 / -6496

执行摘要

升级 FlashInfer 至 0.6.17,移除 Kimi K3 的 cubin pool 与 DCP 补丁

PR body 仅一句话说明意图:"Bump FlashInfer to 0.6.17 and remove Kimi K3 workarounds"。结合代码上下文可知,FlashInfer 0.6.17 已官方发布 SiTU(TRT-LLM-gen)融合 MoE 内核,并使 trtllm_batch_decode_with_kv_cache_mla 的 enable_dcp 参数进入官方签名,因此此前 sglang 自维护的两套补丁机制不再必要:一是需要下载 1696 个 cubin 的私有 SiTU cubin pool(含头文件 staging 与 ctypes cubin loader 的 JIT 胶水层),二是对 flashinfer-python 源码打 DCP 运行时补丁(5639 行 diff)。升级到官方版本后可以整体删除这些 workaround,显著简化 Kimi K3 在 Blackwell 上的部署与 CI 维护,这也是标签中 release-highlight 与 high priority 的含义。

值得精读。这是"上游能力成熟后系统性清理下游 workaround"的样板 PR:以依赖升级为切入点,跨 kernels/srt/docker/ci/docs 五个层面联动删除,并用 E2E rerun 闭环验证。重点阅读 mxfp4.py 的官方 API 迁移方式(枚举替换魔法数字、元组返回值处理、tune_max_num_tokens 的引入)和 overrides.py 的默认策略简化;值得借鉴的是"显式版本门槛 + 删除即验证"的节奏。可关注的点:删除 6000+ 行后单元测试覆盖偏薄,数值等价性主要依赖 B300 E2E,后续若条件允许可补充针对官方 API 签名的契约测试。

讨论亮点

本 PR 没有 formal review 评论(review_comments_count = 0),讨论集中在 issue 评论中的 rerun-test 验证:

  • Kimi K3 B300 E2E 验证:mmangkad 请求 rerun-test test/registered/models_e2e/test_kimi_k3_b300.py,首轮 8-gpu-b300 失败(Run #31187714618),随后两轮 rerun 均通过(Run #31231537149、Run #31303924389),确认升级未破坏 Kimi K3 主链路。
  • spec eagle parity 回归:b8zhong 请求 rerun test/registered/spec/eagle/test_spec_eagle_parity.py1-gpu-h100 通过,推测解码 parity 不受影响。
  • test_penalty flaky 处理:Fridge003 请求 rerun test/registered/sampling/test_penalty.py1-gpu-5090 失败;随后合入 test: make penalty sampling deterministictest: skip flaky negative penalty case 两个 commit,将问题归因于测试自身的不确定性而非依赖升级。
  • 最终确认:Fridge003 在评论中确认 "extra test all passed"(Run #31559901107)并附上 Base test 链接(Run #31539861716),随后 APPROVE 合入。

实现拆解

本 PR 按以下步骤完成依赖升级与 workaround 清理:

  1. 提升依赖基线(强制门槛)

    • python/pyproject.tomlflashinfer-python 依赖提升到 0.6.17
    • python/sglang/srt/entrypoints/engine.py_set_envs_and_config 中,assert_pkg_version("flashinfer_python", ...) 的最低版本从 0.6.15.post1 改为 0.6.17,旧版本环境在启动期即被拦截。
    • python/sglang/srt/utils/common.py 同步更新 check_pkg_version_at_least 的 docstring 示例。
    • 提交历史中的 "Fix FlashInfer prefill planner compatibility" 对应 flashinfer_backend.py 的 1 行适配(上下文未提供更多细节,推测与 0.6.17 prefill planner 行为变化有关)。
  2. MoE 执行路径迁移到官方 API

    • python/sglang/srt/layers/quantization/mxfp4.pyapply 方法中,situ 分支从 sglang.kernels.ops.moe.trtllm_gen_moe 自定义包装器切换到 flashinfer.fused_moe.trtllm_fp4_block_scale_routed_moetrtllm_fp4_block_scale_moe
    • 使用 flashinfer.tllm_enumsActivationType / RoutingMethodType 枚举替换原模块中的魔法数字常量(ACTIVATION_SITU = 9ROUTING_DEEPSEEK_V3 = 2)。
    • 参数与返回结构适配:新增 gemm1_bias=Nonegemm1_clamp_limit=Nonegemm2_bias=Nonetune_max_num_tokens=next_power_of_2(x_quant.shape[0]);precomputed-topk 路径显式声明 routing_method_type=RoutingMethodType.TopK.value,bypassed-topk 路径保持 DeepSeekV3;返回值改为元组,defer_finalize 时解包 (gemm2_out, topk_weights, expanded_idx),否则取 result[0]
    • 移除了 situ_moe.available() 运行时校验与对应的安装指引报错分支。
  3. 删除 cubin pool JIT 机制

    • 删除 python/sglang/kernels/ops/moe/trtllm_gen_moe.py 整个文件(528 行),涉及 cubin_pool_dir_flashinfer_data_diravailable_stage_headers_cuda_home_setup_cubin_loader 等符号。
    • python/sglang/srt/environ.py 删除两处 SGLANG_TRTLLM_GEN_MOE_CUBIN_POOL 环境变量定义(Flashinfer 段与 Kimi-K3 段)。
  4. CI 与部署脚本简化

    • scripts/ci/cuda/ci_install_kimi_k3.sh 从 85 行精简到 14 行:删除 cubin 池下载、sha256 校验、1696 个 cubin 计数验证、FlashInfer DCP patch 应用与 GITHUB_ENV 持久化,只保留 transformers 5.12.1 symlink 兼容修复。
    • docker/kimi_k3/kimi_k3_cu12.Dockerfilekimi_k3_cu13.Dockerfile:移除 cubin pool 下载与 DCP patch 两层,改为直接安装官方 flashinfer-python==0.6.17flashinfer-cubin==0.6.17flashinfer-jit-cache==0.6.17+cu129 三件套并校验版本一致;docker/Dockerfile 同样有 49 行同类删除。
  5. 配置与文档联动

    • python/sglang/srt/arg_groups/overrides.py:删除 _KIMI_K3_DCP_PATCH_URL 常量;_require_kimi_k3_cutedsl_dcp_support 的报错改为要求 "FlashInfer 0.6.17 or newer";_kimi_k3_moe_runner_overrides 移除 cubin pool 可用性检查与安装指引,仅对 moe_runner_backend == "auto" 生效。
    • docs/src/snippets/configs/moonshotai/kimi-k3.jsx 删除 SGLANG_TRTLLM_GEN_MOE_CUBIN_POOL 环境变量项;docs/cookbook/autoregressive/Moonshotai/Kimi-K3.mdx_playground.jsx 同步更新。
  6. 测试配套

    • 主要验证依赖 E2E rerun:test_kimi_k3_b300.pytest_spec_eagle_parity.pytest_penalty.py
    • 合入前 Fridge003 补充两个测试稳定性 commit:test_penalty.py 使 penalty 采样确定性化并跳过 flaky 的 negative 用例;test_unified_mamba_views.py 有 1 行伴随改动(与本 PR 的因果关系不明确,可能是 merge main 带入)。
文件 模块 状态 重要度
python/sglang/srt/layers/quantization/mxfp4.py 量化层 modified 7.24
python/sglang/kernels/ops/moe/trtllm_gen_moe.py MoE 内核 removed 7.75
python/sglang/srt/arg_groups/overrides.py 参数覆盖 modified 6.94
scripts/ci/cuda/ci_install_kimi_k3.sh CI 脚本 modified 5.08
python/sglang/srt/environ.py 环境变量 modified 5.31
docker/kimi_k3/kimi_k3_cu12.Dockerfile 部署镜像 modified 4.38
python/sglang/srt/entrypoints/engine.py 引擎入口 modified 4.53
test/registered/sampling/test_penalty.py 采样测试 modified 3.52

关键符号

apply(mxfp4.py situ 分支迁移) cubin_pool_dir(已删除) available(已删除) _setup_cubin_loader(已删除) _kimi_k3_moe_runner_overrides _require_kimi_k3_cutedsl_dcp_support _set_envs_and_config

关键源码片段

python/sglang/srt/layers/quantization/mxfp4.py dependency-wiring

Kimi K3 MXFP4 MoE runner 的核心执行路径,从私有 trtllm_gen_moe 包装器切换到 FlashInfer 官方 API 与枚举,涉及参数、返回结构与校验逻辑的多处变更。

# FlashInfer 0.6.17+ 将 SiTU(TRT-LLM-gen)内核作为官方发布内容,
# 不再需要 sglang 自维护的 cubin pool 与 JIT 编译胶水层。
if is_flashinfer_available():
    from flashinfer import (
        nvfp4_block_scale_interleave,
        trtllm_fp4_block_scale_moe,
    )
    from flashinfer.fused_moe import trtllm_fp4_block_scale_routed_moe
    from flashinfer.fused_moe.core import get_w2_permute_indices_with_cache
    # 用官方枚举替换原 trtllm_gen_moe.py 中的魔法数字常量
    # (ACTIVATION_SITU = 9、ROUTING_DEEPSEEK_V3 = 2)。
    from flashinfer.tllm_enums import ActivationType, RoutingMethodType
​
    # SM90 mixed-input helpers 仍按版本 gating,避免旧版本直接 ImportError。
    try:
        from flashinfer.fused_moe import (
            interleave_moe_scales_for_sm90_mixed_gemm,
            interleave_moe_weights_for_sm90_mixed_gemm,
        )
        _FI_HAS_SM90_CUTLASS_MXFP4 = True
    except ImportError:
        interleave_moe_scales_for_sm90_mixed_gemm = None
        interleave_moe_weights_for_sm90_mixed_gemm = None
        _FI_HAS_SM90_CUTLASS_MXFP4 = False
else:
    _FI_HAS_SM90_CUTLASS_MXFP4 = False
if self.moe_runner_config.activation == "situ":
    # EP 是 cubin 内部的:每个 rank 计算本地专家切片
    # [offset, +num_local),由调用方 all-reduce;ep=1 即 TP 路径。
    local_expert_offset = layer.moe_ep_rank * layer.num_local_experts
​
    # 预计算路由(radix router 上游):跳过 in-op 路由内核,
    # 小 T 场景下 in-op 单 CTA 路由约 22 us/layer,外部 radix 约 6 us。
    if TopKOutputChecker.format_is_standard(topk_output):
        if prepared_packed_topk is not None:
            packed_topk = prepared_packed_topk
        else:
            packed_topk = PackTopkIds.execute(
                topk_output.topk_ids, topk_output.topk_weights
            )
​
        # 延迟 finalize(K3 forward_deferred_finalize):返回 finalize 输入
        # 而非已完成 finalize 的输出,由调用方决定何时收尾。
        defer_finalize = _deferred_finalize_enabled.get()
        result = trtllm_fp4_block_scale_routed_moe(
            topk_ids=packed_topk,
            routing_bias=None,
            hidden_states=x_quant,
            hidden_states_scale=x_scale,
            gemm1_weights=layer.w13_weight,
            gemm1_weights_scale=layer.w13_weight_scale,
            gemm1_bias=None,
            gemm1_alpha=layer.gemm1_alpha,
            # SiTU beta 是线性半 tanh clip;K3 将其存放在 gemm1_clamp_limit。
            gemm1_beta=layer.gemm1_clamp_limit,
            gemm1_clamp_limit=None,
            gemm2_weights=layer.w2_weight,
            gemm2_weights_scale=layer.w2_weight_scale,
            gemm2_bias=None,
            num_experts=layer.num_experts,
            top_k=packed_topk.shape[1],
            n_group=None,
            topk_group=None,
            intermediate_size=self.intermediate_size_per_partition,
            local_expert_offset=local_expert_offset,
            local_num_experts=layer.num_local_experts,
            routed_scaling_factor=None,
            # 官方 API 默认 FromLogits 路由,这里显式声明 TopK。
            routing_method_type=RoutingMethodType.TopK.value,
            activation_type=ActivationType.Situ.value,
            # 按 token 数向上取整到 2 的幂做 tune,替代原包装器的内置启发式。
            tune_max_num_tokens=next_power_of_2(x_quant.shape[0]),
            output=symm_output,
            do_finalize=not defer_finalize,
        )
        if defer_finalize:
            gemm2_out, topk_weights, expanded_idx = result
            result = FlashInferTrtllmDeferredFinalizeOutput(
                gemm2_out=gemm2_out,
                expert_weights=topk_weights,
                expanded_idx_to_permuted_idx=expanded_idx,
                top_k=packed_topk.shape[1],
            )
        else:
            # 官方 API 返回元组;finalize 已就地完成时取首个元素。
            result = result[0]
        return StandardCombineInput(hidden_states=result)
python/sglang/srt/arg_groups/overrides.py dependency-wiring

Kimi K3 的默认配置策略与 DCP 校验逻辑被简化:删除 cubin pool 可用性检查和 DCP patch 指引,错误信息改为要求 FlashInfer 0.6.17+。

@_register_for("KimiK3ForConditionalGeneration")
def _kimi_k3_moe_runner_overrides(server_args: Any, hf_config: Any) -> dict:
    # trtllm-gen 融合 MoE(flashinfer_mxfp4)在 decode(M=bs)与
    # target-verify(M=bs*(gamma+1))两种阶段都优于 marlin(SM100/SM103)。
    # FlashInfer 0.6.17+ 已将 SiTU 内核作为固定依赖发布,
    # 因此不再需要校验私有 cubin pool,也不再输出安装指引。
    if server_args.moe_runner_backend != "auto":
        # 显式选择非 FlashInfer runner(如 marlin)时不做自动覆盖。
        return {}
    if not (is_sm100_supported() and get_device_sm() in (100, 103)):
        # 非 Blackwell 100/103 不启用该路径。
        return {}
    if not _is_mxfp4_pack_quantized(hf_config):
        # 权重不是 MXFP4 打包格式时不参与。
        return {}
    logger.info(
        "Kimi-K3 on SM100/SM103: moe_runner_backend=flashinfer_mxfp4 "
        "(FlashInfer SiTU kernels)."
    )
    return {"moe_runner_backend": "flashinfer_mxfp4"}

评论区精华

Kimi K3 B300 E2E 测试 rerun 测试

mmangkad 请求 rerun test/registered/models_e2e/test_kimi_k3_b300.py,首轮 8-gpu-b300 失败(Run #31187714618),随后两次 rerun 均通过(Run #31231537149、Run #31303924389)。

结论:B300 E2E 最终通过,确认升级未破坏 Kimi K3 主链路。 · 已解决

test_penalty.py 在 5090 上失败与确定性修复 测试

Fridge003 请求 rerun test/registered/sampling/test_penalty.py,1-gpu-5090 失败;随后合入两个 commit:使 penalty 采样确定性化、跳过 flaky 的 negative penalty 用例。

结论:通过测试自身确定性修复解决 flaky,与依赖升级无直接因果关系。 · 已解决

spec eagle parity 回归验证 测试

b8zhong 请求 rerun test/registered/spec/eagle/test_spec_eagle_parity.py,1-gpu-h100 通过。

结论:推测解码 parity 不受 FlashInfer 升级影响。 · 已解决

CI 全量验证状态确认 测试

Fridge003 确认 "extra test all passed"(Run #31559901107)并附上 Base test 链接(Run #31539861716),随后 APPROVE。

结论:全量验证通过后合入。 · 已解决

风险与影响

  1. 最低版本强制提升(breaking change)engine.pyassert_pkg_version("flashinfer_python", "0.6.17") 对所有使用 flashinfer attention backend 的环境生效,pin 在 0.6.15.post1 或更旧版本的用户将直接启动失败。这是有意的强制门槛,但离线内网环境需提前升级。
  2. 核心 MoE 路径 API 切换风险mxfp4.pytrtllm_fp4_block_scale_routed_moe 的参数语义与返回结构均有变化(元组返回、tune_max_num_tokens、枚举值 routing),若 FlashInfer 0.6.17 官方 SiTU 内核与 0.6.15 + 私有 cubin pool 在数值行为上存在细微差异,可能影响 Kimi K3 输出精度;目前主要依赖 B300 E2E 验证,单元层面没有针对新 API 签名与返回结构的直接断言。
  3. 错误提示降级:删除 trtllm_gen_moe.available() 检查后,若用户安装损坏或通过 SGLANG_SKIP_SGL_KERNEL_VERSION_CHECK=1 跳过版本检查,将直接暴露 FlashInfer 原生错误,而非 sglang 之前提供的安装指引式报错。
  4. 大范围删除的回归面:PR 共删除 6496 行,集中在 Kimi K3 专用路径(DCP 补丁、cubin pool 下载、JIT 胶水),风险面被约束在 Blackwell(SM100/SM103)+ MXFP4 + Kimi K3 的组合内,但一旦 0.6.17 某内核行为回归,回退成本较高。
  • 用户侧:Kimi K3 在 Blackwell 上的部署显著简化——不再需要下载含 1696 个 cubin 的私有 SiTU pool,不再需要手工 patch FlashInfer 源码;前提是升级 flashinfer-python0.6.17+。所有使用 flashinfer backend 的普通用户也会被最低版本门槛影响,需要同步升级依赖。
  • 系统侧:移除了启动期 sglang JIT 编译 trtllm-gen 融合 MoE 的链路(头文件 staging、ctypes cubin 回调),减少启动复杂度、镜像体积与 CI 安装时间。
  • 团队侧docker/kimi_k3scripts/ci/cuda/ci_install_kimi_k3.sh 的维护负担大幅降低;依赖基线收紧后,后续必须跟随 FlashInfer 0.6.17+ 的 API 演进,不能再依赖私有补丁。
  • 影响程度:中高。对 Kimi K3 用户是部署体验的明显改善,对普通用户是一次必须执行的依赖升级。
依赖最低版本强制提升 核心 MoE 路径 API 切换 大范围 workaround 删除 测试以 E2E 为主、单测覆盖偏薄

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论