从 x86 迁移到 ARM/昇腾:内网离线环境下的深度学习环境排雷实录
背景
近期,需要将一套基于 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,传到内网解压。
解压时报错
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'
-
torchdata.datapipes模块缺失
原因:torchdata在 0.10.0 版本后废弃了datapipes模块。
解决:降级安装旧版,单独下载torchdata==0.7.1的纯 Python 包进行替换。 -
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
-
绝对路径硬编码
原因:代码和配置文件中写死了原集群的绝对路径(如/fs1/home/...)。
解决:将配置文件中的root_dir和data_dir全部替换为当前容器内的挂载路径(如/workspace/...)。
总结与经验沉淀
-
先验架构,再看框架:进入未知内网,先跑
uname -m和lscpu,国产化机器普遍是aarch64,直接抛弃nvidia-smi思维。 -
跨平台离线下载:使用
pip download --platform manylinux2014_aarch64 --python-version x.x是拯救内网机器的神技。 -
敬畏基础镜像:官方打好底层的镜像里,PyTorch 和 CANN 的版本是严格绑定的。手工装包时务必使用
--no-deps,防止依赖被意外覆盖。 -
放弃 Conda 幻想:对于跨平台的底层环境迁移,Docker 的隔离性与一致性是降维打击。
希望这篇踩坑记录能帮到正在进行国产化算力适配的同行。
更多推荐


所有评论(0)