最近在折腾一个图像分类项目,单卡跑没问题,但想试试多卡加速。参考官方文档写了DistributedDataParallel,结果一跑就报“RuntimeError: NCCL error: invalid usage”。排查了半天,发现好像是我设了torch.cuda.set_device(local_rank)后,主进程和子进程的设备号对不上?我用的启动命令是torchrun --nproc_per_node=2 train.py,代码里也加了init_process_group(backend='nccl')。是不是我多卡初始化顺序有问题,或者环境变量没设全?有没有大佬能帮忙看看常见坑在哪?先谢谢了!
用PyTorch做多卡训练,DDP一直报错找不到设备,求指点
全部回复
共 147 条这问题我上周刚踩过,八成是init_process_group里没传rank和world_size,torchrun虽然会设环境变量但代码得显式读一下。另外set_device要放在init_process_group后面,顺序反了NCCL就会拿错设备。你试试在init里加rank=int(os.environ['RANK']),应该能解决。
我之前也踩过这个坑,问题多半出在torch.cuda.set_device和init_process_group的顺序上。DDP要求先初始化进程组,再设置当前设备,你反过来写的话NCCL就会拿不到正确的rank对应的卡。另外torchrun其实会自动设置好LOCAL_RANK这些环境变量,你代码里直接用就行,不用自己手动传。还有个容易忽略的点:如果你用了DistributedSampler,记得在每个epoch开头调用set_epoch,不然数据shuffle也会乱。先试试把初始化顺序调换一下,大概率能解决。
八成是set_device和init_process_group的顺序反了,先初始化再设卡试试。
之前也踩过这个坑,torchrun其实会自动设置好LOCAL_RANK和WORLD_SIZE这些环境变量,你手动调set_device反而容易跟它内部的初始化打架。我后来是直接删掉那行,改成在init_process_group之后再根据local_rank设device,顺序对了就没再报错。另外可以检查下MASTER_ADDR和MASTER_PORT有没有配,缺了NCCL也会抽风,单机也建议显式写上。如果还不行,试试把nccl换成gloo跑一次,能通就是网卡或驱动的问题了。
这问题我上周刚踩过一模一样的坑,多半不是环境变量的事,是torch.cuda.set_device和init_process_group的顺序反了。官方demo里是先init_process_group再set_device,你试试把顺序换过来,或者干脆别手动set,让DDP自己根据local_rank去分配。另外确认下你的world_size是不是从环境变量里读的,我上次就是硬编码成2,结果单机双卡跑没事,换到4卡就崩了。
这问题我上周刚踩过一模一样的坑,NCCL报invalid usage八成不是环境变量没设全,而是device和进程绑定的时序错了。torchrun其实会自动帮你设好LOCAL_RANK,你代码里再手动set_device反而容易和torch.cuda.device_count()的默认行为打架。我当时的解法是直接把set_device删了,改成在模型初始化前加一句torch.cuda.set_device(int(os.environ['LOCAL_RANK'])),但关键是这行必须放在init_process_group之后,否则NCCL拿到的上下文还是错的。另外你可以检查下是不是用了DataLoader时没给sampler传rank,多卡下每个进程得用DistributedSampler,不然数据会重复导致某些卡空闲,间接引发NCCL超时。还有个隐蔽点,如果你代码里任何地方用了torch.load且没指定map_location,主进程加载的权重会在cpu上,子进程直接拷到gpu也会触发类似的报错。建议你把启动命令换成torchrun --nproc_per_node=2 --master_port=29500 train.py,然后打印一下每个rank的device和cuda_visible_devices,先确认环境再谈别的。最后如果还不行,试试把nccl换成gloo跑一次,虽然慢但能帮你判断是不是驱动或网络栈的问题。
我之前也踩过这个坑,多半是环境变量MASTER_ADDR和MASTER_PORT没设对,torchrun虽然会带默认值,但有时候和NCCL的通信初始化会冲突。你可以试试在init_process_group之前手动打印一下rank和world_size,确认子进程拿到的local_rank是不是0和1,如果都是0那肯定是环境变量没传透。另外,set_device最好放在init_process_group之后,我之前顺序反了也报过类似错,你可以先调整顺序跑个最简单的all_reduce测试看看。
torchrun其实会自动帮你设好RANK、LOCAL_RANK这些环境变量,你不需要手动调torch.cuda.set_device(local_rank),这个动作反而容易和torchrun内部的设备分配逻辑打架。我之前也踩过类似的坑,后来直接删掉那行,改成在init_process_group之后用torch.cuda.set_device(local_rank)放在模型初始化之前,但关键是local_rank要从os.environ['LOCAL_RANK']读,不能自己传参,不然主进程和子进程拿到的值会错位。另外NCCL报invalid usage很多时候是通信组没建好,你检查下init_method是不是默认的env://,如果是的话确认下MASTER_ADDR和MASTER_PORT有没有被torchrun正确注入。还有个小细节,数据加载器里的sampler一定要用DistributedSampler并在每个epoch调用set_epoch,否则每个进程看到的数据顺序一样,梯度同步会出诡异问题。最后建议把nccl换成gloo试一下,虽然慢但能帮你确认是不是NCCL本身和网卡驱动的兼容性问题。
八成是init_process_group里漏了world_size和rank,torchrun会自动传环境变量,你手动设local_rank反而容易打架。
我之前也踩过这个坑,大概率是环境变量MASTER_ADDR和MASTER_PORT没显式设置,torchrun虽然会默认配,但有时候和NCCL的通信初始化对不上,尤其是多机或容器环境下。你可以试试在代码最前面手动加上os.environ['MASTER_ADDR']='localhost',端口设个不冲突的,比如29500。另外init_process_group里最好显式传world_size和rank,别只靠环境变量,这样设备对不上的问题会好排查很多。如果还不行,把set_device那行挪到init_process_group后面再试试。
我当初也踩过这个坑,大概率是环境变量没配对。torchrun会自动设好RANK和LOCAL_RANK,但你手动set_device之后,得确保和init_process_group里的rank用的是同一个值,不然NCCL就懵了。你可以先打印一下每个进程的LOCAL_RANK和device,看看是不是都对应上了。还有个小细节,nccl后端对CUDA_VISIBLE_DEVICES很敏感,试试不设这个变量或者让每个进程只看到自己的卡。检查完这些应该就能解决,我之前就是这么搞定的。
我之前也踩过这个坑,多半是torch.cuda.set_device和init_process_group的顺序搞反了。你得先初始化进程组,再set_device,不然子进程拿到的是默认卡。另外torchrun其实会自动设置LOCAL_RANK环境变量,你直接用它就行,不用自己传,试试看把set_device那句换成torch.cuda.set_device(int(os.environ['LOCAL_RANK']))。还有个小细节,NCCL报invalid usage有时候是网卡问题,可以加export NCCL_IB_DISABLE=1强制走TCP试试。
我之前也踩过这个坑,其实就是你猜的那样,设备号搞串了。torchrun会自动帮你设置LOCAL_RANK环境变量,但如果你在代码里又手动调了set_device,而且没跟local_rank对齐,主进程可能拿到0号卡,子进程也拿到0号卡,NCCL当然就懵了。建议直接把set_device那行去掉,让torchrun自己管设备,或者确保用的是torch.cuda.set_device(int(os.environ['LOCAL_RANK'])),别再用自己传的rank。另外init_process_group里最好显式传一下world_size和rank,虽然torchrun会注入,但有时候你本地调试或者IDE里跑,环境变量不全就会漏。还有个小细节,NCCL报invalid usage有时是网络栈的问题,比如多机没设MASTER_ADDR和MASTER_PORT,单机的话默认应该没问题。你可以先试试只设CUDA_VISIBLE_DEVICES,然后代码里完全不管设备号,让DDP自己分配,我这么改完就稳了。如果还不行,把报错堆栈里关于nccl版本和卡数的信息发出来,大家再一起看看。
这问题我踩过类似的坑,多半不是环境变量的事,而是torch.cuda.set_device和torchrun的默认行为重复了。你试试把set_device去掉,直接在init_process_group后面用torch.cuda.set_device(local_rank)其实没问题,但关键是dist.init_process_group里要传rank=local_rank,而且world_size别写死。另外确认下你是不是在main函数里调用了spawn,如果用了torchrun就别再spawn了,双重启动会导致设备错乱。可以先打印一下local_rank和cuda.current_device()看看是不是一致。
八成是环境变量MASTER_ADDR没设,torchrun虽然会带,但有些版本得自己补上。
八成是local_rank和CUDA_VISIBLE_DEVICES没对上,试试在init_process_group前先打印下环境变量。
我猜你八成是踩了local_rank和torch.cuda.set_device混用的坑,torchrun其实会自动帮你把环境变量和当前进程的GPU绑定好,你再多写一步set_device反而容易把主进程的设备搞乱。我之前也遇到过类似的NCCL报错,后来发现是代码里在init_process_group之前就碰了cuda,比如提前print了tensor.device或者调用了torch.cuda.current_device(),这会让子进程在初始化前就锁定错误的卡。你试试把init_process_group放到所有cuda操作之前,并且直接删掉set_device,让torchrun自己管设备映射,通常这样就能解决。另外确认一下你的数据集加载部分有没有用DistributedSampler,并且每个epoch要set_epoch,不然多卡数据会重复或者漏样本,虽然那个不直接报NCCL错,但训练起来loss会很诡异。如果还报错,把完整的traceback贴出来,重点看是rank几挂的,不同rank的报错原因可能完全不一样。还有个土办法,先用gloo后端跑通逻辑,再换nccl,能快速排除是通信库的问题还是代码顺序的问题。
八成是init_process_group里漏了rank和world_size参数,torchrun不传的话默认对不上设备号。
大概率是环境变量MASTER_ADDR和RANK没传对,torchrun其实会自己设,你代码里重复set_device反而容易乱。
检查下local_rank是不是从args里取的,用os.environ['LOCAL_RANK']更稳。
这问题我踩过,多半不是set_device的锅,而是torchrun本身会帮你设好LOCAL_RANK环境变量,你再手动set_device反而容易和NCCL的默认通信上下文冲突。试试把torch.cuda.set_device(local_rank)删掉,直接用torch.device(f'cuda:{local_rank}'),然后在init_process_group之后再加一句torch.cuda.set_device(local_rank)看看。另外确认下你的train.py里有没有在init_process_group之前就调用了任何CUDA操作,比如打印cuda.is_available()之类的,这也会导致NCCL初始化时找不到正确的设备。如果还不行,把NCCL_DEBUG=INFO加上跑一遍,日志里会直接告诉你哪个rank在哪个设备上挂的。