摘要:在将一个深度学习镜像从服务器 A 迁移到服务器 B 时,我们遇到了一个典型问题:容器内 nvidia-smi 可以正常显示 GPU 信息,但 torch.cuda.is_available() 却返回 False。本文记录了从现象分析、多轮排查到最终定位并解决问题的完整过程,重点聚焦“配置看似一致却行为不同”的疑难场景,为类似问题提供可复用的解决方案。


一、问题背景

我们有一个基于 nvidia/cuda 构建的深度学习镜像 dy_view_test:v1.0,在服务器 A 上运行正常:

python

import torch
print(torch.cuda.is_available())  # 输出 True

将该镜像导出为 .tar 文件,迁移到服务器 B 后,使用相同命令运行容器:

bash

docker run -it \
  --gpus all \
  -v /home/lzh/dynamic_view/Dy_viewer:/Dy_viewer \
  -w /Dy_viewer \
  dy_view_test:v1.0 /bin/bash

进入容器后执行:

python

import torch
print(torch.cuda.is_available())  # 输出 False

但执行:

bash

nvidia-smi

却能正常输出 GPU 信息,说明容器已经正确挂载了 GPU 设备。

问题定位:GPU 可见,但 PyTorch 无法使用。


二、第一轮排查:确认基础环境

1. 检查宿主机驱动

在服务器 B 上执行:

bash

nvidia-smi

输出正常,驱动版本为 550.54.15,支持 CUDA 12.6,无异常。

2. 检查 NVIDIA Container Toolkit 是否安装

bash

nvidia-ctk --version

输出:

nvidia-ctk: 1.14.3

NVIDIA Container Toolkit 已安装。

3. 检查 Docker 配置

查看 /etc/docker/daemon.json

json

{
  "runtimes": {
    "nvidia": {
      "path": "nvidia-container-runtime",
      "runtimeArgs": []
    }
  },
  "default-runtime": "nvidia"
}

配置正确。

4. 重启 Docker 服务

bash

sudo systemctl restart docker

确保配置已加载。

5. 检查镜像完整性

bash

docker images | grep dy_view_test

镜像存在,IMAGE ID 与原服务器一致。


三、第二轮排查:验证容器 GPU 支持

1. 使用官方镜像测试

bash

docker run --rm --gpus all nvidia/cuda:12.6.0-base-ubuntu22.04 nvidia-smi

✅ 成功输出 GPU 信息,说明 Docker + GPU 集成基本正常。

2. 在目标镜像中测试 GPU

bash

docker run --rm --gpus all dy_view_test:v1.0 nvidia-smi

✅ 同样成功,说明 dy_view_test:v1.0 镜像中 GPU 设备可访问。


四、关键突破:--privileged 模式下 torch.cuda.is_available() 返回 True

所有环境配置都是正常的,为什么调用不到GPU?只能考虑是不是权限问题。于是尝试使用 --privileged 模式运行:

bash

docker run --rm --gpus all --privileged dy_view_test:v1.0 \
  python -c "import torch; print(torch.cuda.is_available())"

输出:

True

🎉 果然是权限问题,问题已解决。

这意味着:

  • PyTorch 安装的是 GPU 版本(torch.version.cuda 非 None
  • CUDA 运行时环境正常
  • 问题出在 容器默认安全策略阻止了 GPU 初始化所需的系统调用

五、深入分析:--privileged 到底放开了什么?

--privileged 模式会:

  • 允许所有系统调用(包括 ioctl
  • 绕过 seccomp、AppArmor、SELinux 等安全模块
  • 赋予 CAP_SYS_ADMIN 等高级能力

PyTorch 在初始化 CUDA 时需要通过 ioctl 与 GPU 驱动通信,而 seccomp 默认策略会过滤这些调用,导致初始化失败。


六、使用最小权限放宽做精准修复(推荐)

全部权限都放开毕竟不太安全,尽量不要长期使用 --privileged,只需放开关键限制即可。

最终解决方案

bash

docker run -it \
  --gpus all \
  --security-opt seccomp=unconfined \
  --security-opt apparmor=unconfined \
  -e OPENBLAS_NUM_THREADS=1 \
  -v /home/lzh/dynamic_view/Dy_viewer:/Dy_viewer \
  -w /Dy_viewer \
  -p 8182:8182 \
  dy_view_test:v1.0 /bin/bash
参数说明:
  • --security-opt seccomp=unconfined:禁用 seccomp 系统调用过滤,允许 ioctl 等关键调用。
  • --security-opt apparmor=unconfined:禁用 AppArmor 限制(Ubuntu 系统常见)。
  • -e OPENBLAS_NUM_THREADS=1:避免 OpenBLAS 多线程冲突(常见 CPU 报错)。

七、生产环境建议

1. 标准化启动命令

--security-opt 写入部署脚本或 CI/CD 流程:

bash

# deploy.sh
docker run \
  --gpus all \
  --security-opt seccomp=unconfined \
  --security-opt apparmor=unconfined \
  ...

2. Kubernetes 部署

securityContext 中设置:

yaml

securityContext:
  seccompProfile:
    type: Unconfined

3. 安全性权衡

  • seccomp=unconfined 是 NVIDIA 官方推荐的兼容性方案。
  • 如需更高安全性,可使用 NVIDIA 官方 seccomp 配置。

八、总结

本次问题的排查路径如下:

  1. 现象nvidia-smi 可用,torch.cuda.is_available() 为 False
  2. 验证:官方镜像能用 → 问题不在驱动
  3. 突破--privileged 模式成功 → 问题在安全策略
  4. 定位:seccomp 或 AppArmor 阻止了 ioctl 调用
  5. 解决:使用 --security-opt seccomp=unconfined 精准放开限制

核心经验

当你在 Docker 容器中遇到 GPU 可见但 PyTorch 无法使用时,优先检查 seccomp 和 AppArmor。使用 --security-opt seccomp=unconfined 往往能快速解决问题。


作者:一位与 GPU 容器斗智斗勇的工程师(阿柴)
关键词:Docker, PyTorch, GPU, CUDA, nvidia-container-toolkit, seccomp, AppArmor, torch.cuda.is_available, Error 304


欢迎收藏、转发。如果你也遇到过类似问题,欢迎在评论区分享你的解决方案。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐