资讯动态

用Docker管理深度学习环境:从GPU透传到deepo镜像实操指南

发布时间:2026/10/22 15:06:41 来源:尧图企业网站定制
搞深度学习的人应该都经历过那种“环境比代码还难搞”的崩溃时刻。我自己的项目X就是从一台机器搬到另一台机器结果同样的代码换了台机器就报错版本不对、依赖缺失、GPU驱动不认折腾一整天最后发现是系统路径里一个库版本不对。后来我下决心把所有环境都塞进Docker容器里用deepo镜像做底子这才算是把环境问题一次解决。如果你也想用Docker来管理深度学习环境尤其是要能真正用到GPU算力那这篇内容应该能帮你少走很多弯路。我会把Docker基础、GPU透传、deepo镜像的完整使用流程都写成实操记录包括我踩过的坑你直接照着做就行。1. 为什么在深度学习里要用Docker以及它到底解决了什么麻烦1.1 环境搭建的噩梦CUDA版本、系统依赖、框架冲突之前没接触Docker的时候我的习惯是每台机器都手动装一遍CUDA、cuDNN、Python环境、PyTorch或TensorFlow。听起来不难但实际执行起来光是CUDA版本和显卡驱动之间的匹配就能耗掉半天。驱动太新CUDA装不上驱动太旧新的框架版本又提示需要更高算力。更离谱的是同一个项目里要用两个不同依赖的库时经常一个装好了另一个就报错而你能不能同时维护多个Python虚拟环境全靠人生的运气。这还没完深度学习项目通常还依赖一些系统级的库比如OpenMPI、libgomp、opencv的系统依赖。Python的pip包可以装但系统库不对编译的时候照样报“找不到头文件”。我遇到过最头疼的一次某个库要求glibc版本不能太高但同一台主机上另一个服务必须用新版glibc这种冲突根本没有优雅的解决方案只能靠重新编译或者废弃一台机器。1.2 Docker的核心思路把环境连同代码一起打包Docker解决这个问题的思路特别“笨”但又特别有效把整个运行环境——操作系统的最小文件系统、依赖库、框架版本、配置文件、环境变量——全部打包成一个“镜像”运行的时候就是把这个镜像跑成“容器”。容器不是一个完整虚拟机没有单独的内核它只是复用了宿主机的操作系统内核但内部的用户空间是独立的。我打个比方普通安装环境就像你租了间毛坯房每次搬进去都要自己买家具、接通水电换房子还得重新装一遍Docker镜像就像一件打包好的集装箱房里面桌椅床铺台灯全都配好了你只要把它吊到地基上就能马上入住。而这个“地基”就是宿主机内核房子和房子之间互相不影响。你在这台机器上拉下来的镜像换一台机器跑只要内核能跑最终的软件环境就是一模一样的。1.3 适合谁来用以及你会得到什么如果你只是在自己的一台电脑上瞎折腾不换机器、不交代码、不部署那Docker的收益可能没那么明显。但只要你需要以下任意一种场景我建议你立刻上手Docker在多台机器上复现同一个实验不想每台机器重新配环境。和别人协作别人写完代码直接给你跑你不希望被对方的“本地环境能跑”给坑了。需要部署模型服务希望把推理环境固定下来避免上线时和开发环境不一致。想同时用多个框架版本比如PyTorch 2.x和1.x互不干扰。用上Docker之后你得到的最直接好处是换机器不再换环境代码直接跑。尤其是配合deepo这种全家桶镜像拉下来就能用连框架都不用自己装。2. Docker基础使用的二十个关键点别跳着学这些都有用2.1 镜像、容器、注册表三个概念讲透先澄清一下核心概念后面才不会绕晕。镜像相当于一个只读的模板文件。镜像里包含了一个操作系统根文件系统、运行环境、应用代码和所有依赖。镜像可以保存为tar包也可以上传到仓库里共享。你可以把镜像理解成电脑里的Ghost系统备份你恢复出来的系统永远是那个状态。容器镜像被运行起来之后就是一个容器。容器是一个可读写的运行环境你在里面安装的软件、生成的文件、修改的参数都只存在于这个容器内。容器可以被启动、停止、删除也可以被重新提交成新的镜像。注册表存放镜像的仓库最常用的是Docker Hub。deepo镜像就托管在上面。你可以用docker pull拉取镜像也可以用docker push上传自己做的镜像。三者关系很简单从注册表拉镜像用镜像创建容器并运行容器可以保存为新镜像再传回注册表。2.2 常用命令速查表镜像拉取、容器运行、退出、清理我把日常最高频的命令整理成一张表你可以直接存下来。操作命令说明查看本地镜像docker images列出所有镜像包含镜像名、标签、大小拉取镜像docker pull 镜像名:标签从仓库下载镜像列出运行中的容器docker ps查看当前在跑的容器列出所有容器docker ps -a包括已停止的运行容器docker run 选项 镜像名:标签创建并启动容器启动已存在的容器docker start 容器ID或名不会重新创建进入运行中的容器docker exec -it 容器ID /bin/bash在容器内打开终端查看容器日志docker logs 容器ID排查看输出停止容器docker stop 容器ID温柔停强制停止docker kill 容器ID粗暴停删除容器docker rm 容器ID删除已停止的容器删除镜像docker rmi 镜像名:标签删除本地镜像容器内文件拷出docker cp 容器ID:路径 宿主机路径把容器里生成的结果拷到宿主宿主机文件拷入docker cp 宿主机路径 容器ID:路径把代码拷进容器注意docker run和docker exec的区别是很多新手容易混的。run是从镜像新建容器来运行exec是进入一个已经存在的容器。如果你run完之后exit容器就停了再想进去得用docker start把它启动然后用docker exec进入。2.3 给新手的第一条建议先别急着装镜像先跑个hello world我的建议是先别一上来就拉deepo那种几个GB的镜像先用一个几十MB的小镜像走通流程docker run hello-world这句命令会从Docker Hub拉取一个最小的测试镜像然后输出一段欢迎信息。如果你能正常看到输出说明Docker引擎工作正常网络能连到仓库。跑通之后再试试交互式的Ubuntudocker run -it ubuntu:22.04 /bin/bash此时你会进入一个Ubuntu的终端这个终端里的环境和你宿主机完全隔离。你可以随意执行apt update、安装软件弄坏了也无所谓直接exit退出下次重新run一个就是全新的。这种“弄坏了马上重来”的特性是Docker最大的魅力之一。提示交互式命令行运行容器时-i表示保持输入流-t表示分配一个伪终端两个一起用-it才能像正常终端一样操作。如果不是交互式操作比如只跑一个脚本就不需要-it。3. GPU到底怎么进容器聊聊nvidia-docker和运行时的那些事3.1 为什么普通的Docker用不了GPUDocker容器默认只能访问CPU和内存因为容器共享的是宿主机的内核但没有共享硬件设备。你要是在普通容器里跑nvidia-smi大概率会提示找不到NVIDIA驱动或者找不到设备文件。这就是GPU透传的难点虽然容器不需要自己的驱动但它必须使用宿主机的显卡驱动同时需要在容器内提供对应版本的CUDA运行时库。Docker本身没有能力做到这一点所以需要额外的工具——业界通常叫nvidia-docker确切的名字是NVIDIA Container Toolkit。3.2 安装nvidia容器工具包两条命令的事NVIDIA Container Toolkit的安装步骤在不同Linux发行版上略有差异但核心逻辑都是一样的把Docker的运行时代替换成能够访问GPU设备的版本。以常见的Ubuntu系统为例大致操作如下# 添加仓库这一句很长建议直接复制 curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add - distribution$(. /etc/os-release;echo $ID$VERSION_ID) curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list sudo apt-get update sudo apt-get install -y nvidia-container-toolkit安装完成后重启Docker服务sudo systemctl restart docker注意如果你的网络访问不了上面这个仓库也可以去项目release页面下载deb包手动装。手动装的好处是稳定但需要自己处理依赖。装完之后运行一个带GPU的测试容器docker run --rm --gpus all nvidia/cuda:11.8-base nvidia-smi如果你能在输出里看到显卡信息、驱动版本和显存大小那就说明GPU透传成功。如果没有后面第六节我会讲排查思路。3.3 用--gpus参数指定GPU以及多卡、显存限制从Docker 19.03开始官方已经内置了GPU支持不再需要单独指定runtimenvidia只要安装了nvidia-container-toolkit就可以直接用--gpus参数。# 使用所有GPU docker run --gpus all 镜像名 nvidia-smi # 使用特定GPU比如第0号和第1号 docker run --gpus device0,1 镜像名 nvidia-smi # 按GPU编号使用0号卡 docker run --gpus device0 镜像名 nvidia-smi多卡训练时通常建议用device0,1指定避免和其他任务抢卡。如果你想限制容器能使用的显存大小可以加环境变量docker run --gpus all -e NVIDIA_VISIBLE_DEVICES0 -e NVIDIA_DRIVER_CAPABILITIEScompute,utility 镜像名其中NVIDIA_VISIBLE_DEVICES0表示容器只看到第0号卡。如果你只有一张卡直接--gpus all就好。另外容器内的显存占用情况用nvidia-smi查看但要注意容器内看到的显存是宿主机的真实显存不是虚拟出来的。所以如果你在宿主机上已经用掉了大部分显存容器里就会分配失败或者OOM这跟宿主机没有Docker时的行为一致。4. deepo镜像一套什么都能跑的深度学习全家桶4.1 deepo镜像里到底有什么deepo是一个开源的深度学习Docker镜像集合它的目标就是让你“一条命令搞定深度学习环境”。deepo全家桶镜像里预装了几乎所有主流的深度学习框架PyTorch、TensorFlow、Keras、MXNet、Caffe、Theano、Chainer以及Jupyter Notebook、JupyterLab等工具还包含了常见的Python库numpy、scipy、pandas、matplotlib等。deepo有两个核心版本一个是deepo默认最新版另一个是deepo:jupyter额外带Jupyter环境。你还可以指定不同的CUDA版本和cuDNN版本比如deepo:cu110对应CUDA 11.0deepo:cu113对应CUDA 11.3。如果电脑没有GPU也有纯CPU版本deepo:cpu不过我不建议用因为跑深度学习不带GPU就没有灵魂。deepo最大的优点不是“东西多”而是经过了大量用户验证。里面的框架版本之间兼容性是被测试过的镜像内部的Python路径、动态库路径都配置好了你不需要自己去解那些纠缠不清的依赖关系。直接拉下来run就能看到import torch和import tensorflow都成功。4.2 拉取deepo并验证GPU可用性拉取deepo的操作很简单docker pull deepo:latest最新版镜像体积通常在十几GB左右所以第一次拉取时要有心理准备视网速可能要十来分钟甚至更久。如果想省流量也可以只拉精简版deepo:lite里面只包含PyTorch和TensorFlow体积会小不少。拉取完成后先验证一下能不能正常起容器docker run --rm -it deepo:latest /bin/bash进入容器后确认Python版本和框架情况python --version python -c import torch; print(torch.__version__) python -c import tensorflow as tf; print(tf.__version__)如果都能正常输出说明环境基本可用。接下来最关键的一步是验证GPUpython -c import torch; print(torch.cuda.is_available())输出True才说明GPU真的被容器用上了。注意这一步必须要在docker run时加上--gpus all否则就算容器里有CUDA库也检测不到显卡。4.3 实际跑一个PyTorch训练脚本看看效果我在模拟项目X里用一个两层卷积网络训练一个图像分类模型直接在deepo容器里跑。先写一个简单的训练脚本存为train.pyimport torch import torch.nn as nn import torch.optim as optim # 构造一个简单的模型 class Net(nn.Module): def __init__(self): super().__init__() self.conv nn.Sequential( nn.Conv2d(3, 32, 3, padding1), nn.ReLU(), nn.MaxPool2d(2), nn.Conv2d(32, 64, 3, padding1), nn.ReLU(), nn.MaxPool2d(2) ) self.fc nn.Linear(64 * 8 * 8, 10) def forward(self, x): x self.conv(x) x x.view(x.size(0), -1) return self.fc(x) model Net().cuda() criterion nn.CrossEntropyLoss() optimizer optim.Adam(model.parameters(), lr0.001) # 模拟批量的输入跑几步看看GPU被使用 for step in range(10): data torch.randn(16, 3, 32, 32).cuda() target torch.randint(0, 10, (16,)).cuda() optimizer.zero_grad() output model(data) loss criterion(output, target) loss.backward() optimizer.step() if step % 2 0: print(fstep {step}: loss{loss.item():.4f})运行命令docker run --rm --gpus all -v /home/me/projectX:/workspace deepo:latest python /workspace/train.py我在运行后日志正常输出每一步的loss值而在宿主机上用nvidia-smi也能看到GPU利用率在工作。整个过程中容器里没有额外安装任何东西因为deepo已经内置了PyTorch和CUDA支持。这就是全家桶的好处。4.4 镜像体积太大怎么办按需裁剪、定制自己的deepodeepo镜像虽然方便但十几GB的体积实在劝退不少人尤其在公司网络里拉取时间很长。如果你只想用PyTorch完全不用拉全家桶。我给两个方案方案一直接用一个单框架的镜像比如pytorch/pytorch:1.13-cu11.7这种官方镜像体积会小很多。但这个镜像需要自己确认cuDNN等库的版本。方案二以deepo为基础自己写Dockerfile裁剪。举个例子如果你只用PyTorch可以新建一个Dockerfile把deepo里的PyTorch相关部分继承过来但去掉其他框架。不过deepo本身就是一层层叠加的裁剪并不容易除非你精通镜像分层结构。我更推荐的做法是保留deepo镜像作为“保险箱”平时真正跑试验用自己写的精简镜像。比如我现在的项目X就是用python:3.10官方镜像做底然后手动装一波PyTorchFROM python:3.10-slim RUN pip install --no-cache-dir torch torchvision加上GPU支持时用nvidia/cuda:11.8-cudnn8-runtime-ubuntu22.04做底FROM nvidia/cuda:11.8-cudnn8-runtime-ubuntu22.04 RUN apt-get update apt-get install -y python3-pip RUN pip install --no-cache-dir torch torchvision这种镜像体积大概5GB左右比deepo小了不少而且依赖可控。deepo更适合做“哪里都能跑”的万能底座定制镜像则适合你明确知道项目需要什么的情况。5. 实操记录从零开始用deepo跑我们的模拟项目X5.1 环境准备主机驱动、Docker安装确认跑GPU容器之前宿主机首先得把驱动装好。在宿主机上运行nvidia-smi如果能看到显卡信息说明驱动OK。接着确认Docker版本和工具包是否安装docker version dockerd --version dpkg -l | grep nvidia-container-toolkit我使用的宿主机是Ubuntu 22.04显卡驱动版本在535左右Docker版本24.xnvidia-container-toolkit正常安装。这里是关键驱动的版本和容器内CUDA版本不需要相等驱动只需要“够新”即可。比如deepo镜像内置的CUDA可能是11.x只要驱动版本大于等于CUDA版本要求的最低驱动版本就行。驱动版本太旧容器会警告CUDA ERROR: Insufficient driver。5.2 运行交互式容器并挂载数据目录在实际项目中代码和数据不可能每次打包进镜像通常是通过-v参数把宿主机目录挂载到容器里。我习惯把所有实验代码放在/workspace下docker run -it --gpus all \ -v /home/me/projectX:/workspace \ -w /workspace \ deepo:latest /bin/bash-v /home/me/projectX:/workspace表示把宿主机的projectX目录映射到容器内的/workspace目录。-w /workspace表示进入容器后默认工作目录就是/workspace。这样容器里和宿主机共享同一份代码你在宿主机上编辑代码容器里直接就能看到。如果还想在容器里访问宿主机的其他数据集目录多挂几个-v就行。注意挂载目录的路径不能有奇怪的符号否则容易被解析错。还有挂载后写文件时容器内进程的UID和宿主机上的UID需要匹配否则会出现“权限不足”的情况。我踩过这个坑容器内以root运行生成的文件宿主机上普通用户删不掉。5.3 安装额外的包比如新的库并保存为新镜像deepo里大部分库都齐了但偶尔还是要装一些额外的东西比如某个项目要用到timm或transformers。在容器内直接pip install可以但容器一旦被删除安装的东西就没了。所以正确的流程是在容器内安装需要的包。退出容器再执行docker commit把容器保存为新镜像。以后用这个新镜像跑项目不必重装。示例# 容器内 pip install timm exit # 宿主机上 docker ps -a # 找到刚才退出的容器ID docker commit 容器ID projectx-deepo:latest以后运行docker run --rm --gpus all -v /home/me/projectX:/workspace projectx-deepo:latest python /workspace/train.py用docker commit确实方便但它会留下很多历史层导致镜像越来越大。更优雅的用法是写一个Dockerfile在deepo的基础上追加安装FROM deepo:latest RUN pip install timm然后构建docker build -t projectx-deepo:latest .这样构建出来的镜像更干净也方便把环境配置版本化推荐后者。5.4 用容器跑分布式训练的小细节分布式训练时容器需要能够访问多个GPU并且节点之间可能要通信。单机多卡还好说直接--gpus all就行。多机多卡就麻烦了Docker的网络模式需要设置为主机模式否则不容易完成分布式通信。我建议跑分布式任务时加上--networkhost参数让容器直接使用宿主机的网络栈。对应的docker run命令docker run --rm --gpus all --networkhost -v /home/me/projectX:/workspace deepo:latest python -m torch.distributed.run --nproc_per_node4 /workspace/train.py--networkhost可以避免NCCL通信时因NAT造成的连接问题。同时注意容器内的共享内存默认只有64MB分布式训练或DataLoader加载数据时单进程占用共享内存过大容易报错提示“unable to open shared memory”。解决办法是增加--shm-sizedocker run --rm --gpus all --shm-size8g -v /home/me/projectX:/workspace deepo:latest python /workspace/train.py我一开始不知道这个训练时Dataloader每次都要卡死报了Bus error (core dumped)后来加了--shm-size才解决。这是Docker跑深度学习最容易忽略的一个参数。6. 避坑指南我踩过的那些坑希望你别再踩6.1 权限问题每次都要sudo docker默认情况下普通用户没有权限直接操作Docker每次都要加sudo很烦。解决方案是把当前用户加入docker组sudo groupadd docker sudo usermod -aG docker $USER然后退出终端重新登录再运行docker ps就不会提示权限不足了。注意加入docker组的用户有等同于root的权限这在多用户机器上有安全隐患。如果你所在的服务器有其他人用建议保留sudo方式别图省事。6.2 镜像拉不动换源还是不换源国内拉deepo这种大镜像经常出现传输中断、速度极慢。Docker Hub对国内网络不友好建议配置镜像加速器。通常在/etc/docker/daemon.json里设置{ registry-mirrors: [https://docker.mirrors.example.com] }注意这个地址需要根据你当地的可用性自己替换。配置完重启Docker。如果还是慢可以用一些小技巧先拉一个较小的标签比如deepo:lite再尝试升级有时候能成功或者用其他镜像站的代理服务但不要轻信来路不明的镜像源。6.3 显存不足容器内看得到显卡但无法分配一种常见现象是容器里nvidia-smi能看到显卡但一运行训练脚本就报显存不足或CUDA OOM。这通常不是容器本身的问题而是宿主机上其他进程已经占用了显存。在宿主机上执行nvidia-smi看看显存使用情况。如果显存被占满了那就只能等或者杀掉占用的进程。另外有些显卡驱动会开启持久化模式显存不会自动完全释放出现“显存空间没有立即腾出来”的情况。可以用sudo nvidia-smi --gpu-reset重置但这个操作在跑着任务的机器上要慎重。6.4 版本不匹配cuda、cudnn、驱动之间的爱恨纠葛常见报错CUDA error: no kernel image is available for execution on the device。这种通常是你的GPU计算能力太老而容器里的CUDA编译时启用了过高的架构比如在Compute Capability 6.1的卡上跑CUDA 11.x的镜像。解决办法是换一个兼容的CUDA版本镜像比如deepo:cu101。也可以在容器内设置环境变量让PyTorch自动选择兼容架构但效果不如换镜像稳定。还有一类是不小心把宿主机和容器里的驱动版本搞混了。记住一条铁律容器内不需要装NVIDIA驱动只需要装CUDA运行时库宿主机上必须装驱动驱动版本决定你能用多高的CUDA版本。如果你在容器里尝试去装驱动反而会破坏覆盖宿主机设备流转的机制导致容器崩溃。6.5 镜像占用磁盘空间疯狂膨胀Docker镜像和容器会占掉大量磁盘空间尤其deepo一个镜像就十几GB再叠加几次commit或者多个版本的镜像磁盘很快告急。我建议养成定时清理的习惯# 清理所有停止的容器 docker container prune # 清理没有被使用的镜像 docker image prune -a # 一键清理所有无用数据 docker system prune -a注意docker system prune -a会删掉所有没有被运行容器引用的镜像执行前最好先确认哪些镜像还要保留。提示可以给Docker配置磁盘空间限制通过修改存储驱动配置或使用单独挂载磁盘避免根分区写满导致系统异常。关于deepo镜像挑选的个人小建议我用deepo前后一年多最大的体会是它适合做“入门一跳”和“备份环境”但不一定适合每一个项目长期使用。如果你是第一次用Docker跑深度学习建议直接拉deepo因为它省掉很多配置步骤让你快速把注意力集中在“代码能不能跑”上。等你熟悉之后再去造自己的小镜像也不迟。最后再分享一个小技巧把docker run参数尽量封装成一个shell脚本或Makefile例如我项目里就有一个run.sh里面写好--gpus all、-v挂载、--shm-size等参数。以后每次启动容器只需执行./run.sh不用每次去敲那一长串命令。多个项目几个脚本一放切换环境跟切换频道一样简单。这个习惯真的能让你省下大量时间值得试试。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑