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

Windows 的 DataLoader 一加 num_workers 就报 bootstrapping phase

num_workers=0 正常、开启多进程后失败时,检查 spawn 导入入口、main 保护及顶层 Dataset 定义;不要只压掉 worker 异常。

Windows 上的 PyTorch 脚本用 num_workers=0 能读取数据,改为 1 后,第一轮迭代就出现:

An attempt has been made to start a new process before the
current process has finished its bootstrapping phase.

后面还可能跟着 DataLoader worker ... exited unexpectedly。后者是工作进程退出的结果;真正需要处理的是前面那条进程启动异常。

本文针对普通 .py 脚本,在 PyTorch 2.8 的 DataLoader 行为下说明。Notebook、冻结为可执行文件的程序和其他启动方式,需要各自确认子进程能否导入入口。

把启动与迭代留在主入口

Windows 的多进程数据读取使用 spawn:启动新的解释器,再导入主脚本。若脚本顶层就建立 DataLoader 并开始迭代,子进程导入时会再次执行这段逻辑,可能继续启动工作进程。

下面是完整的结构示例,使用 CPU 小数据说明入口位置。示例不是性能配置建议:

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


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

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


def main():
    dataset = SmallDataset()
    loader = DataLoader(dataset, batch_size=2, num_workers=1)
    for values, labels in loader:
        print(values.shape, labels.shape)


if __name__ == "__main__":
    main()

数据集实例创建、DataLoader 创建和消费循环都在 main() 中,调用由入口保护控制。实际项目的训练启动、参数解析及会触发外部写入的初始化,也应检查是否需要留在这个入口。

只把最后一条 print() 放进 if __name__ == "__main__": 不够。工作进程通常在开始迭代时创建,因此顶层的 next(iter(loader)) 或 for ... in loader 同样需要处理。

Dataset 与回调定义仍放在模块顶层

入口保护不意味着把所有代码都缩进里面。Dataset 类、自定义 collate_fn、worker_init_fn 应位于可导入模块的顶层,让工作进程能通过名字取得定义。不要把它们变成 main() 中的局部类、嵌套函数或 lambda。

若结构改完后,错误变成 Can't pickle local object,已经进入另一条序列化分支。需要检查实际 Dataset 和回调对象,而不是继续添加入口保护。标准进程池中同类限制可参考 局部函数不能被 pickle 的说明。

Notebook 单元格中的定义未必具有子进程可导入的模块身份。先把数据集与回调移到正常模块,并用受支持的脚本入口验证;num_workers=0 可以作为单进程基线,但不能证明多进程结构已修复。

普通 Python 脚本通常不需要 freeze_support()。它主要用于支持冻结程序的多进程启动;报错示例里出现这行,不代表加上它就能代替 main 保护。也不要在 Windows 强行套用 fork。

入口修好后,保留最早的 worker 错误

DataLoader worker exited unexpectedly 还有数据文件不存在、解码失败等其他原因。确认 bootstrapping 异常消失后,继续读取第一条实际异常;必要时临时用 num_workers=0 观察同一数据处理逻辑的堆栈。

多进程加载通常应返回 CPU Tensor,让主进程按训练流程搬到 GPU。不要为了这个入口故障,把 CUDA 模型初始化或 GPU 数据预先放进每个 worker。数据集如果包含文件句柄、数据库连接等对象,还需按资源所属进程组织创建与释放。

先用小数据确认每个批次只被消费一次、没有递归启动、样本数量与单进程一致,再恢复实际数据量和所需的 worker 数。改大 num_workers 之前观察主内存、预取和资源占用;更大的进程数不保证更快,也不会消除 Dataset 自身的异常。