在信创服务器上部署 AI 项目的人,大概率踩过这个坑:pip install torch 之后,安装流程看着一切正常,import 的时候却报:

Illegal instruction (core dumped)

或者更隐蔽一点,直接提示找不到对应平台的 wheel 包。问题的根源并不复杂——你手里的那台国产化服务器是 ARM 架构的,而 PyPI 上大多数预编译的 whl 文件是为 x86_64 打的包。但要把这个看似简单的问题彻底梳理清楚,需要从指令集、Python 的包分发机制、C 扩展的编译流程,一路讲到 AI 框架内部的算子分发体系。

TL;DR(太长不看):在信创 ARM 服务器上跑 AI 项目,核心矛盾是 x86 预编译包与 ARM 指令集不兼容。务实的三步走是:(1)先找预编译包——PyPI 上 numpy、pandas、PyTorch 等主流库已有 aarch64 版本,优先直接装;(2)遇到 glibc 版本冲突或依赖地狱,果断上容器化——在麒麟宿主机上跑 Ubuntu 容器,彻底绕开基础库束缚;(3)特定领域的库(如 flash-attn、xformers)没有 ARM 版本时,再考虑源码编译——用 pip wheel 在外网有编译器的环境先打好 whl,再搬进内网离线安装。不要裸机硬杠。


一、问题的本质:当 ARM 遇上 x86 的 whl 包

1.1 指令集不是软件层能抹平的差异

国产信创服务器普遍采用 ARMv8 架构(aarch64),代表型号有华为鲲鹏 920、飞腾 FT-2000+/64、申威等。这些 CPU 的指令集属于 RISC(精简指令集计算机),指令长度固定为 32 位,寻址模式和运算指令相对精简。而我们日常开发用的 Intel Xeon 或 AMD EPYC 属于 x86_64 架构,是 CISC(复杂指令集计算机),指令长度可变,单条指令能干的事情更多。

这种差异是硬件层面的。x86 编译出来的机器码里充满了 VFMADD231PDVPCMPGTQ 这类 AVX-512 指令,放到 ARM 上 CPU 根本不认识,直接触发非法指令异常(SIGILL)。反过来也一样,ARM 的 NEON 指令集(ADDPFMLA 等)在 x86 上也是一堆无意义字节。

操作系统内核虽然可以通过二进制翻译或模拟层(如 QEMU user-mode emulation)来跑异架构程序,但性能损失通常在 5-10 倍以上,对于计算密集型的 AI 训练/推理来说完全不可接受。

1.2 Python whl 的平台标签机制

Python 的 wheel 格式(PEP 427)在设计时就考虑了跨平台分发问题。一个典型的 whl 文件名长这样:

numpy-1.24.3-cp39-cp39-manylinux_2_17_x86_64.whl

文件名中的每一段都有明确含义:

  • numpy-1.24.3:包名和版本
  • cp39-cp39:CPython 3.9 的 ABI 标签
  • manylinux_2_17_x86_64:平台标签,表示这个包是在满足 manylinux_2_17 标准的 Linux 发行版上编译的,目标架构是 x86_64

pip 在安装时会根据当前 Python 解释器的平台信息去匹配 whl 文件。这个信息来自 pip debug --verbose 或 Python 的 distutils.util.get_platform(),在 ARM 服务器上通常返回 linux-aarch64

这里需要澄清一个容易误解的点:PyPI 上的基础科学计算包生态,在今天已经远好于几年前manylinux2014_aarch64 如今是相当普遍的标准,numpy、pandas、scipy 这些核心库在 PyPI 上都有稳定的 aarch64 预编译包,甚至连 PyTorch 官方也已经在 PyPI 上持续提供 CPU 版本的 linux-aarch64 wheel。也就是说,如果你只是在 ARM 服务器上跑一个基于标准库的数据分析脚本,pip install 大概率是能直接跑通的。

真正的痛点出在特定领域的 AI 依赖包上——那些带有复杂 C++/CUDA 扩展、高度依赖底层指令集优化、且维护团队较小的库。典型例子包括:

  • flash-attn:手写 CUDA kernel 实现 Flash Attention,几乎没有 ARM 版本
  • xformers:Meta 推出的高效 transformer 组件库,CUDA kernel 密集
  • 量化库(如 autoawq、auto-gptq):依赖 GEMM 的 INT4/INT8 定制 kernel,通常只编译了 x86 + CUDA
  • 某些长期不维护的老旧开源库setup.py 里硬编码了 -mavx2 或 x86 汇编,没有为 ARM 预留编译路径

这些包的共同特点是:它们不是"主流生态"的一部分,PyPI 上通常只有 x86_64 标签的 whl,甚至没有 sdist。一旦你的项目依赖了它们,就必须面对源码编译或找替代方案的现实。

另外,像 PyTorch、TensorFlow 这类大框架,虽然有 aarch64 的 CPU 版本,但带 CUDA 的 GPU 版本在 ARM 服务器上的支持仍然很局限。NVIDIA 的 Jetson 系列虽然有 aarch64 + CUDA 的 PyTorch,但那是针对嵌入式场景裁剪的,和服务器端的 CUDA 版本并不兼容。

1.3 为什么简单的 “pip install” 在信创环境会失败

综合起来,信创服务器上 pip install 失败的典型路径是这样的:

  1. pip 去 PyPI 解析依赖树,找到最新版本
  2. 发现该版本只有 x86_64 的 whl,没有 aarch64arm64
  3. 退而下载 sdist,解压后执行 setup.py bdist_wheel
  4. setup.py 里发现需要编译 C/C++ 扩展,调用系统编译器
  5. 如果系统没有安装 gcc-aarch64-linux-gnupython3-devlibopenblas-dev 等基础开发包,编译报错
  6. 即使编译环境齐全,某些包的 setup.py 里硬编码了 x86 特定的编译选项(如 -mavx2-mfma),或者依赖了只在 x86 上存在的汇编代码(如 OpenBLAS 里的 kernel/x86_64),编译还是会失败

这就是为什么在信创麒麟系统上部署 AI 环境,不能指望 “pip install 走天下”。


二、源码编译:从 C/C++ 到 ARM 可执行文件

2.1 Python C 扩展的编译链路

很多人以为 Python 是解释型语言,装包就是复制 .py 文件。实际上,高性能的 AI 库(numpy、scipy、torch、transformers 的底层)大量依赖 C/C++ 扩展,有些甚至直接嵌入了汇编。以 numpy 为例,它的核心 ndarray 操作底层是用 C 写的,线性代数运算调用了 BLAS/LAPACK,这些库通常又是 Fortran 写的。

当你执行 python setup.py build_ext 时,背后的完整链路是:

  1. Python 的 distutilssetuptools 读取 setup.py / pyproject.toml
  2. 根据平台信息生成编译命令(gcc -pthread -Wno-unused-result ...
  3. C 编译器把 .c 文件编译成 .o 目标文件(目标架构由编译器的 target triple 决定,ARM 服务器上是 aarch64-unknown-linux-gnu
  4. 汇编器把 .s 文件编译成 .o
  5. 链接器把所有 .o 文件和系统库(libc、libm、libopenblas 等)链接成 .so 共享库
  6. Python 的 bdist_wheel 把这些 .so 文件和 .py 文件一起打包成 whl

在 x86 工作站上,这条链路是"开箱即用"的,因为发行版的工具链默认就是 x86_64 目标。但在 ARM 信创服务器上,你需要确认每一个环节都正确指向了 aarch64。

2.2 交叉编译 vs 本地编译

有两种方式可以让 ARM 机器跑上为 ARM 编译的代码:

本地编译(native compilation):直接在 ARM 服务器上编译。编译器和目标架构一致,不需要特殊的 cross-compile 配置。缺点是在低功耗 ARM 芯片上编译大型项目(如 PyTorch)非常慢,可能需要数小时。

交叉编译(cross compilation):在 x86 工作站上用交叉编译器(如 aarch64-linux-gnu-gcc)生成 ARM 可执行文件,再拷贝到 ARM 服务器上运行。优点是编译速度快,缺点是配置复杂——需要准备目标平台的 sysroot(头文件和库文件),处理交叉编译时的依赖解析问题。

在信创环境中,由于服务器通常就在手边,而且往往是内网隔离环境,本地编译是更常见的选择。你需要做的第一件事是确保麒麟系统的 yum/apt 源里安装了基础开发包:

# 麒麟系统(基于 CentOS/ openEuler)
sudo yum install gcc gcc-c++ gcc-gfortran python3-devel make cmake
sudo yum install openblas-devel lapack-devel openssl-devel

2.3 源码编译的典型踩坑点

即使编译环境齐全,AI 依赖包的源码编译在 ARM 上仍然有几类高频问题:

(1)SIMD 指令集的缺失

x86 上的 AI 库重度依赖 SIMD(单指令多数据)加速:SSE、AVX、AVX2、AVX-512。ARM 对应的是 NEON 和 SVE(Scalable Vector Extensions),但两者的指令格式完全不同。许多开源项目的 setup.py 里会这样写:

if platform.machine() == 'x86_64':
    extra_compile_args.append('-mavx2')

这在 ARM 上不会报错,因为条件不满足,直接跳过了。但如果代码里存在 x86 汇编文件(如 .s.asm),而 setup.py 没有为 ARM 提供对应的汇编实现,链接阶段就会报 “undefined reference”。

(2)缺少优化的数学库

numpy 和 scipy 的性能高度依赖底层的 BLAS(Basic Linear Algebra Subprograms)实现。x86 上通常用 Intel MKL 或 OpenBLAS,而 ARM 上 OpenBLAS 虽然也支持,但默认可能没有启用 NEON 优化。你需要手动编译 OpenBLAS 并指定目标架构:

make TARGET=ARMV8 USE_OPENMP=1

(3)CUDA/ROCm 的 ARM 支持空白

如果你的信创服务器上安装了国产 GPU(如某些基于 ARM 的 AI 加速卡),情况会更复杂。NVIDIA 的 CUDA 在 aarch64 上的支持并不完整,很多算子只有 x86 版本。这时候即使你能编译通过 PyTorch,也可能发现 torch.cuda.is_available() 返回 True,但某些 CUDA kernel 在运行时直接崩溃。


三、信创适配的 conda 镜像源:另一条路

3.1 conda 与 pip 在包分发上的根本区别

pip 安装的是 wheel,本质上是 zip 压缩包,里面包含了预编译好的 .so 文件和 Python 源码。wheel 的平台兼容性完全依赖文件名里的平台标签,灵活性很低。

conda 走的是另一条路。conda 的包(.tar.bz2.conda 格式)不仅包含库文件,还携带了自己的依赖元数据,并且 conda 会管理整个环境的路径和动态链接。更重要的是,conda 的 channel 机制允许社区维护独立的包仓库,而不像 PyPI 那样只有一个中央仓库。

这意味着,如果某个组织(比如清华 TUNA、中科大 USTC,或者信创厂商自己)维护了一个 ARM 架构的 conda 镜像源,你就可以直接从里面拉取预编译好的 aarch64 包,跳过本地编译的痛苦。

3.2 在麒麟系统上配置信创 conda 源

Anaconda 官方并没有为 Linux aarch64 提供完整的渠道,但社区有。比如 conda-forge 就支持 linux-aarch64 平台,很多常见的科学计算包(numpy、scipy、pandas)都有预编译版本。

在信创内网环境中,通常的做法是:

  1. 在外网机器上搭建一个 conda 仓库镜像,同步 conda-forge 的 linux-aarch64 子集
  2. 把镜像仓库搬到内网的 Web 服务器或 NFS 上
  3. 在 ARM 服务器上配置 .condarc 指向内网镜像
channels:
  - https://internal-mirror.company.com/conda-forge/linux-aarch64
  - defaults
show_channel_urls: true
platform: linux-aarch64

配置完后,conda install pytorch numpy 会自动解析 aarch64 版本的依赖并下载。这比源码编译快得多,也稳定得多。

3.3 conda 源的局限性

conda-forge 的 aarch64 覆盖范围虽然不错,但并不是 100%。特别是一些小众的 AI 库(比如某些 NLP 工具包、图神经网络库),conda-forge 上可能没有 aarch64 版本,这时候还是得回到源码编译。另外,conda 的包版本通常滞后于 PyPI,如果你需要最新特性,可能需要权衡。


四、GPU 算子的降级与 CPU 兼容模式

4.1 AI 框架的算子分发机制

以 PyTorch 为例,它的算子实现分层很清晰。最上层是 Python API(torch.matmultorch.relu 等),中间层是 C++ 的 ATen 库,底层是具体的 kernel 实现。ATen 内部有一个 dispatch 机制,根据输入张量的 device type(CPU、CUDA、Meta)、数据类型(float32、float16、int64 等)、布局(strided、sparse)来选择合适的 kernel。

这个 dispatch 系统本质上是一张巨大的分派表。当调用 torch.matmul(a, b) 时,ATen 会查表找到匹配的 kernel。如果 a 和 b 都在 CPU 上,就调用 CPU kernel;如果在 CUDA 上,就调用 CUDA kernel。

4.2 什么时候会走到 CPU fallback

在信创 ARM 服务器上,如果没有 NVIDIA GPU(或者 GPU 驱动/CUDA 环境不完整),张量默认会创建在 CPU 上。这时候所有算子自然走 CPU kernel 路径,不存在"降级"一说——它一开始就没试图用 GPU。

但问题往往出在那些强制要求 CUDA 的第三方库上。很多 AI 项目(特别是 CV 和 NLP 领域的开源实现)的代码里硬编码了 .cuda()device='cuda'。在 x86 工作站上这没问题,因为开发者假设你有 GPU。但在 ARM 信创服务器上,如果没有 CUDA,这些代码会直接报错:

RuntimeError: No CUDA GPUs are available

这时候的"降级"实际上是在代码层面做的修改:把 .cuda() 改成 .to(device),让 device 根据环境动态选择 CPU 或 GPU。这不是框架自动完成的,需要开发者手动处理。

4.3 另一种"降级":关闭非兼容的 CPU 加速

还有一种情况,某些 AI 库在 x86 上默认启用了 AVX-512 加速,但在 ARM 上没有对应实现。以 ONNX Runtime 为例,它的 x86 版本默认编译了 AVX2/AVX-512 kernel,ARM 版本则需要用 NEON 或 ACL(ARM Compute Library)重新编译。如果你在 ARM 上跑的是 x86 版本的 ONNX Runtime(通过某种方式),它会尝试调用 AVX 指令,直接崩溃。

正确的做法是在 ARM 上重新编译这些库,编译时指定 ARM 的 SIMD 后端。比如 TensorFlow 的编译选项里有 --config=mkl(Intel MKL,x86 专用)和 --config=non_mkl,在 ARM 上必须选后者,并且可能还需要手动启用 XNNPACK(Google 的神经网络推理加速库,支持 ARM NEON)。


五、信创内网环境的特殊挑战

5.1 外网隔离与依赖完整性

信创环境通常是内网隔离的,这意味着你不能直接 pip installconda install 从公网拉包。需要在外网机器上把完整的依赖树(包括所有 transitive dependencies)下载下来,打包搬进内网。

很多人的第一反应是用 pip download

pip download torch==2.0.1 numpy==1.24.3 -d ./packages --platform linux_aarch64

但这里有个经典的踩坑点:--platform 只能确保下载指定平台的 whl,如果某个包在 PyPI 上没有 aarch64 版本,pip 会退而求其次下载它的 sdist(源码包,.tar.gz)。当你把这一堆包拷进内网 ARM 服务器执行 pip install 时,它依然会在内网触发源码编译。如果内网机器没有安装 gcc、python3-dev 等编译工具,部署照样崩溃。

更稳妥的做法是在外网先把所有依赖强制编译成 whl,再把纯 whl 目录搬进内网。这可以通过 pip wheel 实现:

# 在有外网、有编译器的环境(最好是同架构的 ARM 机器,或 x86 上的 ARM 模拟器/容器)
pip wheel -w ./packages -r requirements.txt

pip wheel 会遍历依赖树,对每一个包都执行 bdist_wheel。如果某个包只有 sdist,它会当场编译,生成 .whl 文件。最终 ./packages 目录里全是纯 whl,没有源码包。把这些 whl 拷进内网后,执行:

pip install --no-index --find-links ./packages -r requirements.txt

--no-index 表示不从 PyPI 拉取任何内容,完全依赖本地目录。这样内网部署才真的是"纯离线安装",不会半路触发编译。

conda 在这方面也有优势:conda create --name myenv --offline --file spec-file.txt 可以从本地 channel 安装。前提是本地 channel 已经完整同步了所有需要的包。

5.2 国产芯片生态的碎片化

信创 ARM 服务器不是铁板一块。华为鲲鹏 920 基于 ARMv8.2-A,飞腾 FT-2000+ 基于 ARMv8.1-A,两者的 SIMD 扩展支持有细微差别。某些针对鲲鹏优化的编译选项(如 -march=armv8.2-a+fp16)在飞腾上可能跑不起来。

这导致了一个尴尬的局面:你在一台鲲鹏服务器上源码编译好的 whl,拿到飞腾服务器上不一定能用。最安全的做法是把目标架构定为最低的公共子集(-march=armv8-a),牺牲一部分性能换取兼容性。

5.3 麒麟系统的 glibc 版本陷阱

麒麟操作系统基于 CentOS 或 openEuler,其 glibc 版本通常比 Ubuntu 要保守。很多在 Ubuntu 22.04(glibc 2.35)上编译的 whl,拿到麒麟 V10(glibc 2.28)上运行时,会直接抛出:

version GLIBC_2.29 not found

这就是 why even 在 ARM 平台上,你也不应该随便拿一个 aarch64 的 whl 来用——需要确认它是在一个兼容的 Linux 发行版上编译的。

5.4 容器化:绕过 glibc 地狱的降维方案

现代 AI 部署很少直接在裸机系统上硬杠 glibc 版本问题,容器化是更务实的选择。只要宿主机的 Linux 内核兼容(ARM 内核跑 ARM 容器,不需要模拟),你完全可以在麒麟系统上起一个 Ubuntu 22.04 或更高版本的容器。

# 在 ARM 服务器上运行 ARM 架构的 Ubuntu 容器
docker run --platform linux/arm64 -it ubuntu:22.04 bash

容器内部是一个完整的 Ubuntu 用户空间,自带 glibc 2.35,和你从 PyPI 下载的 manylinux 包天然兼容。所有源码编译、依赖安装、环境配置都可以在 Dockerfile 里完成:

FROM ubuntu:22.04

RUN apt-get update && apt-get install -y \
    python3 python3-pip python3-dev \
    gcc g++ gfortran \
    libopenblas-dev liblapack-dev \
    && rm -rf /var/lib/apt/lists/*

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . /app
WORKDIR /app
CMD ["python3", "inference.py"]

这种做法在工业界已经是信创服务器上落地 AI 项目的主流姿势。它不仅彻底绕开了麒麟系统老旧基础库的依赖地狱,还保证了环境的高度可复现——同一台服务器上可以同时跑多个不同 glibc 版本的容器,互不干扰。对于内网环境,只需要把构建好的镜像通过 docker save 导出,搬入内网后用 docker load 恢复即可。


六、工程实践:一个可落地的适配流程

面对信创 ARM 服务器上的 AI 依赖包兼容性问题,一个务实的工作流是这样的:

第一步:环境诊断

# 确认 CPU 架构
uname -m          # aarch64
lscpu | grep "Model name"  # 鲲鹏 920 或 飞腾 FT-2000+

# 确认 OS 和 glibc 版本
cat /etc/.kyinfo   # 麒麟系统版本
cat /lib64/libc.so.6 | head -n 1  # glibc 版本

# 确认 Python 和 pip 版本
python3 --version
pip --version

第二步:决策树

对于每一个需要安装的 AI 依赖包,按以下顺序决策:

  1. conda-forge 或信创镜像源是否有 aarch64 预编译包?

    • 有 → 直接用 conda 安装,最省心
    • 没有 → 下一步
  2. PyPI 上是否有 manylinux_aarch64 的 whl?

    • 有 → pip install 直接装
    • 没有 → 下一步
  3. 是否有官方/社区提供的 ARM 编译指南?

    • 有 → 按指南源码编译,注意 SIMD 和 BLAS 后端配置
    • 没有 → 下一步
  4. 该包是否可以在 CPU 模式下降级运行?

    • 可以 → 修改代码去掉 GPU 硬编码,安装 CPU 版本
    • 不可以 → 考虑替换替代库,或评估是否需要该功能

第三步:源码编译的模板化

对于必须源码编译的包,建议写一个通用的编译脚本模板:

#!/bin/bash
export CC=gcc
export CXX=g++
export CFLAGS="-O3 -march=armv8-a -fopenmp"
export CXXFLAGS="-O3 -march=armv8-a -fopenmp"

# 指定 OpenBLAS 路径
export BLAS=/usr/lib64/libopenblas.so
export LAPACK=/usr/lib64/liblapack.so

python setup.py build_ext --inplace bdist_wheel

第四步:验证与测试

装完一个包后,不要急着装下一个。先跑它的测试套件或至少 import 一下:

import numpy; numpy.test()
import torch; torch.randn(10, 10).mm(torch.randn(10, 10))

有些问题(比如非法指令)只有在运行特定算子时才会暴露,简单的 import 测不出来。


七、总结

信创麒麟系统上的 AI 依赖包兼容性问题,表面上是"x86 的包装不到 ARM 上",底层其实涉及指令集差异、Python wheel 的平台标签机制、C 扩展的编译链路、AI 框架的算子分发体系,以及国产内网环境的隔离约束。三类基础处理手段——源码本地编译、信创 conda 镜像源、GPU 算子降级——各有适用场景:

  • 源码编译是最通用的解法,但耗时耗力,需要对编译工具链和 SIMD 后端有一定了解
  • conda 镜像源是最省心的解法,前提是社区或厂商已经维护了对应的 aarch64 包仓库
  • GPU 降级到 CPU 是无奈的妥协,但在没有 GPU 或 GPU 驱动不兼容的信创硬件上,这是让代码跑起来的必要手段

最终目标不是让每个包都跑到最快,而是在国产化内网环境中搭建一个稳定、可复现、可维护的 Python AI 运行环境。信创适配的真正成本往往不是某一次的编译时间,而是对整个依赖链的系统性梳理和版本冻结。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐