执行摘要
- 一句话:ROCm Kimi-K3 预填充去掉 chunk KDA 流水线停顿
- 推荐动作:值得精读:重点看
vllm/models/kimi_k3/amd/kda_metadata.py 的设备端 metadata 构建思路,以及 gdn_attn.py 中 _build_chunk_metadata 的可覆写设计。合入前应确认新增 Triton kernel 与 CPU 路径输出一致,建议后续补充边界条件测试(空序列、混合 prefill/decode batch、num_seqs 超过 _MIN_BLOCK_N 等)。同时留意与 #51540 的关系:本 PR 是 ROCm Kimi-K3 的定向优化,#51540 是通用 GDN 优化,合并策略由维护者 tjtanaa 拍板为先合本 PR。
功能与动机
PR body 指出:在 ROCm 的 Kimi-K3 路径,每个 prefill/mixed step 在 prepare_chunk_indices 上出现 stall,原因是 .tolist() 的 D2H 拷贝让 host 等待设备队列排空,且 host 端操作在下一个 H2D 拷贝前执行时设备处于空闲。作者进一步说明,host 等待的数据其实已由 GDNAttentionMetadataBuilder 从 query_start_loc_cpu 构建并保存在 attention metadata 中,ROCm Kimi-K3 路径却没有使用,属重复劳动。
实现拆解
-
抽取可覆写构建钩子:在 vllm/v1/attention/backends/gdn_attn.py 的 GDNAttentionMetadataBuilder 中新增 _build_chunk_metadata(prefill_query_start_loc, prefill_query_start_loc_cpu, device),把 build() 内联的 cutedsl 分支与“CPU 预计算 + async_tensor_h2d”分支收拢为可覆写方法;build() 统一调用该方法。默认逻辑与原来等价,NVIDIA 及 Kimi-Linear 等其它 GDN 模型路径不受影响。
-
新增 ROCm Kimi-K3 专属 backend:新建 vllm/models/kimi_k3/amd/kda_metadata.py,定义 Triton kernel _chunk_metadata_kernel 和 prepare_chunk_metadata_device,在 GPU 上从 cu_seqlens 直接生成 chunk_indices(每个元素为 (seq_idx, chunk_idx) 对)与累加偏移 chunk_offsets,全程无 D2H/H2D 同步;KimiK3ROCmKDAMetadataBuilder 覆写 _build_chunk_metadata,KimiK3ROCmKDABackend 注册名字 KIMI_K3_KDA_ROCM。
-
KDA 内核签名扩展与数据透传:在 vllm/models/kimi_k3/amd/ops/third_party/kda/chunk.py 的 chunk_kda_with_fused_gate、chunk_kda_with_fused_gate_fwd、_chunk_kda_fwd_with_cumulative_g 中新增可选参数 chunk_indices / chunk_offsets;只有调用方未提供 chunk_indices 时才回退到 prepare_chunk_indices(cu_seqlens, chunk_size),避免设备端重建。KimiK3DeltaAttention(vllm/models/kimi_k3/amd/kda.py)新增 get_attn_backend() 返回新 backend,并把 m.chunk_indices、m.chunk_offsets 传入 chunk_kda_with_fused_gate。
-
平台分支导入重构:vllm/model_executor/layers/mamba/gdn/kimi_gdn_linear_attn.py 把 AMD/NVIDIA KDA 内核的运行时导入改为 TYPE_CHECKING 加平台分支,运行时按 current_platform.is_rocm() 选择实现,既满足类型检查又保持平台隔离。
-
验证与测试配套:本 PR 未新增自动化测试文件;作者通过 MI355X 上的 vllm bench serve 与 lm_eval gsm8k 做端到端验证,并报告并发 8 到 64 的吞吐、TTFT、TPOT 对比表(低并发提升约 3%,高并发基本持平);期间因合并冲突 rebase 后重新触发 Buildkite CI。
关键文件:
vllm/models/kimi_k3/amd/kda_metadata.py(模块 模型层;类别 source;类型 data-contract;符号 _chunk_metadata_kernel, prepare_chunk_metadata_device, KimiK3ROCmKDAMetadataBuilder, _build_chunk_metadata): 新增文件,包含设备端 Triton chunk metadata 内核、ROCm Kimi-K3 专属 metadata builder 与 attention backend,是消除 prefill stall 的核心。
vllm/v1/attention/backends/gdn_attn.py(模块 注意力后端;类别 source;类型 core-logic;符号 _build_chunk_metadata): 共享 GDN metadata builder 的构建逻辑被抽取为可覆写方法 _build_chunk_metadata,是通用路径的关键改造点。
vllm/models/kimi_k3/amd/kda.py(模块 模型层;类别 source;类型 data-contract;符号 get_attn_backend): KimiK3DeltaAttention 注册新的 attention backend,并把 metadata 中的 chunk_indices/chunk_offsets 透传给 KDA 内核,是端到端消除 stall 的接线层。
vllm/model_executor/layers/mamba/gdn/kimi_gdn_linear_attn.py(模块 模型层;类别 source;类型 refactor): AMD/NVIDIA KDA 内核的平台分支导入改用 TYPE_CHECKING,保证不同厂商实现签名差异不会破坏类型检查。
vllm/models/kimi_k3/amd/ops/third_party/kda/chunk.py(模块 内核层;类别 source;类型 data-contract;符号 chunk_kda_with_fused_gate, chunk_kda_with_fused_gate_fwd, _chunk_kda_fwd_with_cumulative_g): KDA 内核签名扩展,接受调用方现成的 chunk_indices/chunk_offsets,避免在 kernel 内部重复构建造成的同步。
关键符号:_chunk_metadata_kernel, prepare_chunk_metadata_device, KimiK3ROCmKDAMetadataBuilder._build_chunk_metadata, KimiK3ROCmKDABackend.get_name, KimiK3ROCmKDABackend.get_builder_cls, GDNAttentionMetadataBuilder._build_chunk_metadata, GDNAttentionMetadataBuilder.build, KimiK3DeltaAttention.get_attn_backend, chunk_kda_with_fused_gate, chunk_kda_with_fused_gate_fwd, _chunk_kda_fwd_with_cumulative_g
关键源码片段
vllm/models/kimi_k3/amd/kda_metadata.py
新增文件,包含设备端 Triton chunk metadata 内核、ROCm Kimi-K3 专属 metadata builder 与 attention backend,是消除 prefill stall 的核心。
# vllm/models/kimi_k3/amd/kda_metadata.py
# 设备端 Triton kernel:一次生成 chunk_indices 与 chunk_offsets,
# 避免 host 侧 Python 循环、D2H `.tolist()` 与 H2D 拷贝带来的同步点。
@triton.jit(do_not_specialize=["N"])
def _chunk_metadata_kernel(
cu_seqlens,
chunk_indices,
chunk_offsets,
N,
BT: tl.constexpr,
BLOCK_N: tl.constexpr,
BLOCK_T: tl.constexpr,
):
# 每个 program 负责一个序列,先取 bos/eos,再算该序列的 chunk 数。
i_n = tl.program_id(0)
offs_n = tl.arange(0, BLOCK_N)
is_seq = offs_n < N
bos = tl.load(cu_seqlens + offs_n, mask=is_seq, other=0).to(tl.int32)
eos = tl.load(cu_seqlens + offs_n + 1, mask=is_seq, other=0).to(tl.int32)
nt = tl.where(is_seq, tl.cdiv(eos - bos, BT), 0)
# 用 tl.sum 归约出当前序列的起始 chunk 偏移 base,以及自身 chunk 数。
base = tl.sum(tl.where(offs_n < i_n, nt, 0))
num_chunks = tl.sum(tl.where(offs_n == i_n, nt, 0))
tl.store(chunk_offsets + i_n, base)
if i_n == 0:
tl.store(chunk_offsets + N, tl.sum(nt))
# 把 (seq_idx, chunk_idx) 展平写入 chunk_indices,每对占两列。
for t0 in range(0, num_chunks, BLOCK_T):
offs_t = t0 + tl.arange(0, BLOCK_T)
mask_t = offs_t < num_chunks
row = (base + offs_t) * 2
tl.store(chunk_indices + row, tl.full([BLOCK_T], i_n, tl.int32), mask=mask_t)
tl.store(chunk_indices + row + 1, offs_t.to(tl.int32), mask=mask_t)
def prepare_chunk_metadata_device(
cu_seqlens: torch.Tensor,
cu_seqlens_cpu: torch.Tensor,
chunk_size: int,
) -> tuple[torch.Tensor, torch.Tensor]:
# host 侧只用 cu_seqlens_cpu 计算总 chunk 数以分配显存,
# 不等待 device 队列排空;kernel 本身完全在 device 上计算。
num_seqs = cu_seqlens_cpu.numel() - 1
seq_lens = cu_seqlens_cpu[1:] - cu_seqlens_cpu[:-1]
num_chunks = int(((seq_lens + chunk_size - 1) // chunk_size).sum())
chunk_indices = torch.empty(
num_chunks, 2, dtype=cu_seqlens.dtype, device=cu_seqlens.device
)
chunk_offsets = torch.empty(
num_seqs + 1, dtype=torch.int64, device=cu_seqlens.device
)
_chunk_metadata_kernel[(num_seqs,)](
cu_seqlens,
chunk_indices,
chunk_offsets,
num_seqs,
BT=chunk_size,
BLOCK_N=max(_MIN_BLOCK_N, next_power_of_2(num_seqs + 1)),
BLOCK_T=_BLOCK_T,
num_warps=4,
)
return chunk_indices, chunk_offsets
vllm/v1/attention/backends/gdn_attn.py
共享 GDN metadata builder 的构建逻辑被抽取为可覆写方法 _build_chunk_metadata,是通用路径的关键改造点。
# vllm/v1/attention/backends/gdn_attn.py
# 新增可覆写的 metadata 构建钩子:默认实现仍用 CPU 预计算 + 异步 H2D,
# ROCm Kimi-K3 子类可以覆写为纯 device 端构建,从而消除流水线停顿。
def _build_chunk_metadata(
self,
prefill_query_start_loc: torch.Tensor,
prefill_query_start_loc_cpu: torch.Tensor,
device: torch.device,
) -> tuple[torch.Tensor, torch.Tensor]:
from vllm.third_party.flash_linear_attention.ops.utils import FLA_CHUNK_SIZE
# cutedsl 后端走原有设备端路径:直接在 GPU 上准备 metadata。
if self.gdn_prefill_backend == "cutedsl":
from vllm.model_executor.layers.mamba.ops.gdn_chunk_cutedsl import (
prepare_metadata_cutedsl,
)
assert prefill_query_start_loc is not None
assert prefill_query_start_loc_cpu is not None
total_tokens = int(prefill_query_start_loc_cpu[-1].item())
return prepare_metadata_cutedsl(
prefill_query_start_loc,
total_tokens,
FLA_CHUNK_SIZE,
)
# 默认 Triton 路径:CPU 预计算 chunk 索引后异步拷贝到 GPU,
# 避免 GPU→CPU 的 .tolist() 同步;ROCm Kimi-K3 覆写后不再走这里。
from vllm.third_party.flash_linear_attention.ops.index import (
prepare_chunk_indices,
prepare_chunk_offsets,
)
assert prefill_query_start_loc_cpu is not None
return (
async_tensor_h2d(
prepare_chunk_indices(prefill_query_start_loc_cpu, FLA_CHUNK_SIZE),
device=device,
),
async_tensor_h2d(
prepare_chunk_offsets(prefill_query_start_loc_cpu, FLA_CHUNK_SIZE),
device=device,
),
)
评论区精华
风险与影响
- 风险:
- 新增 Triton kernel 的正确性:
_chunk_metadata_kernel 用 tl.sum 归约计算每个序列的 chunk 偏移,若与原有 CPU 路径在边界条件(空序列、序列数非 2 的幂、超大 chunk 数)上不一致,会导致索引错乱;目前没有对应的单元测试。
- 数据契约变化:
chunk_kda_with_fused_gate 等函数签名新增参数,AMD/NVIDIA 两条 kimi_k3 路径共用同一份调用约定;kimi_gdn_linear_attn.py 用 TYPE_CHECKING + 平台分支导入,若某平台实现签名未来漂移,会在运行时暴露而非类型检查期。
- 共享后端重构风险:
gdn_attn.py 的 GDNAttentionMetadataBuilder.build() 是 GDN 家族共用路径,抽取 _build_chunk_metadata 必须保持默认行为与原来完全等价,Kimi-Linear 等其他 GDN 模型需要回归验证。
- 平台覆盖:本 PR 只影响 AMD 路径;若后续共享代码被 NVIDIA 路径复用,需确认
chunk_offsets 的语义一致。
- 影响:
- 用户与系统:ROCm 上 Kimi-K3 服务的 prefill/mixed step 不再因 chunk metadata 构建而停顿,低并发(8/16)下总吞吐提升约 2.9%-3.3%,TTFT 缩短 1.4%-2.6%,TPOT 缩短约 3.2%-3.4%;高并发(32/64)下基本持平或略有改善。NVIDIA 路径完全不受影响。
- 代码结构:GDN metadata 构建从
build() 内联逻辑中解耦为可覆写钩子,为后续通用方案(如 #51540)和厂商专属 backend 提供扩展点;新增的 KimiK3ROCmKDABackend 也是 vendor 专属 attention backend 的注册样板。
- 团队协作:本 PR 与 #51540 正交,先合本 PR 让 ROCm Kimi-K3 立即受益,后续 #51540 再统一 NVIDIA/ROCm 通用路径。
- 风险标记:核心路径变更, 新增设备端内核, 缺少自动化测试, ROCm 专属
关联脉络
- PR #51540 (上下文未提供标题,讨论中仅以链接引用): 同一功能线:移除 GDN chunk KDA 的 prefill pipeline stall;维护者确认其更通用但未覆盖 ROCm Kimi-K3,与本 PR 正交。
- PR #51772 [Attention][MLA] Fuse Kimi-K3 chunked-context K/V packing: 同属 Kimi-K3 预填充性能优化线,但作用于 NVIDIA/MLA 路径;本 PR 是 ROCm/KDA 路径的对应优化。
参与讨论