1. 在16.04上做这件事前,先想清楚三件事
1.1 为什么Ubuntu 16.04折腾:GLIBC和Python生态的年代差
先说个现实问题:Ubuntu 16.04发布于2016年,自带GLIBC 2.23,而mmdetection的现代版本和配套编译工具链,很多编译产物都依赖更高的GLIBC版本。之前我在一台老机器上直接照搬新教程,结果在import torch那一步就报GLIBC_2.29 not found,当时整个人是懵的。
这个报错的意思很直白:你想用的PyTorch或mmcv编译时是基于新版系统的,动态库链接的GLIBC符号在你的系统里根本不存在。解决办法不是去网上找一堆不安全的二进制替换libc,而是老老实实选择一套和系统年代匹配的版本组合。
同样的问题也出现在Python层面。16.04系统自带的Python 3.5已经非常老了,很多新版本的mmdet、mmcv都不再兼容Python 3.5。我在实际配置中把它锁定在Python 3.7,不追新,原因后面会展开说。
所以,入手16.04装mmdetection的第一认知就是:不要试图全上最新版本,而是要构建一套“古董但自洽”的版本矩阵。谁先接受“老系统配老版本”这个现实,谁就能少踩三分之二的坑。
适合看这篇内容的人,我总结一下:手里正好有16.04的老机器、或者因为显卡驱动原因只能留在16.04、再或者在某些工控设备上必须用老内核的人。如果你的机器可以随便装20.04以上系统,我建议你直接上新系统,别回头。
1.2 显卡、驱动、CUDA:三角约束先搞定
在装任何软件之前,先把显卡型号确认清楚。在16.04这种老系统上,显卡驱动版本和CUDA版本是互相锁死的,顺序不能乱。
我当时的做法是三步走。第一步,用lspci | grep -i nvidia查看显卡型号;第二步,去NVIDIA官网看该型号支持的最新驱动版本;第三步,根据驱动版本反推CUDA版本。
实际中遇到过这样一个例子:一台机器是GTX 1080 Ti,驱动版本390.xx,这个驱动最高只支持到CUDA 9.2左右。这意味着你最多也就用PyTorch 1.0左右的版本,mmdetection基本没法跑。这时候就得考虑升级驱动到440以上,才能用CUDA 10.2。
| 显卡驱动版本 | 可用的CUDA版本 | 可搭配的PyTorch建议 |
|---|---|---|
| 390.xx | CUDA 9.2及以下 | PyTorch 1.0以下(不推荐跑mmdetection) |
| 418/430 | CUDA 10.1/10.2 | PyTorch 1.5~1.8 |
| 450+ | CUDA 11.0/11.3 | PyTorch 1.8~1.10 |
这里额外提一句,如果你连显卡都没有,只想用CPU跑一跑示例,那也请保持CPU版PyTorch的版本对应关系,不要混装。
1.3 是不是非16.04不可
很多人的挣扎在于:周围教程都是18.04、20.04,自己这台机器偏偏是16.04。这时候我通常给出的建议排序是:
- 能上20.04就上20.04,这是最优解,一套新版本组合随便配。
- 因为老显卡或软件依赖无法升级系统的,选择16.04 + 老版本组合,也就是本文的主题。
- 既不能换系统、又想用新功能——可以考虑用Docker容器,在Docker里跑新版Ubuntu镜像。但这对显卡直通和nvidia-container-toolkit有要求,而且16.04主机内核老,Docker的gpu参数兼容性需要专门测试,我试下来反倒是裸装更快。
我个人的倾向是:如果确定留在16.04,就干脆把心气放平,把本文推荐的老版本组合当标准答案,不要再惦记mmdetection最新的3.0版本。
2. 驱动、CUDA与conda虚拟环境:一锅端式的准备方案
2.1 禁用nouveau,安装NVIDIA驱动
16.04自带的nouveau开源驱动和NVIDIA闭源驱动之间的冲突,是环境安装的第一道坎。不把它禁掉,装完驱动重启后大概率出现黑屏或循环登录。
操作顺序是这样的:
- 编辑配置文件:
sudo vim /etc/modprobe.d/blacklist-nouveau.conf,写入下面两行内容:
blacklist nouveau options nouveau modeset=0- 更新内核配置并重启:
sudo update-initramfs -u sudo reboot- 重启后先确认nouveau没被加载:
lsmod | grep nouveau如果没有任何输出,说明禁用成功。接着去NVIDIA官网下载对应显卡的runfile安装包,直接.run文件安装比apt源里的版本更可控,特别是老环境。
安装命令:
chmod +x NVIDIA-Linux-x86_64-450.142.run sudo ./NVIDIA-Linux-x86_64-450.142.run -no-x-check -no-nouveau-check装完用nvidia-smi验证。如果显示驱动版本和显存信息,说明驱动没问题。这一步看似基础,但我见过太多人跳过后在mmdet训练时报各种CUDA相关崩溃,回头查才发现是驱动层的cuda版本根本不兼容。
2.2 CUDA与cuDNN安装:尽量用runfile而不是deb包
CUDA和cuDNN的安装方式,会影响后期mmcv编译的顺利程度。16.04上用deb包安装有个问题:它会把驱动也一并更新掉,导致你辛辛苦苦装好的驱动版本被覆盖。所以我的方案是:驱动用runfile装,CUDA也用只装工具库的方式,不动驱动。
CUDA下载页里选择Linux、x86_64、Ubuntu 16.04版本对应的runfile。假设我们选用CUDA 10.2:
wget https://developer.download.nvidia.com/compute/cuda/10.2/Prod/local_installers/cuda_10.2.89_440.33.01_linux.run sudo sh cuda_10.2.89_440.33.01_linux.run安装时注意,在提示是否安装驱动时选No,只安装CUDA Toolkit。
然后设置环境变量,把下面内容加到~/.bashrc:
export PATH=/usr/local/cuda-10.2/bin:$PATH export LD_LIBRARY_PATH=/usr/local/cuda-10.2/lib64:$LD_LIBRARY_PATHcuDNN的安装更简单,把下载的cudnn-10.2-linux-x64-v8.2.1.32.tgz解压,然后把文件内容拷贝到CUDA目录下:
tar -xzvf cudnn-10.2-linux-x64-v8.2.1.32.tgz sudo cp cuda/include/cudnn*.h /usr/local/cuda/include/ sudo cp cuda/lib64/libcudnn* /usr/local/cuda/lib64/这一步完成后记得同步一下软链接,否则编译时可能出现libcudnn.so.8 not found。这是老系统上非常经典的坑,因为16.04不太会主动刷新动态库缓存。
2.3 conda环境与Python 3.7:给所有依赖建一个隔离区
到这一步,我强烈建议装Anaconda或Miniconda,绝对不要直接用系统Python。16.04自带Python 3.5,且很多系统工具链依赖它,直接覆盖会出大问题。
Miniconda对老系统更友好,安装命令:
wget https://repo.anaconda.com/miniconda/Miniconda3-py37_4.12.0-Linux-x86_64.sh bash Miniconda3-py37_4.12.0-Linux-x86_64.sh安装时一路yes,最后初始化conda。
创建虚拟环境:
conda create -n mmdet python=3.7 -y conda activate mmdet为什么锁Python 3.7?因为mmdetection 2.x时代的官方README和大量预编译的mmcv wheel都明确支持Python 3.7,而且3.7在AGES工具箱里兼容性最高。整个mmdet全家桶对这个Python版本支持得最稳,实际测试中3.8也可以,但在16.04的GLIBC限制下,3.7的轮子最听话。
3. mmcv与mmdetection的版本匹配:系统成败的分水岭
3.1 版本对应关系,别信任何单点教程
mmcv和mmdetection的版本匹配关系极其严格,不同大版本之间API有变化,连配置字段都可能改过名。在16.04上,我最终是装成了mmdetection 2.25.1 + mmcv-full 1.7.5 + PyTorch 1.10.0 + CUDA 10.2这一组合。
这个搭配的思考逻辑是:
- Python 3.7 + PyTorch 1.10 + CUDA 10.2:在老驱动能用、且版本不要太老之间取了平衡。
- mmcv-full 1.7.5:这个版本提供丰富的预编译包选项,支持PyTorch 1.10。
- mmdetection 2.25.1:是2.x系列的后期版本,功能足够,且配置组织方式成熟。
这里还需要看一个关键对应表(mmcv与mmdet官方支持关系):
| mmdetection版本 | mmcv全版本 |
|---|---|
| 2.25.1 | mmcv-full >= 1.5.1, < 1.8.0 |
| 2.18.1 | mmcv-full >= 1.4.6, < 1.7.0 |
| 2.10.0 | mmcv-full >= 1.3.8, < 1.4.0 |
别小看这些区间,mmcv版本装高了或装低了,轻则警告,重则直接报AttributeError或KeyError,比如mmdet里用到的某个函数在mmcv新版本里被改名了。
3.2 安装PyTorch与mmcv-full的完整命令
先装PyTorch 1.10.0,对应CUDA 10.2,直接用conda安装最省事:
conda install pytorch==1.10.0 torchvision==0.11.0 torchaudio==0.10.0 cudatoolkit=10.2 -c pytorch -y这里有个易错点:如果你预先下载了本地wheel安装,必须保证wheel是CUDA 10.2编译的。我曾经贪快用了pip install torch==1.10.0默认源,结果拉到了CPU版本,训练时torch.cuda.is_available()一直返回False,排查了半天才发现基础装错了。
接着装mmcv-full。有两个选择,一是直接用官方预编译wheel,二是源码编译。在16.04上,如果预编译wheel能匹配到你的Python和CUDA版本,那是最快路径;如果报找不到对应的wheel,就老老实实从源码编译。
预编译方式:
pip install mmcv-full==1.7.5 -f https://download.openmmlab.com/mmcv/dist/cu102/torch1.10/index.html源码编译方式(预编译失败时的备选):
git clone -b v1.7.5 https://github.com/open-mmlab/mmcv.git cd mmcv pip install -r requirements/optional.txt MMCV_WITH_OPS=1 FORCE_CUDA=1 pip install -e .注意,源码编译时MMCV_WITH_OPS=1这个环境变量不能省,不然只会装纯Python版本,后续运行检测时会报一些算子在自定义CUDA扩展上缺失的错误。
3.3 验证安装是否真的可用
装完后,我建议不止做import测试,而是直接跑一次简单测试,看CUDA是否可用:
python -c "import torch; print(torch.__version__, torch.cuda.is_available())" python -c "import mmcv; print(mmcv.__version__)" python -c "import mmdet; print(mmdet.__version__)"同时在Python里跑一个小算子:
import torch from mmcv.ops import nms boxes = torch.tensor([[0, 0, 10, 10, 0.9]], dtype=torch.float32).cuda() keep = nms(boxes, torch.tensor([0.5]).cuda(), 0.4) print(keep)如果这段输出正常,说明整个CUDA编译链路都是通的,可以进入数据准备阶段了。很多人在这一步直接卡住,有的报torch.cuda.is_available() False,有的报nms算子加载失败。前者多半是PyTorch版本和CUDA不匹配,后者多半是mmcv未安装带ops的版本。
4. 数据集准备与配置文件改动:训练前最后的硬骨头
4.1 COCO格式:目录结构就是规则
mmdetection默认推荐COCO格式数据集。如果你手上是VOC或自建数据集,推荐先转成COCO格式,因为后续一切可视化脚本、评测接口都基于这个格式,省得绕路。
目录组织如下:
data/ coco/ annotations/ instances_train2017.json instances_val2017.json train2017/ 000001.jpg 000002.jpg ... val2017/ 000001.jpg ...自建数据集时,你需要写一个脚本把标注转换为COCO json,最少包含categories、images、annotations三个节点。提醒一句:images里的id和annotations里的image_id必须一致,否则训练时数据加载会直接报找不到图片的index,这个初学最容易漏。
4.2 配置文件继承体系:从base到sub-config
mmdetection的配置成熟度很高,“拿来改”比“从零写”靠谱太多。它的逻辑是先有基础配置,再逐层覆盖字段。
实际训练时主要改三个文件:
configs/_base_/datasets/coco_detection.py:改数据路径、训练图片尺度、数据增强参数。configs/_base_/schedules/schedule_1x.py:改学习率、step、总epoch数。configs/_base_/default_runtime.py:改checkpoint保存间隔、日志配置、工作目录。
比如我们训练SSD或Faster R-CNN,通常会创建一个新配置文件:
_base_ = [ '../_base_/datasets/coco_detection.py', '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py' ] model = dict( type='FasterRCNN', backbone=dict(...), ... ) data = dict( samples_per_gpu=2, # 每个GPU的batch大小 workers_per_gpu=2, # 每个GPU的DataLoader线程数 ) optimizer = dict(lr=0.02/8) # 如果samples_per_gpu调整过,学习率也要等比调整我当时自作主张把samples_per_gpu调成4,却没动学习率,结果损失直接NAN,后来参照官方文档里“linear scaling rule”把学习率调小才恢复。
4.3 数据增强与类别匹配中的隐藏要求
模型头和数据集类别数必须匹配。COCO默认是80类,如果你用自建数据集只有3个类别,必须在配置里显式覆盖:
model = dict( roi_head=dict( bbox_head=dict( num_classes=3, )), ... )这个问题很隐蔽,因为不覆盖也不会报错,默认80类会和你的标注id对不上,导致所有框都往错误的类别上套。我在某次实验里完全没注意,训练了很久mAP一直很低,直到可视化检测结果才发现类别全乱,这属于白跑几个小时的经典失误。
数据增强方面,mmdet的默认pipeline已经包含随机翻转、随机缩放、多尺度resize。在16.04老机器上,如果显存不宽裕,建议把img_scale从(1333, 800)改成(1000, 600),训练速度会提升明显,mAP损失在接受范围内。
5. 训练流程实操:从命令行到恢复断点
5.1 单卡训练命令与常见参数
一切就绪后,进入训练:
conda activate mmdet cd /path/to/mmdetection python tools/train.py configs/faster_rcnn_r50_fpn_1x_coco.py --work-dir work_dirs/my_experiment参数说明:
--work-dir:保存日志和checkpoint的目录,不设置时会自动生成在work_dirs/下。--cfg-options:临时修改配置,比如--cfg-options optimizer.lr=0.01,适合做小实验时用。--resume-from:从指定checkpoint恢复。--auto-resume:自动从最近一次中断点恢复,这个参数在2.x版本里很实用。
启动后日志会输出每个epoch的loss、bbox_mAP等指标。这里有个小习惯:启动训练后别急着离开,先等两三个iteration确认loss在合理范围(比如0.5到2之间震荡),如果一开始loss就NAN,立刻Ctrl+C回去查配置,别心疼这几分钟。
5.2 多卡训练与GPU选择
如果你的老机器插了多张显卡,可以用官方脚本:
bash tools/dist_train.sh configs/faster_rcnn_r50_fpn_1x_coco.py 2 --work-dir work_dirs/my_experiment其中2是GPU卡数。但是16.04机器多为老平台,PCIe带宽和CPU都可能是瓶颈。实测下来双卡加速往往达不到2倍,甚至会因为通信开销拖慢。所以老机器上我反而推荐先单卡跑通,再考虑分布式的test-time augmentation或推理加速。
显存不够是另一个高频问题。如果训练时报CUDA out of memory,优先做三件事:
- 把
samples_per_gpu降到1。 - 把
img_scale进一步缩小。 - 开启梯度累积,在配置里使用
optimizer_config = dict(grad_clip=None, type='GradientCumulativeOptimizerHook', cumulative_iters=4)。
这个梯度累积技巧在显存受限时堪称救星,等效增大batch size,又不炸显存。
5.3 训练中途崩溃怎么续
训练跑着跑着,因为机房断电、SSH断开、显存溢出被杀进程,是常态。mmdetection的断点续训机制很完善,做法是:
python tools/train.py configs/faster_rcnn_r50_fpn_1x_coco.py --resume-from work_dirs/my_experiment/epoch_10.pth它会从epoch 10继续训练,优化器状态、学习率调度都会一并恢复。如果不想手动指定,--auto-resume会自动找最新的checkpoint。
迭代过程中还建议定期把配置文件复制到work_dirs里留档,比如cp configs/faster_rcnn_r50_fpn_1x_coco.py work_dirs/my_experiment/,否则后续想回溯当时实验用的哪套参数,会非常被动。
评测模型时用:
python tools/test.py configs/faster_rcnn_r50_fpn_1x_coco.py work_dirs/my_experiment/epoch_12.pth --eval bbox注意--eval bbox只评测检测框,如果要评测分割结果要改成segm,参数用错了不会报错,但输出metric缺失,容易误判。
6. 我在16.04上踩过的坑:编译、训练中的典型报错与排查链路
6.1 编译报错别只看最后一行,要看完整日志
源码编译mmcv时,最常见的报错是:
error: command 'gcc' failed with exit status 1这个错误信息太笼统,90%以上的人卡在这里。我当时的排查思路是往上翻日志,找到真正的根源。通常有几种情况:
- 缺少python头文件:需要先
conda install python-dev或conda install python3.7-dev。 - 缺少CUDA头文件:确认
/usr/local/cuda/include/cuda.h存在。 - gcc版本太老:如果CUDA 10.2自带驱动的约束,官方要求gcc 5.4没问题;但如果升级到CUDA 11,5.4就不够了,需要gcc 7.5。
说到gcc,16.04默认gcc是5.4。解决办法是apt安装gcc-7:
sudo add-apt-repository ppa:ubuntu-toolchain-r/test sudo apt update sudo apt install gcc-7 g++-7 sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-7 70 sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-7 70注意,如果换gcc版本,之前已经编译过的CUDA工具链不一定需要重装,但重新编译mmcv时一定要确认gcc --version是预期版本。
6.2 训练中出现NAN的排查链路
NAN是训练阶段最常见的脏数据类问题,在16.04老环境里还多了一个特殊性:硬件稳定性降低,某个PCIe插槽接触不良或电源供电不足,也可能随机产生NAN。
我的排查顺序是:
- 先排除数据:如果随机抽样几百张验证图片,loss都在合理范围,那数据本身没问题。
- 再看学习率:线性缩放规则,batch减半那么lr也要减半,否则NAN概率极高。
- 检查BN层的num_batches_tracked:低batch size下BN统计不稳定,可以在配置里
norm_cfg = dict(type='BN', requires_grad=True)改成SyncBN或减小lr进一步观察。 - 硬件层面:查
dmesg | grep -i error看有没有GPU相关报错;用nvidia-smi -pl 200给老卡降一点功耗墙,训练稳定性会好很多。
这里强调一下,环境越老,越不能把NAN直接归因到算法,要按层、按数据、按硬件一步步隔离,才能找到真凶。
6.3 GPU利用率上不去、CPU爆满的老机器优化
16.04老机器CPU核数普遍不多,训练时GPU利用率经常只在30%左右波动,这是因为DataLoader在疯狂做图像解码和resize,CPU成了瓶颈。
解法:
data = dict( samples_per_gpu=2, workers_per_gpu=4, # 适当调大worker数 )workers_per_gpu从2调到4或6,通常能让GPU利用率明显回升。但也不要盲目开太大,worker太多会造成内存占用飙升,老机器内存不够时反而触发swap,速度暴跌。我的一般经验是,内存16G配2~4个worker,32G以上配8个也没问题。
还有一个容易被忽略的点:可以用torch.backends.cudnn.benchmark = True,在输入尺寸固定时开启cudnn自动调优,对小数据集训练有明显加速效果。
老机器上跑训练,最忌讳看着GPU没吃满就着急换大batch,很多时候瓶颈不在GPU,而在数据管线和IO。先诊断再优化,才是正道。
最后再分享一个实际操作中的体会:Ubuntu 16.04 + mmdetection这套配置,如果你能接受“不追新、版本锁死、一切按部就班”的心态,其实是可以很稳定地跑起来的。我多次在重装环境后照着一份自制的版本清单装,基本半小时内能恢复到昨天的工作状态。建议你也把最终成功的版本组合和安装命令写成一份备忘,而不是依赖记忆。老系统的容错率低,但确定性高,一次配好,持续使用,体验反而比每次升级都要折腾的人舒畅得多。