Prhub

#50387 [CPU] Bump up CPU kernels to latest version

原始 PR 作者 bigPYJ1151 合并时间 2026-07-30 19:18 文件变更 15 提交数 4 评论 10 代码增减 +2183 / -1292

执行摘要

同步 SGLang 上游 CPU 内核,修复多项 bug 并优化 GDN 性能

随着 SGLang 上游 CPU 内核的持续演进,vLLM 需要同步以获取最新的性能优化和 bug 修复。自上次同步(#41924)以来,vLLM 在 CPU 内核上积累了大量特有补丁。本次同步将这些补丁重新融合到新版本内核中,并利用上游新特性(快速 silu 近似、池化状态索引)优化了 GDN 路径的性能。同时修复了合并过程中发现的上游回归(TORCH_CHECK 强度被削弱),确保量化的正确性。

建议所有 CPU 用户升级此 PR,尤其是使用 GDN 模型的用户可直接获得性能提升。但需注意:尽快在后续 PR 中修复 reviewer 指出的 bias 应用和 UB 问题;对于生产部署,建议在目标模型上执行完整的精度对比测试(如 GSM8K 等任务),确保数值变化在可接受范围内。开发者可研究 fla.cpp 中的循环融合和 AMX 优化技巧,这些设计模式对其他路径的优化有借鉴意义。

讨论亮点

Review 中有两个由 depthfirst-app[bot] 提出的低严重性评论,但均未在 PR 中得到回复或解决:

  • Bias 应用错误:当不使用 brgemm 且激活函数为 silu_and_mul 时,融合 tinygemm 路径将结果直接写入 ic1 而非 C0/C1,后续对 C0/C1 的 bias 添加导致 bias 静默丢失。
  • 空指针算术未定义行为:当 with_bias 为 false 时,代码仍对 w1_bias/w2_bias 执行指针算术(nullptr 偏移),属于未定义行为,可能被编译器优化移除后续的条件保护。
    这些问题表明虽然 PR 合并,但可能存在尚未发现的推理正确性隐患。

实现拆解

  1. 拉取上游代码并解决冲突:从 SGLang 主分支同步 csrc/cpu/sgl-kernels/,将所有 vLLM 特有补丁(MXFP4 MoE、AMX GDN dispatch、ISA 可移植 BLAS fallback、RISC-V 标量/向量支持、chunked-prefill has_initial_state 修复、DFlash 推测解码等)应用到新代码上,解决合并冲突。

  2. 采用池化状态索引优化 GDN 预填充:在 fla.cpp 中,chunk_gated_delta_rule_cpu 函数签名新增 initial_state_indices 参数。CPU GDN 预填充直接通过索引读写池化的 ssm_state 缓冲区,替代之前的 gathering 本地拷贝,减少每次预填充的额外开销,实测吞吐和时延提升 3%-8%。

  3. 拒绝上游回归并修复 ARM 回退路径:拒绝了一个会静默削弱 MXFP4 规模检查的上游 TORCH_CHECK 变更,确保障序正确性。同时为 ARM/非AVX512 架构编写了基于 at::vec::Vectorized 的向量化回退实现,替换标量循环,并修正了 chunk_gated_delta_rule_cpu 的非 AVX512 回退中的逻辑错误(例如 kNumHeadkHeadDim 在 ARM 上的条件判断错误)。

  4. 更新 MoE 内核以支持新激活函数和 bias:在 moe.cppmoe.h 中,fused_experts_kernel_impl 新增 alphalimitact_func(支持 silu_and_mulswiglugelu_and_mul)和 with_bias 参数,使 MoE 层能灵活处理不同的门控激活和偏置注入。同时将原先的通用仿射 silu 替换为使用 _mm512_rcp14_silu_ps 的快速近似版本(仅 AVX512),提升性能。

  5. 更新 Python 绑定和测试:调整 torch_bindings.cpp 中的算子注册以匹配新签名,并修改 tests/cpu/gdn/ops/test_cpu_gdn_ops.py 来适配 chunk_gated_delta_rule_cpu 的新参数。

文件 模块 状态 重要度
csrc/cpu/sgl-kernels/fla.cpp CPU 内核 modified 7.6
csrc/cpu/sgl-kernels/moe.cpp CPU 内核 modified 7.54
csrc/cpu/sgl-kernels/vec.h CPU 内核 modified 7.21
csrc/cpu/sgl-kernels/gemm.h CPU 内核 modified 6.63

关键符号

fused_experts_kernel_impl chunk_gated_delta_rule_kernel_impl pack_vnni2 store_from_float_ext load_float_vec sigmoid_and_mul_stub silu_and_mul_stub

关键源码片段

csrc/cpu/sgl-kernels/fla.cpp core-logic

GDN 内核完全重写,引入池化状态索引、循环融合、AMX BF16 状态更新等关键优化,是本次 PR 的核心性能改进所在。

// fla.cpp: pack_vnni2 — 将 FP32 数据转换为 VNNI 格式 (BF16) 并缩放
// 这是 AMX 优化的关键步骤:将布局从 [K/2, 2, N] 转换为 [K/2, N, 2] 以匹配 AMX 输入格式
// 同时将 src 乘以 exp(g_last) 实现门控衰减,避免后续单独处理
template <typename scalar_t, int K, int N>
void pack_vnni2(scalar_t* __restrict__ dst, float* __restrict__ src,
                const float g_last, int ld_src, int ld_dst) {
  static_assert(K % 32 == 0);
  static_assert(N % 32 == 0);  const float scale = std::exp(g_last);
#if defined(CPU_CAPABILITY_AVX512)
  constexpr int KB = K / 2;
  constexpr int NB = N / 32;
  __m512i s0, s1, d0, d1;
  __m512 vd = _mm512_set1_ps(scale);  // Unroll 遍历 KB * NB 个 tile,每个 tile 处理 32 个元素
  const auto trans = [&](auto i) {
    constexpr int kb = i / NB;
    constexpr int nb = i % NB;    // 从 [K/2, 2, N/32, 32] 布局读取
    constexpr int k0 = kb * 2 + 0;
    constexpr int k1 = kb * 2 + 1;
    __m512 v00 = _mm512_loadu_ps(src + k0 * ld_src + nb * 32);
    __m512 v01 = _mm512_loadu_ps(src + k0 * ld_src + nb * 32 + 16);
    __m512 v10 = _mm512_loadu_ps(src + k1 * ld_src + nb * 32);
    __m512 v11 = _mm512_loadu_ps(src + k1 * ld_src + nb * 32 + 16);
    s0 = (__m512i)_mm512_cvtne2ps_pbh(v01, v00); // 两路 BF16 压缩
    s1 = (__m512i)_mm512_cvtne2ps_pbh(v11, v10);    std::tie(d0, d1) = transpose_2x32_16bit(s0, s1); // 转置为 [N/32, 32, 2]
    _mm512_storeu_si512(dst + kb * ld_dst * 2 + nb * 32 * 2, d0);
    _mm512_storeu_si512(dst + kb * ld_dst * 2 + nb * 32 * 2 + 32, d1);    // 原地缩放 src 以避免后续再遍历
    _mm512_storeu_ps(src + k0 * ld_src + nb * 32, _mm512_mul_ps(v00, vd));
    _mm512_storeu_ps(src + k0 * ld_src + nb * 32 + 16, _mm512_mul_ps(v01, vd));
    _mm512_storeu_ps(src + k1 * ld_src + nb * 32, _mm512_mul_ps(v10, vd));
    _mm512_storeu_ps(src + k1 * ld_src + nb * 32 + 16, _mm512_mul_ps(v11, vd));
  };
  Unroll<KB * NB>{}(trans);
#else
  // 非 AVX512 回退:简单循环,等价但未微架构优化
  for (int k = 0; k < K; k += 2) {
    for (int n = 0; n < N; ++n) {
      const float v0 = src[(k + 0) * ld_src + n];
      const float v1 = src[(k + 1) * ld_src + n];
      dst[(k >> 1) * ld_dst * 2 + n * 2 + 0] = static_cast<scalar_t>(v0);
      dst[(k >> 1) * ld_dst * 2 + n * 2 + 1] = static_cast<scalar_t>(v1);
      src[(k + 0) * ld_src + n] *= scale;
      src[(k + 1) * ld_src + n] *= scale;
    }
  }
#endif
}
csrc/cpu/sgl-kernels/moe.cpp core-logic

MoE 核心内核实现,新增 bias 支持和多种激活函数,优化 tinygemm 的 silu 融合,是 MoE 前向的正确性和性能关键。

// moe.cpp: tinygemm_kernel_nn2 的 storec 改造 — 使用快速 rcp14 silu 近似
// 原实现:x0 = x0 / (one + x0.neg().exp_u20())
// 新实现:使用 _mm512_rcp14_silu_ps 指令,降低延迟且数值足够接近
// 同时输出格式直接从两个 FP32 向量打包为 BF16 存储
auto storec = [&](auto i) {
  constexpr int row = i / COLS;
  constexpr int col = i % COLS;
  if constexpr (col % 2 == 0) { // 每两列合并为一个 AVX512 存储
    __m512 x0 = vc0[row * COLS + col + 0];
    __m512 x1 = vc0[row * COLS + col + 1];
    __m512 y0 = vc1[row * COLS + col + 0];
    __m512 y1 = vc1[row * COLS + col + 1];
    // 快速 silu: silu(x) = x * sigmoid(x) 的倒数近似
    x0 = _mm512_mul_ps(_mm512_rcp14_silu_ps(x0), y0);
    x1 = _mm512_mul_ps(_mm512_rcp14_silu_ps(x1), y1);
    // 直接打包为 BF16 存储,去掉中间转换步骤
    _mm512_storeu_si512(
        reinterpret_cast<__m512i*>((C + row * ldc + col * 16)),
        (__m512i)(_mm512_cvtne2ps_pbh(__m512(x1), __m512(x0))));
  }
};

评论区精华

Bias 应用作用于错误数据路径 正确性

depthfirst-app[bot] 指出,当 !use_brgemm && act_func == silu_and_mul 时,融合 tinygemm 路径将结果直接写入 ic1 而非 C0/C1,后续的 bias 添加针对 C0/C1 操作,导致 bias 静默丢失,输出完全错误。

结论:PR 作者未回复,PR 已合并,问题未解决。 · unresolved

空指针算术未定义行为 安全

depthfirst-app[bot] 指出,当 with_bias 为 false 时,w1_bias 和 w2_bias 为 nullptr,但代码仍然执行指针算术(如 w1_bias + expert_id * 2 * N),这属于未定义行为,可能被编译器优化利用消除后续的 with_bias 判断。

结论:PR 作者未回复,PR 已合并,问题未解决。 · unresolved

风险与影响

  1. 数值精度偏移:在 12 个测试模型中,2 个模型(gpt-oss-20b MXFP4、Qwen3-30B-A3B-GPTQ-Int4)在首个 token 后输出与基线不同,说明量化内核的数值精度有微小变化,可能影响对输出一致性要求极高的场景。
  2. 未解决的 UB 风险:depthfirst-app[bot] 指出的空指针算术 UB 仍未修复,在特定编译器优化下可能导致逻辑错误。
  3. bias 应用逻辑错误:bias 添加作用于错误缓冲区的 bug 可能导致带 bias 的 MoE 层输出完全错误(影响范围:使用 MoE 且门控带 bias 的模型)。
  4. 回归覆盖不足:测试主要覆盖量化模型,对非量化路径(BF16、FP16)以及 RISC-V 等新架构的验证有限。

影响范围:所有使用 CPU 后端进行推理的用户,特别是使用量化模型(W4A16、W8A8、FP8、MXFP4)和 GDN 模型(如 Qwen3.5)的用户。性能方面,GDN 预填充提升 3%-8%;MoE 路径在噪声范围内。正确性方面,大多数模型输出字节一致,但少数模型有微小差异,可能需要模型评估验证。兼容性:需要重新编译 CPU 的三个 ISA 目标。对于 RISC-V 用户,提供了基础支持但未经充分测试。
团队影响:维护者需要关注并解决 reviewer 指出的两个低严重性 bug,避免影响扩大。同时,本次同步为后续 CPU 内核的继承和更新建立了规范流程。

核心路径变更 未修复的 UB 风险 部分模型输出偏移 review 问题未跟踪

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论