Prhub

#50219 [CPU][s390x] Optimize inference perf and add oneDNN INT8 GEMM for s390x

原始 PR 作者 R3hankhan123 合并时间 2026-07-31 17:49 文件变更 6 提交数 3 评论 6 代码增减 +182 / -27

执行摘要

s390x CPU 推理优化并新增 oneDNN INT8 GEMM 支持

PR body 明确目标是启用 s390x 专属 CPU 优化:通过 VXE 指令集的 minimax 多项式实现 attention softmax 快速 exp、用向量指令实现 reduce_sum/reduce_max、用 vec_cts/vec_packs 实现 FP32→INT8 打包,并启用 oneDNN INT8 W8A8 GEMM。此前 vLLM 的 CPU 量化矩阵中 compressed-tensor INT8 W8A8 仅标注 x86 only,作者在 s390x 主机上以 RedHatAI/granite-3.1-2b-instruct-quantized.w8a8 跑通了端到端推理,验证目标达成。

值得精读的文件是 csrc/cpu/cpu_types_vxe.hpp 与 cmake/cpu_extension.cmake:前者展示了 SIMD 树形归约与 minimax 多项式近似的工程写法,可对照 FP32Vec8/FP32Vec16 的职责拆分;后者展示了构建期对第三方库打补丁的 hack 方式,可作为反面教材关注其可持续性。建议重点关注两点:一是 INT8Vec16::save 的越界检查是否在合并前补齐,二是 fadara01 提出的统一 exp 分发方向(将 DEFINE_FAST_EXP 收敛为所有后端共用的 vec.exp() 调用)是否会被后续 PR 采纳。

讨论亮点
  1. fadara01 对 DEFINE_FAST_EXP 宏的架构性质疑:在 cpu_arch_macros.h 第 190 行评论指出——既然 fast exp 与 VXE 类自身的 exp 实现并无区别,不如直接修改 cpu_attn_impl.hpp 让所有后端统一走 vec.exp(),这样也能加速其他后端;并引用了为缺失向量化器补齐 exp 的 PR#50133 作为佐证。作者未在该线程回复,但 PR 最终获得 fadara01 的 APPROVED,说明该质疑被当作可接受的后续演进方向而非阻塞项。
  2. depthfirst-app[bot] 的越界检查提示(LOW):bot 指出 INT8Vec16::save 的循环缺少 i < VEC_ELEM_NUM 上界检查,与本文件其他 save(ptr, elem_num) 的防御模式不一致,elem_num > 16 时可能造成栈上越界读与目标缓冲区越界写,并给出了补上界检查的修复建议。评论未获作者回复,最终代码状态无法从现有材料核实,属于遗留的低风险隐患。

实现拆解

  1. SIMD 原语层补齐(csrc/cpu/cpu_types_vxe.hpp):为 FP32Vec16 新增 exp(委托 FP32Vec8 的 5 阶 minimax 多项式近似)、abs、min、clamp、reduce_min 等算子,供 attention softmax 和 dnnl_kernels 使用;新增 INT8Vec16 类型,用 vec_cts/vec_packs 完成 FP32→INT8 打包,服务于 W8A8 GEMM 的量化激活准备工作;BF16Vec16 增加带 elem_num 参数的 save 重载,按实际元素数写出,供尾部不足 16 个元素时使用。
  2. 归约逻辑向量化(csrc/cpu/cpu_types_vxe.hpp):FP32Vec8/FP32Vec16 的 reduce_sum 与 reduce_max 从 AliasReg + unroll_loop 的逐元素标量累加,改为 vec_add/vec_max 先合并 128 位块、再用 vec_sld 按 8/4 字节步长折叠的树形归约,最后仅做一次 vec_extract。这是本 PR 性能提升的主要来源之一,也顺带修复了旧实现中 union 类型重解释带来的隐患。
  3. 平台宏接线(csrc/cpu/cpu_arch_macros.h):新增 __s390x__ 分支的 DEFINE_FAST_EXP 宏,将 fast_exp/fast_exp_f16 绑定到 FP32Vec16::exp(),与 PowerPC 分支的模式保持一致,使 attention kernel 中的 softmax 计算自动走向量化 exp。
  4. oneDNN 构建与算子注册(cmake/cpu_extension.cmake、csrc/cpu/torch_bindings.cpp):cmake 将 S390_FOUND 纳入 oneDNN 构建条件;由于 oneDNN 的 src/cpu/s390x/helpers.h 中 vec_type_t<T> &ALWAYS_INLINE operator+= 在 GCC 14/C++20 下无法编译,改为先 FetchContent_Populate 再对源码做字符串替换后 add_subdirectory。torch_bindings.cpp 将 __s390x__ 加入 oneDNN 量化算子注册的预编译条件,使得 create_onednn_scaled_mm_handler、onednn_scaled_mm 等 op 在 s390x 下可见。
  5. 文档配套(docs/getting_started/installation/cpu.md、cpu.s390x.inc.md):将 INT8 W8A8 的支持范围由 x86 only 更新为 x86、s390x only,并把 s390x 的硬件基线从 Z14 提升到 Z15(VXE 全量指令需求的明确声明)。
  6. 测试配套说明:本 PR 未附带直接对应的测试文件改动,验证主要依赖作者在 s390x 主机上运行 W8A8 模型的端到端推理日志;CI 侧由维护者触发 Buildkite 验证。
文件 模块 状态 重要度
csrc/cpu/cpu_types_vxe.hpp CPU 内核 modified 7.16
csrc/cpu/cpu_arch_macros.h CPU 内核 modified 5.2
cmake/cpu_extension.cmake 构建脚本 modified 3.79
csrc/cpu/torch_bindings.cpp 算子注册 modified 4.32
docs/getting_started/installation/cpu.md 文档 modified 1.72
docs/getting_started/installation/cpu.s390x.inc.md 文档 modified 1.72

关键符号

FP32Vec8::reduce_sum FP32Vec16::reduce_sum FP32Vec16::reduce_max FP32Vec16::exp FP32Vec16::abs FP32Vec16::min FP32Vec16::clamp FP32Vec16::reduce_min BF16Vec16::save INT8Vec16

关键源码片段

csrc/cpu/cpu_types_vxe.hpp core-logic

本 PR 的核心:新增 FP32Vec16 的 exp/abs/min/clamp/reduce_min 算子、INT8Vec16 打包原语,并把 reduce_sum/reduce_max 改为 vec_sld 树形归约,是 s390x 性能提升的主体

// FP32Vec8:8 个 FP32 元素的 256 位寄存器组,本 PR 中归约从
// 逐元素标量累加改为向量树形归约,减少指令依赖链长度
struct FP32Vec8 : public Vec<FP32Vec8> {
  f32x4x2_t reg;  // 先横向相加两个 128 位块,再用 vec_sld 按 8/4 字节步长折叠,
  // 最终只做 1 次标量提取,替代原先的 unroll_loop 逐元素累加
  float reduce_sum() const {
    __vector float sum = vec_add(reg.val[0], reg.val[1]);
    __vector float hi = vec_sld(sum, sum, 8);
    sum = vec_add(sum, hi);
    __vector float lo = vec_sld(sum, sum, 4);
    sum = vec_add(sum, lo);
    return vec_extract(sum, 0);
  }
};// FP32Vec16:16 个 FP32 元素,由 4 个 128 位块组成
struct FP32Vec16 : public Vec<FP32Vec16> {
  f32x4x4_t reg;  // 4 块先两两相加,再按 8/4 字节步长折叠归约,
  // 相比标量循环减少了 15 次加法串行依赖
  float reduce_sum() const {
    __vector float sum = vec_add(vec_add(reg.val[0], reg.val[1]),
                                 vec_add(reg.val[2], reg.val[3]));
    __vector float hi = vec_sld(sum, sum, 8);
    sum = vec_add(sum, hi);
    __vector float lo = vec_sld(sum, sum, 4);
    sum = vec_add(sum, lo);
    return vec_extract(sum, 0);
  }  // 与 reduce_sum 同构的树形最大归约,用于量化 scale 计算等场景
  float reduce_max() const {
    __vector float m = vec_max(vec_max(reg.val[0], reg.val[1]),
                               vec_max(reg.val[2], reg.val[3]));
    __vector float hi = vec_sld(m, m, 8);
    m = vec_max(m, hi);
    __vector float lo = vec_sld(m, m, 4);
    m = vec_max(m, lo);
    return vec_extract(m, 0);
  }  // 5 阶 minimax 多项式近似 exp:拆成两个 FP32Vec8 复用其实现,
  // 由 DEFINE_FAST_EXP 宏接入 attention softmax 热路径
  FP32Vec16 exp() const {
    FP32Vec8 lo(f32x4x2_t{reg.val[0], reg.val[1]});
    FP32Vec8 hi(f32x4x2_t{reg.val[2], reg.val[3]});
    auto lo_e = lo.exp();
    auto hi_e = hi.exp();
    return FP32Vec16(f32x4x4_t{lo_e.reg.val[0], lo_e.reg.val[1],
                               hi_e.reg.val[0], hi_e.reg.val[1]});
  }
};
csrc/cpu/cpu_arch_macros.h core-logic

为 s390x 增加 DEFINE_FAST_EXP 宏分支,使 attention softmax 自动使用向量化近似 exp;也是 review 中架构性质疑的焦点位置

// IBM Z(s390x)VXE 扩展:把 attention softmax 的快速 exp 绑定到向量化实现
#ifdef __s390x__
  // FP32Vec16::exp() 在 cpu_types_vxe.hpp 中委托给 FP32Vec8::exp(),
  // 后者用 VXE 内建指令实现 5 阶 minimax 多项式近似,
  // fast_exp_f16 复用同一实现以满足 FP16 路径的调用约定
  #define DEFINE_FAST_EXP \
    auto fast_exp = [&](const vec_op::FP32Vec16& vec) \
                        __attribute__((always_inline)) { return vec.exp(); }; \
    auto fast_exp_f16 = fast_exp;#endif // __s390x__

评论区精华

DEFINE_FAST_EXP 宏是否有必要,还是应统一调用 vec.exp() 设计

fadara01 在 cpu_arch_macros.h 评论:既然 fast exp 与 VXE 类自身的 exp 实现没有区别,为什么不直接修改 cpu_attn_impl.hpp 让所有后端都走 vec.exp(),从而让其他向量化后端也获得 SIMD exp 加速;并引用 PR#50133 作为补充 exp 实现的参考。

结论:作者未在评论线程答复;fadara01 最终 APPROVED,说明该问题被视为可接受的后续演进方向而非本 PR 的阻塞项。 · 已解决

INT8Vec16::save 缺少元素数上界检查 正确性

depthfirst-app[bot] 指出 INT8Vec16::save 的循环缺少 `i < VEC_ELEM_NUM` 上界检查,与本文件其他 save(ptr, elem_num) 的防御模式不一致;若 elem_num > 16,会栈上越界读并写越目标缓冲区,并给出了补上界检查的修复建议。

结论:LOW 严重性;评论无作者回复,最终代码是否修复无法从现有材料核实,合并时需抽查确认。 · unresolved

风险与影响

  1. 越界风险(INT8Vec16::save):review bot 指出 INT8Vec16::save 未带上界检查,与其他 save(ptr, elem_num) 的模式不一致;若调用方传入 elem_num > 16 会出现栈上越界读和目标缓冲区越界写。材料中无法核实最终代码是否已修复,建议合并后抽查该函数。
  2. oneDNN 源码补丁脆弱:cmake/cpu_extension.cmake 在构建期改写 oneDNN 的 src/cpu/s390x/helpers.h(ALWAYS_INLINE 位置调整以兼容 GCC 14/C++20)。oneDNN 版本升级后该字符串替换很可能失效,且由于是内联补丁,失败时错误信息不直观。
  3. 无自动化测试覆盖:本 PR 没有配套测试文件,CPU 量化路径(create_onednn_scaled_mm_handler、onednn_scaled_mm)在 s390x 上的正确性仅靠手工推理日志验证;后续回归时缺少守护。
  4. 近似精度差异:FP32Vec16::exp 是 5 阶 minimax 多项式近似,与 libm exp 有极小误差,softmax 输出可能产生 epsilon 级偏差;对多数推理场景可接受,但依赖严格数值行为的场景需留意。
  5. 硬件基线收紧:文档将 s390x 要求从 Z14 提升到 Z15,Z14 用户升级后可能遇到指令不支持的问题。

用户侧:s390x(IBM Z)用户首次获得 INT8 W8A8 量化模型的可用路径,且 attention softmax、归约等热点的向量化使推理吞吐与延迟显著改善,granite w8a8 类模型的端到端验证日志即为直接证据。系统侧:构建系统增加了一个针对 oneDNN 的源码补丁分支,所有 s390x 构建都会走 FetchContent_Populate + add_subdirectory 路径,未来 oneDNN 升级需要同步维护。团队侧:为后续其他向量化后端的性能对齐提供了参考模式,但 DEFINE_FAST_EXP 宏与 vec.exp() 的职责划分问题尚未收敛,可能需要统一重构。影响范围局限于 s390x + CPU 后端,x86/ARM/RISC-V/PowerPC 均不受影响。

缺少测试覆盖 依赖 oneDNN 源码补丁 SIMD 近似精度差异 潜在越界风险

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论