GPU 利用率突然掉到 0%,磁盘却 100% 打满?一次深度学习训练卡死的真实排查

做深度学习训练时,很多人都见过一种很迷惑的现象:
GPU 利用率突然掉到 0%,训练像是“停住了”;与此同时,磁盘占用却冲到 100%,系统开始发卡,最后训练直接挂掉。

第一眼看上去,这很像是“磁盘太慢”或者“数据读取太重”。
但这次排查下来,我发现真正的根因并不是磁盘本身,而是更隐蔽的一类问题:

系统内存被 DataLoader 顶爆,进入疯狂换页,最后被内核 OOM Killer 直接杀进程。

这篇文章就把这次排查过程和结论完整整理一下。

一、现象:GPU 不干活了,磁盘却忙到飞起

训练过程中出现了这些症状:

  • GPU 利用率突然降到 0%
  • 磁盘 I/O 占满,系统明显变卡
  • 训练日志不再正常推进
  • 最后分布式训练报错,进程退出

如果只看监控面板,很容易得出一个结论:

“是不是数据集在慢盘上,或者日志/权重写太多,把磁盘打满了?”

但这次并不是。

二、最开始的误判:像磁盘问题,其实不是

这类现象之所以容易误判,是因为系统在内存不足时会触发大量交换和回收。
一旦进入这个阶段,外在表现就会非常像“磁盘瓶颈”:

  • 磁盘占用 100%
  • GPU 空转
  • 训练不推进
  • 系统整体卡顿

但磁盘只是“表象”。
真正发生的是:

  1. DataLoader 供数变慢
  2. GPU 等不到下一批数据
  3. 系统内存被撑爆,开始频繁换页
  4. 磁盘因此被打满
  5. 最终内核触发 OOM,直接杀掉 Python 进程

三、关键证据:这次是 OOM,不是单纯 I/O

排查过程中,最关键的证据有四个。

1. data_time 异常升高

日志里在出事前出现了明显异常:

  • 正常时 data_time 大约在 0.06 ~ 0.08
  • 出问题前突然变成了 0.7384

这说明训练瓶颈已经不在模型计算,而是在“等数据”。

换句话说,GPU 没活干了。

2. 分布式训练开始报同步超时

后续日志里又出现了:

  • RendezvousTimeoutError
  • DataLoader worker ... is killed by signal: Killed

这表明不是正常收敛中断,而是某些 worker 或 rank 已经出问题,导致整个分布式同步被拖死。

3. 内核日志明确出现 oom-kill

真正的定锤来自内核日志:

  • oom-kill
  • Out of memory: Killed process ... (python)
  • anon-rss: 39634916kB

也就是说,被杀掉的是一个 Python 进程,匿名内存峰值接近 39GB

4. 匿名页占用极高

同一段日志里还有:

  • inactive_anon: 122085532kB

这说明大量匿名内存页已经堆积,系统正在拼命回收和交换。
到了这个阶段,磁盘 100% 忙碌其实只是 OOM 前夜的常见症状。

四、根因链路:为什么 GPU 归零,磁盘却爆满

这次问题的链路,可以概括成下面这五步:

  1. DataLoader 配置过激,CPU 侧缓存和预取占用大量内存
  2. 数据读取越来越慢,data_time 飙升
  3. GPU 等不到 batch,利用率掉到 0%
  4. 系统开始 swap / reclaim,磁盘被打满
  5. 最后内核 OOM Killer 出手,直接 SIGKILL 某个 Python 进程

所以这类问题的本质其实是:

内存爆了,磁盘只是被连带打爆。

五、这次把内存顶爆的配置长什么样

问题配置大致是这样的:

train_dataloader.batch_size = 40
train_dataloader.num_workers = 8
persistent_workers = True

如果你的数据又是这种类型,风险会更高:

  • 双时相遥感图像
  • TIFF 文件
  • rasterio / GDAL 读取
  • 多 worker 并发
  • 持久 worker 不释放
  • DataLoader 预取较多 batch

这几项叠加起来,很容易出现下面这种情况:

  • batch_size 很大
  • num_workers 很多
  • 每个 worker 都在提前准备数据
  • persistent_workers=True 导致 worker 常驻
  • TIFF 解码、缓存、中间数组都占内存
  • 最后不是显存先炸,而是 系统内存先炸

很多人会盯着显存看,其实这类问题里,先死的往往不是 GPU 显存,而是 CPU 内存

六、为什么大显存机器也会踩这个坑

一个常见误区是:

“我有两张 96G 卡,显存很大,batch 开大点应该没问题吧?”

不一定。

因为:

  • batch_size 撑的是训练吞吐和部分内存
  • num_workers 撑的是 CPU 侧的数据准备压力
  • TIFF、遥感、多通道、双时相读取,CPU 内存和 I/O 压力本来就重
  • 多 worker 预取数据时,吃的是 系统 RAM,不是显存

所以显卡再大,也挡不住 DataLoader 把主机内存先顶爆。

七、这次更合理的修正建议

如果目标是“稳定优先”,那这组参数更合理:

batch_size = 16 or 24
num_workers = 2
persistent_workers = False
prefetch_factor = 1

同时建议:

  • 数据放在本地 SSD / NVMe
  • work_dir 也尽量放本地高速盘
  • 不要一开始就把 batch 顶到很大
  • 遥感 TIFF 场景下,优先控制 DataLoader 压力

如果一定要给一个保守起步值,我会推荐:

batch_size = 24
num_workers = 2
persistent_workers = False
prefetch_factor = 1

这组参数通常比“batch_size=40, num_workers=8”稳定得多。

八、以后遇到类似问题,怎么快速判断是不是 OOM

如果你也遇到“GPU 0%,磁盘 100%”这种情况,可以按这个顺序排查:

  1. 看训练日志里的 data_time
  2. 看是否出现 DataLoader worker killed
  3. 看是否有 RendezvousTimeoutError
  4. dmesg 或系统日志里有没有 oom-kill
  5. 看系统内存和 swap 是否被打满

如果同时满足下面几条,基本就可以锁定 OOM 链路:

  • data_time 明显变大
  • GPU 利用率掉到 0
  • 磁盘突然忙碌
  • worker 被 Killed
  • 内核日志出现 oom-kill

九、结论

这次问题的结论很明确:

GPU 利用率掉到 0、磁盘占满,并不一定是磁盘问题。
在很多训练场景里,这其实是 DataLoader 把系统内存打爆后的连锁反应

尤其是下面这种组合,风险非常高:

  • 大 batch
  • 多 workers
  • persistent_workers=True
  • TIFF / 遥感 / 大图 / 重增强
  • 分布式训练

所以真正该盯的,不只是显存,还包括:

  • 系统 RAM
  • swap
  • DataLoader 行为
  • data_time
  • worker 生命周期

一句话总结:

看到 GPU 归零 + 磁盘打满,不要先怪磁盘,先查 OOM。

更多推荐