已开启
[Bug] whl 包名为 py3-none-any 导致跨 Python 版本/架构错装;host C++ CXX11 ABI 不一致 #199
hedongdong创建于  6月9日
hedongdong成员
6月9日 创建

该问题是怎么引起的?

deepseek-pynative 网络开启 mHC + 融合算子训练,运行时报:

ImportError: Python version mismatch: module was compiled for Python 3.10,
but the interpreter version is incompatible: 3.12.10

报错对应的 .so 是 hyper-parallel 提供的预编译 mindspore 自定义算子库(hyper_parallel_custom_ops_ms.so 等)。用户安装的是 OpenEuler 上由 Python 3.10 编出的 hyper-parallel whl,在 Python 3.12 环境执行时报错。

现状(根因分析)

hyper-parallel 的 setup.py 没有把"自身是平台相关包"这件事告诉 setuptools,导致 wheel 出包时被默认标为纯 Python 包:

项 现状
Wheel 文件名 tag hyper_parallel-0.1.0-py3-none-any.whl
是否声明 has_ext_modules 否
是否注册 bdist_wheel cmdclass 否
实际打包内容 通过 package_data 引入 host 编出的多个 .so
这些 .so 依赖什么 特定 CPython ABI(链接 libpython3.X.so)、特定 CPU 指令集(aarch64/x86_64)、特定 libstdc++ / glibc 下限
pip 安装时 看 py3-none-any tag 直接通过,运行时 dlopen 失败

同时仓内不同 .so 链路的 host C++ _GLIBCXX_USE_CXX11_ABI 设置不一致:

子库 现状 实际链接的框架 该用哪个 ABI
libaclshmem_symmetric_memory_kernel.so(公共层) 硬编码 =0 被两个链路同时链接 保持 =0(对外是 POD 接口,安全)
libaclshmem_ms.so / hyper_parallel_custom_ops_ms.so / hyper_parallel_mega_moe_ms.so 未控制(跟编译器默认) mindspore(=0) 应 =0
libaclshmem_torch.so / hyper_parallel_mega_moe_pta.so 未控制(跟编译器默认) PyTorch ≥ 2.7(=1)、torch_npu(=1) 应 =1

ABI 不一致时表现:跨 .so 边界传 std::string / std::list 等 STL 类型时,链接成功但运行时崩溃于 STL 析构,或直接 undefined symbol: _ZNSt7__cxx11...。

修复方案

  1. wheel 文件名升级:setup.py 引入 BinaryDistribution(has_ext_modules()=True)+ 自定义 BdistWheel(root_is_pure=False),产出 cpXY-cpXY-linux_{aarch64|x86_64} 形态文件名。python_requires 收窄到 [3.10, 3.13),classifier 同步清理。
  2. 按链路统一 CXX11 ABI:
    • mindspore 链路所有 .so 强制 _GLIBCXX_USE_CXX11_ABI=0
    • torch 链路所有 .so 强制 _GLIBCXX_USE_CXX11_ABI=1
    • 公共层(libaclshmem_symmetric_memory_kernel.so)保持 =0,对外仅暴露 POD 接口(与 CANN ops-transformer cmake/intf_pub.cmake 同款政策)
  3. GCC 版本校验:新增 scripts/check_gcc_version.sh,三个 build 脚本顶部 source;setup.py 也加同口径校验。强制 GCC ∈ [7.3.0, 11.3.0],与 mindspore/CMakeLists.txt:5-8 对齐。
  4. README 声明:明确 wheel 产出与 host glibc 强绑定的事实,给出最低 glibc 要求。
  5. 构建产物兜底:非纯 wheel 切换后 setuptools build_py 的写入目录变化,导致 symmetric_memory .so 漏装;在 BuildPy.run() 加 copy_tree 兜底。

不做(明确范围外):

  • 不引入 manylinux 转换 / docker 发布镜像(靠 README 文档化 glibc 要求兜底)
  • 不做 CANN / torch / mindspore 版本运行时绑定
  • 不在 hyper_parallel/platform/{torch,mindspore}/__init__.py 里加 ABI 运行时校验(会让 platform 模块对 torch 产生强 import 依赖)
  • 不引入顶层 build.sh(坚持现有 python setup.py bdist_wheel 入口)

附录:_GLIBCXX_USE_CXX11_ABI=0 vs =1 标准库数据结构差异

C++11 标准对 std::string(禁止 COW、要求线程安全)和 std::list::size()(要求 O(1))提出了新要求。GCC 4.x 实现的是 C++98 时代的旧布局;GCC 5 起为兼容老二进制引入双 ABI 共存机制。

维度 =0(old ABI) =1(new ABI / "cxx11")
std::string 实现 COW(写时复制),引用计数 SSO(短串栈上存)+ 不可 COW,线程安全
std::list::size() O(N) O(1)
std::string 在符号里的命名 std::basic_string<...> std::__cxx11::basic_string<...>,mangled 为 _ZNSt7__cxx1112basic_string...
兼容性 兼容 GCC ≤ 4.9 编的旧二进制 现代默认,性能更好
同进程内可否共存 可以;但两边的 std::string、std::list 等是不同类型,跨 .so 边界传 = UB

典型崩溃模式:A.so 用 =0 编、B.so 用 =1 编。A 暴露 void f(std::string),B 调用它。链接时 B 找的是 f(std::__cxx11::basic_string...),A 提供的是 f(std::basic_string...) → undefined symbol。少数情况下符号靠 extern "C" 或纯 POD 接口绕过,但 A/B 内部 std::string 类型不一致仍可能踩坏堆。

框架站队:

  • MindSpore:全栈 =0(mindspore/CMakeLists.txt:28)
  • PyTorch:官方 manywheel 自 2.7 起默认 =1(.ci/manywheel/build_common.sh:115)
  • torch_npu:默认 =1(setup.py:55,注释明写"change to use cxx11.abi in default since 2.7")
  • CANN(含 ops-transformer):host 侧统一 =0,对外仅暴露 C / POD 接口屏蔽内部 ABI

hyper-parallel 同时跨 mindspore 和 PyTorch 两个生态,唯一稳的做法就是"按链路分 ABI",公共层走 CANN 同款"POD 接口屏蔽"模式。

重现步骤

  1. 在 Python 3.10 + aarch64 + 已 source CANN 的环境编 wheel:python setup.py bdist_wheel
  2. 文件落到 dist/hyper_parallel-0.1.0-py3-none-any.whl
  3. 切到 Python 3.12 环境,pip install dist/hyper_parallel-0.1.0-py3-none-any.whl 成功
  4. import hyper_parallel.<...mindspore custom ops...> 触发 .so 加载 → 报上面的 ImportError

报错信息

ImportError: Python version mismatch: module was compiled for Python 3.10,
but the interpreter version is incompatible: 3.12.10

关联 PR:mindspore/hyper-parallel#775(fix-wheel-platform-tag 分支)。

likedislike