使用大型数据集训练大型深度神经网络(DNN)的问题是深度学习领域的一大挑战。随着DNN和数据集规模的增加,训练这些模型的计算和内存需求也会增加。这使得在计算资源有限的单机上训练这些模型变得困难甚至不可能。使用大型数据集训练大型DNN的一些主要挑战包括:训练时间长:训练过程可能需要数周甚至数月才能完成,具体取决于模型的复杂性和数据集的大小。内存限制:大型DNN可能需要大量内存来存储训练期间的所有模型参数、梯度和中间激活。这可能会导致内存不足错误并限制可在单台机器上训练的模型大小。为了应对这些挑战,已经开发了各种技术来扩大具有大型数据集的大型DNN的训练,包括模型并行性、数据并行性和混合并行性,以及硬件、软件和算法的优化。在本文中,我们将使用PyTorch演示数据并行性和模型并行性。我们所说的并行性一般是指在多个GPU或多台机器上训练深度神经网络(DNN),以实现更少的训练时间。数据并行背后的基本思想是将训练数据分成更小的块,让每个GPU或机器处理一个单独的数据块。然后将来自每个节点的结果组合起来并用于更新模型参数。在数据并行中,模型架构在每个节点上都是相同的,但模型参数是跨节点分区的。每个节点使用分配的数据块训练自己的本地模型,并且在每次训练迭代结束时,模型参数在所有节点之间同步。重复此过程,直到模型收敛到令人满意的结果。下面我们使用ResNet50和CIFAR10数据集的完整代码示例:在DataParallel中,模型架构在每个节点上保持相同,但模型参数在节点之间进行分区,每个节点使用分配的数据块训练自己的本地模型。PyTorch的DistributedDataParallel库可以进行跨节点梯度和模型参数的高效通信和同步,从而实现分布式训练。本文提供了一个示例,说明如何使用ResNet50和CIFAR10数据集通过PyTorch实现数据并行性,其中代码在多个GPU或机器上运行,每个GPU或机器处理训练数据的一个子集。训练过程使用PyTorch的DistributedDataParallel库并行化。importrequiredlibrariesimportosfromdatetimeimportdatetimefromtimeimporttimeimportargparseimporttorchvisionimporttorchvision.transformsastransformsimporttorchimporttorch.nnasnnimporttorch.distributedasdistfromtorch.nn.parallelimportDistributedDataParallel接下来,我们将检查GPUimportsubprocessresult=subprocess.run(['nvidia-smi'],stdout=subprocess.PIPE)print(result.stdout.decode())因为我们需要在多台服务器上运行,所以手动一一执行不现实,所以需要有一个调度器。这里我们使用SLURM文件来运行代码(slurm是一个用于Linux和类Unix内核的免费开源作业调度程序),defmain():#从Slurm环境获取分布式配置parser=argparse.ArgumentParser()parser.add_argument('-b','--batch-size',default=128,type=int,help='batchsize.它将为每个worker分成mini-batch')parser.add_argument('-e','--epochs',default=2,type=int,metavar='N',help='要运行的总epochs')parser.add_argument('-c','--checkpoint',default=None,type=str,help='要加载的检查点路径')args=parser.parse_args()rank=int(os.environ['SLURM_PROCID'])local_rank=int(os.environ['SLURM_LOCALID'])size=int(os.environ['SLURM_NTASKS'])master_addr=os.environ["SLURM_SRUN_COMM_HOST"]port="29500"node_id=os.environ['SLURM_NODEID']ddp_arg=[rank,local_rank,size,master_addr,port,node_id]train(args,ddp_arg)然后我们使用DistributedDataParallel库进行分布式训练deftrain(args,ddp_arg):rank,local_rank,size,MASTER_ADDR,port,NODE_ID=ddp_arg#displayinfoifrank==0:#print(">>>Trainingon",len(hostnames),"节点和",size,"processes,masternodeis",MASTER_ADDR)print(">>>Trainingon",size,"GPUs,masternodeis",MASTER_ADDR)#print("-Process{}对应节点的GPU{}{}".format(rank,local_rank,NODE_ID))print("-进程{}对应节点{}的GPU{}".format(rank,local_rank,NODE_ID))#配置分发方式:定义地址和端口主节点和初始化通信后端(NCCL)'tcp://{}:{}'.format(MASTER_ADDR,port),world_size=size,rank=rank)#分发模式ltorch.cuda.set_device(local_rank)gpu=torch.device("cuda")#model=ResNet18(classes=10).to(gpu)模型=torchvision.models.resnet50(pretrained=False).to(gpu)ddp_model=DistributedDataParallel(model,device_ids=[local_rank])如果args.checkpoint不是None:map_location={'cuda:%d'%0:'cuda:%d'%local_rank}ddp_model.load_state_dict(torch.load(args.checkpoint,map_location=map_location))#分配批量大小(小批量)batch_size=args.batch_sizebatch_size_per_gpu=batch_size//size#定义损失函数(标准)和优化器criterion=nn.CrossEntropyLoss()optimizer=torch.optim.SGD(ddp_model.parameters(),1e-4)transform_train=transforms.Compose([transforms.RandomCrop(32,padding=4),transforms.RandomHorizo??ntalFlip(),transforms.ToTensor(),transforms.Normalize((0.4914,0.4822,0.4465),(0.2023,0.1994,0.2010)),])#使用分布式采样器加载数据分布式采样器train_dataset=torchvision.datasets.CIFAR10(root='./data',train=True,transform=transform_train,download=False)train_sampler=torch.utils.data.distributed.DistributedSampler(train_dataset,num_replicas=size,rank=等级)train_loader=torch.utils.data.DataLoader(数据集=train_dataset,batch_size=batch_size_per_gpu,shuffle=False,num_workers=0,pin_memory=True,sampler=train_sampler)#训练(定时器和显示由进程0处理)ifrank==0:start=datetime.now()total_step=len(train_loader)forepochinrange(args.epochs):ifrank==0:start_dataload=time()fori,(images,labels)inenumerate(train_loader):#向所有GPU分配图像和标签images=images.to(gpu,non_blocking=True)labels=labels.to(gpu,non_blocking=True)如果rank==0:stop_dataload=time()如果rank==0:start_training=time()#前向传递outputs=ddp_model(images)loss=criterion(outputs,labels)#向后优化optimizer.zero_grad()loss.backward()optimizer.step()ifrank==0:stop_training=time()if(i+1)%10==0andrank==0:print('Epoch[{}/{}],Step[{}/{}],Loss:{:.4f},Timedataload:{:.3f}ms,时间训练:{:.3f}ms'.format(epoch+1,args.epochs,i+1,total_step,loss.item(),(stop_dataload-start_dataload)*1000,(stop_training-start_training)*1000))ifrank==0:start_dataload=time()#Savecheckpointateveryendofepochifrank==0:torch.save(ddp_model.state_dict(),'./checkpoint/{}GPU_{}epoch.checkpoint'.format(size,epoch+1))ifrank==0:print(">>>Trainingcompletein:"+str(datetime.now()-start))if__name__=='__main__':main()代码将数据和模型拆分到多个gpu中,分布式更新模型。下面对代码做一些解释:train(args,ddp_arg)有两个参数,args和ddp_arg,其中args是传递给脚本的,ddp_arg的命令行参数包含分布式训练相关的参数。rank,local_rank,size,MASTER_ADDR,port,NODE_ID=ddp_arg:解压ddp_arg中分布式训练相关的参数。如果rank为0,则打印当前使用的GPU数量和master节点的IP地址信息。dist.init_process_group(backend='nccl',init_method='tcp://{}:{}'.format(MASTER_ADDR,port),world_size=size,rank=rank):使用NCCL后端初始化分布式进程组。torch.cuda.set_device(local_rank):为这个进程选择指定的GPU。model=torchvision.models.ResNet50(pretrained=False).to(gpu):从torchvision模型中加载ResNet50模型,移动到指定的gpu。ddp_model=DistributedDataParallel(model,device_ids=[local_rank]):将模型包裹在DistributedDataParallel模块中,这意味着我们可以通过加载CIFAR-10数据集并应用数据增强转换来进行分布式训练。train_sampler=torch.utils.data.distributed.DistributedSampler(train_dataset,num_replicas=size,rank=rank):创建一个DistributedSampler对象,将数据集分割成多个gpus。train_loader=torch.utils.data.DataLoader(dataset=train_dataset,batch_size=batch_size_per_gpu,shuffle=False,num_workers=0,pin_memory=True,sampler=train_sampler):创建DataLoader对象,数据会加载到模型中batches,这和我们平时的训练步骤是一样的,只是增加了一个分布式数据采样DistributedSampler。为指定数量的epoch训练模型,使用optimizer.step()以分布式方式更新权重。rank0在每轮结束时保存一个检查点。rank0每10批显示一次损失和训练时间。训练结束时打印训练模型的总时间也在rank0上。代码测试在1个具有1/2/3/4个GPU的节点、2个具有6/8个GPU的节点以及每个具有3/4个GPU的节点上进行训练。Resnet50在Cifar10上的测试如下图所示,每个测试的batchsize保持不变。完成每个测试所花费的时间以秒为单位记录。随着使用的GPU数量的增加,完成测试所需的时间会减少。使用8个GPU时,需要320秒才能完成,这是有记录以来最快的时间。这是肯定的,但我们可以看到训练速度并没有随着GPU数量的增加而线性增加。这可能是因为Resnet50被认为是一个相对较小的模型,不需要并行训练。在多个GPU上使用数据并行性可以显着减少在给定数据集上训练深度神经网络(DNN)所需的时间。随着GPU数量的增加,完成训练过程所需的时间减少,这表明可以更有效地并行训练DNN。这种方法在处理大型数据集或复杂的DNN架构时特别有用。通过利用多个GPU,可以加速训练过程,从而实现更快的模型迭代和实验。但需要注意的是,通过DataParallelism实现的性能提升可能会受到通信开销和GPU内存限制等因素的限制,需要仔细调整才能获得最佳效果。
