HPC(高性能计算)圈子里不用 pip 和 conda——用 Spack。Spack 是一个专为科学计算设计的包管理器,能同时管理一个软件包的多个版本(不同编译器、不同依赖版本、不同架构),每个变体独立安装在 spack/opt/ 下,互不冲突。cann-spack-package 把 CANN 的整套工具链(编译器、算子库、驱动、运行时)打包成 Spack 包,让 HPC 中心可以一键在集群上部署 CANN。

Spack 的核心理念

Spack 的包管理哲学(和 pip/conda 完全不同)

pip install torch==2.0.0
→ 只能装一个版本的 PyTorch

spack install py-torch@2.0.0%gcc@11.2.0
spack install py-torch@2.0.0%gcc@12.1.0
spack install py-torch@2.1.0%gcc@11.2.0+cuda
→ 三个 PyTorch 2.0.0 实例共存
  (不同编译器、不同版本、不同 CUDA 支持)

关键技术:spec 字符串
py-torch@2.0.0%gcc@11.2.0+cuda arch=linux-rhel8-x86_64
     ↑      ↑       ↑        ↑             ↑
  包名@版本  编译器  变体(variant)  目标架构(arch)

Spec 字符串是 Spack 的身份证——每个包的唯一标识。module load 时直接指定 spec,加载精确的那一个包。

CANN 的 Spack 包

cann-spack-package 把 CANN 的每个组件打包成 Spack 包:

# 查看 CANN 相关的 Spack 包
spack list ascend

# 输出:
# ascend-cann           # CANN 全套工具链(meta-package)
# ascend-toolkit         # CANN 开发工具包(编译器+算子库)
# ascend-driver         # NPU 驱动
# ascend-firmware       # NPU 固件
# ascend-runtime        # CANN 运行时
# ascend-mindspore      # MindSpore 框架(CANN 后端)
# ascend-pytorch        # PyTorch CANN 适配
# ascend-ops-advanced   # 进阶算子(ops-transformer 等)

# 安装 CANN 8.0 全套
spack install ascend-cann@8.0.0%gcc@11.2.0 arch=linux-rhel8-x86_64

# 这个命令会:
# 1. 安装 gcc@11.2.0(如果没装)
# 2. 安装 npu-driver(依赖)
# 3. 安装 npu-firmware(依赖)
# 4. 安装 ascend-toolkit(核心)
# 5. 安装 ascend-runtime
# 6. 安装 ascend-pytorch(可选)
# 整个依赖树自动解析、自动安装、自动激活

多版本共存

HPC 集群的真实需求:32 个节点,22 个跑 CANN 8.0(稳定版),10 个跑 CANN 8.5 RC(测试新特性)。Spack 天然支持多版本共存。

# 同时安装两个 CANN 版本
spack install ascend-cann@8.0.0
spack install ascend-cann@8.5.0

# 查看已安装的版本
spack find ascend-cann

# -- linux-rhel8-x86_64 / gcc@11.2.0 -----------
# ascend-cann@8.5.0  /abc123
# ascend-cann@8.0.0  /def456

# version 8.5.0 在上层(默认)
# version 8.0.0 也在系统中可用(不会冲突)

# 切换版本
spack load ascend-cann@8.0.0
# 环境变量已更新:
# LD_LIBRARY_PATH → .../spack/opt/.../ascend-cann-8.0.0/...
# ASCEND_HOME_PATH → .../spack/opt/.../ascend-cann-8.0.0/

# 验证
npU-smi version
# CANN 8.0.0

# 换到 8.5
spack unload ascend-cann
spack load ascend-cann@8.5.0
npU-smi version
# CANN 8.5.0

两个版本共享同一个 Node,通过 Spack 的 module 机制控制版本切换——不会污染系统级 LD_LIBRARY_PATH

Spack 的环境锁定和 reproducibility

HPC 集群的一个关键需求是 reproducibility(可重复性)——3 个月前集群运行的训练结果,现在重建集群应该跑出一模一样的结果。Spack 用 spack.lock 文件锁定所有依赖的精确版本。

# 创建 spack environment
spack env create cann-training-env
spack env activate cann-training-env

# 添加所有需要的包
spack add ascend-cann@8.0.0%gcc@11.2.0
spack add ascend-pytorch@2.1.0%gcc@11.2.0
spack add ascend-ops-advanced@8.0.0
spack add py-numpy@1.24.0
spack add py-transformers@4.36.0

# 环境生效 → 每个包的精确依赖被锁定
spack install

# 导出锁文件(spack.lock → 包含所有依赖的精确 hash 和版本)
spack env lock

# 3 个月后,从锁文件重建一模一样的集群环境
spack env create cann-training-env-repro
spack env activate cann-training-env-repro
spack env lock --file=../cann-training-env/spack.lock
spack install
# 保证所有依赖版本和 3 个月前完全一致

锁文件记录了 100+ 个依赖的精确 hash(包括 gcc、cmake、mpi、python 本身),在相同硬件上重现完全一致的软件栈。

踩坑一:Spack 的 concretization 时间过长

Spack 的 concretization(依赖解析)是 NP 困难问题——100+ 个包各有 3-5 个版本选择,3 个编译器版本,树搜索空间巨大。CANN 的依赖树尤其深(ascend-cann → ascend-toolkit → 20+ 个底层库)。

# 直接 concretize —— 可能卡几分钟
spack spec ascend-cann@8.0.0%gcc@11.2.0

# 输出(截断):
# Input spec
# --------------------------------
# ascend-cann@8.0.0%gcc@11.2.0
#
# Concretized
# --------------------------------
# ascend-cann@8.0.0%gcc@11.2.0 arch=linux-rhel8-x86_64
#     ^ascend-toolkit@8.0.0%gcc@11.2.0
#         ^nccl@2.18.3%gcc@11.2.0
#         ^openmpi@4.1.5%gcc@11.2.0
#             ^hwloc@2.9.1%gcc@11.2.0
#                 ^libxml2@2.10.3%gcc@11.2.0
#                     ^xz@5.4.1%gcc@11.2.0
#         ^ascend-runtime@8.0.0%gcc@11.2.0
#         ^ascend-driver@8.0.0
#         ...(100+ 行截断)
#
# Time: 3m42s  ← 解析耗时

优化:用 --reuse 复用已 conreteized 的依赖。

# 第一次 concretize(耗时最长)
spack install ascend-cann@8.0.0

# 第二次 concretize(--reuse 复用之前的解析结果)
spack install ascend-pytorch@2.1.0 --reuse
# 大部分依赖已经在第一次 concretize 中解析过了
# Time: 12s(比 3m42s 快 18×)

踩坑二:CANN 的固件和驱动版本锁定

CANN 8.0 的 driver 8.0 和 firmware 8.0 是一对绑定的——用 CANN 8.0 的 driver 配合 8.5 的 firmware 会导致驱动兼容性问题。但 Spack 的版本管理不识别这种「硬绑定」关系——它只关心包级别的依赖,不关心固件和驱动的物理兼容性。

错误

# 用户想「只升级 firmware 到 8.5」
spack install ascend-firmware@8.5.0
# Spack 不会报错——因为 ascend-firmware 和 ascend-driver 在 spec 上没有硬性依赖
# 物理上:8.5 firmware + 8.0 driver → NPU 初始化失败

正确:在 Spack 包定义里添加 conflicts 约束。

# cann-spack-package/packages/ascend-driver/package.py

class AscendDriver(Package):
    version("8.0.0", sha256="...")
    version("8.5.0", sha256="...")

    depends_on("ascend-firmware@8.0.0", when="@8.0.0")
    depends_on("ascend-firmware@8.5.0", when="@8.5.0")

    # 显式冲突检查
    conflicts("@8.0.0", msg="ascend-driver@8.0 requires ascend-firmware@8.0.0 (not 8.5.0)")

加了 conflicts 后,Spack 的 concretizer 会自动拒绝不兼容的组合。

踩坑三:Spack 编译 CANN 时依赖的 C++ 标准库版本

CANN 8.0 的某些组件内部用了 C++17 特性(std::optionalif constexpr)。如果系统中只有 gcc@7.5(最大支持 C++14),编译会失败。

# 错误:系统默认 gcc 版本太老
spack install ascend-cann@8.0.0%gcc@7.5.0

# 编译错误:
# error: 'if constexpr' is a C++17 feature
# error: 'std::optional' is not available with this compiler

# Spack 不会自动升级 gcc——它用你指定的编译器

正确:在 Spack 包定义里加 depends_on + conflicts

class AscendCann(Package):
    # 显式声明最低编译器版本
    conflicts("%gcc@:8.0", msg="CANN requires GCC 8.1+ (C++17 support)")
    depends_on("gcc@8.1:", type="build")

如果系统 GCC 版本太老,Spack 会自动先装 gcc@8.1+,然后用这个编译器编译 CANN——这一切都是自动处理的。


cann-spack-package 的价值在于把 CANN 融入 HPC 集群的标准化工作流。大多数 HPC 中心已经用 Spack 管理 GCC、OpenMPI、FFTW、NetCDF 等 200+ 个科学计算软件包——CANN 作为 Spack 包接入后,可以一键部署、多版本并存、锁文件 reproducibility。这不是为开发者提供便利(开发者用 pip 就够了),而是为集群管理员提供便利——CANN 的生产环境部署需要统一管理工具链版本。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐