最近在折腾一个图像分类项目,单卡跑没问题,但想试试多卡加速。参考官方文档写了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 条八成是rank没传对,DDP要显式传rank和world_size,光靠set_device不够。
大概率是init_process_group之前没设好MASTER_ADDR和MASTER_PORT,试试在代码里显式配一下。
我之前也踩过这个坑,大概率是local_rank和全局rank混用了。torchrun会自动设置好环境变量,你直接用dist.get_rank()拿当前进程号就行,别自己再set_device,否则子进程可能拿到主进程的卡。
另外检查下init_process_group是不是放在set_device之前了,顺序反了NCCL会找不到后端。我之前还把--master_port指定成固定端口才稳定,你可以试试加个--master_port=29500。
要是还不行,可以打印下dist.get_rank()和torch.cuda.current_device()对比看看,基本能定位到是环境变量没读对还是设备分配错位。
我之前也踩过这个坑,NCCL那个invalid usage十有八九就是设备ID和进程绑定的问题。你用torchrun启动的话,其实不需要手动调torch.cuda.set_device,torchrun会自动帮你把local_rank映射到正确的GPU上,你代码里只要用local_rank去拿当前进程对应的device就行。我之前是这么写的:在init_process_group之后,直接让当前进程的默认CUDA设备等于local_rank,然后所有模型和tensor都别显式指定cuda:0,全部靠.cuda()或to(device)来走,这样就不会乱了。另外你检查下环境变量里有没有设CUDA_VISIBLE_DEVICES,有时候这个和torchrun的分配会互相干扰,最好别手动设,让torchrun全权管理。我之前还遇到过一个问题,就是代码里在init_process_group之前就调用了任何CUDA操作,比如提前print了tensor.cuda(),这也会导致NCCL初始化时找不到设备。所以顺序必须是:先init_process_group,再set_device,再创建模型和搬数据。你可以试试在init之后加一行torch.cuda.set_device(local_rank),然后看看每个进程打印一下torch.cuda.current_device()是不是对应的rank,如果一致基本就能跑了。
大概率是init_process_group里漏了world_size和rank,torchrun会自动注入环境变量,显式传参反而会冲突。
我之前也踩过这个坑,多半是torch.cuda.set_device和torchrun自带的环境变量冲突了。torchrun其实已经帮你设好了LOCAL_RANK,你只要在init_process_group之后直接用torch.cuda.set_device(local_rank)就行,但顺序一定不能反,而且别手动改CUDA_VISIBLE_DEVICES。另外试试把init_method显式写成env://,有时候默认值在不同版本里会抽风。如果还报NCCL错误,可以先换成gloo跑通流程再排查通信问题。
碰到这个报错大概率就是设备分配那步写拧了。你用torchrun启动的话,其实不用自己调torch.cuda.set_device,torchrun会把local_rank自动放到环境变量里,但你得在init_process_group之后再用,而且代码里得明确读一下local_rank这个参数,不能想当然地直接用。我之前也踩过这个坑,后来改成先init再set_device,顺序反了就会导致主进程和子进程看到的设备不一致,NCCL自然就懵了。另外你检查下是不是忘了在训练循环外面包一层if dist.get_rank() == 0来做日志或者保存模型,有时候主进程没干活子进程却在等它同步,也会触发奇怪的NCCL错误。还有个细节,data loader里shuffle=True的时候记得设一下sampler,不然每个卡拿到的数据顺序一样,虽然不直接报你这个错,但会影响收敛。你可以先试试把所有显式set_device的代码删掉,完全靠torchrun分配,再把环境变量打印出来看看rank和local_rank对不对,基本能定位。
这问题我熟,大概率不是环境变量的事,而是set_device和local_rank的搭配在torchrun下容易踩坑。你试试把set_device去掉,直接让DDP自己按local_rank分配,或者改成torch.cuda.set_device(int(os.environ['LOCAL_RANK']))。另外确认下init_process_group里有没有传world_size和rank,torchrun其实会自动注入,不写也没关系,但写了别跟实际对不上。我之前就是漏了在子进程里先torch.cuda.set_device再构造模型,导致NCCL找不到卡。
试试把set_device删了,让torchrun自己管设备分配,经常就是这行搞乱的。
八成是local_rank和CUDA_VISIBLE_DEVICES没配对,试试在init前先打印下环境变量看看。
遇到过一模一样的情况,最后发现是torch.cuda.set_device和torchrun的默认行为冲突了。你用torchrun启动时,它其实会自动给每个进程设置好LOCAL_RANK对应的设备,你再手动set一遍反而会把主进程的设备号搞乱,尤其是当机器上有多张卡但你没设CUDA_VISIBLE_DEVICES的时候。建议你先去掉那行set_device,直接用torch.device(f'cuda:{local_rank}')来创建模型和tensor,然后确保init_process_group里的rank和world_size是从环境变量取的,比如int(os.environ['RANK'])和int(os.environ['WORLD_SIZE'])。另外检查一下你的数据加载器里有没有设置sampler,DDP必须配合DistributedSampler用,不然每个进程会重复读同一批数据,虽然不直接报NCCL错但可能导致奇怪的同步问题。还有个坑是NCCL版本和PyTorch不匹配,特别是用conda装的时候,可以试试pip install torch --upgrade强制更新一下。最后确认你的网络能走IB或TCP,有些集群上nccl默认用IB但没配置好就会报invalid usage,临时设NCCL_SOCKET_IFNAME=eth0或者NCCL_IB_DISABLE=1跑一下看看。如果还不行,把完整报错堆栈和torch.distributed.get_world_size()打印出来贴一下,大家能更快定位。
大概率是rank和device没绑对,试试在init_process_group前面加torch.cuda.set_device(local_rank)。
我之前也踩过这坑,把环境变量MASTER_ADDR和MASTER_PORT显式写上就稳了。
大概率是local_rank没从环境变量读,试试int(os.environ['LOCAL_RANK']),别自己设。
遇到过一模一样的问题,最后发现是torch.cuda.set_device和torchrun的环境变量冲突了。你用了torchrun,它会自动帮你设置LOCAL_RANK,但如果你在代码里手动调set_device(local_rank),得确认这个local_rank是从os.environ['LOCAL_RANK']拿的,而不是从args里传的,否则主进程和子进程的设备ID就会错位。我当时就是犯了这个低级错误,改成从环境变量读就正常了。
另外,NCCL报invalid usage有时候是因为init_process_group里没指定world_size和rank,虽然torchrun会注入这些环境变量,但如果你在代码里用rank参数硬编码了,或者没加local_rank到device_ids里,也会出问题。建议你在init_process_group后面打印一下torch.distributed.get_rank()和torch.cuda.current_device(),对比两个进程的输出,看看是不是真的对不上。
还有个小坑,torchrun默认会用MASTER_ADDR和MASTER_PORT,如果你本地多机测试没设这两个环境变量,NCCL也会报错。可以试试在启动命令前加上export MASTER_ADDR=127.0.0.1和export MASTER_PORT=29500,或者直接在init_process_group里显式传入这两个值。
最稳妥的做法其实是完全不用set_device,让torchrun自己管理设备分配,你只需要在模型创建后把模型.to(device),然后确保输入数据也放到同一个device上就行。我后来重写了一遍,去掉所有手动设备设置,代码反而更简洁,DDP跑起来贼稳。你试试把set_device删掉,只在init_process_group里用backend='nccl',然后数据加载时用local_rank索引,应该能解决。
这问题我遇到过,多半不是set_device的锅,而是torchrun自己会管环境变量,你再手动设local_rank反而容易跟NCCL的通信组对不上。试试把set_device那行去掉,直接靠torch.cuda.device(local_rank)取当前卡,或者干脆用device = f'cuda:{local_rank}'。另外检查下init_process_group是不是在set_device之前调的,顺序反了也会出这种幺蛾子。还有个小坑,NCCL的报错有时候是网络防火墙拦了,本地多卡倒是少见,你可以先加个ncclDebug=INFO看看具体卡在哪一步。
碰到这个报错太正常了,NCCL的invalid usage十有八九就是rank和设备绑定没对上。你用了torchrun,那环境变量其实都帮你设好了,关键是你代码里set_device那块和DDP的初始化顺序。我一般习惯在init_process_group之前就先把torch.cuda.set_device(local_rank)做了,而且local_rank一定要从环境变量里取,别自己硬编码,比如os.environ['LOCAL_RANK'],这样最稳。
另外你说主进程和子进程设备号对不上,我猜你是不是在main函数外面就调用了set_device?那种全局作用域下每个进程都会执行一次,但torchrun会先fork再执行,所以顺序容易乱。建议把所有的初始化逻辑全放进if name == 'main'里面,严格按“取rank→set_device→init_process_group→构造模型→包DDP”这个顺序走,基本不会出幺蛾子。
还有个小坑,如果你用torchrun,其实不需要自己设MASTER_ADDR和MASTER_PORT,它默认会给,但如果你在代码里手动覆盖了就得确保所有进程拿到一样的值。我之前有一次就是环境变量没清干净,旧端口被占用,导致NCCL握手失败报这个错误,清了环境重新跑就好了。
最后检查下显卡驱动和NCCL版本,有时候不是代码问题,是底层库版本不兼容。你可以先加一句torch.distributed.barrier()在初始化之后,看看是不是卡在同步上。如果还不行,把报错堆栈贴出来,大家帮你细看。
大概率是init_process_group里漏了rank和world_size,torchrun虽然会传但代码里没读全。试试打印下环境变量看看对不对。
八成是rank和device没绑对,试试在init前先设好local_rank再进group。我之前踩过这坑,换个顺序就好了。
我之前也踩过这个坑,多半是torch.cuda.set_device和local_rank用的时机不对。DDP要求每个进程在init_process_group之后立刻绑定对应的GPU,你试试把set_device放到init后面,别在模型初始化之前乱设。另外torchrun会自动设好LOCAL_RANK环境变量,你代码里得用int(os.environ['LOCAL_RANK'])去拿,别自己传参,不然主进程和子进程设备号肯定对不上。还有个细节,nccl后端对显卡可见性很敏感,确认一下CUDA_VISIBLE_DEVICES没被手动设置成固定值,不然多卡之间通信会找不到设备。
试试把set_device放init_process_group前面,local_rank用args传别用环境变量读。