前言
这是我炼丹日记的第一篇,这个专栏主要是分享我在浮现模型的时候遇到的问题和解决经验。过程中会穿插一点训练技巧和GPU训练基本原理的讲解,希望能帮助大家更好的理解训练过程,练出更SOTA的丹。
在这篇文章的最后是我总结的GPU训练速度调优的最佳实践,这里我直接先写出来:
后台运行防断线,
散热莫教温度蹿。
炼中关注炉火候,
功耗利用两相看。
工人数量取一半,(逻辑核心的一半)
核心线程莫占满。
批次大小非越满,
带宽瓶颈是大患。
吞吐高低见分晓,
万勿拼写错一环。
谨慎细查免心烦,
功成丹熟笑开颜。
这是我在实践中得到的血与泪的教训,如果你对我是怎么总结出来的感兴趣,可以继续往下读下去。
任务背景
之前我用docker配好了MMPose的基本环境,并且跑通了基本的demo用例,接下来我的工作就到了模型训练复现部分了,我要复现的模型是的是ViTPose-S,我给自己定的目标很明确,也很简单:让ViTPose-S模型,在COCO数据集上,以256x192的分辨率,跑210个epoch。
这听起来工作量很小,但是实际上有非常多的坑,下面我就一一列出来,以便大家快速定位和debug。
坑1:环境不全!
依葫芦画瓢,跟着MMPose现有的配置文件把自己的配置文件写出来之后,按下回车训练命令后出现这样的报错:
ValueError: No module named 'mmpretrain'
这个坑的解决方法很简单,把这个库装上就行。
mim install "mmpretrain>=1.0.0rc8"
坑2:ssh连接中断会把训练进程也中断!
我这里连接的是我内网穿透之后的windows主机,显卡是4070。在服务器上训练的同学要注意这一点,如果你是ssh连到你的服务器,用python train.py --config
这种格式启动进程,那么一旦你的ssh 会话断开,你的训练进程就死翘翘了。
解决方案
解决这个问题的方法如下:
nohup python tools/train.py --configs
这样你的进程就会一直在后台运行,并且把输出重定向到nohup.out
,就算ssh断开这个也会接着跑了。
tips:watch -n 3 tail nohup.out 可以在终端输出训练进度信息的最后三行,并且实时刷新
到这我以为坑已经差不多了,马上就可以开始愉快的训练了。然而,真正的噩梦才刚刚开始…
坑3:GPU干烧了!
训练愉快的跑了几十分钟,突然我nohup.out
一动不动了。我赶紧跑回家看我的主机咋回事——居然自动关机了!
什么玩意?我好歹也是ROG枪神7Plus啊!跑个模型还能整崩溃?不信邪的我去看了日志,ohho, 原来是GPU过热,触发电脑防反,自动关机了!
当时我的电脑没有做任何散热措施,甚至连瓶盖大法都没有用,所以在训练的时候GPU温度会长期在85-90度,甚至以上。时间一久电脑就感觉有危险自动关机了。
解决方案
我个人的解决方案是直接斥巨资买了个风压式散热器(广告位招租),不得不说效果非常好,打开之后直接把我GPU从训练时候的90度干到60度了,散热问题完美解决,接下来就是要考虑我的显卡会不会感冒了。
tip:
nvidia-smi -l 1
这个命令可以每秒给你的GPU把一次脉,看看温度,功耗,利用率什么之类的,可以让你实时掌握GPU的状态如何。
坑3:训练的太慢!
解决散热问题之后,我以为就完事大吉,坐等训练结束就OK了——直到我看到如下画面:
注意这里面的eta
信息,其代表预计训练结束还需要的时间,这个信息一出来我立刻两眼一黑:尼玛,怎么要训练15天?吞吐量只有20个样本每秒!这进度谁都接受不了啊!这个龟速铁定是哪里出了什么问题,于是我首先去刚才打开的nvidia-smi -l 1
窗口监控GPU的性能,然后,我发现了几个有意思的点:
tips: 吞吐量计算公式:batch / mean(time), time代表一个batch跑完需要的时间。在上面的图片里面可以找到是5.32s左右,这时候我的batch是128,所以算出来的吞吐量就是20个样本每秒左右。
- GPU功耗低的吓人,满载是140W,而训练的时候只有30W左右
- GPU利用率(GPU-Util)像心电图一样跳,一会只有百分之几,一会又能给你蹦到100%
这个时候,结合我之前的cuda编程经验,我大概可以猜到可能是RTX 4070的PCIe带宽被巨大的数据量给喂撑了,也就是CPU-GPU之间的I/O瓶颈问题,我而且我知道这和batch_size
和num_workers
的配置有很大关系,于是我开始了如下的实验
tips:
batch_size
: 批处理大小。一次丢给GPU多少个样本进行计算。它直接影响显存占用和训练收敛效果。理论上,在显存允许范围内,batch_size越大,GPU并行处理的效率越高。
num_workers
: 数据加载器(Dataloader)使用的子进程数。它负责在CPU上预处理数据(比如读取图片、数据增强等)。如果num_workers太小,CPU备菜的速度跟不上GPU吃饭的速度,GPU就会饿肚子(摸鱼),导致利用率低下。
解决方案
Round 1: 矛头指向num_workers
我一看我原本的num_workers
: 我日,这怎么才2个?只有两个工人在把数据从CPU搬到GPU,这GPU能一直满载运行才怪!给GPU饿坏了要。
于是我决定:保持batch_size=128,我有16个物理核心,32个逻辑核心,所以先把num_workers设为16看看会咋样。
tips:
查看逻辑核心数命令:nproc
结果: 速度没啥变化。看来问题不在num_workers。
Round 2: 降低批次,解放带宽
既然问题目前还不在workers这里,我猜测是大batch_size和数据预处理共同阻塞了数据通路。
操作: 干脆把batch_size降到32,同时把num_workers也设为32,让CPU火力全开去备菜。
结果: 奇迹发生! 吞吐量飙升到 133.33 样本/秒!GPU功耗终于能上到100W以上,利用率在75%-100%之间稳定浮动。这说明小批量、高并发的数据准备策略,更适合我的硬件配置。
Round3: 开挂——混合精度训练
虽然这133样本/秒的吞吐量已经很快了,但是我还想更快一点,我琢磨来琢磨去,最终在MMPose里面看到了混合精度训练的介绍和手法:
tips: 混合精度训练:辨别需要使用全精度的步骤并在其中使用32位浮点(例如权重),而在其他地方使用16位浮点(梯度,中间结果)。
不过最重要的是它开启之后的效果:更少的CUDA MEMORY, 更快的训练速度, 而且GPU只要有tensor core 就能用!
我靠!这不就是免费的午餐吗?直接用上!
操作:训练命令里加上–amp参数。
结果: 再次见证奇迹! GPU功耗和利用率反而降低了,但训练速度不降反升!
继续操作:在amp继续开启的情况下,我斗胆把batch_size提高到64。
结果: 吞吐量达到 193.94 样本/秒!
tips: 自动混合精度训练(AMP)一种在训练时同时使用32位浮点(FP32)和16位浮点(FP16)的技术。FP16占用的显存更少,计算速度更快(尤其是在有Tensor Core的NVIDIA显卡上)。通过–amp,框架会自动将一部分计算切换到FP16,从而在不显著影响精度的情况下,实现显存减半,速度翻倍(理想情况)的神奇效果。这就是为什么功耗降低,速度反而提升的原因。
坑4:最终的考验——val阶段过不了!
正当我以为一切都将一帆风顺直到210个epoch结束时,一个幽灵般的问题出现了:训练(train)过程很顺利,但每当一个epoch结束,进入验证(validation)阶段时,我的服务器内存(是RAM,不是VRAM)就直接爆掉,然后所有进程都被系统无情地杀死。
我猜测是因为train阶段的32个worker进程没有被立即释放,而val阶段又创建了自己的一套worker进程(我在dataloader配置里面写的都是32)。两拨worker加起来,直接把系统内存撑爆了。
解决方案
为了解决这个问题,我将train_dataloader
和val_dataloader
的num_workers
都统一设置为16。这样即使两者同时存在,总worker数也只是32,等于我的逻辑核心数,内存压力会小很多。batch_size
则继续保持为64。
结果: 吞吐量稳定在 204.70 样本/秒,验证阶段顺利通过!
最后的坑5:拼写错误
为了追求极致,我又试着把batch_size降回32(num_workers保持16), 我这么做的理由是考虑到我的GPU带宽不是很足,下批量的数据更容易让GPU等待时间更少一些,让GPU满载时间更多。
结果: 吞吐量达到了 216.79 样本/秒!
最快的一集,功耗稳定在85W左右,利用率周期性波动的现象也大大缓解。这很可能就是我的硬件(RTX 4070 + CPU)、架构和任务所能达到的“甜点”了。(实际上我也懒得再优化了)
在我以为终于可以迎来世界大和平的时候,最后一个KeyError跳了出来:KeyError: ‘cocosubset/AP’。原来是我在配置文件里写保存最佳模型的评价指标时,写错了名字…应该是’coco/AP’。
改完这个,训练终于、终于、终于在轨道上稳定地跑了起来。
炼丹经验总结(榨干GPU最佳实践)
回顾这段充满挣扎的经历,从最初简单的目标,到最后复杂的性能调优,我几乎把一个新手能犯的错误都犯了一遍。最终,我总结出如下的GPU训练时注意事项的打油诗:
后台运行防断线,
散热莫教温度蹿。
炼中关注炉火候,
功耗利用两相看。
工人数量取一半,(逻辑核心的一半)
核心线程莫占满。
批次大小非越满,
带宽瓶颈是大患。
吞吐高低见分晓,
万勿拼写错一环。
谨慎细查免心烦,
功成丹熟笑开颜。
这很明显不是我写的,是谁写的我就不多说了。希望这个文章能够帮到大家,让大家早日更快练出更加SOTA的模型。