任务:在GPU服务器上使用 Docker 搭建代码运行环境,并测试一个模型是否可以正常运行。

项目目录中包含代码压缩包、预训练模型压缩包和说明文档。目标不是立刻复现README 中的完整指标,而是先完成以下几件事:

  1. 理清项目目录结构;
  2. 解压代码和模型文件;
  3. 根据 README 确认环境依赖;
  4. 使用 Docker 创建可复现运行环境;
  5. 跑通数据读取、训练、模型保存和推理测试流程;

这篇文章主要记录整个 Docker 部署和排障过程,也作为一次实践复盘。

为什么要用 Docker

在深度学习这类项目中,经常会遇到一个问题:

代码本身没问题,但环境很难配。

比如一个项目可能要求:

Python == 3.9
CUDA == 12.x
PyTorch == 2.x
transformers == 4.x
scikit-learn
dpkt
libpcap

如果直接在服务器系统里安装这些依赖,可能会出现很多问题:

  1. 服务器上原来已经有别人的 Python 环境;
  2. 不同项目需要不同版本的 PyTorch;
  3. CUDA、驱动、PyTorch 版本可能不匹配;
  4. pip、conda 安装依赖容易污染系统环境;
  5. 这次能跑,换一台机器又跑不起来;
  6. 多个人共用服务器时,互相改环境容易影响别人。

Docker 的价值就在于:

把运行代码需要的系统环境、Python 环境、依赖包、启动方式封装到一个独立容器里,让代码在一个相对隔离、可复现的环境中运行。

简单说,Docker 可以让我们做到:

同一份镜像
  ↓
在不同机器启动相似环境
  ↓
减少“环境不一致”导致的问题

Docker 概念

在实际使用 Docker 之前,先理解几个基础概念。

镜像 Image

Docker 镜像可以理解为:

一个打包好的运行环境模板。

比如:

python:3.9-slim
nvidia/cuda:11.8.0-devel-ubuntu20.04
nvcr.io/nvidia/pytorch:25.03-py3
ufoym/deepo:all-jupyter

这些都是镜像。镜像里通常包含:

操作系统基础环境
Python
CUDA
PyTorch
系统依赖
部分常用工具

你可以把镜像理解成“安装包”或者“系统快照”。

但镜像本身不会运行,真正运行的是容器。


容器 Container

容器是由镜像启动出来的运行实例。

比如有一个镜像:

ufoym/deepo:all-jupyter

用下面命令启动:

docker run -it ufoym/deepo:all-jupyter bash

就会进入一个容器环境。可以理解为:

镜像 = 模板
容器 = 根据模板启动出来的运行环境

类似于:

类 → 对象
镜像 → 容器

一个镜像可以启动多个容器,每个容器之间相互隔离。

Dockerfile

Dockerfile 是用来描述如何构建镜像的文件。例如:

FROM python:3.9-slim

WORKDIR /workspace/project

RUN pip install torch transformers scikit-learn

CMD ["bash"]

它表示:

  1. 基于 python:3.9-slim 镜像;
  2. 设置工作目录为 /workspace/project
  3. 安装 Python 依赖;
  4. 默认启动 bash。

然后通过:

docker build -t my-project:latest .

就可以构建出一个新镜像。

docker build 和 docker run

这两个命令很重要。docker build用于根据 Dockerfile 构建镜像:

docker build -f Dockerfile.gpu -t traffic-test:py39 .

含义:

-f Dockerfile.gpu     指定 Dockerfile 文件
-t traffic-test:py39  给镜像起名字和标签
.                     当前目录作为构建上下文

docker run用于根据镜像启动容器:

docker run -it --rm traffic-test:py39 bash

含义:

-it     交互式终端
--rm    容器退出后自动删除
bash    进入 bash 命令行

挂载目录 Volume

容器内部和宿主机是隔离的。如果代码在宿主机:

/home/user/project

容器里默认看不到这个目录。所以需要用 -v 做挂载:

docker run -it --rm \
  -v /home/user/project:/workspace/project \
  -w /workspace/project \
  image_name \
  bash

意思是:

把宿主机 /home/user/project
挂载到容器里的 /workspace/project

这样容器里就可以访问宿主机代码。

本次实践中,项目目录通过类似方式挂载:

-v /project_root/work:/workspace
-w /workspace/code

这样容器里看到的 /workspace/code 实际上就是宿主机上的项目代码目录。

 GPU 容器

普通 Docker 容器默认不能直接使用 GPU。

如果要在容器里使用 NVIDIA GPU,需要服务器安装 NVIDIA 驱动和 nvidia-container-runtime,然后启动容器时加:

--gpus all

或者指定某一张 GPU:

--gpus '"device=1"'

例如:

docker run -it --rm \
  --gpus '"device=1"' \
  image_name \
  bash

进入容器后,可以用下面命令验证 GPU 是否可用

python -c "import torch; print(torch.cuda.is_available())"

如果输出:

True

说明 PyTorch 在容器中可以调用 GPU。


Docker 网络

Docker 默认使用 bridge 网络。

有些服务器环境里,Docker 默认网络可能无法正常解析域名,比如:

Temporary failure resolving 'archive.ubuntu.com'
Temporary failure resolving 'security.ubuntu.com'

这时可以尝试使用宿主机网络:

--network host

例如:

docker build --network=host -f Dockerfile.gpu -t image_name .

或者:

docker run -it --rm --network host image_name bash

本次实践中,Docker build 过程中就遇到了默认 bridge 网络 DNS 解析失败,最终通过 --network=host 定位问题。

docker commit

docker commit 可以把一个正在运行的容器保存成新的镜像。

比如你进入容器后,手动安装了一些依赖、修改了一些环境,然后想把当前状态保存下来,就可以在宿主机执行:

docker commit 容器名 新镜像名:标签

例如:

docker commit traffic_test traffic-test:debug

以后就可以直接用这个新镜像启动,不用重复安装依赖。注意:

docker commit 要在宿主机执行,不是在容器内部执行。

宿主机提示符一般类似:

user@server:~$

容器内部提示符一般类似:

root@container:/workspace/project#

2.接下来实践:了解初始目录结构

/project_root
├── code_package
│   └── code.tar.gz
└── pretrained_model
    ├── pretrained_model.tar.gz
    └── 说明文档.pdf

第一步不是直接写 Dockerfile,而是先查看压缩包内部结构。

tar -tzf code_package/code.tar.gz | head -50
tar -tzf pretrained_model/pretrained_model.tar.gz | head -50

通过查看发现,代码包中包含:

README.md
说明文档.pdf
process_pcap.py
process_txt.py
data_process_multiview.py
count_classes.py
inference/
models/
uer/

模型包中包含预训练模型、标签文件和数据文件。

为了避免污染原始目录,我新建了一个 work 目录用于解压和测试:

mkdir -p work

tar -xzf code_package/code.tar.gz -C work
tar -xzf pretrained_model/pretrained_model.tar.gz -C work

3. 阅读 README,确认环境要求

进入代码目录后,先查看 README:

cd work/code
sed -n '1,200p' README.md

README 中给出的环境要求大致如下:

Python == 3.9
CUDA == 12.x
torch == 2.x
transformers == 4.x
scikit-learn
scipy
dpkt

同时 README 中给出了完整的数据处理和测试流程。

4. 确认数据和模型文件

在正式构建 Docker 环境之前,需要确认代码中是否已经包含测试数据。

find work/code/inference -maxdepth 3 -type f | head -100

可以看到类似文件:

processed_data/train.txt
processed_data/valid.txt
processed_data/encrypted_test.txt
processed_data/plaintext_test.txt
processed_data/attack_encrypted_open.txt
processed_data/attack_plaintext_open.txt

继续查找模型文件:

find work -name "*.bin" -o -name "*.pt" -o -name "*.pth" -o -name "*.ckpt"

发现主要模型包括:

models/pre-trained_model.bin
pretrained_model/dataset_xxx.pt

其中:

  • pre-trained_model.bin 是预训练模型;
  • 后续训练会生成一个 fine-tuned 模型;

5. 检查服务器 Docker 和 GPU

在宿主机上检查 Docker:

docker --version
docker ps

检查 GPU:

nvidia-smi

确认服务器上有 NVIDIA GPU,并且驱动版本支持当前 CUDA。之后还需要验证 Docker 容器能否访问 GPU。


6. 第一次尝试:基于 CUDA 镜像写 Dockerfile

一开始根据 README 里的 CUDA 要求,尝试基于 CUDA 镜像构建 Docker 环境。

创建 requirements.txt

cat > requirements.txt <<'EOF'
scikit-learn
scipy
torch
transformers
dpkt
numpy
tqdm
six
regex
tokenizers
packaging
matplotlib
pandas
EOF

创建 Dockerfile.gpu

FROM nvidia/cuda:12.x-runtime-ubuntuXX.XX

ENV DEBIAN_FRONTEND=noninteractive
ENV PYTHONUNBUFFERED=1
ENV PIP_NO_CACHE_DIR=1

WORKDIR /workspace/project

RUN apt-get update && apt-get install -y \
    python3.9 \
    python3.9-dev \
    python3.9-distutils \
    python3-pip \
    gcc \
    g++ \
    make \
    libpcap-dev \
    tcpdump \
    iputils-ping \
    net-tools \
    vim \
    less \
    git \
    curl \
    && rm -rf /var/lib/apt/lists/*

RUN ln -sf /usr/bin/python3.9 /usr/bin/python && \
    ln -sf /usr/bin/pip3 /usr/bin/pip

COPY requirements.txt /tmp/requirements.txt

RUN python -m pip install --upgrade pip -i https://pypi.tuna.tsinghua.edu.cn/simple && \
    python -m pip install -r /tmp/requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple

CMD ["bash"]

构建镜像:

docker build -f Dockerfile.gpu -t traffic-test:cuda-py39 .

遇到的问题

构建时出现 DNS 解析失败:

Temporary failure resolving 'archive.ubuntu.com'
Temporary failure resolving 'security.ubuntu.com'
Temporary failure resolving 'developer.download.nvidia.com'
E: Unable to locate package python3.9
E: Unable to locate package gcc
E: Unable to locate package curl

初步判断原因是:Docker 默认 bridge 网络下 DNS 解析失败,导致 apt-get update 没有正常执行,后续所有软件包都无法找到。


7. 解决 Docker 构建网络问题

先测试容器在 host 网络下 DNS 是否正常:

docker run --rm --network host nvidia/cuda:12.x-runtime-ubuntuXX.XX \
  bash -lc "cat /etc/resolv.conf && getent hosts archive.ubuntu.com || true"

测试发现,在 --network host 模式下可以正常解析域名。因此后续构建镜像时需要加:

--network=host

例如:

docker build --network=host -f Dockerfile.gpu -t traffic-test:cuda-py39 .

这一步解决了 Docker 默认网络 DNS 异常的问题。


8. 第二个问题:无法拉取 Python 镜像

为了避免在 CUDA Ubuntu 镜像里折腾 Python 3.9,我又尝试换成 Python 官方基础镜像:

FROM python:3.9-slim

但是构建时报错:

docker.io/library/python:3.9-slim: 403 Forbidden

也尝试过使用内部镜像仓库中的 Python 镜像,但出现匿名 token 获取失败:

failed to fetch anonymous token

因此可以确认:当前服务器无法稳定拉取外部 Docker 镜像,继续依赖远程镜像不现实。

最终策略调整为:

不再从外部拉取新镜像,而是基于服务器本地已有镜像创建环境。


9. 使用服务器本地已有镜像

查看本地镜像:

docker images | grep -E "cuda|python|pytorch|deepo|nvidia"

发现服务器上已有多个镜像

为了快速验证代码流程,先使用已有的 镜像启动容器:

docker run -it --rm \
  --gpus '"device=1"' \
  --network host \
  --name traffic_test \
  -v /project_root/work:/workspace \
  -w /workspace/code \
  ufoym/deepo:all-jupyter \
  bash

进入容器后检查环境:

python --version
pip --version

python -c "import torch; print(torch.__version__); print(torch.version.cuda); print(torch.cuda.is_available())"

输出显示:

Python 3.8.x
torch 1.x + CUDA
True

虽然这个环境不是 README 中要求的 Python 3.9 + torch 2.x,但它至少证明:

  1. Docker 容器可以启动;
  2. 项目目录可以挂载;
  3. 容器内可以访问 GPU;
  4. 可以先用于代码流程快速验证。

10. 运行脚本

先不直接跑大模型训练,而是运行最轻量的脚本:

cd /workspace/code

python count_classes.py --paths \
  ./inference/processed_data/plaintext_test.txt \
  ./inference/processed_data/encrypted_test.txt

继续统计 open / attack 数据:

python count_classes.py --paths \
  ./inference/processed_data/attack_encrypted_open.txt \
  ./inference/processed_data/attack_plaintext_open.txt

11. 第一次运行推理脚本:缺少 fsspec

接下来运行核心脚本:

cd /workspace/code/inference

python run_classifier_multiview.py \
  --seq_length 128 \
  --labels_num 200 \
  --threshold 1 \
  --lambda_epochs 1 \
  --packet_num 4 \
  --packet_len 128 \
  --device_id 0 \
  --dataset_name dataset \
  --train_dataset ./processed_data/train.txt \
  --valid_dataset ./processed_data/valid.txt \
  --test_dataset ./processed_data/encrypted_test.txt \
  --open_dataset ./processed_data/attack_encrypted_open.txt

报错:

ModuleNotFoundError: No module named 'fsspec'

尝试使用 pip 安装:

pip install fsspec -i https://pypi.tuna.tsinghua.edu.cn/simple

但容器中访问 pip 源出现 SSL 错误:

SSLError(SSLEOFError)
RemoteDisconnected

更换清华源、阿里源、官方源后仍然失败。


12. 处理 fsspec 导入问题

通过搜索代码:

grep -R "fsspec" -n .

发现 fsspec 只在几个 opts*.py 文件第一行被导入:

from fsspec.registry import default

继续检查后发现项目并未实际使用这个 default 变量,因此判断它是无用导入。

为了继续推进测试,先备份文件,然后注释该导入:

cp uer/opts.py uer/opts.py.bak
cp uer/opts_origin.py uer/opts_origin.py.bak
cp uer/opts_multiview.py uer/opts_multiview.py.bak
cp uer/opts_twoview.py uer/opts_twoview.py.bak

sed -i '1s/^/# /' uer/opts.py
sed -i '1s/^/# /' uer/opts_origin.py
sed -i '1s/^/# /' uer/opts_multiview.py
sed -i '1s/^/# /' uer/opts_twoview.py

这一步属于临时排障处理,正式环境中更推荐通过固定 requirements 或内部 pip 源安装依赖。


13. 第二次运行:fine-tuned 模型缺失

再次运行脚本后,程序可以正常读取数据:

total: 60579
total: 19623
total: 7863
total: 300000
Batch size: 1024
The number of training instances: 20
Test set evaluation.

但随后报错:

FileNotFoundError:
../models/dataset_finetuned_model_4packet_128bytes_multiview.bin

检查模型目录:

ls -lh models

发现只有预训练模型:

pre-trained_model.bin

没有 fine-tuned 模型。

继续查看代码:

sed -n '630,680p' inference/run_classifier_multiview.py

发现训练代码已经被注释,脚本直接进入 evaluation 阶段加载 fine-tuned 模型。

README 中有一句说明:

微调一次后可以将训练相关代码注释,后续只进行推理。

这说明当前是首次运行,fine-tuned 模型还没有生成,因此必须先恢复训练代码,跑一次微调保存模型。


14. 恢复训练代码

备份脚本:

cp inference/run_classifier_multiview.py inference/run_classifier_multiview.py.bak_before_train

恢复训练代码,即取消训练阶段的注释,使代码重新执行:

print("Start training.")
for epoch in tqdm.tqdm(range(1, args.epochs_num + 1)):
    train_loss, train_acc = train_one_epoch(...)
    val_acc = evaluate(...)
    if val_acc > best_result:
        best_result = val_acc
        save_model(model, args.output_model_path)

恢复后重新运行脚本。


15. 第三次运行:CUDA OOM

恢复训练后,程序成功进入训练阶段:

Start training.

但默认 batch_size=1024 时出现显存不足:

RuntimeError: CUDA out of memory
Tried to allocate xxx MiB

查看原因:

  • 当前 GPU 本身已有其他进程占用显存;
  • 默认 batch size 太大;
  • Transformer attention 计算显存占用较高;
  • 每条样本使用多视角输入,进一步放大显存占用。

解决方案是降低 batch size:

PYTORCH_CUDA_ALLOC_CONF=max_split_size_mb:128 python run_classifier_multiview.py \
  --seq_length 128 \
  --labels_num 200 \
  --threshold 1 \
  --lambda_epochs 1 \
  --epochs_num 1 \
  --batch_size 16 \
  --packet_num 4 \
  --packet_len 128 \
  --device_id 0 \
  --dataset_name dataset \
  --train_dataset ./processed_data/train.txt \
  --valid_dataset ./processed_data/valid.txt \
  --test_dataset ./processed_data/encrypted_test.txt \
  --open_dataset ./processed_data/attack_encrypted_open.txt

此时输出:

Batch size: 16
The number of training instances: 1263
Start training.

说明训练流程已经正常启动。


16. 快速训练完成

使用快速验证参数

epochs_num=1
lambda_epochs=1
batch_size=16

训练完成:

Start training.
100%|███████████████████████████████████████████████| 1/1 [04:08<00:00, 248.16s/it]
Test set evaluation.
Closed Test result:
  Acc. (Balanced) 0.0050

Open Test result:
  Acc. (Balanced) 0.0000

因此这次运行的意义是:

验证 Docker 环境、GPU、数据读取、训练循环、模型保存和测试流程均可跑通。


17. fine-tuned 模型生成

训练完成后检查模型目录:

ls -lh models | grep finetuned

可以看到生成了 fine-tuned 模型:

dataset_finetuned_model_4packet_128bytes_multiview.bin

模型大小约 1GB 以上。

这说明模型保存流程正常。


18. 只推理模式验证

生成 fine-tuned 模型后,恢复只推理版本代码:

cp inference/run_classifier_multiview.py.bak_before_train inference/run_classifier_multiview.py

再次执行测试命令。

这次日志中没有出现:

Start training.

而是直接进入:

Test set evaluation.
Closed Test result:
  Acc. (Balanced) 0.0050

Open Test result:
  Acc. (Balanced) 0.0000

说明:

  1. fine-tuned 模型可以被正常加载;
  2. 只推理流程跑通;
  3. 从训练、模型保存到推理测试的完整链路已验证完成。

19. 固化当前 Docker 环境

由于当前容器中已经做过一些临时环境调整和代码修改,为避免退出容器后环境丢失,在宿主机中执行:

docker ps | grep traffic_test

找到容器名后执行:

docker commit traffic_test traffic-test:debug

输出类似:

sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

之后可以通过以下命令复用环境:

docker run -it --rm \
  --gpus '"device=1"' \
  --network host \
  --name traffic_test \
  -v /project_root/work:/workspace \
  -w /workspace/code \
  traffic-test:debug \
  bash

这一步完成了当前可运行环境的固化。


20. 当前阶段结论

截至目前,已经完成以下工作:

  1. 完成项目目录梳理和代码/模型解压;
  2. 确认核心脚本;
  3. 确认数据集;
  4. 确认 Docker 默认网络存在 DNS 问题,构建时需要使用 --network=host
  5. 确认服务器无法稳定拉取外部 Python 镜像,改用本地已有镜像;
  6. 确认容器中 GPU 可用;
  7. 处理了 fsspec 缺失问题;
  8. 处理了 fine-tuned 模型缺失问题;
  9. 处理了默认 batch size 导致 OOM 的问题;
  10. 完成 1 epoch 快速训练;
  11. 成功生成 fine-tuned 模型;
  12. 成功跑通只推理测试流程;
  13. 已将当前容器固化为可复用镜像。

22. 这次实践中的几个 Docker 经验

1. 不要一开始就写复杂 Dockerfile

深度学习项目环境复杂,建议先确认:

README 环境要求
代码入口
数据路径
模型路径
服务器 GPU 状态
本地已有镜像

再决定 Dockerfile 方案。

2. Docker build 失败不一定是 Dockerfile 写错

本次主要问题其实是:

DNS 解析失败
镜像仓库访问受限
pip 源 SSL 异常

而不是代码本身错误。

3. --network=host 是内网服务器排障常用手段

当 Docker build 中出现:

Temporary failure resolving ...

可以先测试:

docker run --rm --network host 镜像 bash -lc "getent hosts archive.ubuntu.com"

如果 host 网络可用,则构建时可以加:

docker build --network=host ...

4. 优先利用本地已有镜像

当外网镜像拉取失败时,继续尝试不同远程镜像意义不大。更实际的做法是:

docker images

看看本地已有 PyTorch、CUDA、deepo、NGC 镜像,先基于可用镜像跑通流程。

5. 先跑轻量脚本,再跑训练脚本

不要一开始就跑大模型训练。推荐顺序是:

python --version
torch.cuda.is_available()
count_classes.py
小 batch / 1 epoch 训练
只推理模式
完整训练配置

6. 训练结果低不一定是模型问题

快速验证时为了节省时间和避免 OOM,经常会使用:

epochs_num=1
batch_size=16

这种配置的结果只能说明流程是否跑通,不能用于评价模型性能。

24. 总结

这次 Docker 环境部署的核心收获是:深度学习代码的环境问题往往不是单一依赖安装问题,而是由服务器网络、镜像仓库、GPU 显存、代码路径、模型文件和数据格式共同决定的。

先理解目录和 README
再确认数据和模型
再测试 Docker / GPU
远程镜像失败后改用本地已有镜像
先跑轻量脚本验证数据
再处理依赖和源码小问题
恢复训练生成 fine-tuned 模型
降低 batch size 解决 OOM
最后完成训练、推理和镜像固化
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐