Pytorch dataloader 错误 “DataLoader worker (pid xxx) is killed by signal” 解决方法

本文最后更新于:2021年11月1日 中午

在使用 pytorch dataloader 时,出现了当把num_workers 设置不为0即报错的问题,本文记录两种此类错误的解决方案。

Dataloader - num_workers

  • Pytorch 中加载数据的模块Dataloader有个参数num_workers,该参数表示使用dataloader时加载数据的进程数量,可以理解为为网络搬运数据的工人数量;

  • 所以如果dataloader比较复杂,工人多的时候自然可以节省大量数据加载时间,他们可以在网络训练时同时进行数据加载工作,等网络训练结束直接从内存中取走加载完成的数据,因此当num_worker大于1时可以对数据加载进行加速,当数量多到网络不需要加载数据的时间时就是工人们为加速训练做工作的极限收益了;

  • 使用大于1的工人会占用更多的内存和cpu,同时也会占用更多的共享内存(share memory);

  • 使用大于1的工人会调用多线程。

问题说明

根据num_worker的工作思路,可能会在工作中出现两种错误(我遇到的两种):

  • 共享内存不足:
1
RuntimeError: DataLoader worker (pid XXX) is killed by signal: Bus error
  • 多线程出现段错误导致死锁,进而导致程序卡住,线程阻塞:
1
ERROR: Unexpected segmentation fault encountered in worker.

1
RuntimeError: DataLoader worker (pid 4499) is killed by signal: Segmentation fault. 

1
RuntimeError: DataLoader worker (pid(s) ****) exited unexpectedly

下面给出两种问题的解决方案。

问题1 RuntimeError: DataLoader worker (pid XXX) is killed by signal: Bus error

问题原因

  • 一般这种问题发生在docker中,由于docker默认的共享内存为64M,导致工人数量多时空间不够用,发生错误。

解决方案

1 自废武功
  • num_workers设置为0
2 解决问题
  • 创建docker时配置较大的共享内存,加入参数--shm-size="15g",设置15g(根据实际情况酌量设置)的共享内存:
1
nvidia-docker run -it --name [container_name] --shm-size="15g" ...
  • 通过 df -h查看
1
2
3
4
5
6
7
8
9
10
11
12
13
# df -h
Filesystem Size Used Avail Use% Mounted on
overlay 3.6T 3.1T 317G 91% /
tmpfs 64M 0 64M 0% /dev
tmpfs 63G 0 63G 0% /sys/fs/cgroup
/dev/sdb1 3.6T 3.1T 317G 91% /workspace/tmp
shm 15G 8.1G 7.0G 54% /dev/shm
tmpfs 63G 12K 63G 1% /proc/driver/nvidia
/dev/sda1 219G 170G 39G 82% /usr/bin/nvidia-smi
udev 63G 0 63G 0% /dev/nvidia3
tmpfs 63G 0 63G 0% /proc/acpi
tmpfs 63G 0 63G 0% /proc/scsi
tmpfs 63G 0 63G 0% /sys/firmware
  • 其中shm即为共享内存空间

问题2 RuntimeError: DataLoader worker (pid(s) ****) exited unexpectedly

问题原因

  • 由于dataloader使用了多线程操作,如果程序中存在其他有些问题的多线程操作时就有可能导致线程套线程,容易出现死锁的情况
  • 具体的情况可能会根据具体环境不同,我的是由于opencv中的多线程与dataloader的杂糅出现了问题;
  • 此时cv版本 3.4.2,相同的代码在 4.2.0.34 的cv中没有出现问题。

解决方案

1 自废武功
  • num_workers设置为0
2 解决问题
  • 在dataloader 的 __getitem__ 方法中禁用opencv的多线程:
1
2
3
4
def __getitem__(self, idx):
import cv2
cv2.setNumThreads(0)
...

参考资料