Prhub

#54565 [K3 Perf] Enable DSV3 GEMM for inner-contiguous and row-strided tensors, 12%~81% kernel performance improvement

原始 PR 作者 yewentao256 合并时间 2026-09-03 03:22 文件变更 3 提交数 2 评论 8 代码增减 +48 / -35

执行摘要

为 Kimi K3 的 DSV3 GEMM 内核添加对内部连续、行步幅张量的直接支持,显著提升内核性能。

动机源于 Kimi K3 模型中某些投影层(如 KDA f_b_proj 和 MLA q_b_proj)的激活张量是从一个更大的存储缓冲区中切片得到的视图。这些视图虽然在内存中内部连续(stride(1) == 1),但不满足之前内核要求的严格 packed row-major 布局(stride(0) == K)。这导致它们回退到通用的 F.linear 或 NVJet 实现,未能利用针对特定形状优化的 DSV3 高性能内核。PR body 中明确指出:They previously fell back to F.linear/NVJet because they were not fully contiguous. This PR fixes the issue。

该 PR 值得精读。它是一个典型的高性能内核优化案例,展示了如何在保持核心路径性能的前提下,通过精细的内存布局检查和动态参数传递,扩展内核的适用范围。关键设计决策在于:

1) 在 Python 层定义清晰的张量布局约束;
2) 在 CUDA 内核中正确传递和使用动态步幅;
3) 通过详实的基准测试数据来评估和证明性能影响。这些实践对于优化其他计算密集型内核具有参考价值。

讨论亮点

核心讨论围绕性能回归风险展开。reviewer gau-nernst 指出:This can cause regression on fully contiguous tensor because row_stride is now dynamic instead of constexpr, you may want to double check on that(将行步幅从 constexpr 改为动态参数可能导致完全连续张量的性能回退)。作者 yewentao256 通过详细的基准测试数据回应,展示了对完全连续张量(各种 KDA、MLA 等投影形状)在修改前后的性能对比。数据显示,性能变化范围在 -3.0% 至 +1.5% 之间,大多数变化小于 1%,证明了性能回归风险可控。结论是,在保持对完全连续张量性能基本不变的前提下,显著提升了对非连续行步幅视图的性能。

实现拆解

变更集中在三个文件,核心逻辑是放宽输入张量布局检查并传递动态行步幅至内核。

  1. 放宽 Python 层调度条件:修改 vllm/models/kimi_k3/nvidia/low_latency_gemm.py 中的 _runtime_ok 函数。将原来要求输入 x 为 packed row-major (_is_packed_row_major(x)) 的检查,改为要求 x 是二维张量、内步幅为 1 (x.stride(1) == 1),且行步幅对齐(单 token 时任意,多 token 时行步幅必须为 8 的倍数,即 x.stride(0) % 8 == 0)。同时在 _run_plan 函数中,为 cute 后端增加了针对多 token 且非 packed row-major 的额外回退检查。对 _residual_ok 函数也增加了对输入 x 必须是 packed row-major 的要求。
  2. 修改 CUDA 内核以支持动态行步幅:更新 csrc/libtorch_stable/dsv3_fused_a_gemm.cu 中的 dsv3_fused_a_gemm 算子。移除了对输入 mat_a 必须为 packed row-major 的严格断言,改为检查内步幅为 1 且行步幅对齐。新增 input_row_stride 参数,并在内核的加载(GmemLoaderB::issue_mainloop)和计算部分使用该动态步幅,替代原先硬编码的 gemm_k。
  3. 更新测试用例:修改 tests/kernels/test_bf16_skinny_gemm.py。将原先验证非 packed 视图会回退的测试 test_nonpacked_single_token_dsv3_falls_back,替换为验证非 packed 但满足新条件的视图能够正确调度到 DSV3 内核的测试 test_kda_f_b_nonpacked_dsv3_dispatches。
文件 模块 状态 重要度
vllm/models/kimi_k3/nvidia/low_latency_gemm.py K3 低延迟线性层 modified 6.07
csrc/libtorch_stable/dsv3_fused_a_gemm.cu DSV3 CUDA 内核 modified 5.26
tests/kernels/test_bf16_skinny_gemm.py BF16 内核测试 modified 5.15

关键符号

_runtime_ok _run_plan _residual_ok dsv3_fused_a_gemm invokeFusedAGemmForTokens fused_a_gemm_kernel GmemLoaderB::issue_mainloop

关键源码片段

vllm/models/kimi_k3/nvidia/low_latency_gemm.py core-logic

核心调度逻辑所在,定义了是否使用 DSV3 内核的运行时检查。本 PR 放宽了输入张量布局检查,是启用新路径的入口。

def _runtime_ok(x: torch.Tensor, weight: torch.Tensor) -> bool:
    # 放宽了对输入 x 的内存布局检查。
    # 之前 : x 必须是 packed row-major (stride(0)==K, stride(1)==1)
    # 现在 : x 只需满足 :
    # 1. 二维张量
    # 2. 内步幅为 1 ( 内部连续 )
    # 3. 行步幅对齐 : 单 token 时任意,多 token 时 stride(0) 必须是 8 的倍数
    # 这样就能支持从大缓冲区切片出来的视图,例如 x = storage[:, 6144:6144+128]
    return (
        x.dim() == 2
        and x.stride(1) == 1
        and (x.shape[0] == 1 or x.stride(0) % 8 == 0)
        and _is_packed_row_major(weight) # 权重仍然要求 packed
        and x.dtype == torch.bfloat16
        and weight.dtype == torch.bfloat16
        and x.is_cuda
        and weight.is_cuda
        and x.device == weight.device
        and x.shape[1] == weight.shape[1]
    )
csrc/libtorch_stable/dsv3_fused_a_gemm.cu core-logic

CUDA 内核实现,是性能提升的根源。本 PR 将硬编码的步幅改为动态参数,以支持非连续行步幅。

// 入口函数:更新检查,传入行步幅
void dsv3_fused_a_gemm(torch::stable::Tensor& output,
                       torch::stable::Tensor const& mat_a,
                       torch::stable::Tensor const& mat_b,
                       bool enable_pdl) {
    // ... 其他检查 ...
    // 新增检查:内步幅为 1,且多 token 时行步幅对齐到 16 字节(即 8 个 bf16 元素)
    STD_TORCH_CHECK(
        mat_a.stride(1) == 1 && (num_tokens == 1 || mat_a.stride(0) % 8 == 0),
        "mat_a must have unit inner stride and 16-byte aligned rows");
    // ... 其他检查 ...
    int const input_row_stride = mat_a.stride(0); // 获取动态行步幅
    // ... 传递 input_row_stride 到内核调度宏 ...
}
​
// 内核函数:使用动态行步幅进行内存寻址
template <...>
void fused_a_gemm_kernel(..., int input_row_stride) {
    // ... 计算块坐标 ...
    // 使用 input_row_stride 计算 B 矩阵的基础地址
    bf16_t const* gmem_b_local = mat_b + cta_n_idx * input_row_stride;
    // ... 在加载阶段,将 input_row_stride 传递给 GmemLoaderB ...
    b_loader.issue_mainloop(input_row_stride);
}

评论区精华

动态行步幅可能引入完全连续张量的性能回退 性能

reviewer `gau-nernst` 指出,将 `row_stride` 从 `constexpr` 改为动态参数,可能会影响对完全连续张量的优化,导致性能回退。 作者 `yewentao256` 回复并提供了详细的基准测试数据,对比了多种形状的完全连续张量在修改前后的性能。数据显示,性能变化在 -3.0% 到 +1.5% 之间,大部分小于 1%,表明回退风险可控。

结论:结论是,为了支持更灵活的张量布局而将步幅参数动态化,所引入的微小性能波动(在多数情况下甚至略有提升)是可以接受的。核心性能路径(完全连续张量)未受到显著负面影响。 · 已解决

风险与影响

主要技术风险是性能回归和兼容性。

  1. 性能回归风险:正如讨论中指出,将 CUDA 内核中的 row_stride 从编译期常量 (constexpr) 改为运行时动态参数,理论上可能影响编译器优化和指令调度。然而,作者提供的基准测试表明,对于核心的完全连续张量路径,性能变化在可接受范围内(±1.5%),风险较低。
  2. 兼容性风险:放宽 _runtime_ok 检查可能允许更多类型的张量传入内核。需要确保 csrc/libtorch_stable/dsv3_fused_a_gemm.cu 中新增的对齐检查(行步幅 % 8 == 0)足够严格,防止非法内存访问。从代码看,CUDA 内核和 Python 层的检查是一致的。
  3. 测试覆盖风险:虽然测试用例已更新,但主要覆盖了新增的 KDA f_b_proj 和 MLA q_b_proj 场景。对 _run_plan 中 cute 后端新增的回退逻辑,其测试覆盖未在本次 PR 中体现,可能依赖于其他现有测试。

对用户和系统有直接且积极的性能影响。

  1. 用户影响:所有在 NVIDIA GPU(特别是 SM100/SM103 架构)上运行 Kimi K3 模型的用户,在涉及 KDA f_b_proj、MLA q_b_proj 等投影层的计算时,将获得显著的延迟降低(最高达 81%)。这直接提升了模型的推理吞吐量和响应速度。
  2. 系统影响:此变更优化了模型内核调度路径,使得高性能专用内核(DSV3)的适用场景更广。它不会影响模型的正确性(输出与 F.linear 一致),但减少了对通用 fallback 路径的依赖,可能略微降低计算资源消耗。
  3. 团队影响:为 Kimi K3 的性能优化提供了新的范式,即通过放宽内核输入约束来适配实际模型中的非连续数据布局,可能启发类似优化。
内核内存访问模式变更 性能回归风险(已通过测试缓解) 测试覆盖范围有限

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论