Prhub

#50089 [Model] Add Kimi K3 support: model files and kernels [1/N]

原始 PR 作者 ZJY0516 合并时间 2026-07-29 14:10 文件变更 109 提交数 34 评论 6 代码增减 +28009 / -773

执行摘要

新增 Kimi K3 多模态模型推理支持(第一阶段)

Kimi K3 是 Kimi 推出的新一代多模态大模型,vLLM 社区需要集成该模型以扩展支持的模型生态。PR#50089 是原 PR#50000 的第一部分拆分,用于提前合入模型定义与内核代码,后续将补充前端集成与测试使其可运行(参见 PR body 说明)。

建议核心架构师与模型组重点审阅:(1)vllm/models/kimi_k3/ 的模块划分是否合适;(2)CuTe DSL 内核与现有 custom_ops 的边界;(3)Latent MoE 融合路径的正确性。对于一般开发者,该 PR 是学习 vLLM 模型扩展与高性能内核的绝佳案例。

讨论亮点
  • 模型注册绕过风险:chatgpt-codex-connector[bot] 指出 KimiLinearForCausalLM 仍映射到旧的 kimi_linear 模块,新定义的 KimiK3ForConditionalGeneration 不会被实例化,导致 K3 特定行为被忽略。(P1 严重性)
  • Profiling 未初始化输出:同一 bot 发现 profiling 路径返回 torch.empty,未经写入直接被归一化投影,可能传播 NaN 值;建议恢复零初始化。(P2 严重性)
  • CI 测试回归:AndreasKaratzas 报告该 PR 在 Buildkite 上引起了潜在回归,并已创建 revert PR#50304 还原部分测试文件改动。

实现拆解

  1. 模型架构:在 vllm/models/kimi_k3/ 下按平台组织,amd/linear.py 定义 KimiDecoderLayerKimiMoEKimiMLAAttention 等通用模块;nvidia/model.py 提供 NVIDIA 专用逻辑。
  2. 注意力机制:NVIDIA 端引入 KDA(Gated Delta Net Attention),通过 nvidia/kda.py 实现线性注意力核算子,nvidia/kda_metadata.py 构建运行时元数据,支持 PDL 提前启动。
  3. MoE 与激活:新增 SituAndMul 激活函数(vllm/model_executor/layers/activation.py),LatentMoERunner 将 latent MoE 的 allreduce 与输出变换融合,减少通信开销;配套 CuTe DSL fused 内核 KimiK3LatentMoETailOp
  4. 低延迟 GEMMcute_dsl/_skinny_gemm.py 编写 CuTe 模板内核,skinny_gemm.py 封装 ShapeDynamicSkinnyGemm,根据 (M, N, K) 动态选择最优配置。
  5. 推测解码nvidia/dspark_mla.py 实现 K3DSparkModel,配合复制版 Markov Head 完成 DSpark 草稿模型;amd/mtp.py 实现多 token 预测(MTP)。
  6. 多模态预处理common/mm_preprocess.py 提供图像缩放、Processor 初始化等,注册到模型配置。
  7. 配套变更:更新模型注册表(registry.py)、Kimi Linear 配置解析、新增 16+ 个测试文件、Buildkite CI 入口。
文件 模块 状态 重要度
vllm/models/kimi_k3/nvidia/kda.py KDA 注意力 added 9.36
vllm/models/kimi_k3/nvidia/low_latency_gemm.py 低延迟 GEMM added 9.17
vllm/model_executor/layers/fused_moe/runner/latent_moe_runner.py MoE 运行器 added 9.17
vllm/model_executor/kernels/linear/cute_dsl/skinny_gemm.py CuTe 封装 added 9.28
vllm/model_executor/kernels/linear/cute_dsl/_skinny_gemm.py CuTe 内核 added 9.17
vllm/models/kimi_k3/amd/linear.py AMD 模型层 added 9.28
vllm/models/kimi_k3/common/mm_preprocess.py 多模态预处理 added 9.08

关键符号

KimiMLP KimiRoutedOutputTransform KimiMoE KimiMLAAttention KimiDecoderLayer CuteSkinnyGemm ShapeDynamicSkinnyGemm LatentMoERunner KimiK3KDAMetadata KimiK3ForConditionalGeneration ReplicatedDSparkMarkovHead K3DSparkModel SharedHead KimiK3MultiTokenPredictor select_kimi_k3_backend

关键源码片段

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

Latent MoE 融合运行器,将 routed 与 shared expert 的 allreduce 合并为一次,并支持 CuTe DSL 融合尾操作,是 K3 MoE 性能关键。

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
import torchfrom vllm.config import get_current_vllm_config
from vllm.distributed import tensor_model_parallel_all_reduce
from vllm.model_executor.layers.fused_moe.runner.moe_runner import MoERunner
​
​
class LatentMoERunner(MoERunner):
    """MoE runner for latent MoE with a replicated routed up-projection.    Fused path (tp>1, un-reduced combine output, shared expert, no SP):
    concatenates the un-reduced latent partial (dim d) and the un-reduced
    shared partial (dim D) into one contiguous buffer, all-reduces once, then
    splits. The latent half is normed and up-projected locally (replicated
    up-proj -> full hidden), and the shared add folds into the GEMM epilogue
    (``torch.addmm``). One collective total, no post-reduction communication.
    """
​
    def __init__(self, *args, enable_k3_latent_moe_tail_fusion: bool = False, **kwargs):
        super().__init__(*args, **kwargs)
        self.enable_k3_latent_moe_tail_fusion = enable_k3_latent_moe_tail_fusion
        # 检查是否满足 K3 尾融合的条件:TP=8 或 16 且不使用 ubatching/sleep 模式
        use_fused_path = self._use_fused_path()
        if (self.enable_k3_latent_moe_tail_fusion and use_fused_path
                and self.moe_config.tp_size not in (8, 16)):
            # TP 不匹配时回退默认路径
            self.enable_k3_latent_moe_tail_fusion = False
​
        if self.enable_k3_latent_moe_tail_fusion and use_fused_path:
            vllm_config = get_current_vllm_config()
            if vllm_config.parallel_config.use_ubatching:
                raise ValueError("K3 latent-MoE tail fusion does not support DBO or ubatching.")
            # 初始化 CuTe DSL 融合算子
            from vllm.models.kimi_k3.nvidia.ops.latent_moe_tail import KimiK3LatentMoETailOp
            self._k3_latent_moe_tail_op = KimiK3LatentMoETailOp.initialize(
                hidden_size=..., latent_size=..., dtype=..., device=..., rms_eps=...
            )
​
    def _use_fused_path(self):
        # 融合路径要求:TP>1、有 shared expert、未 reduce 输出且不是 sequence parallel
        return (self.moe_config.tp_size > 1
                and self._shared_experts is not None
                and not self._fused_output_is_reduced
                and not self.moe_config.is_sequence_parallel)
​
    def forward(self, hidden_states, router_logits, input_ids=None, shared_experts_input=None):
        if self._use_fused_path() and self.enable_k3_latent_moe_tail_fusion:
            return self._fused_forward(...) # 单次 allreduce + 融合 epilogue
        return super().forward(...) # 回退基类两条 collectives 路径
vllm/model_executor/kernels/linear/cute_dsl/skinny_gemm.py data-contract

ShapeDynamicSkinnyGemm 封装,管理 CuTe 内核的编译缓存与动态配置。

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
from __future__ import annotations
from dataclasses import dataclass
from functools import partial
from typing import Any
import torch
​
​
@dataclass(frozen=True, slots=True)
class SkinnyGemmConfig:
    num_rows: int # M 维(token 数)
    block_size: int # 线程块大小
    outputs_per_block: int # 每个线程块处理的输出列数
    k_unroll: int = 1 # K 循环展开因子
    vector_width: int = 8 # 向量加载宽度
​
​
class ShapeDynamicSkinnyGemm:
    """形状动态的低延迟 GEMM,为小 token 数量优化。"""
​
    def __init__(self):
        self._compiled: dict[tuple[torch.dtype, SkinnyGemmConfig, bool], Any] = {}
​
    @staticmethod
    def is_available() -> bool:
        """检查 CuTe DSL 是否可用。"""
        try:
            import cutlass
            import cutlass.cute
            return True
        except ImportError:
            return False
​
    @staticmethod
    def _config(m: int, n: int, k: int) -> SkinnyGemmConfig:
        """根据形状启发式选择最佳配置。"""
        num_rows = m
        wide_block = 224
        if m == 1 and k >= 7168 and k % (wide_block * 8) == 0:
            # 宽 K 时使用大 block 以利用高并行
            if n % 3 == 0:
                k_unroll = 2 if n <= 2304 else 4
                return SkinnyGemmConfig(num_rows, wide_block, 3, k_unroll)
        # 默认 : 根据 K 大小选择 block_size 64 或 128
        block_size = 64 if 4096 <= n < 8192 else 128
        outputs_per_block = 1 if m == 1 and n <= 2304 else 2
        return SkinnyGemmConfig(num_rows, block_size, outputs_per_block, k_unroll=2)
​
    def _compile(self, dtype, config, has_residual):
        """编译 CuTe 内核并缓存。"""
        from ._skinny_gemm import CuteSkinnyGemm
        import cutlass.cute as cute
        # 创建 fake tensor 用于编译时形状推断
        k = cute.sym_int(divisibility=config.block_size * config.vector_width)
        a = ... ; b = ... ; c = ... ; residual = ...
        kernel = CuteSkinnyGemm(element_type=..., num_rows=config.num_rows, ...)
        self._compiled[(dtype, config, has_residual)] = cute.compile(kernel, a, b, residual, c, ...)
vllm/model_executor/kernels/linear/cute_dsl/_skinny_gemm.py data-contract

CuTe DSL 内核主体,使用 cute.jit 和 cute.kernel 装饰器实现形状动态的低延迟 GEMM。

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
from __future__ import annotations
import cutlass
import cutlass.cute as cute
from cuda.bindings.driver import CUstream
from cutlass import const_expr
​
​
class CuteSkinnyGemm:
    """Shape-dynamic low-latency GEMM for small token counts.    计算 C[M,N] = A[M,K] @ B[N,K].T + residual,支持 BF16/FP16 输入、
    FP32 累加。N 和 K 为运行时值,M 编译时固定并完全展开。
    """
​
    def __init__(self, *, element_type, num_rows: int, block_size: int,
                 outputs_per_block: int, vector_width: int = 8,
                 k_unroll: int = 1, has_residual: bool = False,
                 use_pdl: bool = False):
        if block_size % cute.arch.WARP_SIZE != 0:
            raise ValueError("block_size must be a multiple of warp size")
        self.num_rows = num_rows
        self.block_size = block_size
        self.outputs_per_block = outputs_per_block
        self.vector_width = vector_width
        self.k_unroll = k_unroll
        self.has_residual = has_residual
        self.use_pdl = use_pdl
​
    @cute.jit
    def __call__(self, gA: cute.Tensor, gB: cute.Tensor,
                 gResidual: cute.Tensor, gC: cute.Tensor,
                 stream: CUstream) -> None:
        n = cute.size(gB, mode=[0]) # 运行时 N
        k = cute.size(gA, mode=[1]) # 运行时 K
        # 加载配置原子操作:A 使用 ALWAYS cache,B 使用 STREAMING 以减小 L2 污染
        copy_a = cute.make_copy_atom(cute.nvgpu.CopyG2ROp(), self.element_type,
                                     num_bits_per_copy=self.vector_width * self.element_type.width,
                                     load_cache_mode=cute.nvgpu.LoadCacheMode.ALWAYS)
        copy_b = cute.make_copy_atom(cute.nvgpu.CopyG2ROp(), self.element_type,
                                     num_bits_per_copy=self.vector_width * self.element_type.width,
                                     load_cache_mode=cute.nvgpu.LoadCacheMode.STREAMING)
        self.kernel(gA, gB, gResidual, gC, k, copy_a, copy_b).launch(
            grid=[cute.ceil_div(n, self.outputs_per_block), 1, 1],
            block=[self.block_size, 1, 1],
            smem=self.num_rows * self.outputs_per_block * (self.block_size // cute.arch.WARP_SIZE) * 4,
            stream=stream, use_pdl=self.use_pdl, min_blocks_per_mp=1)
​
    @cute.kernel
    def kernel(self, gA, gB, gResidual, gC, k_extent: cutlass.Int32,
               copy_a: cute.CopyAtom, copy_b: cute.CopyAtom):
        # 线程局部初始化累加器
        acc_layout = cute.make_layout((self.num_rows, self.outputs_per_block), stride=(self.outputs_per_block, 1))
        acc = cute.make_rmem_tensor(acc_layout, cutlass.Float32)
        acc.fill(0.0)
        if const_expr(self.use_pdl):
            cute.arch.griddepcontrol_wait() # PDL 等待前驱�
        # 主循环:分块加载 K 维度,执行矩阵乘法
        for k_tile in cutlass.range(k_extent // (self.block_size * self.vector_width), unroll=self.k_unroll):
            # 加载 A 和 B 分块到寄存器,执行累加
            ...
        # 写回结果,可选加余量
        if const_expr(self.has_residual):
            cute.add(acc, ...) # 累加前已加载余量
        cute.copy(acc, gC)

评论区精华

模型注册映射错误导致 K3 逻辑被绕过 设计

chatgpt-codex-connector[bot] 指出 `vllm/models/kimi_k3/nvidia/model.py` 中的 `KimiK3ForConditionalGeneration` 不会被实例化,因为 `registry.py` 中 `KimiLinearForCausalLM` 仍然映射到旧模块 `kimi_linear`。

结论:未在该 PR 中修复,需后续 PR 修正注册表或条件加载逻辑。 · unresolved

Profiling 路径未初始化输出导致的 NaN 风险 正确性

chatgpt-codex-connector[bot] 发现 profiling 路径中 `_forward` 返回 `torch.empty` 缓冲区,未经写入即被归一化投影,可能传播 NaN。该问题同样出现在新的 NVIDIA K3 KDA 实现中。

结论:未确认修复;建议恢复零初始化或写入确定性值。 · unresolved

CI 测试回归与部分文件 revert 测试

AndreasKaratzas 报告该 PR 在 Buildkite 上引起了潜在回归,并创建了 revert PR #50304 来还原部分测试文件改动。

结论:已创建 revert PR #50304 应对回归,但未在此 PR 中直接修复。 · resolved_with_revert

风险与影响

  1. 模型注册绕过:若 registry.pyKimiLinearForCausalLM 未正确映射到 kimi_k3 模块,系统将使用旧代码运行 K3 模型,导致大量新增逻辑失效。
  2. Profiling 路径未初始化:KDA 的 profiling 分支可能产出 NaN,影响运行时状态,需立即修复。
  3. 跨平台兼容性:AMD 后端和 NVIDIA 后端差异大,AMD 端缺少 KDA 等内核,可能无法正确推理。
  4. 变更范围大:109 个文件 +28k 行新增,容易引入跨模块回归(如调度器或缓存交互)。
  5. 尚不可运行:PR 明确标注 not runnable,合并后可能影响主分支稳定性。

对用户:获得 Kimi K3 模型基础支持,但需后续 PR 才能实际使用。对系统:新增约 28k 行代码,增加构建依赖(CuTe DSL、flash-linear-attention),并引入新的 CUDA 内核。对团队:需要多平台维护,且存在因测试 revert 导致的覆盖缺口。影响程度:高,涉及核心模型层和内核层。

模型注册绕过风险 Profiling 未初始化 CI 测试回归 仅 NVIDIA 验证 尚不可运行 变更范围过大 (109 文件 )

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论