Kimi K3 三平台架构对比解读

vLLM 上游 (NVIDIA / AMD) vs vLLM-Ascend 插件 — 源码级深度对比

1. 整体架构总览

同一个 Kimi K3 模型,在三个硬件平台上需要不同的底层算子实现。vLLM 的设计是:

图1:整体架构总览
graph TB subgraph upstream["上游 vLLM (vllm-main)"] direction TB init["__init__.py
平台路由"] common["common/
公共模块"] nv["nvidia/
完整实现
~2155行"] amd["amd/
完整实现
~900行"] init --> common init -->|"GPU (CUDA)"| nv init -->|"ROCm"| amd init -->|"TPU"| tpu_pass["pass (插件自己注册)"] end subgraph plugin["vLLM-Ascend 插件 (vllm-ascend-main)"] direction TB ascend_model["kimi_k3.py
适配层 ~879行"] ascend_ops["ops/kimi_kda.py
NPU 算子 ~752行"] ascend_triton["ops/triton/kimi_k3/
NPU Triton 算子"] ascend_model --> ascend_ops ascend_model --> ascend_triton end common -.->|"复用"| ascend_model amd -.->|"继承"| ascend_model style upstream fill:#f6f8fa,stroke:#d0d7de,color:#1f2328 style plugin fill:#f0e3ff,stroke:#8250df,color:#1f2328 style nv fill:#dafbe1,stroke:#1a7f37,color:#1f2328 style amd fill:#fff8c5,stroke:#9a6700,color:#1f2328 style ascend_model fill:#f0e3ff,stroke:#8250df,color:#1f2328

2. 目录结构对比

NVIDIA vllm/models/kimi_k3/nvidia/

model.py         2155行 完整模型
mla.py            MLA注意力
kda.py           1058行 KDA注意力
latent_moe_runner.py
dspark_mla.py
mtp.py
ops/
  attn_res.py      Triton kernel
  latent_moe_tail.py
  cute_dsl/        CUDA底层
  third_party/kda/ KDA chunk算子

AMD vllm/models/kimi_k3/amd/

linear.py        ~900行 完整实现
model.py         249行 多模态封装
kda.py           619行 KDA注意力
mla.py           MLA注意力
latent_moe_runner.py
mtp.py
ops/
  attn_res.py      Triton kernel
  kda_decode.py
  third_party/kda/ KDA算子

Ascend vllm_ascend/

models/kimi_k3.py    879行 适配层
ops/kimi_kda.py      752行 NPU KDA
ops/triton/kimi_k3/
  attention_residual.py  NPU attn_res
注意:Ascend 没有完整独立实现,
而是继承上游 AMD 版本再替换

3. 平台路由机制

上游入口:vllm/models/kimi_k3/__init__.py

上游通过检测当前平台,自动路由到对应的实现:

# vllm/models/kimi_k3/__init__.py
from vllm.platforms import current_platform

if TYPE_CHECKING:
    from .nvidia.model import KimiK3ForConditionalGeneration
elif current_platform.is_rocm():
    from .amd.model import KimiK3ForConditionalGeneration  # AMD
elif current_platform.device_type == "tpu":
    pass  # TPU插件自己注册,不在此加载
else:
    from .nvidia.model import KimiK3ForConditionalGeneration  # NVIDIA

Ascend 入口:vllm_ascend/models/kimi_k3.py

Ascend 插件绕过上述路由,直接继承上游 AMD 版本的类,并替换关键组件:

# vllm_ascend/models/kimi_k3.py 的导入链
from vllm.models.kimi_k3.amd.linear import KimiDecoderLayer as UpstreamKimiDecoderLayer
from vllm.models.kimi_k3.amd.linear import KimiMLAAttention as UpstreamKimiMLAAttention
from vllm.models.kimi_k3.amd.model import KimiK3ForConditionalGeneration as UpstreamKimiK3ForConditionalGeneration

from vllm_ascend.ops.kimi_kda import AscendKimiK3DeltaAttention  # NPU KDA后端
from vllm_ascend.ops.triton.kimi_k3.attention_residual import apply_attn_res  # NPU attn_res
图2:平台路由机制(时序图)
sequenceDiagram participant User as 用户启动推理 participant Registry as 模型注册表 participant Init as __init__.py participant NV as NVIDIA实现 participant AMD as AMD实现 participant Ascend as Ascend插件 alt NVIDIA GPU User->>Registry: 请求 Kimi K3 Registry->>Init: 查找模型类 Init->>Init: current_platform检测 Init->>NV: 路由到 nvidia/model.py NV-->>User: KimiK3ForConditionalGeneration else AMD ROCm User->>Registry: 请求 Kimi K3 Registry->>Init: 查找模型类 Init->>Init: current_platform检测 Init->>AMD: 路由到 amd/model.py AMD-->>User: KimiK3ForConditionalGeneration else Ascend NPU User->>Registry: 请求 Kimi K3 Registry->>Ascend: Ascend插件已注册 Ascend->>AMD: 继承上游AMD基类 Ascend->>Ascend: 替换NPU特定组件 Ascend-->>User: AscendKimiK3ForConditionalGeneration end

4. 组件级对比:同一模块,三种实现

4.1 Attention 模块对比

维度 NVIDIA AMD Ascend
类定义 独立完整实现 KimiMLAAttention
独立完整实现
AscendKimiMLAAttention
继承 UpstreamKimiMLAAttention
KDA 层 nvidia/kda.py
1058行, Triton+CUDA
amd/kda.py
619行, ROCm Triton
ops/kimi_kda.py
752行, AscendC+Triton
MLA 层 自建 CUDA MLA wrapper amd/mla.py
ROCm MLA wrapper
复用上游 MLA wrapper
仅配置 RoPE + DSpark
底层算子 CUDA / Triton kernel ROCm / Triton kernel AscendC / NPU Triton kernel
forward 逻辑 完整实现 完整实现 委托给 self.mla_attn()
上游已构建好
图3:KDA 层同一接口,三种算子实现
graph LR subgraph "KDA 层:同一接口,三种算子" KDA_IF["KDA 接口
前向计算"] KDA_NV["NVIDIA
Triton chunk_kda
fused_recurrent_kda"] KDA_AMD["AMD
ROCm chunk_kda
fused_recurrent_kda_packed_decode"] KDA_ASC["Ascend
AscendC NPU算子
npu_stream_switch"] KDA_IF --> KDA_NV KDA_IF --> KDA_AMD KDA_IF --> KDA_ASC end style KDA_IF fill:#f0f3f6,stroke:#0969da,color:#1f2328 style KDA_NV fill:#dafbe1,stroke:#1a7f37,color:#1f2328 style KDA_AMD fill:#fff8c5,stroke:#9a6700,color:#1f2328 style KDA_ASC fill:#f0e3ff,stroke:#8250df,color:#1f2328

4.2 MoE 模块对比

维度 NVIDIA AMD Ascend
类定义 内联在 model.py 中 KimiMoE 独立类 AscendKimiMoE 独立类
Gate GateLinear GateLinear GateLinear ✓ 相同
Shared Experts KimiMLP KimiMLP KimiMLP ✓ 从上游导入
Routed Experts FusedMoEFactory FusedMoEFactory
+ ROCmLatentMoERunner
FusedMoEFactory
+ 序列并行支持
Latent MoE 内建降维→专家→升维 ROCmLatentMoERunner 复用上游 KimiRoutedOutputTransform
量化配置走 Ascend 量化
关键差异 — TP padding 处理 Ascend 量化配置透传
+ 序列并行开关

4.3 MoE 初始化代码对比

AMD KimiMoE.__init__

self.experts = FusedMoEFactory(          # ↳ 专家容器来自上游 FusedMoE 体系
    ...
    runner_cls=ROCmLatentMoERunner       # ↳ AMD 特有:潜空间 MoE 专用 Runner
        if self.use_latent_moe else None,
)
if self.padded_moe_intermediate_size
    != moe_intermediate_size:
    # AMD 特有的 TP padding 处理
    w13_weight.data.zero_()   # ↳ AMD 特有:TP 切分 pad 需要先清零
    w2_weight.data.zero_()    #    (昇腾无此处理)

Ascend AscendKimiMoE.__init__

latent_quant_config = (
    quant_config if quant_config is not None
    and quant_config.get_name() == "ascend"
    else None                            # ⚡ 只有昇腾量化配置才透传给潜空间专家
)
self.experts = FusedMoEFactory(
    ...
    is_sequence_parallel=use_sequence_parallel,  # ⚡ 昇腾新增:序列并行开关
    # ↳ 无需 runner_cls,复用上游默认 Runner
)

5. Attention 残差 (attn_res) 三平台对比

Attention Residual 是 Kimi K3 独有的机制:将传统的加法残差替换为学习式 softmax 加权混合。三平台的数学逻辑完全相同,但算子实现不同。

图4:attn_res 数学流程(三平台相同)
graph TD subgraph "attn_res 数学流程(三平台相同)" PS["prefix_sum
累积残差"] --> CAT["拼接
block_residual + prefix_sum"] BR["block_residual
块级残差"] --> CAT CAT --> RMS["RMSNorm
归一化"] RMS --> SCORE["score = norm(x) · weight
计算每条路径分数"] SCORE --> SOFTMAX["softmax
路径权重"] SOFTMAX --> MIX["加权求和
混合输出"] end style PS fill:#f0f3f6,stroke:#0969da,color:#1f2328 style BR fill:#f0f3f6,stroke:#0969da,color:#1f2328 style CAT fill:#f0f3f6,stroke:#0969da,color:#1f2328 style RMS fill:#fff8c5,stroke:#9a6700,color:#1f2328 style SCORE fill:#fff8c5,stroke:#9a6700,color:#1f2328 style SOFTMAX fill:#fff8c5,stroke:#9a6700,color:#1f2328 style MIX fill:#dafbe1,stroke:#1a7f37,color:#1f2328

NVIDIA attn_res

实现在 nvidia/ops/attn_res.py

@triton.jit
def _attn_res_kernel(
  prefix_ptr, delta_ptr,          # 累积残差 / 本块增量
  blocks_ptr, norm_weight_ptr,    # 块级残差 / RMSNorm 权重
  qk_weight_ptr, ...              # 打分投影权重
):
  # ↳ NVIDIA 路径:Triton GPU kernel(CUDA 后端编译)
  # ⚡ 对应昇腾的 _apply_attn_res_kernel,数学逻辑相同

AMD attn_res

实现在 amd/ops/attn_res.py

@triton.jit
def _attn_res_kernel(
  prefix_ptr, delta_ptr,          # 与 NVIDIA 版同参同名
  blocks_ptr, norm_weight_ptr,
  qk_weight_ptr, ...
):
  # ↳ AMD 路径:同一份 Triton 源码,改由 ROCm 后端编译
  # → 昇腾版本见 9.3 源码外的 apply_attn_res(triton-ascend 编译)

Ascend _apply_ascend_attn_res

实现在 models/kimi_k3.py:92

def _apply_ascend_attn_res(...):
  if apply_attn_res is not None
    and prefix_sum.device.type=="npu":
    return apply_attn_res(...)  # NPU Triton
  # 回退:纯PyTorch实现
  values = torch.cat(...)
  scores = (normalized * weight).sum(-1)
  return softmax @ values

关键代码对比:残差计算核心

# ─── AMD 版 (amd/linear.py:141) ─── 调用 ROCm Triton kernel
def _apply_attn_res(prefix_sum, block_residual, proj, norm, num_valid_blocks, ...):
    return attn_res(prefix_sum, delta, block_residual,
                    norm.weight, proj.weight.squeeze(0), ...)

# ─── Ascend 版 (kimi_k3.py:92) ─── 优先 NPU Triton,回退纯 PyTorch
def _apply_ascend_attn_res(prefix_sum, block_residual, proj, norm, num_valid_blocks):
    if apply_attn_res is not None and prefix_sum.device.type == "npu":
        return apply_attn_res(...)                    # ← NPU 加速路径
    # 纯 PyTorch 回退(数学等价)
    values = torch.cat((block_residual[:,:num_valid_blocks,:], prefix_sum.unsqueeze(1)), dim=1)
    values_fp32 = values.float()
    inverse_rms = torch.rsqrt(values_fp32.square().mean(-1, keepdim=True) + norm.variance_epsilon)
    normalized_without_gamma = values_fp32 * inverse_rms
    score_weight = norm.weight.float() * proj.weight.squeeze(0).float()
    scores = (normalized_without_gamma * score_weight).sum(-1)
    probabilities = scores.softmax(-1).unsqueeze(1)
    return torch.matmul(probabilities, values_fp32).squeeze(1).to(values.dtype)

6. Decoder Layer 对比(最核心差异)

Decoder Layer 是差异最大的组件。三平台的核心分歧在于 Attention 后端的选择 和 残差路径的处理。

图5:Decoder Layer 组件选择
graph TB subgraph "Decoder Layer 组件选择" CFG["config.is_kda_layer(idx)?"] CFG -->|"是 KDA 层"| KDA_Q["选择 KDA 后端"] CFG -->|"非 KDA 层"| MLA_Q["选择 MLA 后端"] KDA_Q --> NV_KDA["NVIDIA
nvidia/kda.py
Triton+CUDA"] KDA_Q --> AMD_KDA["AMD
amd/kda.py
ROCm Triton"] KDA_Q --> ASC_KDA["Ascend
ops/kimi_kda.py
AscendC+NPU"] MLA_Q --> NV_MLA["NVIDIA
自建 CUDA MLA wrapper"] MLA_Q --> AMD_MLA["AMD
amd/mla.py
ROCm MLA wrapper"] MLA_Q --> ASC_MLA["Ascend
继承上游MLA
+ DSpark RoPE配置"] end MOE_Q["is_moe_layer?"] MOE_Q -->|"是"| NV_MOE["NVIDIA KimiMoE"] MOE_Q -->|"是"| AMD_MOE["AMD KimiMoE + ROCmRunner"] MOE_Q -->|"是"| ASC_MOE["Ascend AscendKimiMoE + SP"] MOE_Q -->|"否"| DENSE["KimiMLP (三平台共用)"] style CFG fill:#f0f3f6,stroke:#0969da,color:#1f2328 style KDA_Q fill:#f0f3f6,stroke:#0969da,color:#1f2328 style MLA_Q fill:#f0f3f6,stroke:#0969da,color:#1f2328 style NV_KDA fill:#dafbe1,stroke:#1a7f37,color:#1f2328 style AMD_KDA fill:#fff8c5,stroke:#9a6700,color:#1f2328 style ASC_KDA fill:#f0e3ff,stroke:#8250df,color:#1f2328 style NV_MLA fill:#dafbe1,stroke:#1a7f37,color:#1f2328 style AMD_MLA fill:#fff8c5,stroke:#9a6700,color:#1f2328 style ASC_MLA fill:#f0e3ff,stroke:#8250df,color:#1f2328

6.1 初始化对比

AMD KimiDecoderLayer.__init__

if config.is_kda_layer(layer_idx):
    kda_config = config.linear_attn_config
    if kda_config.get("use_full_rank_gate", False):
        self.self_attn = KimiK3DeltaAttention(...)      # ↳ AMD KDA(ROCm Triton)
    else:
        self.self_attn = KimiLinearGatedDeltaNetAttention(...)
else:
    self.self_attn = KimiMLAAttention(...)              # ↳ AMD MLA wrapper
# ⚡ 昇腾版把上面两个分支整体换成 Ascend 实现(见右侧 Ascend 版对比)

if self.is_moe_layer:
    self.block_sparse_moe = KimiMoE(...)
    self.mlp = self.block_sparse_moe
else:
    self.mlp = KimiMLP(...)

Ascend AscendKimiDecoderLayer.__init__

if config.is_kda_layer(layer_idx):
    self.self_attn = AscendKimiK3DeltaAttention(...)
    # ↑ NPU 专用 KDA 后端
else:
    self.self_attn = AscendKimiMLAAttention(...
        use_rope=False,  # 无RoPE
    )

if self.is_moe_layer:
    self.block_sparse_moe = AscendKimiMoE(...
        use_sequence_parallel=use_sequence_parallel,
    )
    self.mlp = self.block_sparse_moe
else:
    self.mlp = KimiMLP(...)

6.2 forward 对比

AMD _run_self_attn

def _run_self_attn(self, positions, hidden_states):
    attn_output = torch.empty_like(hidden_states)
    self.self_attn(
        hidden_states=hidden_states,
        positions=positions,
        output=attn_output,  # 写入预分配缓冲
    )
    return attn_output
AMD 版要求预先分配输出缓冲
attention 结果写入传入的 output 参数

Ascend _run_self_attn

def _run_self_attn(self, positions, hidden_states):
    return self.self_attn(
        positions=positions,
        hidden_states=hidden_states,
        # ↑ Ascend attention 直接返回结果
    )
Ascend 版直接返回结果
无需预分配缓冲区(NPU 内存管理不同)

6.3 forward_attn_residual 对比

AMD forward_attn_residual

def forward_attn_residual(self, positions, hidden_states, block_residual):
    prefix_sum = hidden_states
    hidden_states = _apply_attn_res(
        prefix_sum, block_residual,
        self.self_attention_res_proj,
        self.self_attention_res_norm,
        self.prev_valid_blocks,
        output_norm=self.input_layernorm,
        block_write_idx=(self.block_write_idx
            if self.is_block_write_layer else -1),
    )
    if self.is_block_write_layer:
        prefix_sum = None

    hidden_states = self._run_self_attn(positions, hidden_states)

    if prefix_sum is None:
        prefix_sum = hidden_states
        prefix_delta = None
    else:
        prefix_delta = hidden_states

    hidden_states = _apply_attn_res(
        ..., delta=prefix_delta,
        output_norm=self.post_attention_layernorm,
    )
    hidden_states = self.mlp(hidden_states)
    prefix_sum = prefix_sum + hidden_states
    return prefix_sum, block_residual

Ascend forward_attn_residual

def forward_attn_residual(self, positions, hidden_states, block_residual):
    prefix_sum = hidden_states
    hidden_states = _apply_ascend_attn_res(
        prefix_sum, block_residual,
        self.self_attention_res_proj,
        self.self_attention_res_norm,
        self.prev_valid_blocks,
    )
    if self.is_block_write_layer:
        block_residual[:, self.block_write_idx, :].copy_(prefix_sum)
        prefix_sum = None

    hidden_states = self.input_layernorm(hidden_states)
    if self.use_sequence_parallel:
        hidden_states = sp_all_gather(hidden_states)
        hidden_states = hidden_states[:positions.shape[0]]
    hidden_states = self.self_attn(...)
    if self.use_sequence_parallel:
        hidden_states = sp_reduce_scatter(hidden_states)

    prefix_sum = hidden_states if prefix_sum is None else prefix_sum + hidden_states
    hidden_states = _apply_ascend_attn_res(...)
    hidden_states = self.post_attention_layernorm(hidden_states)
    hidden_states = self.mlp(hidden_states)
    hidden_states = prefix_sum + hidden_states
    return hidden_states, block_residual

7. 继承关系全图

图6:继承关系全图
graph TB subgraph "上游 vLLM (vllm-main)" NV_MODEL["NVIDIA
KimiK3ForConditionalGeneration
2155行 完整实现"] AMD_MODEL["AMD
KimiK3ForConditionalGeneration
249行 封装层"] AMD_LINEAR["AMD
KimiLinearForCausalLM"] AMD_LM["AMD
KimiLinearModel"] AMD_DEC["AMD
KimiDecoderLayer"] AMD_MLA["AMD
KimiMLAAttention"] AMD_KDA["AMD
KimiK3DeltaAttention"] AMD_MOE["AMD
KimiMoE"] AMD_MODEL --> AMD_LINEAR AMD_LINEAR --> AMD_LM AMD_LM --> AMD_DEC AMD_DEC --> AMD_MLA AMD_DEC --> AMD_KDA AMD_DEC --> AMD_MOE end subgraph "vLLM-Ascend 插件" ASC_MODEL["Ascend
AscendKimiK3ForConditionalGeneration"] ASC_LINEAR["Ascend
AscendKimiLinearForCausalLM"] ASC_LM["Ascend
AscendKimiLinearModel"] ASC_DEC["Ascend
AscendKimiDecoderLayer"] ASC_MLA["Ascend
AscendKimiMLAAttention"] ASC_KDA["Ascend
AscendKimiK3DeltaAttention
ops/kimi_kda.py"] ASC_MOE["Ascend
AscendKimiMoE"] ASC_PROJ["Ascend
AscendKimiK3MultiModalProjector"] end ASC_MODEL -.->|"继承"| AMD_MODEL ASC_LINEAR -.->|"继承"| AMD_LINEAR ASC_LM -.->|"继承"| AMD_LM ASC_DEC -.->|"继承"| AMD_DEC ASC_MLA -.->|"继承"| AMD_MLA ASC_PROJ -.->|"继承"| K25_PROJ["上游
KimiK25MultiModalProjector"] ASC_KDA -.->|"替换 (非继承)"| AMD_KDA ASC_MOE -.->|"替换 (非继承)"| AMD_MOE linkStyle 0,1,2,3,4,5 stroke-dasharray: 5 5, stroke: #bc8cff linkStyle 6,7 stroke-dasharray: 3 3, stroke: #f85149 style ASC_MODEL fill:#f0e3ff,stroke:#8250df,color:#1f2328 style ASC_LINEAR fill:#f0e3ff,stroke:#8250df,color:#1f2328 style ASC_LM fill:#f0e3ff,stroke:#8250df,color:#1f2328 style ASC_DEC fill:#f0e3ff,stroke:#8250df,color:#1f2328 style ASC_MLA fill:#f0e3ff,stroke:#8250df,color:#1f2328 style ASC_KDA fill:#ffebe9,stroke:#cf222e,color:#1f2328 style ASC_MOE fill:#ffebe9,stroke:#cf222e,color:#1f2328 style ASC_PROJ fill:#f0e3ff,stroke:#8250df,color:#1f2328 style AMD_MODEL fill:#fff8c5,stroke:#9a6700,color:#1f2328 style AMD_LINEAR fill:#fff8c5,stroke:#9a6700,color:#1f2328 style AMD_LM fill:#fff8c5,stroke:#9a6700,color:#1f2328 style AMD_DEC fill:#fff8c5,stroke:#9a6700,color:#1f2328 style AMD_MLA fill:#fff8c5,stroke:#9a6700,color:#1f2328 style AMD_KDA fill:#fff8c5,stroke:#9a6700,color:#1f2328 style AMD_MOE fill:#fff8c5,stroke:#9a6700,color:#1f2328

继承策略图例

继承上游类,复用通用逻辑
完全替换,NPU 专用实现

规律:越靠近底层(算子级),越需要替换;越靠近上层(模型结构、权重加载),越可以继承复用。

8. 设计哲学总结

图7:设计哲学——为什么需要每个平台写一遍?
graph LR subgraph "为什么需要每个平台写一遍?" A["硬件算子不同"] --> B["CUDA ≠ ROCm ≠ AscendC"] C["内存模型不同"] --> D["GPU HBM ≠ NPU HBM
缓冲区管理策略不同"] E["编译工具链不同"] --> F["nvcc ≠ hipcc ← AscendC
Triton 三平台各需适配"] end subgraph "为什么 Ascend 可以少写很多?" G["继承上游 AMD 版"] --> H["模型结构/权重加载
前向逻辑/接口规范
全部复用"] I["只替换差异部分"] --> J["KDA 后端算子
MLA RoPE 配置
attn_res NPU 实现
序列并行逻辑"] end style A fill:#ffebe9,stroke:#cf222e,color:#1f2328 style C fill:#ffebe9,stroke:#cf222e,color:#1f2328 style E fill:#ffebe9,stroke:#cf222e,color:#1f2328 style G fill:#dafbe1,stroke:#1a7f37,color:#1f2328 style I fill:#dafbe1,stroke:#1a7f37,color:#1f2328

NVIDIA / AMD(内建平台)

Ascend(外部插件)

一图总结:代码复用 vs 硬件替换

图8:代码复用 vs 硬件替换
graph TB subgraph "模型层级(从上到下)" L1["多模态顶层
KimiK3ForConditionalGeneration"] L2["Causal LM
KimiLinearForCausalLM"] L3["文本模型
KimiLinearModel"] L4["Decoder Layer
KimiDecoderLayer"] L5A["Attention
MLA / KDA"] L5B["FFN
MoE / MLP"] L6["底层算子
attn_res / kda_kernel / moe_kernel"] end L1 --> L2 --> L3 --> L4 --> L5A L4 --> L5B L5A --> L6 L5B --> L6 L1 -.- R1["🟢 几乎全复用"] L2 -.- R2["🟢 几乎全复用"] L3 -.- R3["🟡 大部分复用,SP新增"] L4 -.- R4["🟡 继承+覆盖forward"] L5A -.- R5A["🔴 完全替换(KDA)
🟡 继承+配置(MLA)"] L5B -.- R5B["🟡 继承+SP/量化适配"] L6 -.- R6["🔴 完全替换(NPU算子)"] style R1 fill:#dafbe1,stroke:#1a7f37,color:#1f2328 style R2 fill:#dafbe1,stroke:#1a7f37,color:#1f2328 style R3 fill:#fff8c5,stroke:#9a6700,color:#1f2328 style R4 fill:#fff8c5,stroke:#9a6700,color:#1f2328 style R5A fill:#ffebe9,stroke:#cf222e,color:#1f2328 style R5B fill:#fff8c5,stroke:#9a6700,color:#1f2328 style R6 fill:#ffebe9,stroke:#cf222e,color:#1f2328
🟢 几乎全复用(继承即可)
🟡 部分复用,需覆盖/扩展
🔴 完全替换(硬件强相关)

最终结论

不是每个平台"都写一遍"同一份代码,而是:

9. K3 逐层看:从一次推理到 NPU 算子

图9-1 把一次推理分成了六步,其中第④步「模型逐层计算」占 95% 以上的计算量。 接下来两节只聚焦它:9.2 放大看 30 层里一层的实际调用时序(顺便看清哪些代码来自 AMD 基类、 哪些是昇腾新写的);9.3 顺着时序钻进源码。

9.1 一次推理的完整旅程

先快速过一遍全流程(上一版已有,紫色是本章主角):

图9-1:K3 一次推理的六步(本章只展开紫色第④步)
graph TD A["① 请求排队"] --> B["② 调度器决定本步跑什么"] B --> C["③ 执行器准备输入
(图片先过视觉编码器)"] C --> D["④ 模型逐层计算 ×30 层
残差混合 → 注意力 → 专家前馈"] D --> E["⑤ 采样出下一个词"] E --> F["⑥ 返回 / 存回队列"] style A fill:#f6f8fa,stroke:#d0d7de,color:#1f2328 style B fill:#f6f8fa,stroke:#d0d7de,color:#1f2328 style C fill:#f6f8fa,stroke:#d0d7de,color:#1f2328 style D fill:#f0e3ff,stroke:#8250df,color:#1f2328 style E fill:#f6f8fa,stroke:#d0d7de,color:#1f2328 style F fill:#f6f8fa,stroke:#d0d7de,color:#1f2328

9.2 第④步放大:一层计算的实际时序

第④步 = 30 次循环,每次调 AscendKimiDecoderLayer.forward_attn_residual (models/kimi_k3.py:478,这个类继承自 AMD 的 KimiDecoderLayer,同名方法被覆盖重写)。 下面是一次层计算的完整时序——注意三列参与者的来源:中间是昇腾写的,右列是 NPU 底层算子, 方法名旁标 ↳ 的行为/组件直接来自 AMD 基类。

图9-2:一层计算的调用时序(↳来自AMD基类 · ⚡昇腾新实现 · 红色=NPU底层算子)
sequenceDiagram autonumber participant Layer as AscendKimiDecoderLayer
(models/kimi_k3.py:478) participant Res as 残差混合
_apply_ascend_attn_res participant Attn as _run_self_attn
(kimi_k3.py:470) participant KDA as AscendKimiK3DeltaAttention
(ops/kimi_kda.py:145) participant OPS as NPU 底层算子 Note over Layer: ↳ forward_attn_residual 骨架与 AMD 基类同名同序,只换实现 Layer->>Res: ① 残差混合(block_residual 加权) Res->>OPS: ⚡ @triton.jit _apply_attn_res_kernel Layer->>Layer: ② input_layernorm ↳ 直接用 AMD 基类的组件 Layer->>Attn: ③ ⚡ _run_self_attn:按层类型二选一(AMD 基类没有此分派) alt KDA 层(delta attention) Attn->>KDA: forward(hidden_states, positions) KDA->>OPS: ⚡ _C_ascend.npu_causal_conv1d_custom KDA->>OPS: ⚡ _C_ascend.chunk_kda_fwd(prefill)
或 recurrent_kda(decode) else MLA 层 Attn->>OPS: ⚡ AscendKimiMLAAttention(kimi_k3.py:344,继承上游MLA)
→ AscendMLAImpl → FIA end Layer->>Res: ④ 残差混合② + post_attention_layernorm ↳ Layer->>Layer: ⑤ MoE/MLP:AscendKimiMoE.forward(kimi_k3.py:232)
⚡ → npu_grouped_matmul_swiglu_quant

那"依赖 AMD"到底是什么意思?——三种机制,对应到代码

# 机制一:整类继承 —— 类体是空的或只加参数,方法全部来自 AMD 基类
class AscendKimiMLAAttention(UpstreamKimiMLAAttention):   # Upstream = vllm/models/kimi_k3/amd/linear.py
    def forward(self, positions, hidden_states):
        return self.mla_attn(positions, hidden_states)     # 只改一个委托点

# 机制二:继承 + 覆盖 —— 方法签名沿用 AMD 基类,实现重写
class AscendKimiDecoderLayer(UpstreamKimiDecoderLayer):
    def forward_attn_residual(self, ...): ...              # 同名同序,换掉残差函数/注意力分派

# 机制三:整组件替换 —— 上游类彻底不用,接口对齐
self.self_attn = AscendKimiK3DeltaAttention(...)           # AMD 的 KimiK3DeltaAttention 被丢弃

一句话:AMD 提供的是"层的组织方式"(方法顺序、组件接线、权重加载),昇腾提供的是"每个动作怎么算"。 时序图里的编号 ①→⑤ 就是 AMD 基类定下的顺序;每个编号算什么,红色算子说了算。

9.3 沿时序看源码:KDA 从 Python 到 NPU 算子

时序图 alt 分支里 KDA 是最特殊的一条(上游没有、AMD 用 ROCm Triton、昇腾用自研 AscendC 算子)。 下面三段源码就是时序图 消息 4 → 6 → 7 的真身(截自 ops/kimi_kda.py, ⚡ 昇腾新逻辑 / ↳ 与 AMD·上游的关系 / →NPU 底层算子,见行内注释)。

源码① —— 对应时序图消息 4:KDA 入口 forward(kimi_kda.py:145)
def forward(  # ↳ 接口与上游 KimiK3DeltaAttention 一致(整组件替换,机制三)
    self,
    hidden_states: torch.Tensor,
    positions: torch.Tensor,
) -> torch.Tensor:
    if self.uses_mixed_projection:
        num_tokens = hidden_states.size(0)
        mixed_qkv = self.in_proj_qkvgfab(hidden_states)  # ⚡ 融合投影:一次出 q/k/v/门控多路[0]
        projected_gfab = self.in_proj_gfab(hidden_states)[0]
        split_sizes = [
            self.local_projection_size,
            self.head_dim,
            self.local_num_heads,
        ]
        if self.in_proj_padding:
            split_sizes.append(self.in_proj_padding)
源码② —— 对应时序图消息 6 前置:分派与 AscendC 契约(kimi_kda.py:321)
def _forward(
    self,
    mixed_qkv: torch.Tensor,
    g1: torch.Tensor,
    g2: torch.Tensor,
    beta: torch.Tensor,
    core_attn_out: torch.Tensor,
) -> None:
    """Dispatch speculative, prefill, and decode tokens through KDA kernels."""
    forward_context = get_forward_context()
    attn_metadata_raw = forward_context.attn_metadata
    if attn_metadata_raw is None:
        core_attn_out.zero_()
        return

    assert isinstance(attn_metadata_raw, dict)
    attn_metadata = attn_metadata_raw[self.prefix]
    assert isinstance(attn_metadata, GDNAttentionMetadata)

    num_actual_tokens = attn_metadata.num_actual_tokens
    mixed_qkv = mixed_qkv[:num_actual_tokens]
    g1 = g1[:, :num_actual_tokens]
源码③ —— 对应时序图消息 7:真正跨过 Python/硬件边界的调用(kimi_kda.py:199)
def _run_causal_conv1d(
    mixed_qkv: torch.Tensor,
    conv_weights_t: torch.Tensor,
    conv_state: torch.Tensor,
    query_start_loc: torch.Tensor,
    cache_indices: torch.Tensor,
    initial_state_mode: torch.Tensor | None,
    *,
    run_mode: int,
    num_accepted_tokens: torch.Tensor | None = None,
) -> torch.Tensor:
    output = torch.empty_like(mixed_qkv)
    # Consume the operator's declared output alias. Returning ``output``
    # independently would let graph functionalization treat the custom-op
    # result as dead and expose the uninitialized allocation instead.
    return torch.ops._C_ascend.npu_causal_conv1d_custom(  # →NPU:自研 AscendC 算子,CANN 调度到 AI Core
        output,
        mixed_qkv,
        conv_weights_t,
        conv_state=conv_state,
        bias_opt=None,
        query_start_loc_opt=query_start_loc,
        cache_indices_opt=cache_indices,
        initial_state_mode_opt=initial_state_mode,

读完这三段,时序图右边一列(NPU 底层算子)就有了实体: torch.ops._C_ascend.* 是 vllm-ascend 自己编译的 C++ 算子库(AscendC 语言编写、随包发布), 调用之后编译与调度全部交给 CANN 落到 AI Core。MLA 分支同理,走的是第二个通道 torch_npu(第 10 章)。

10. Ascend 软件栈:从 Python 一路到 NPU 硬件

第 9 章调用链里出现了三种"红色"底层调用:torch.ops._C_ascend.*、torch_npu.npu_*、 @triton.jit。它们就是 vllm-ascend 与"Ascend 底层"的三个接口。这一章把它们与 CANN、硬件串成一条线。

图10:vllm-ascend 软件栈分层(含三个算子通道)
graph LR subgraph L1["① 模型/后端层(vllm_ascend Python)"] M1["models/kimi_k3.py
attention/mla_v1.py
ops/kimi_kda.py"] end subgraph L2["② 算子封装层(三个通道)"] C1["torch.ops._C_ascend.*
vllm_ascend_C(C++ 扩展)
算子用 AscendC 语言编写
例:chunk_kda_fwd / recurrent_kda
/ npu_causal_conv1d_custom"] C2["torch_npu.npu_*
华为官方 aclnn 算子库
例:npu_grouped_matmul_swiglu_quant
npu_fused_infer_attention_score"] C3["@triton.jit
triton-ascend 编译器
例:_apply_attn_res_kernel"] end subgraph L3["③ CANN 异构计算架构"] K["AscendC Kernel(设备侧 C++)"] ACL["libascendcl / aclnn
Host 侧运行时 + Stream 调度"] K --> ACL end subgraph L4["④ 硬件"] HW["NPU AI Core
Cube(矩阵)+ Vector(向量)单元
+ HBM 显存"] end M1 --> C1 M1 --> C2 M1 --> C3 C1 --> K C2 --> ACL C3 -->|"triton-ascend 编译为设备代码"| K ACL --> HW K --> HW style L1 fill:#faf5ff,stroke:#8250df,color:#1f2328 style L2 fill:#ffffff,stroke:#d0d7de,color:#1f2328 style C1 fill:#ffebe9,stroke:#cf222e,color:#1f2328 style C2 fill:#fff8c5,stroke:#9a6700,color:#1f2328 style C3 fill:#dafbe1,stroke:#1a7f37,color:#1f2328 style L3 fill:#f0f3f6,stroke:#0969da,color:#1f2328 style L4 fill:#f6f8fa,stroke:#d0d7de,color:#1f2328

三个算子通道怎么区分(对应真实代码)

通道是什么Kimi K3 里在哪用为什么选它
torch.ops._C_ascend.* vllm-ascend 自己编译的 C++ 扩展 vllm_ascend_C(utils.enable_custom_op 惰性注册),内部算子用华为 AscendC 语言写,随包发布 KDA 全部核心计算:chunk_kda_fwd(prefill 分块)、recurrent_kda(decode 递归)、npu_causal_conv1d_custom(因果卷积) 上游没有的算子(KDA 是 Kimi K3 新结构),必须自己写设备代码
torch_npu.npu_* 华为官方 torch_npu 库提供的 aclnn 算子(CANN 自带,随固件/驱动发布) MLA 的融合推理注意力(FIA)、MoE 的 npu_grouped_matmul_swiglu_quant、各类基础算子 通用大算子华为已高度优化,直接调用即可,无需自研
@triton.jit(triton-ascend) Triton 语言的 Ascend 后端,Python 写 kernel、运行时编译到 NPU 注意力残差 _apply_attn_res_kernel(ops/triton/kimi_k3/attention_residual.py) 与 NVIDIA/AMD 共享同一份 Triton 源码风格,移植成本低;无环境时回退纯 PyTorch

"和 Ascend 底层啥关系"——一次 KDA 计算的完整下沉过程

# ① Python 层:ops/kimi_kda.py(文档里能打开的源码)
def _run_prefill(self, ...):
    ...准备 q/k/v/beta 并按 AscendC 契约整理(_prepare_beta)...
    return torch.ops._C_ascend.chunk_kda_fwd(q, k, v, g, beta, ...)   # ← 跨过 Python/USA 边界

# ② C++ 层:vllm_ascend_C 扩展(随包发布的 .so,源码在仓库 vllm_ascend/C 下)
#    chunk_kda_fwd 的 C++ 实现负责:参数校验 → 取 NPU Stream → launch AscendC kernel

# ③ CANN 层:AscendC kernel(设备侧 C++/类 CUDA 语法)编译产物在 AI Core 上执行
#    Host 侧由 libascendcl(acl)管理设备上下文、内存、Stream 任务下发

# ④ 硬件层:AI Core 的 Cube 单元做矩阵乘(q·k、状态更新),Vector 单元做激活/门控
对照 AMD:同样的四层是 Python → hipcc 编译的 ROCm Triton kernel → ROCm 运行时(HIP/ROCR)→ CDNA 计算单元。 平台差异被压缩在 ②③ 两层,这就是为什么第 9 章里调用链的上半段两平台长得一样。

把两个问题串起来(本章结论)

首页