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 自身的异常。


