当前位置: 首页 > 科技观察

代码解释:用Pytorch训练快速神经网络的9个技巧

时间:2023-03-12 18:25:21 科技观察

其实你的模型可能还停留在石器时代的水平。我猜您仍在使用32位精度或*GASP(通用活动模拟语言)*进行训练,甚至可能只在单个GPU上进行训练。如果那里有99个速度指南,但您可能只看过1个怎么办?(对,就是那样)。但是这个终极指南会一步步教你清除模型中的所有(GP模型)。本指南从简单到复杂,涵盖了您可以进行的大多数PITA修改,以充分利用您的网络。这些示例将包括一些Pytorch代码和相关标记,以供在Pytorch-Lightning培训师中使用,以防您不想键入自己的代码!本指南适用于哪些人?任何使用Pytorch从事非平凡深度学习模型工作的人,例如工业研究人员、博士生、学者等……这些模型可能需要几天、甚至几周、几个月的时间来训练。指南(从易到难)使用DataLoader。DataLoader中的进程数。批量大小。累积梯度。保留计算图。转到单个GPU。16位混合精度训练。转到多GPU(模型复制)。转到多GPU节点(8+GPU)。模型加速的想法和技巧Pytorch-Lightning论文中讨论的各种优化在名为Pytorch-Lightning(https://github.com/williamFalcon/pytorch-lightning?source=post_page)的Pytorch库中可用。Lightning是基于Pytorch的轻型封装器,可以帮助研究人员自动训练模型,但关键的模型组件仍然完全由研究人员控制。请参阅本教程以获得更强大的示例(https://github.com/williamFalcon/pytorch-lightning/blob/master/examples/new_project_templates/single_gpu_node_template.py?source=post_page)。Lightning采用最新的尖端方法来最大限度地减少错误的可能性。MNIST定义的Lightning模型(https://github.com/williamFalcon/pytorch-lightning/blob/master/examples/new_project_templates/lightning_module_template.py?source=post_page)可以应用到trainer上。frompytorch-lightningimportTrainermodel=LightningModule(...)trainer=Trainer()trainer.fit(model)1.DataLoader这大概是最容易提速的地方了。保存h5py或numpy文件以加速数据加载的日子已经一去不复返了。使用Pytorch数据加载器(https://pytorch.org/tutorials/beginner/data_loading_tutorial.html?source=post_page)加载图像数据非常简单。(NLP数据请参考TorchText:https://torchtext.readthedocs.io/en/latest/datasets.html?source=post_page)dataset=MNIST(root=self.hparams.data_root,traintrain=train,download=True)loader=DataLoader(dataset,batch_size=32,shuffle=True)forbatchinloader:x,y=batchmodel.training_step(x,y)...在Lightning中不需要指定训练循环,定义即可dataLoaders,trainer它们会在需要的时候被调用(https://github.com/williamFalcon/pytorch-lightning/blob/master/examples/new_project_templates/lightning_module_template.py?source=post_page--------------------------#L163-L217)。2.DataLoaders中的进程数提速的第二个秘诀是允许批量并行加载。因此,您可以一次加载多个批次,而不是一次加载一个。#slowloader=DataLoader(dataset,batch_size=32,shuffle=True)#fast(use10workers)loader=DataLoader(dataset,batch_size=32,shuffle=True,num_workers=10)3.批量大小(Batchsize)开始下步骤在优化步骤之前,将批处理大小调高到CPU内存或GPU内存允许的最大值。接下来的部分将重点关注减少内存占用,以便我们可以继续增加批处理大小。请记住,您很可能需要再次更新学习率。如果将批量大小加倍,最好也将学习率加倍。4.CumulativeGradient如果计算资源已经使用到最大,但是batchsize还是太小(假设为8),那么我们需要模拟一个更大的batchsize进行梯度下降来进行准确的估计。假设您希望将批量大小设为128。然后,在执行单个优化器步骤之前执行16次前向和反向传递(批量大小8)。#clearlaststeoptimizer.zero_grad()#16accumulatedgradientstepsscaled_loss=0foraccumulated_step_iinrange(16):out=model.forward()loss=some_loss(out,y)loss.backward()scaled_loss+=loss.item()#updateweightsafter8steps.effective6opatcht=8izerstep()#lossisnowscaledupbythenumberofaccumulatedbatchesactual_loss=scaled_loss/16在Lightning中,这些已经自动执行。只需设置标记:https://williamfalcon.github.io/pytorch-lightning/Trainer/Training%20Loop/?source=post_page--------------------------#accumulated-gradientstrainer=Trainer(accumulate_grad_batches=16)trainer.fit(model)5.保持计算图爆内存很简单,只要不指向计算图的指针即可发布,例如...以保存日志丢失。losses=[]...losses.append(loss)print(f'currentloss:{torch.mean(losses)'})上面的问题是loss仍然有图表的副本。在这种情况下,可以使用.item()释放它。#badlosses.append(loss)#goodlosses.append(loss.item())Lightning会特别注意,因此它无法保留图形的副本(示例:https://github.com/williamFalcon/pytorch-lightning/blob/master/pytorch_lightning/models/trainer.py?source=post_page----------------------------#L767-L768)6.单GPU训练完成完成前面的步骤后,就可以进入GPU训练了。GPU训练将在许多GPU核心上并行进行数学计算。您可以加速多少取决于所使用的GPU类型。个人用推荐2080Ti,公司用推荐V100。一开始您可能会觉得压力很大,但实际上只有两件事要做:1)将模型移至GPU,以及2)在GPU上运行时将数据导入GPU。#putmodelonGPUmodel.cuda(0)#putdataongpu(cudaonavariablereturnsacudacopy)xx=x.cuda(0)#runsonGPUnowmodel(x)如果你使用闪电,你不需要对代码做任何事情。只需设置标记(https://williamfalcon.github.io/pytorch-lightning/Trainer/Distributed%20training/?source=post_page--------------------------#single-gpu):#asklightningtousegpu0fortrainingtrainer=Trainer(gpus=[0])trainer.fit(model)在GPU上训练时,注意限制CPU和GPU之间的传输量。#expensivexx=x.cuda(0)#veryexpensivexx=x.cpu()xx=x.cuda(0)例如,如果内存不足,不要将数据移回CPU以节省内存。尝试通过其他方式优化代码,或者在使用此方法之前将代码跨GPU分布。还要注意强制GPU同步的操作。例如清除内存缓存。#reallybadidea.StopsalltheGPUsuntiltheyallcatchuptorch.cuda.empty_cache()但是如果使用Lightning,这种问题可能只有在定义Lightning模块时才会出现。Lightning会特别注意避免此类错误。7.16位精度16位精度可以有效减少一半的内存占用。大多数模型都使用32位精度数字进行训练。然而,最近的研究发现该模型也适用于16位精度。混合精度是指某些特定模型使用16位进行训练,而权重类使用32位进行训练。要在Pytorch中使用16位精度,首先从NVIDIA安装apex库并对您的模型进行这些更改。#enable16-bitonthemodelandtheoptimizermodel,optimizers=amp.initialize(model,optimizers,opt_level='O2')#whendoing.backward,letampdoitsoitcanscalethelosswithamp.scale_loss(loss,optimizer)scaled_loss:scaled_loss.backward()amp包处理了大部分事情。如果梯度爆炸或趋于零,它甚至会扩大损失。在Lightning中,使用16位很简单(https://williamfalcon.github.io/pytorch-lightning/Trainer/Distributed%20training/?source=post_page---------------------------#16-bit-mixed-precision),你不需要对你的模型做任何改动,也不需要完成上面的操作。trainer=Trainer(amp_level='O2',use_amp=False)trainer.fit(model)8.转向多GPU现在,事情变得有趣起来。有3种(也许更多?)方法可以在多个GPU上进行训练。(1)批量训练A)在每个GPU上复制模型;B)将批处理的一部分分配给每个GPU。第一种方法称为批量训练。该策略将模型复制到每个GPU,每个GPU获得批次的一部分。#copymodeloneachGPUandgiveafourthofthebatchtoeachmodel=DataParallel(model,devices=[0,1,2,3])#outhas4outputs(oneforeachgpu)out=model(x.cuda(0))在Lightning中可以直接指示trainer增加GPU个数不执行上述任何操作。#asklightningtouse4GPUsfortrainingtrainer=Trainer(gpus=[0,1,2,3])trainer.fit(model)(2)子模型训练将模型的不同部分分配给不同的GPU,按顺序分配批次有时模型可能太大,内存不足支持。例如,带有编码器和解码器的SequencetoSequence模型在生成输出时可能占用多达20GB的内存。在这种情况下,我们希望将编码器和解码器放在不同的GPU上。#eachmodelissooobigwecan'tfitbothinmemoryencoder_rnn.cuda(0)decoder_rnn.cuda(1)#runinputthroughencoderonGPU0out=encoder_rnn(x.cuda(0))#runoutputthroughdecoderonthenextGPUout=decoder_rnn(x.cuda(1))#normallywewanttobringalloutputsouttocuda0out这种类型的训练没有卸载适用于任何GPU的闪电训练器。相反,只需将您的模块导入正确的GPULightning模块:classMyModule(LightningModule):def__init__():self.encoder=RNN(...)self.decoder=RNN(...)defforward(x):#modelswon'tbemovedafterthefirstforward因为#theyarealreadyonthecorrectGPUsself.encoder.cuda(0)self.decoder.cuda(1)out=self.encoder(x)out=self.decoder(out.cuda(1))#don'tpassGPUstotrainermodel=MyModule()trainer=Trainer()trainer.fit(model)(3)混合两种训练方法在上面的示例中,编码器和解码器仍然会受益于并行化每个操作。我们现在可以更有创意。#changetheselinesself.encoder=RNN(...)self.decoder=RNN(...)#tothese#noweachRNNisbasedondifferentgpusetself.encoder=DataParallel(self.encoder,devices=[0,1,2,3])self.decoder=DataParallel(self.encoder,devices=[4,5,6,7])#inforward...out=self.encoder(x.cuda(0))#noticeinputsonfirstgpuindevicesout=self.decoder(out.cuda(4))#<---the4here(4)使用多个GPU时的注意事项如果model.cuda()已经存在于设备上,它不会做任何事情。始终在设备列表中的第一个设备上输入。跨设备传输数据非常昂贵,除非绝对必要,否则不应这样做。优化器和梯度将存储在GPU0上。因此GPU0很可能使用比其他处理器更多的内存。9.多节点GPU训练每台机器上的每个GPU都可以获得模型的副本。每台机器都会获取一部分数据,并仅在该部分数据上进行训练。这些机器彼此同步梯度。完成此操作后,您可以在几分钟内训练Imagenet数据集!它没有你想象的那么难,但是需要更多关于计算集群的知识。这些说明假定您在集群上使用SLURM。Pytorch在每个GPU上跨节点复制模型并同步梯度,从而实现多节点训练。因此,每个模型都在每个GPU上独立初始化,本质上是在数据分区上独立训练,除了它们都从所有模型接收梯度更新。高级阶段:在每个GPU上初始化模型的副本(确保设置种子,使每个模型初始化为相同的权重,否则操作将失败。)将数据集划分为子集。每个GPU只训练自己的子集。在.backward()上,所有副本都将收到每个模型梯度的副本。只有这样,模型才会相互通信。Pytorch有一个很好的抽象,叫做分布式数据并行处理,可以为你做这件事。要使用DDP(分布式数据并行处理),您需要做4件事:deftng_dataloader():d=MNIST()#4:Adddistributedsampler#samplersendsaportionoftngdatatoeachmachinedist_sampler=DistributedSampler(dataset)dataloader=DataLoader(d,shuffle=False,sampler=dist_sampler)defmain_process_entrypoint(gpu_nb):#2:setupconnectionsbetweenallgpusacrossallmachines#allgpusconnecttoasingleGPU"root"#thedefaultusesenv://world=nb_gpus*nb_nodesdist.init_process_group("nccl",rank=gpu_nb,worldworld_size=world)#3:wrapmodelinDPPtorch.cuda.set_device(gpu_nb))model.cuda(gpu_nb)model=DistributedDataParallel(model,device_ids=[gpu_nb])#trainyourmodelnow...if__name__=='__main__':#1:spawnnumberofprocesses#yourclusterwillcallmainforeachmachinemp.spawn(main_process_entrypoint,nprocs=8)Pytorch团队在此里面有详细的实战教程(https://github.com/pytorch/examples/blob/master/imagenet/main.py?source=post_page------------------------------)。然而,在Lightning中,这是一个内置功能。只需设置节点数标志,让闪电处理其余部分。#trainon1024gpusacross128nodestrainer=Trainer(nb_gpu_nodes=128,gpus=[0,1,2,3,4,5,6,7])Lightning还带有一个SlurmCluster管理器,可以帮助您简单地提交SLURM任务的正确细节(示例:https://github.com/williamFalcon/pytorch-lightning/blob/master/examples/new_project_templates/multi_node_cluster_template.py?source=post_page----------------------------#L103-L134)10.奖金!更快的多GPU单节点训练事实证明,分布式数据并行处理比数据并行处理快得多,因为唯一的通信是梯度同步。因此,最好用分布式数据并行处理代替数据并行,即使你只是在进行单机训练。在Lightning中,这可以通过将distributed_backend设置为ddp(分布式数据并行处理)并设置GPU的数量来轻松实现。#trainon4gpusonthesamemachineMUCHfasterthanDataParalleltrainer=Trainer(distributed_backend='ddp',gpus=[0,1,2,3])关于模型加速的思考和技巧如何通过寻找瓶颈来思考问题?该模型可以分为几个部分:首先,确保数据加载没有瓶颈。为此,你可以使用上面提到的现有数据加载方案,但如果不适合你,你可以使用离线处理和缓存作为高性能数据存储,就像h5py。让我们看看在训练期间要做什么。确保快速转发,避免冗余计算,并最大限度地减少CPU和GPU之间的数据传输。最后,避免降低GPU的速度(在本指南中介绍)。接下来,最大化批量大小。一般来说,GPU的显存大小会限制batchsize。从这个角度来看,这实际上都是关于跨GPU的分布,但要最大程度地减少延迟,请有效地使用大批量(例如,在数据集中,您可能会在多个GPU上获得8000+的有效批量大小)。但大批量需要小心处理。检查文献以了解具体问题并了解其他人如何处理这些问题!