DEBUG.WIKI
京东云;让 AI 进入真实业务;零售 · 物流 · 客服 · 硬件;查看方案 →
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
Evoxt;最高 6.0GHz CPU;高频云服务器 · 免费每周备份;查看配置 →

DataLoader 一开 worker 就报 Cannot re-initialize CUDA,检查 Dataset 是否在上 GPU

CUDA fork 错误与普通主入口递归不同。历史复现是在 Dataset 中创建 CUDA 张量;让 worker 返回 CPU 数据,再由主进程搬到 GPU,并分别验证多进程读取和真实计算。

num_workers=0 可以读取数据,开多个 worker 后报:

Cannot re-initialize CUDA in forked subprocess.

PyTorch issue #77159的复现使用 PyTorch 1.11.0、CUDA 10.2 与 Python 3.7.13,Dataset 在 __getitem__ 中直接创建 CUDA 张量。这个版本信息属于原报告;下面示例按 PyTorch 2.8 的数据加载形态说明修正。

它与 Windows 的 bootstrapping 主入口错误不同。CUDA 运行时不能按普通 fork 方式继承已初始化的状态;多进程文档说明,确需 CUDA 子进程时要采用合适的启动方式与资源生命周期。仅换启动方法并不会替你设计跨进程 GPU 张量的所有权。

普通数据加载,worker 留在 CPU

检查 Dataset、transform、collate 和 worker 初始化函数,查找 .cuda()、.to("cuda") 或带 CUDA device 的张量构造。不只检查训练循环那一行。

下面是一个小脚本。它要求测试环境有可用 CUDA,GPU 运算只有简单乘法:

import torch
from torch.utils.data import Dataset, DataLoader

class CpuRows(Dataset):
    def __len__(self):
        return 8

    def __getitem__(self, index):
        return torch.tensor([float(index)], dtype=torch.float32)

def main():
    loader = DataLoader(CpuRows(), batch_size=2, num_workers=2)
    for batch in loader:
        assert batch.device.type == "cpu"
        gpu_batch = batch.to("cuda")
        result = (gpu_batch * 2).cpu()
        assert result.shape == batch.shape

if __name__ == "__main__":
    main()

Dataset 类放在可导入的模块顶层,启动和消费循环留在主入口。原数据处理逻辑应保留;示例只是把 GPU 迁移放回拥有模型的主进程。

可先移除示例中的 GPU 两行,验证所有 CPU 批次能读完,再恢复实际 GPU 步骤。这样可以分开定位数据读取异常与 CUDA 计算异常。

num_workers=0 是诊断基线

单进程可以帮助读到原始数据异常,但它没有验证多进程修复。对真实数据集还要检查文件句柄、连接和局部函数的可序列化性;这些资源不能因 CUDA 错误消失就自动视为可共享。

确实需要让子进程执行 CUDA 时,应按 spawn 或 forkserver 的模型设计,分别创建相应资源,并处理发送者寿命、消费者退出与异常清理。框架、服务器或调度器可能自行管理进程,应用里的一次 set_start_method 不保证覆盖它们。

验收用多 worker 遍历代表性数据,确认批次数和样本数正确;在主进程完成小批计算,再恢复真实训练或推理。检查正常退出后没有残留 worker。不能只看到错误不再出现,就认定吞吐或显存使用已经改善。