背景

近期,需要将一套基于 PyTorch 的深度学习工程,从传统的 x86_64 架构服务器,迁移到物理隔离的内网华为昇腾(ARM/aarch64 架构)算力集群上。

原本以为只是常规的 scp 传文件和 conda activate,结果却遭遇了堡垒机限制、硬件架构差异、跨版本 Python 兼容、以及内网断网等一系列问题。本文完整记录了本次迁移的排查逻辑与最终解决方案。


一、 网络与传输:打破隔离限制

1. 多层跳板与路由不通

内网的安全策略通常极其严格,初始网络架构为:本地 -> 堡垒机网关 -> 内网计算节点。
直接尝试 SSH 连接目标节点时,报错 No route to host。这并非 SSH 服务未启动,而是跨网段的路由未放行或防火墙拦截。排查 ip route 确认路径后,联系管理员开通了相应的 ACL 策略。

2. Xftp 代理隧道被安全策略拦截

为了传文件,常规操作是在 Xshell 中建立 SOCKS5 动态隧道,然后让 Xftp 走代理。但连接时触发了统一认证网关的拦截:

[UMA] (tunnel) Permission denied: SSO_PERMISSION_DENIED, Permission denied (code: 11)

结论:该内网环境的管控策略完全禁用了 SSH 端口转发。最终只能回归合规通道,依靠终端自带的 Web 直传或命令行 scp 将文件推入内网。


二、 硬件与存储:摸清昇腾底座

1. 硬件驱动的“乌龙”

进入计算节点后,习惯性执行 nvidia-smi 报错找不到命令,lspci 也找不到显卡。

# 查看具体的 NVIDIA 显卡型号
lspci | grep -i nvidia

经过排查,发现该节点使用的是华为昇腾 NPU。
在国产化架构中,必须使用专用的管理工具:

# 查看昇腾 NPU 设备信息
npu-smi info

2. 寻找共享存储与磁盘规划

内网节点的本地系统盘通常空间有限(极易触发 100% 告警导致宕机)。通过 df -h 检查,发现集群挂载了大容量分布式共享存储。
最佳实践:千万不能将环境或数据集放在 ~(家目录)或 / 下,必须重定向至 /data 或共享存储挂载点,使其他节点也可以访问。


三、 环境迁移死局:Conda 跨架构的灾难

最初方案是使用 conda-pack 将源服务器上跑通的虚拟环境打包成 .tar.gz,传到内网解压。

参考:深度学习模型环境跨平台迁移全指南:从 Linux 服务器到本地/堡垒机-CSDN博客

解压时报错

conda-unpack

# 报错
/root/anaconda3/envs/.../bin/python: 1: Syntax error: ")" unexpected

排查可能是架构不同的问题:

# 查看CPU架构
arch

原因:源服务器为 x86_64 架构,目标昇腾节点为 aarch64 (ARM) 架构。Conda 环境内的二进制文件和底层 C 扩展库无法跨 CPU 指令集运行,且两边 Python 版本相差较大。

结论:跨架构迁移深度学习环境,放弃 Conda,转向 Docker 容器化是唯一解。

建议删除原来报错的conda环境

conda deactivate
rm -rf /root/anaconda3/envs/your_env

四、 容器化破局与设备挂载

内网机器上通常会有官方预置的昇腾基础镜像(如带有 CANN 和特定版本 PyTorch 的镜像)。启动容器时,核心难点在于如何将宿主机的 NPU 设备透传给容器。

1. 查找已有的镜像列表:若有,可使用已有的镜像来创建新容器

docker images  # 查看本地有没有现成的镜像

2. 启动命令需包含完整的设备节点与驱动挂载(只读挂载 :ro 可保护宿主机驱动文件):

docker run -it -d \
  --name my_train_env \
  --privileged \
  --net=host \
  --device /dev/davinci0 \
  --device /dev/davinci_manager \
  --device /dev/devmm_svm \
  --device /dev/hisi_hdc \
  -v /usr/local/dcmi:/usr/local/dcmi:ro \
  -v /usr/local/bin/npu-smi:/usr/local/bin/npu-smi:ro \
  -v /usr/local/Ascend/driver/lib64/:/usr/local/Ascend/driver/lib64/:ro \
  -v /usr/local/Ascend/driver/version.info:/usr/local/Ascend/driver/version.info:ro \
  -v /etc/ascend_install.info:/etc/ascend_install.info:ro \
  -v /宿主机代码路径:/workspace \
  <镜像名> /bin/bash
  • -d:让容器在后台一直跑,不会一关终端它就死掉

  • --name my_train_env:容器名

  • --net=host --ipc=host:共享宿主机的网络和内存,多卡通信必须

  • --device 参数:必须显式挂载 /dev/davinci0 到 /dev/davincix(共x张卡)以及 davinci_manager、devmm_svm、hisi_hdc 等管理设备,容器才能识别到 NPU

  • -v 挂载:将宿主机的 npu-smi 工具、驱动库(lib64)和配置文件挂载进容器,解决 command not found 和 libascend_hal.so 缺失的问题。

  • -v /...:/workspace:将项目代码挂载到容器里的 /workspace 目录。在宿主机改代码,容器里立刻生效

  • 注意只挂载自己的目录,容器中对挂载目录执行rm -rf *,宿主机上那个目录的文件也会删除!

3. 进入容器

docker exec -it my_train_env /bin/bash

4. 验证环境:前提是源镜像中已包含相关模块

# 1. 检查是否能识别卡
npu-smi info

# 2. 检查 Python 里的 PyTorch 和 NPU 是否就绪
python3 -c "import torch; import torch_npu; print('PyTorch 版本:', torch.__version__); print('NPU 是否可用:', torch.npu.is_available())"
  • 如果打印True,则基本环境已具备

注意:进入容器后,必须加载昇腾底层环境变量,否则无法调用 torch_npu。

source /usr/local/Ascend/ascend-toolkit/set_env.sh 

其他相关命令

# 退出容器
exit

# 删除容器
docker rm -f <容器名>

五、 断网环境的依赖安装:离线 Wheels 方案

容器虽然跑起来了,但由于内网隔离,容器内无法解析外网域名,也无法解析 K8s 内部的 PyPI 镜像源(报 Temporary failure in name resolution)。只能采用离线安装。

0. 从已有可运行环境中导出requirement.txt

在源服务器上激活环境并导出

# 1. 激活原来能跑通的环境
conda activate your_env

# 2. 导出所有的 pip 依赖
pip freeze > requirements.txt

过滤不需要的包:目标环境已经有了昇腾专属的 torch 和 torch_npu,如果把源环境的 torch 装过去,环境会直接炸掉

# 过滤掉 torch、numpy、pandas 等底层和易冲突的包,保留其余的纯净依赖
grep -v -i -E "torch|numpy|pandas|scipy|matplotlib|tensorboard" requirements.txt > req_clean.txt

# 看一眼剩下的内容
cat req_clean.txt

req_clean.txt可能混杂了大量本地路径依赖、NVIDIA CUDA 专属包、老旧的与Python版本不匹配的包。如果不清理直接下载,不仅下载会失败,传到目标机安装时也会报错 No matching distribution found 或 Exec format error。

建议将文件发给大模型,让其生成对应的纯净依赖

1. 在 x86 外网机上下载 ARM 架构的轮子

(1)借助 pip download 的跨平台参数,直接在 x86 机器上强制下载目标为 aarch64 和 Python 3.12 的二进制包(Wheels):

# 创建目录
mkdir wheels_arm

pip download -r req_clean.txt -d ./wheels_arm \
  --platform manylinux2014_aarch64 \
  --python-version 312 \
  --only-binary=:all: \
  --use-deprecated=legacy-resolver
  • --use-deprecated=legacy-resolver :pip不解决依赖冲突

注:如果遇到某些包解析失败,可尝试加上 --no-deps 参数单独下载主包。

(2)打包传输:将wheels目录压缩,传输到目标服务器中

# 源服务器打包
tar -czvf wheels_arm.tar.gz ./wheels_arm

2. 在容器内进行离线安装

进入容器,解压上传的wheels目录:

# 目标服务器解压
docker exec -it <容器名> /bin/bash
cd /workspace
tar -xzvf wheels_arm.tar.gz

    在容器内执行离线安装,并强制忽略依赖关系(使用 --no-deps)。

    pip install --no-index --find-links=./wheels_arm -r req_clean.txt --no-deps

    由于基础镜像中已经包含了完美的 PyTorch 和 torch_npu,使用 --no-deps 可以避免 pip 去覆盖底层核心库。

    不加 --no-deps可能报错:

    Processing ./wheels_arm/aiohttp-3.14.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl (from -r ./req_clean.txt (line 3))
    ERROR: Could not find a version that satisfies the requirement matplotlib (from mmengine) (from versions: none)
    ERROR: No matching distribution found for matplotlib

    3. 安装可编辑包(导入代码中的自定义模块)

    pip install -e . --no-deps --no-build-isolation
    • --no-deps :只把当前代码作为一个包链接进来,不下载和安装任何依赖

    • --no-build-isolation :让 pip 直接使用容器内现有的环境进行构建

    六、 代码层面的兼容性排雷

    环境打通后,直接跑代码依然会遇到跨版本的报错:

    ModuleNotFoundError: No module named 'torchdata.datapipes
    
    ModuleNotFoundError: No module named 'pytorch_lightning'
    1. torchdata.datapipes 模块缺失
      原因:torchdata 在 0.10.0 版本后废弃了 datapipes 模块。
      解决:降级安装旧版,单独下载 torchdata==0.7.1 的纯 Python 包进行替换。

    2. pytorch_lightning 缺包
      原因:离线安装时漏掉了 torchmetrics。
      解决:单独下载 torchmetrics 的 whl 包,上传安装。

    解决方案:单独下载对应.whl文件,并上传到内网服务器安装

    (1)在官网获取所需文件,例如

    👉 Client Challenge
    找到并下载名为 torchdata-0.7.1-py3-none-any.whl 的文件。

    (2)将.whl文件上传到内网服务器

    (3)在容器内安装包

    # 进入容器
    docker exec -it <容器名> /bin/bash
    
    # 先卸载之前装错的新版本
    pip uninstall -y torchdata
    
    # 安装刚传上来的本地文件
    pip install /workspace/torchdata-0.7.1-py3-none-any.whl
    1. 绝对路径硬编码
      原因:代码和配置文件中写死了原集群的绝对路径(如 /fs1/home/...)。
      解决:将配置文件中的 root_dir 和 data_dir 全部替换为当前容器内的挂载路径(如 /workspace/...)。


    总结与经验沉淀

    1. 先验架构,再看框架:进入未知内网,先跑 uname -m 和 lscpu,国产化机器普遍是 aarch64,直接抛弃 nvidia-smi 思维。

    2. 跨平台离线下载:使用 pip download --platform manylinux2014_aarch64 --python-version x.x 是拯救内网机器的神技。

    3. 敬畏基础镜像:官方打好底层的镜像里,PyTorch 和 CANN 的版本是严格绑定的。手工装包时务必使用 --no-deps,防止依赖被意外覆盖。

    4. 放弃 Conda 幻想:对于跨平台的底层环境迁移,Docker 的隔离性与一致性是降维打击。

    希望这篇踩坑记录能帮到正在进行国产化算力适配的同行。

    Logo

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

    更多推荐