yolov5 DDP
目录
- 0. 一些概念:
- 1. local_rank参数
- 2. init_process_group, torch.distributed.barrier需要先初始化一下
- 3. 注意随机种子需要设置每个进程不一样
- 4. model 需要ddp包装一下
- 5. SyncBatchNorm
- 6. distributed.DistributedSampler
- 7. train_loader.sampler.set_epoch(epoch)
- 8. 只rank=0保存模型
- 9. batch_size
- 10. 链接:
跑yolov5的DDP记录
一开始是pytorch1.7,python3.7 跑ddp模式训练,各种问题报错。但是后来好像把一个参数不兼容的给注释就好了?但是从机没有打印以为是有问题的?
# EarlyStoppingif RANK != -1: # if DDP trainingbroadcast_list = [stop if RANK == 0 else None]dist.broadcast_object_list(broadcast_list, 0) ##pytorch1.7 接口这里不一样 然后就重新搭建高版本环境torch1.10.1_py38,环境搭好了跑yolov5果然很顺滑。
这里先给出运行指令
python -m torch.distributed.launch --nproc_per_node 1 --nnodes 2 --node_rank 0 --master_addr "10.188.192.11" --master_port 12379 train.py --batch 12 --data coco128.yaml --epochs 300 --weights yolov5s.ptpython -m torch.distributed.launch --nproc_per_node 2 --nnodes 2 --node_rank 1 --master_addr "10.188.192.11" --master_port 12379 train.py --batch 12 --data coco128.yaml --epochs 300 --weights yolov5s.pt # 假设我们只在一台机器上运行,可用卡数是8
python -m torch.distributed.launch --nproc_per_node 8 main.py 这里的--nproc_per_node 2可以根据当前机器指定,有一张卡就1,用2张卡就2
跑起来的时候需要两个机器都要运行才能训练,某一个机器运行起来就会等待另外一个机器跑起来来运行。
但是这个代码跑起来的时候只有主机才会有训练的log打印出来,从机没有打印。
一开始我还以为哪里有问题,但是观察到从机有显存占用,然后我在从机代码添加打印信息,发现其实从机是在跑的,只是打印都屏蔽了,代码里面默认只打印rank0的。
0. 一些概念:
在16张显卡,16的并行数下,DDP会同时启动16个进程。下面介绍一些分布式的概念。
group
即进程组。默认情况下,只有一个组。这个可以先不管,一直用默认的就行。
world size
表示全局的并行数,简单来讲,就是2x8=16。
# 获取world size,在不同进程里都是一样的,得到16
torch.distributed.get_world_size() rank
表现当前进程的序号,用于进程间通讯。对于16的world sizel来说,就是0,1,2,…,15。
注意:rank=0的进程就是master进程。
# 获取rank,每个进程都有自己的序号,各不相同
torch.distributed.get_rank() local_rank
又一个序号。这是每台机子上的进程的序号。机器一上有0,1,2,3,4,5,6,7,机器二上也有0,1,2,3,4,5,6,7
# 获取local_rank。一般情况下,你需要用这个local_rank来手动设置当前模型是跑在当前机器的哪块GPU上面的。
torch.distributed.local_rank() 这里给出DDP模式下需要修改的代码:
1. local_rank参数
需要添加 parser.add_argument('--local_rank', type=int, default=-1, help='Automatic DDP Multi-GPU argument, do not modify')
使用torch.distributed.launch启动DDP模式,
其会给main.py一个local_rank的参数。这就是之前需要"新增:从外面得到local_rank参数"的原因
python -m torch.distributed.launch --nproc_per_node 4 main.py
代码里面需要根据local_rank这个值来初始化具体哪个卡加载
if LOCAL_RANK != -1:msg = 'is not compatible with YOLOv5 Multi-GPU DDP training'assert not opt.image_weights, f'--image-weights {msg}'assert not opt.evolve, f'--evolve {msg}'assert opt.batch_size != -1, f'AutoBatch with --batch-size -1 {msg}, please pass a valid --batch-size'assert opt.batch_size % WORLD_SIZE == 0, f'--batch-size {opt.batch_size} must be multiple of WORLD_SIZE'assert torch.cuda.device_count() > LOCAL_RANK, 'insufficient CUDA devices for DDP command'torch.cuda.set_device(LOCAL_RANK)device = torch.device('cuda', LOCAL_RANK)dist.init_process_group(backend="nccl" if dist.is_nccl_available() else "gloo") # 构造模型
device = torch.device("cuda", local_rank)
model = nn.Linear(10, 10).to(device)
# 新增:构造DDP model
model = DDP(model, device_ids=[local_rank], output_device=local_rank) 2. init_process_group, torch.distributed.barrier需要先初始化一下
def init_distributed_mode(args):""" init for distribute mode """if 'RANK' in os.environ and 'WORLD_SIZE' in os.environ:args.rank = int(os.environ["RANK"])args.world_size = int(os.environ['WORLD_SIZE'])args.gpu = int(os.environ['LOCAL_RANK'])elif 'SLURM_PROCID' in os.environ:args.rank = int(os.environ['SLURM_PROCID'])args.gpu = args.rank % torch.cuda.device_count()else:print('Not using distributed mode')args.distributed = Falsereturnargs.distributed = Truetorch.cuda.set_device(args.gpu)args.dist_backend = 'nccl''''This is commented due to the stupid icoding pylint checking.print('distributed init rank {}: {}'.format(args.rank, args.dist_url), flush=True)'''torch.distributed.init_process_group(backend=args.dist_backend, init_method=args.dist_url,world_size=args.world_size, rank=args.rank)torch.distributed.barrier() # DDP modedevice = select_device(opt.device, batch_size=opt.batch_size)if LOCAL_RANK != -1:msg = 'is not compatible with YOLOv5 Multi-GPU DDP training'assert not opt.image_weights, f'--image-weights {msg}'assert not opt.evolve, f'--evolve {msg}'assert opt.batch_size != -1, f'AutoBatch with --batch-size -1 {msg}, please pass a valid --batch-size'assert opt.batch_size % WORLD_SIZE == 0, f'--batch-size {opt.batch_size} must be multiple of WORLD_SIZE'assert torch.cuda.device_count() > LOCAL_RANK, 'insufficient CUDA devices for DDP command'torch.cuda.set_device(LOCAL_RANK)device = torch.device('cuda', LOCAL_RANK)dist.init_process_group(backend="nccl" if dist.is_nccl_available() else "gloo") @contextmanager
def torch_distributed_zero_first(local_rank: int):# Decorator to make all processes in distributed training wait for each local_master to do somethingif local_rank not in [-1, 0]:dist.barrier(device_ids=[local_rank])yieldif local_rank == 0:dist.barrier(device_ids=[0]) 3. 注意随机种子需要设置每个进程不一样
在DDP训练中,如果还是像以前一样,使用0作为随机数种子,不做修改,就会造成以下后果:
DDP的N个进程都使用同一个随机数种子在生成数据时,如果我们使用了一些随机过程的数据扩充方法,那么,各个进程生成的数据会带有一定的同态性。
比如说,YOLOv5会使用mosaic数据增强(从数据集中随机采样3张图像与当前的拼在一起,组成一张里面有4张小图的大图)。这样,因为各卡使用了相同的随机数种子,你会发现,各卡生成的图像中,除了原本的那张小图,其他三张小图都是一模一样的!同态性的数据,降低了训练数据的质量,也就降低了训练效率!最终得到的模型性能,很有可能是比原来更低的。
所以,我们需要给不同的进程分配不同的、固定的随机数种子:
device = "cpu"if torch.cuda.is_available():device = "cuda"cudnn.benchmark = True# fix the seed for reproducibilityseed = config.seed + dist.get_rank()torch.manual_seed(seed)np.random.seed(seed) def init_seeds(seed=0, deterministic=False):# Initialize random number generator (RNG) seeds https://pytorch.org/docs/stable/notes/randomness.htmlrandom.seed(seed)np.random.seed(seed)torch.manual_seed(seed)torch.cuda.manual_seed(seed)torch.cuda.manual_seed_all(seed) # for Multi-GPU, exception safe# torch.backends.cudnn.benchmark = True # AutoBatch problem https://github.com/ultralytics/yolov5/issues/9287if deterministic and check_version(torch.__version__, '1.12.0'): # https://github.com/ultralytics/yolov5/pull/8213torch.use_deterministic_algorithms(True)torch.backends.cudnn.deterministic = Trueos.environ['CUBLAS_WORKSPACE_CONFIG'] = ':4096:8'os.environ['PYTHONHASHSEED'] = str(seed)init_seeds(opt.seed + 1 + RANK, deterministic=True) 4. model 需要ddp包装一下
但是需要在optimizer之后
def smart_DDP(model):# Model DDP creation with checksassert not check_version(torch.__version__, '1.12.0', pinned=True), \'torch==1.12.0 torchvision==0.13.0 DDP training is not supported due to a known issue. ' \'Please upgrade or downgrade torch to use DDP. See https://github.com/ultralytics/yolov5/issues/8395'if check_version(torch.__version__, '1.11.0'):return DDP(model, device_ids=[LOCAL_RANK], output_device=LOCAL_RANK, static_graph=True)else:return DDP(model, device_ids=[LOCAL_RANK], output_device=LOCAL_RANK) - 优化器optimizer应用gradient,更新参数(optimizer.step())。这一步,是和DDP没关系的。
虽然DDP的实现代码与optimizer没有关系,但是关于optimizer有个额外的东西需要说明。更新后的参数最终能在各进程间保持一致,是由以下因素保证的:
- 参数初始值相同
- 参数更新值相同,更新值相同由optimizer初始状态相同和每次opimizer.step()时的梯度相同保证的。
因为optimizer和DDP是没有关系的,所以optimizer初始状态的同一性是不被DDP保证的!
大多数官方optimizer,其实现能保证从同样状态的model初始化时,其初始状态是相同的。所以这边我们只要保证在DDP模型创建后才初始化optimizer,就不用做额外的操作。但是,如果自定义optimizer,则需要你自己来保证其统一性!
从代码中看出optimizer确实是在DDP之后定义的。这个时候的模式已经是被初始化为相同的参数,所以能够保证优化器的初始状态是相同的。
# 新增:构造DDP model
model = DDP(model, device_ids=[local_rank], output_device=local_rank)# 优化器:要在构造DDP model之后,才能初始化optimizer。
optimizer = optim.SGD(model.parameters(), lr=0.001, momentum=0.8)
5. SyncBatchNorm
# SyncBatchNormif opt.sync_bn and cuda and RANK != -1:model = torch.nn.SyncBatchNorm.convert_sync_batchnorm(model).to(device)LOGGER.info('Using SyncBatchNorm()') 6. distributed.DistributedSampler
sampler = distributed.DistributedSampler(dataset, shuffle=shuffle)generator = torch.Generator()generator.manual_seed(6148914691236517205 + seed + RANK)return loader(dataset,batch_size=batch_size,shuffle=shuffle and sampler is None,num_workers=nw,sampler=sampler,pin_memory=PIN_MEMORY,collate_fn=LoadImagesAndLabels.collate_fn4 if quad else LoadImagesAndLabels.collate_fn,worker_init_fn=seed_worker,generator=generator) # 假设我们的数据是这个
def get_dataset():transform = torchvision.transforms.Compose([torchvision.transforms.ToTensor(),torchvision.transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])my_trainset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)# DDP:使用DistributedSampler,DDP帮我们把细节都封装起来了。# 用,就完事儿!sampler的原理,第二篇中有介绍。train_sampler = torch.utils.data.distributed.DistributedSampler(my_trainset)# DDP:需要注意的是,这里的batch_size指的是每个进程下的batch_size。# 也就是说,总batch_size是这里的batch_size再乘以并行数(world_size)。trainloader = torch.utils.data.DataLoader(my_trainset, batch_size=16, num_workers=2, sampler=train_sampler)return trainloader 7. train_loader.sampler.set_epoch(epoch)
for epoch in iterator:# DDP:设置sampler的epoch,# DistributedSampler需要这个来指定shuffle方式,# 通过维持各个进程之间的相同随机数种子使不同进程能获得同样的shuffle效果。trainloader.sampler.set_epoch(epoch) for epoch in range(start_epoch, config.solver.epochs):if args.distributed:train_loader.sampler.set_epoch(epoch) train(model_full, train_loader, optimizer, criterion, scaler,epoch, device, lr_scheduler, config, classes_features, logger) for epoch in range(start_epoch, epochs): # epoch ------------------------------------------------------------------callbacks.run('on_train_epoch_start')model.train()print("epoch=", epoch)if RANK != -1:train_loader.sampler.set_epoch(epoch)for i, (imgs, targets, paths, _) in pbar:pass 8. 只rank=0保存模型
# DDP:# 1. save模型的时候,和DP模式一样,有一个需要注意的点:保存的是model.module而不是model。# 因为model其实是DDP model,参数是被`model=DDP(model)`包起来的。# 2. 只需要在进程0上保存一次就行了,避免多次保存重复的东西。if dist.get_rank() == 0:torch.save(model.module.state_dict(), "%d.ckpt" % epoch) 9. batch_size
# DDP:需要注意的是,这里的batch_size指的是每个进程下的batch_size。# 也就是说,总batch_size是这里的batch_size再乘以并行数(world_size)。trainloader = torch.utils.data.DataLoader(my_trainset, batch_size=16, num_workers=2, sampler=train_sampler) ```摘自网络 https://blog.csdn.net/weixin_44823313/article/details/124182370
注意,在DDP模式中Batchnorm会出现inplace操作导致梯度无法反传,可以用nn.SyncBatchNorm()替代nn.BatchNormxd()
如果必须要用到batchnorm,按照这个设置将DDP模型broadcast_buffers设置为False,https://github.com/pytorch/pytorch/issues/22095
model = torch.nn.parallel.DistributedDataParallel(model.cuda(args.local_rank),
device_ids=[args.local_rank],
output_device=args.local_rank,
broadcast_buffers=False)
```
10. 链接:
ddp的github工程GitHub - whwu95/Text4Vis: 【AAAI 2023】Revisiting Classifier: Transferring Vision-Language Models for Video Recognition
该作者知乎链接:Pytorch多机多卡分布式训练 - 知乎
GitHub - ultralytics/yolov5: YOLOv5 🚀 in PyTorch > ONNX > CoreML > TFLite
比较好的讲解的链接
[原创][深度][PyTorch] DDP系列第一篇:入门教程 [原创][深度][PyTorch] DDP系列第一篇:入门教程 - 知乎
二 [原创][深度][PyTorch] DDP系列第二篇:实现原理与源代码解析 - 知乎
三 [原创][深度][PyTorch] DDP系列第三篇:实战与技巧 - 知乎
PyTorch分布式训练基础--DDP使用 PyTorch分布式训练基础--DDP使用 - 知乎
Pytorch多机多卡分布式训练(有git工程demo) Pytorch多机多卡分布式训练 - 知乎
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
