pytorch dataloader 默认支持异步预加载,关键在于num_workers>0且pin_memory=true;需注意跨平台spawn设置、dataset可序列化、prefetch_factor与内存平衡及worker异常排查。

PyTorch 的 DataLoader 默认就是异步的,但容易被误关
很多人以为要手动写多线程/协程才能“异步预加载”,其实 DataLoader 从设计上就支持后台数据准备——关键在 num_workers > 0 且 pin_memory=True。但常见错误是:训练脚本用了 if __name__ == '__main__': 却没加 torch.multiprocessing.set_start_method('spawn')(尤其 Windows/macOS),导致子进程卡死或报 RuntimeError: DataLoader worker (pid XXX) is killed by signal: Bus error。
实操建议:
-
num_workers不宜盲目设高:设为 CPU 核心数的 1–2 倍通常最优;超过 8 可能因进程调度开销反而变慢 - 必须配合
pin_memory=True(仅对 GPU 训练有效),否则数据从 pinned memory 拷贝到 GPU 的速度会打折扣 - 若用 Jupyter 或某些 IDE(如 PyCharm 的 debugger 模式),
num_workers > 0可能触发 fork 问题,临时改用num_workers=0排查,确认不是数据集__getitem__里的全局状态导致的崩溃
自定义 Dataset 里不能有不可序列化对象
当 num_workers > 0 时,每个 worker 进程会通过 pickle 复制 Dataset 实例。如果 __init__ 中存了文件句柄、数据库连接、torch.nn.Module、Lambda 函数等,就会报 AttributeError: Can't pickle local object 或直接静默失败。
实操建议:
- 所有耗资源的对象(如 OpenCV
cv2.VideoCapture、h5py 文件句柄)必须延迟到__getitem__内创建,并在函数退出前显式释放(cap.release()) - 避免在 Dataset 初始化时加载整个大数组到内存;改用索引式读取(例如用
np.memmap或 HDF5 的dataset[i]) - 若需共享只读状态(如归一化参数),用普通 Python 基础类型(
dict、tuple、numpy.ndarray)传递,别传torch.Tensor(它带 device 信息,pickle 行为不稳定)
prefetch_factor 控制预取缓冲区大小,不是越大越好
从 PyTorch 1.7 开始,DataLoader 加入 prefetch_factor 参数(默认 2),表示每个 worker 预取多少个 batch 到队列中。设太高会导致内存占用陡增,尤其当 batch size 大或图像分辨率高时,可能 OOM;设太低则 GPU 等待时间变长,利用率下降。
实操建议:
- 先用默认值(
prefetch_factor=2),再根据nvidia-smi观察 GPU 利用率波动:若常低于 60%,可尝试调到 3 或 4 - 注意该参数和
num_workers耦合:总预取 batch 数 =num_workers × prefetch_factor;比如 4 workers + 3 prefetch → 最多缓存 12 个 batch,务必确保主机内存 ≥ 12 × 单 batch 字节数 - 在内存受限环境(如 Colab 免费版),宁可降低
prefetch_factor甚至设为 1,也不要让系统开始 swap
验证是否真在异步预加载:看 GPU 利用率曲线和 worker 进程状态
光看训练速度提升不保险——可能只是 batch size 变大了。真正异步生效的表现是:GPU 利用率平稳(>85%),且 CPU 上有稳定数量的 python 子进程持续运行(ps aux | grep "python.*data")。
实操建议:
- 用
torch.utils.data.get_worker_info()在__getitem__中打印 worker id,确认不同样本确实由不同进程处理(避免所有请求都落到同一个 worker) - 在训练循环中加
torch.cuda.synchronize()后测单 step 时间,排除 GPU 计算部分干扰,聚焦数据加载耗时 - 如果发现 worker 进程频繁重启,检查
__getitem__是否抛异常(如路径错、解码失败)——默认行为是 worker crash 后重建,但会拖慢整体节奏
真正卡点往往不在配置参数,而在 Dataset 的实现细节:一个没关闭的文件句柄、一次意外的 tensor.device 不一致、或者 numpy 随机种子没在每个 worker 里重置,都足以让异步变成假异步。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











