☰
YOLOv5训练加速指南:从数据加载到混合精度,全面排查训练慢的根因
2026/10/20 4:16:46 网站建设 项目流程

刚开始接触YOLOv5那阵子,我手里的项目数据量并不夸张,两万多张标注图,GPU也是当时主流的24G显存卡,结果打开了训练脚本,进度条一个epoch要跑40多分钟,看着loss慢慢往下掉,心里别提多急了。后来我才意识到,训练速度慢这件事,绝大多数情况下都不是显卡不够好,而是数据没喂进来、增强管线太重、日志写盘太频繁、参数没调对,每一层都在“拖后腿”。这篇文章就是冲着“训练速度慢,加速训练”来的,适合正在用YOLOv5复现模型、跑业务数据、但又觉得每轮训练时间长得离谱的人。我会从排查思路讲起,把数据加载、训练参数、模型选型、多卡并行这些环节逐层说透,最后给一个完整的提速复盘案例。

先说一个结论:提速之前,先搞清楚瓶颈到底卡在哪一环。不测量就直接套网上的参数,常常治标不治本。

1. 训练慢是玄学?先从性能剖析看起

很多人的第一反应是“换更好的显卡”。但换卡的成本很高,而且很多时候瓶颈根本不在GPU算力上。有个朋友和我吐槽,说项目组买了双卡,结果训练速度几乎没变化。我一听就知道,问题大概率卡在数据读取或者预处理上。

1.1 先确认GPU有没有真的跑起来

提速的第一步,不是改代码,而是观察。训练过程中打开另一个终端,跑一条命令:

nvidia-smi -l 1

这个命令每秒刷新一次显存和利用率。重点看utilization.gpu这一行:如果它长时间在90%以上,说明GPU确实在满负荷工作,那么想提速只能靠模型轻量化、混合精度、多卡这些真刀真枪的手段;如果它只有三四十,或者像波浪一样50%-20%-50%地抖动,那问题就不在算力上,而在上游的“投喂”环节——CPU来不及处理图片,GPU在等数据。

还可以用更精细的查询命令:

nvidia-smi --query-gpu=utilization.gpu,utilization.memory,power.draw,temperature.gpu --format=csv -l 1

这里power.draw(功耗)其实很能说明问题。GPU利用率低,功耗也低,说明它在“摸鱼”;如果利用率高但功耗被卡在墙上了,可能是功耗限制或者散热导致降频。很多旧型号的卡跑大模型时,温度一高就自动降频,速度反而比温度低的时候慢一截,这也是容易被忽略的点。

1.2 判断瓶颈在数据侧还是在计算侧

光看GPU利用率还不够,得区分是“CPU供不上货”还是“磁盘读得太慢”。我常用的方法是,观察训练日志里每个batch的平均耗时。

YOLOv5训练过程中,进度条会显示每张图片的处理时间,比如0.23s/it表示每迭代一次要0.23秒。如果这个数字忽大忽小极不稳定,刚才还是0.1秒,突然跳到0.8秒,多半是数据加载阶段卡了一下。这时候打开系统监控看一眼CPU占用率:

  • CPU占用率很高、GPU利用率不高:说明预处理和增强操作把CPU吃满了,图片加工不过来。
  • CPU占用率也不高、GPU也不高:大概率是磁盘I/O在拖后腿,也就是“读图太慢”。
  • 内存和交换分区使用异常:如果系统内存被占满开始用swap,每一轮迭代都伴随磁盘读写,训练速度会断崖式下跌。

如果想更精确地定位,可以在datasets.py的__getitem__方法里插几个时间戳,分别统计“读取图片耗时”和“做增强耗时”。先跑100个batch看看时间都花在哪。这一步看似原始,但比拍脑袋靠谱得多。

1.3 不要一上来就怀疑算法

还有一个挺常见的问题:训练一慢,就有人开始怀疑是不是模型结构不对、学习率不对、损失函数有问题,然后一通乱改。等你把代码改得面目全非,才发现瓶颈只是数据没喂上来。

我的建议是,任何提速操作之前,先记录一组“初始基线”:一个epoch的耗时、平均batch耗时、GPU利用率、CPU占用率、内存占用。之后每改一项,对照基线看收益。没有基线,你根本没法判断哪一步真正有效,这也是下面几节里我一直强调的理念。

2. 数据加载才是最容易拖垮速度的隐形瓶颈

如果你已经确认GPU利用率上不去,那么十有八九问题出在数据加载这条流水线上。YOLOv5的默认配置其实不差,但不同机器环境、不同数据集规模下,默认值远不是最优值。

2.1 Dataloader的四个关键参数

PyTorch的DataLoader是对齐的关键。YOLOv5的train.py里,主要通过下面几个参数控制:

  • num_workers:加载数据的子进程数量。默认是8,但8并不是万能值。一般来说,把它设为CPU物理核心数或逻辑线程数的一半左右比较稳妥。比如一台16线程的CPU,设为8或者12,效果都不错。设太多反而会因为进程切换、内存带宽争抢而变慢,这不是玄学,是实测能复现的现象。
  • pin_memory:如果用的是GPU训练,建议打开。它申请的是锁页内存,省去了一次内存拷贝。小数据集上感觉不明显,但图像数据集里每张图动辄几百KB到几MB,累积差距就大了。
  • prefetch_factor:控制每个worker提前预取多少个batch。默认值是2,显存充足的情况下可以调到4或8。预取越多,GPU等数据的概率越小,代价是内存占用上升。
  • persistent_workers:设为True可以让worker进程在多个epoch之间存活,不用每个epoch重新创建。对训练数据集大、epoch多的任务有提升,但如果你的自定义数据集里带了不可序列化的对象,开了反而报错。

另外提一句,在Windows平台上,DataLoader的多进程默认是spawn模式,经常出现卡死或者重复加载的怪问题。先用num_workers=0跑通一个epoch,再慢慢往上加,是最稳妥的调试路径。

2.2 在线增强管线是隐藏的速度杀手

YOLOv5训练时默认启用了一整套在线增强:mosaic拼图、随机仿射变换、HSV色彩抖动、随机翻转、多尺度缩放。这些增强可以明显提升模型泛化能力,但代价是每一张图在喂给网络之前,都要做一串很重的图像变换操作,而且这些操作是在CPU上执行的。

处理一下模型:假设一次mosaic要拼接4张原图,再做缩放、旋转、剪切、色彩调整。单张图消耗的CPU时间可能是普通读取的5到10倍。如果你的数据集有几万张、每epoch要跑一遍,这些增强操作占用的总时间相当可观。

我常用的做法是分阶段调整:训练刚开始的几十个epoch,保持mosaic和色彩增强,让模型充分看到多样的样本;等loss下降趋缓,再逐步削减增强强度,或者直接关掉mosaic,让模型在“更真实”的分布上精调。对应的超参数在YOLOv5的hyp.yaml里都能找到,比如mosaic: 1.0改成mosaic: 0.5,或者训练后期通过代码把增强关闭。

如果只是粗训、先验证pipeline通不通,直接把增强全部关掉,速度能翻倍都不止。

2.3 cache images 的双刃剑

YOLOv5有一个“作弊”级参数:--cache-images,或者新版本里的--cache-ram。它的作用是把整个训练集的图片预加载到内存里,训练时直接从内存读,跳过磁盘I/O和解码。

我用过几次深有体会:当数据集是几千张的小规模集,内存足够时,这个参数几乎能一键让训练速度起飞,epoch时间直接腰斩。但也有两个坑要提醒:

一是冷启动时间。第一次缓存会把全部图片读一遍并转换成内存镜像,这个时间可能在几分钟甚至更长。有人在缓存过程中以为卡死了,其实只是在默默加载。

二是内存占用。如果数据量大到几十万张,再加上Dataloader的预取,内存会非常吃紧。系统开始用swap之后,速度反而比不缓存还慢。所以用之前先看看机器内存,free -g看一眼剩余量。还有一个选择是--cache-disk,把缓存写到本地SSD,虽然不是纯内存,但也比反复读原始图片快。

2.4 别把数据放在网络盘上跑

有种情况我碰到过不止一次:训练数据放在共享存储或网络盘上,每训练一个batch,worker进程就要通过网络读一次图片。训练日志里动不动就出现“waiting for data”,GPU利用率上不去,问题就这么简单。把数据复制到本地NVMe SSD之后,epoch时间直接降了一半。

另外建议训练前先写个脚本,把数据集里所有图片扫描一遍,确认有没有坏图、有没有格式异常的文件。个别损坏的图片可能会导致某个batch卡住很几秒,小问题累积起来就是大开销。数据很大的时候,还可以考虑打包成LMDB或者TFRecord格式,减少海量小文件的inode查询开销,尤其是在Windows上,同样数量的图片,打包前后读取速度差得很明显。

3. 训练参数调整:在保精度和提速度之间找到平衡点

数据和预处理层面的优化做完后,GPU利用率通常已经上来了。接下来把目光转向训练参数本身,这些参数直接影响每次迭代的计算量和迭代次数。

3.1 batch_size先给足,学习率跟着走

batch_size和训练速度的关系最直接。显存允许的情况下,尽量把batch开大。这里有个简单的理解:GPU在一个batch里处理32张图和处理16张图,耗时并不是两倍关系,因为矩阵运算在更大batch下更容易把算力喂饱,而且kernel launch之类的固定开销被摊薄了。batch翻倍,每轮迭代次数减半,单次耗时可能只增加20%-30%,整体吞吐收益往往能到20%-30%。

注意,batch变大之后学习率也要跟着调。YOLOv5实际是自带学习率缩放逻辑的,它会根据当前batch自动计算初始学习率;但如果你在自定义训练脚本里复现一个模型,记得按比例调整:batch翻倍,学习率通常也线性或平方根地放大,否则收敛会变得很慢。

如果显存不够,不要急着把batch降到4、8这种极低的数。更优雅的解法是梯度累积,也就是前向传播和反向传播正常跑,但每累积若干个batch才更新一次权重。这能模拟出一个更大的batch,又不需要一次性占满显存。

3.2 混合精度训练为什么快,为什么偶尔翻车

混合精度训练是提升训练速度最甜的一个开关。YOLOv5默认开启AMP,少数老版本叫--fp16。它的原理简单说:用FP16(半精度)做前向计算和梯度计算,同时保留一份FP32(单精度)的权重副本用于参数更新。FP16的矩阵运算在支持TensorCore的NVIDIA GPU上吞吐量几乎是FP32的两倍,所以收益明显。

实测下来,在V100、T4、A100这些带TensorCore的卡上,打开混合精度通常能带来20%到50%的加速。但在一些老卡上(比如没有TensorCore的P100、Titan X),开了AMP反而会因为精度来回转换而付出额外开销,这时候实测优先,别盲目相信参数名。

还有人说“开了AMP之后loss变成nan了”。我遇到的大多数情况其实不是AMP的问题,而是学习率太大、batch太小导致的梯度爆炸,或者数据里混入了异常标签。正确的排查顺序是:先把学习率降到原来的十分之一,看nan还出不出现;再关掉AMP对比。直接把帽子扣在AMP头上,往往会掩盖真正的问题。

3.3 自动化辅助项要适可而止

YOLOv5默认每个epoch结束时都会做一次validation。如果你的验证集很大,每轮验证耗掉的时间不可忽视。对于训练过程而言,你可以让验证每5个epoch跑一次,甚至只在最后一个epoch跑;指标记得看就行,没必要每轮都盯得那么紧。YOLOv5里可以用验证间隔参数来控制,不同版本写法略有差异,具体看当前版本文档。

日志和模型保存也有开销。训练过程中默认会画一些曲线图、保存checkpoint。如果这些操作太频繁,磁盘写入会成为隐形瓶颈。我习惯把plots关掉,模型保存间隔调大,只保留最后一个epoch的权重和最佳的权重就够了。等训练全部结束,再统一可视化分析曲线。

还有一点容易被忽略:如果开启了多尺度训练(multi-scale),每轮迭代输入图片的尺寸都会随机变化,预处理端的工作量会成倍增加。多尺度确实能提升精度,但它和速度天生是冲突的。项目调试阶段建议先关掉,最后精调阶段再打开。

另一个思路是早停。YOLOv5训练中可以设置patience,如果验证集指标连续几十个epoch不涨,就提前结束。从“总训练时长”角度看,早停往往比任何参数优化都管用。我见过不少项目,模型其实在60个epoch就已经到顶了,却硬生生跑了300个epoch。

4. 模型结构与硬件层面的提速:从轻量化到多卡并行

如果数据加载和训练参数都调到位了,GPU利用率也打上去了,还想再快,那就得从模型本身和硬件使用方式上做文章了。

4.1 先看清楚你的精度余量,再选模型大小

YOLOv5提供了s、m、l、x几种规模的模型结构。这几种模型的速度差异很大,YOLOv5m的参数量大约是YOLOv5s的三倍,计算量差距更明显。很多人项目一上来就习惯用最大的模型,其实很多任务用s甚至n就够了。

我建议的做法是:先想清楚自己任务的精度需求。如果你只是要跑通pipeline、验证数据标注质量、测试训练流程,直接用s模型和低分辨率跑,速度快几十倍。等所有流程都稳定了,再换大模型做最终精调。这个“先小后大”的过程,往往能省下大半天时间。

输入分辨率也是提速的强力杠杆。YOLOv5默认训练分辨率是640,如果改成416,计算量大约变成原来的(416/640)^2 ≈ 42%,训练速度几乎翻倍。代价是精度会有一定下降,小目标检测尤其明显。初期探索阶段用416粗训,最后要出结论时回到640精调,这是我个人很常用的策略。

4.2 稀疏化训练与剪枝:长线投资

如果项目需要在同一类数据上反复训练模型,比如每隔一段时间增量更新一次,那么对模型做一次通道剪枝是值得的。YOLOv5自带稀疏化训练参数,核心思路是在训练时对BN层的缩放因子施加L1正则,让一批通道的缩放因子趋近于零。训练完成后,把这些“不重要”的通道剪掉,模型体积和计算量都会显著下降。

流程大概是:先正常训练一个base模型,再植入稀疏化正则继续训练几十个epoch,然后执行剪枝脚本,最后对剪完的模型做fine-tune。剪枝的比例需要谨慎把握,剪得太狠掉点明显,一般从30%开始试。

不过说实话,剪枝是个需要耐心的手工活。如果只是跑一两次训练就完事,不如直接换小模型来得痛快;如果这个项目要跑一年,剪枝省下来的每一轮训练时间都是复利。

4.3 多卡训练的正确打开方式

多卡并行(数据并行)是终极提速手段,但这里也有讲究。很多人以为多卡就是把batch直接除以卡数这么简单,实际恰恰相反。数据并行下,每张卡各自处理一部分数据,所以全局batch应当是“单卡batch × 卡数”。相应地,学习率也需要放大,否则模型的收敛行为会和单卡训练不一致。

YOLOv5的分布式训练自带DDP封装。old版本一般用torch.distributed.launch启动,新版本更推荐torchrun,具体命令在仓库README里都有。第一次跑多卡时,我建议先用小数据集、小模型跑通,确认loss正常下降、各卡利用率都不错,再上全量数据。避免一张卡利用率99%、另一张卡利用率30%的尴尬局面。

多卡训练有几个容易踩的坑。一是数据存储:所有节点都去同一个共享存储读数据,如果存储性能不够,多卡带来的吞吐提升会被读数据阻塞抵消。二是通信:NCCL库依赖网络和防火墙,多机训练时尤其容易出问题。三是sync_bn:同步批归一化在卡数少、单卡batch小的时候很有价值,但它也有额外通信开销,卡多了以后反而是负担,按需开启,不要无脑开。

4.4 环境层面别忽视的那点事

硬件环境也有不少“隐形扣分项”。有个同事的GPU利用率怎么都上不去,后来发现驱动版本太旧,AMP和DDP都跑得磕磕绊绊,升级驱动和CUDA后正常了。如果你的机器要多卡训练,检查NCCL版本是否和CUDA匹配,这个很重要。

再一个容易被忽略的是“别人也在用显卡”。如果一台机器上同时跑着好几个训练任务,显存和算力都被分走了,你再怎么调代码也没用。用nvidia-smi看看有没有其他进程占着GPU,有的话商量错峰,或者明确指定只用某一张卡。

CPU核数也不是越多越好。数据加载进程开得过多后,CPU在进程调度和内存带宽上会产生浪费,反而拖慢整个训练。合理值是先看机器逻辑核数,再根据GPU空闲率逐渐往上调,找到那个“再加大也不提速”的临界点,就是最合适的值。

5. 一次完整复盘:从56分钟到8分钟的训练过程

前面讲的都是零散的方法论,这里我把一次实际项目中的提速过程完整复盘一下。给抽象出数值参考,不同机器上绝对数字会变,但调优的顺序和思路是可以照搬的。

5.1 原始基线和纸面分析

项目背景:某个模拟场景的工业缺陷检测任务,训练集2.1万张已标注图片。硬件是单张24G显存的某型号GPU,CPU是16线程,系统内存64GB。初始用YOLOv5m、输入分辨率640、batch_size=16、默认训练参数。

第一次跑,一个epoch耗时约56分钟。打开nvidia-smi一看,GPU利用率在35%-55%之间剧烈波动,CPU总体负载却很高。结论很清楚:GPU没被喂饱,瓶颈在数据侧,而且CPU上的预处理太吃力。

5.2 逐项改动与实测收益

改动顺序很重要,因为每一步的效果都可以单独评估。

第一步,数据迁移。把数据集从共享存储切到本地NVMe SSD,同时开启--cache-ram把图片预载到内存。系统内存还有富余,这一步之后epoch时间降到32分钟。收益来源:磁盘I/O和图像解码开销大幅下降。

第二步,调Dataloader。num_workers从8调到12,打开pin_memory,预取因子调到4。epoch时间降到24分钟。CPU利用率和GPU利用率同步抬升,数据投喂变得顺畅了。

第三步,开混合精度,调大batch。打开AMP,把batch_size从16提到32,同时按比例微调学习率。这一步GPU利用率稳定到了80%以上,epoch时间降到12分钟左右。

第四步,换小模型、降分辨率粗训。因为当前阶段目的是验证方案而不是刷最高精度,直接把模型换成YOLOv5s,输入分辨率降到416训练,同时关闭plots,验证改为每5个epoch一次。epoch时间降到7到8分钟。粗训的mAP比原方案低约1-2个点,但pipeline验证完全够用;后面需要汇报结果时,再切回640和m模型精调一轮。

5.3 最终配置参考

整个提速过程对应的命令形态大概是这样的(新版本YOLOv5):

python train.py \ --data custom.yaml \ --weights yolov5s.pt \ --batch 32 \ --device 0 \ --amp \ --cache-ram \ --workers 12 \ --pin-memory

不同版本参数名可能有差异,老版本里--amp可能叫--fp16,--cache-ram可能叫--cache-images。以你手上的版本文档为准。

5.4 实话实说的边界

写到这里需要泼一点冷水。上面这些提速手段不是没有代价的:AMP可能在极端情况下让精度轻微波动;cache images要占内存;关闭验证意味着训练过程中少看指标;换小模型和降分辨率要牺牲精度上限。所以我的建议是:精度敏感项目,激进操作收敛着用;速度优先、探索阶段,大胆上全套。

我的切身感受是:训练慢绝大多数不是某一个环节差了好几倍,而是每个环节都差一点,叠在一起就成了十几倍的差距。数据加载慢一点、增强重一点、日志写盘频繁一点、batch没打满、精度没用上,五个“一点”叠起来,56分钟变8分钟一点都不夸张。

如果你手头的项目也卡在训练慢这个坎上,先别急着换卡或者改网络结构。回到第1节,把基线测出来,然后从Dataloader参数开始一项一项试。等你亲眼看到GPU利用率从40%爬到90%,那种感觉比任何参数模板都让人觉得踏实。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询