Prhub

#43754 [HARDWARE][POWER] Enable SHM communicator support for PowerPC

原始 PR 作者 Rukhaiya2004 合并时间 2026-06-02 18:06 文件变更 6 提交数 20 评论 7 代码增减 +125 / -10

执行摘要

为 PowerPC 启用 SHM 通信器支持

Enable SHM communicator support for PowerPC systems. 参考 PR Body 中测试计划 vllm serve ibm-granite/granite-3.3-8b-instruct --tensor-parallel-size 2 --dtype bfloat16 --max-model-len 512 --port 8000,旨在为 PowerPC 平台提供与 x86 和 ARM 同等的 SHM 通信加速能力。

值得关注的设计决策:

  • 采用条件宏而非抽象接口扩展平台支持,保持了最小改动量,适合成熟模块的横向移植。
  • 通过新增 FP16Vec16 类型而非修改原有类型转换逻辑,避免了影响 x86/ARM 的代码路径。
  • 自旋等待指令的低级优化体现了对微架构特性的深入理解,可作为跨平台 busy-wait 模式的参考。
    建议后续为 PowerPC 添加 CI 运行或至少补充集成测试,以确保长期兼容性。
讨论亮点

主要讨论集中在 depthfirst-app[bot] 发现的安全问题:

  • 问题FP16Vec16::save 没有像 BF16Vec16::save 那样对 elem_numstd::max(0, ...) 夹紧,负值传参时会使 num * 2 隐式转换为非常大的 size_t,导致 vec_xst_len 越界写入 16 字节。
  • 响应:作者 Rukhaiya2004 采纳建议并推送修复,最终版本已使用 std::max(0, ...) 防护。
  • 其他:作者提出与 PR 无关联的测试失败(test_import_utils.py),维护者未回应即合并,表明这些失败被认为与变更无关。

实现拆解

该 PR 主要通过条件编译扩展和数据类型补充来移植 SHM 通信器,具体步骤如下:

  1. 扩展原子操作保护宏:在 csrc/cpu/shm.cpp 中将所有 #ifdef __aarch64__ 替换为 #if defined(__aarch64__) || defined(__powerpc64__),使 PowerPC 同样使用 std::atomic 保证内存序,并添加 PowerPC 自旋等待指令 or 1,1,1 以降低忙等时 CPU 争用。

  2. 新增 FP16Vec16 向量类型:在 csrc/cpu/cpu_types_vsx.hpp 中新增 struct FP16Vec16,支持 16 个 FP16 元素的加载、存储及与 FP32Vec16 的双向转换;同时为 BF16Vec16FP32Vec16 添加 bool 标记构造函数以满足模板兼容性。这是 SHM 通信器在处理 c10::Half 类型时的编译必要条件。

  3. 注册 SHM 操作:在 csrc/cpu/torch_bindings.cpp 中,将 shm_allreduceshm_gather 等 Torch 绑定的编译条件从 __AVX512F__ || (__aarch64__ && !__APPLE__) 扩展为包含 __powerpc64__,确保 PowerPC 平台能编译并注册这些算子。

  4. 启用 Python 端 SHM 后端:在 vllm/distributed/device_communicators/cpu_communicator.pyCpuCommunicator.__init__ 条件中增加 current_platform.get_cpu_architecture() == CpuArchEnum.POWERPC,使得 PowerPC 上的 TP/PP 组能够使用 _CPUSHMDistributed 替代默认的 torch.distributed

  5. 构建系统适配:在 cmake/cpu_extension.cmake 添加 if (POWER9_FOUND OR POWER10_FOUND OR POWER11_FOUND) 块,将 csrc/cpu/shm.cpp 追加到 VLLM_EXT_SRC 中,确保 SHM C++ 代码在支持 Power 的机器上参与编译。

  6. 安全修复:根据 reviewer 反馈,修复了 FP16Vec16::save 中未对 elem_num 进行非负夹紧的漏洞,通过 std::max(0, std::min(elem_num, VEC_ELEM_NUM)) 避免负数导致 vec_xst_len 写出越界。

文件 模块 状态 重要度
csrc/cpu/cpu_types_vsx.hpp CPU 向量类型 modified 6.8
csrc/cpu/shm.cpp SHM 通信器 modified 5.89
csrc/cpu/torch_bindings.cpp Torch 绑定 modified 4.98
vllm/distributed/device_communicators/cpu_communicator.py 分布式通信 modified 4.3
cmake/cpu_extension.cmake 构建系统 modified 3.01

关键符号

init_shm_manager join_shm_manager shm_allreduce shm_all_gather FP16Vec16::save FP16Vec16::FP16Vec16(const void*) FP32Vec16::FP32Vec16(const FP16Vec16&) ThreadSHMContext::get_curr_stamp ThreadSHMContext::next_stamp CpuCommunicator.__init__

关键源码片段

csrc/cpu/cpu_types_vsx.hpp core-logic

新增 FP16Vec16 数据类型及其转换构造,是 SHM 通信器在 PowerPC 上编译的必要条件,也是改动量最大的文件。

// FP16Vec16:支持 16 个 FP16 元素的向量,用于 SHM 通信器在 PowerPC 上的编译
struct FP16Vec16 : public Vec<FP16Vec16> {
  constexpr static int VEC_ELEM_NUM = 16;
  ss16x8x2_t reg;  // 从内存加载 16 个 FP16 值(分两批各 8 个)
  explicit FP16Vec16(const void* ptr) {
    reg.val[0] = (__vector signed short)vec_xl(0, (signed short*)ptr);
    reg.val[1] = (__vector signed short)vec_xl(16, (signed short*)ptr);
  }  // bool 标记构造函数,兼容模板代码
  explicit FP16Vec16(bool, const void* ptr) : FP16Vec16(ptr) {}  // 从 FP32Vec16 转换
  explicit FP16Vec16(const FP32Vec16&);  // 保存全部 16 个元素到内存
  void save(void* ptr) const {
    vec_xst(reg.val[0], 0, (signed short*)ptr);
    vec_xst(reg.val[1], 16, (signed short*)ptr);
  }  // 保存指定数量的元素,使用 vec_xst_len 处理非对齐长度
  void save(void* ptr, int elem_num) const {
    // 防止负值溢出:先夹紧到 [0, VEC_ELEM_NUM]
    int num = std::max(0, std::min(elem_num, VEC_ELEM_NUM));
    if (num <= 8) {
      vec_xst_len(reg.val[0], (signed short*)ptr, num * 2);
    } else {
      vec_xst(reg.val[0], 0, (signed short*)ptr);
      vec_xst_len(reg.val[1], (signed short*)ptr + 8, (num - 8) * 2);
    }
  }
};
csrc/cpu/shm.cpp core-logic

核心条件编译扩展:将 ARM 原子和内存序保护扩展至 PowerPC,并添加 PowerPC 专用自旋等待指令,是 SHM 运行时正确性的基础。

struct ThreadSHMContext {
  // 对 ARM 和 PowerPC 使用 atomic(弱内存模型需要 acquire/release 语义)
#if defined(__aarch64__) || defined(__powerpc64__)
  std::atomic<char> _curr_thread_stamp[2];
  std::atomic<char> _ready_thread_stamp[2];
  static_assert(std::atomic<char>::is_always_lock_free);
#else
  volatile char _curr_thread_stamp[2];
  volatile char _ready_thread_stamp[2];
#endif  // ...  char get_curr_stamp(int idx) const {
#if defined(__aarch64__) || defined(__powerpc64__)
    return _curr_thread_stamp[idx].load(std::memory_order_acquire);
#else
    return _curr_thread_stamp[idx];
#endif
  }  void next_stamp() {
#if defined(__aarch64__) || defined(__powerpc64__)
    _curr_thread_stamp[local_stamp_buffer_idx].fetch_add(
        1, std::memory_order_release);
#else
    _mm_mfence();
    _curr_thread_stamp[local_stamp_buffer_idx] += 1;
#endif
  }  // 自旋等待循环中的 CPU 让步指令
  while (condition) {
    // ...
#if defined(__aarch64__)
    __asm__ __volatile__("yield");
#elif defined(__powerpc64__)
    __asm__ __volatile__("or 1,1,1"); // PowerPC 低优先级提示
#else
    _mm_pause();
#endif
  }
};

评论区精华

FP16Vec16::save 负值缓冲区溢出 正确性

depthfirst-app[bot] 指出 FP16Vec16::save 未对 elem_num 做非负夹紧,负数传入时函数内 num * 2 隐式转换为 size_t 导致越界写入。

结论:作者采纳建议,添加 std::max(0, std::min(elem_num, VEC_ELEM_NUM)) 夹紧,并在最后提交中修复。 · 已解决

风险与影响

  1. 平台特异性风险:PowerPC 的内存模型和原子操作行为与 x86/ARM 存在差异,虽然改用 std::atomic 统一处理,但自旋等待指令 or 1,1,1 在不同 POWER 微架构上的效果未经广泛验证,可能在老型号上有性能退步。
  2. 编译兼容性:条件宏覆盖了 __powerpc64__,但未处理 32 位 PowerPC 或特殊的 big-endian 配置,如果未来遇到此类环境可能编译失败。
  3. 无配套测试:本次改动未新增自动化测试,回归风险依赖 CI 中的 CPU 测试套件,但当前 CI 可能未覆盖 PowerPC 真实硬件。

影响范围:仅限在 PowerPC(PPC64LE)平台上运行 vLLM 且使用 --tensor-parallel-size--pipeline-parallel-size 大于 1 的用户。
性能影响:测试数据显示输出吞吐量从 97.92 提升至 100.26 tok/s(+2.4%),TTFT 和 TPOT 分别改善 2.8% 和 2.2%,对长序列推理有正向收益。
兼容性:条件宏保护使非 PowerPC 平台零影响;Python 端的新架构枚举值不会破坏现有逻辑。

平台特定原子操作 缓冲区溢出已修复 仅 PowerPC 测试

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论