本指南从最简单的结构到最复杂的改动都有,可以使你的网络得到最大的好处。我会给你展示示例Pytorch代码以及可以在Pytorch- lightning Trainer中使用的相关flags,这样你可以不用自己编写这些代码!
不要让你的神经网络变成这样
让咱们面对现实吧,你的模型或许还停留在石器年代。我敢打赌你依然运用32位精度或GASP乃至只在一个GPU上练习。
我理解,网上都是各种神经网络加快攻略,可是一个checklist都没有(现在有了),运用这个清单,一步一步保证你能榨干你模型的一切功用。
本攻略从最简略的结构到最杂乱的改动都有,能够使你的网络得到最大的优点。我会给你展现示例Pytorch代码以及能够在Pytorch- lightning Trainer中运用的相关flags,这样你能够不必自己编写这些代码!
**这本攻略是为谁预备的?**任何运用Pytorch进行深度学习模型研讨的人,如研讨人员、博士生、学者等,咱们在这里议论的模型或许需求你花费几天的练习,乃至是几周或几个月。
咱们会讲到:
- 运用DataLoaders
- DataLoader中的workers数量
- Batch size
- 梯度累计
- 保存的核算图
- 移动到单个
- 16-bit 混合精度练习
- 移动到多个GPUs中(模型仿制)
- 移动到多个GPU-nodes中 (8+GPUs)
- 考虑模型加快的技巧
Pytorch-Lightning
你能够在Pytorch的库Pytorch- lightning中找到我在这里评论的每一个优化。Lightning是在Pytorch之上的一个封装,它能够主动练习,徘徊让研讨人员彻底操控要害的模型组件。Lightning 运用最新的最佳实践,并将你或许犯错的当地最小化。
咱们为MNIST界说LightningModel并运用Trainer来练习模型。
- frompytorch_lightningimportTrainer
- model=LightningModule(…)
- trainer=Trainer()
- trainer.fit(model)
1. DataLoaders
这或许是最简略取得速度增益的当地。保存h5py或numpy文件以加快数据加载的年代现已一去不复返了,运用Pytorch dataloader加载图画数据很简略(关于NLP数据,请检查TorchText)。
在lightning中,你不需求指定练习循环,只需求界说dataLoaders和Trainer就会在需求的时分调用它们。
- dataset=MNIST(root=self.hparams.data_root,traintrain=train,download=True)
- loader=DataLoader(dataset,batch_size=32,shuffle=True)
- forbatchinloader:
- x,y=batch
- model.training_step(x,y)
- ...
2. DataLoaders 中的 workers 的数量
另一个加快的奇特之处是答应批量并行加载。因而,您能够一次装载nb_workers个batch,而不是一次装载一个batch。
- #slow
- loader=DataLoader(dataset,batch_size=32,shuffle=True)
- #fast(use10workers)
- loader=DataLoader(dataset,batch_size=32,shuffle=True,num_workers=10)
3. Batch size
在开端下一个优化过程之前,将batch size增大到CPU-RAM或GPU-RAM所答应的最大规模。
下一节将要点介绍怎么协助削减内存占用,以便你能够持续添加batch size。
记住,你或许需求再次更新你的学习率。一个好的经历法则是,假如batch size加倍,那么学习率就加倍。
4. 梯度累加
在你现已到达核算资源上限的情况下,你的batch size依然太小(比方8),然后咱们需求模仿一个更大的batch size来进行梯度下降,以供给一个杰出的估量。
假定咱们想要到达128的batch size巨细。咱们需求以batch size为8履行16个前向传达和向后传达,然后再履行一次优化过程。
- #clearlaststep
- optimizer.zero_grad()
- #16accumulatedgradientsteps
- scaled_loss=0
- foraccumulated_step_iinrange(16):
- out=model.forward()
- loss=some_loss(out,y)
- loss.backward()
- scaled_loss+=loss.item()
- #updateweightsafter8steps.effectivebatch=8*16
- optimizer.step()
- #lossisnowscaledupbythenumberofaccumulatedbatches
- actual_loss=scaled_loss/16
在lightning中,全部都给你做好了,只需求设置accumulate_grad_batches=16:
- trainer=Trainer(accumulate_grad_batches=16)
- trainer.fit(model)
5. 保存的核算图
一个最简略撑爆你的内存的办法是为了记载日志存储你的loss。
- losses=[]
- ...
- losses.append(loss)
- print(f'currentloss:{torch.mean(losses)'})
上面的问题是,loss依然包括有整个图的副本。在这种情况下,调用.item()来开释它。
- ![1_CER3v8cok2UOBNsmnBrzPQ](9TipsForTrainingLightning-FastNeuralNetworksInPytorch.assets/1_CER3v8cok2UOBNsmnBrzPQ.gif)#bad
- losses.append(loss)
- #good
- losses.append(loss.item())
Lightning会十分当心,保证不会保存核算图的副本。
6. 单个GPU练习
一旦你现已完成了前面的过程,是时分进入GPU练习了。在GPU上的练习将使多个GPU cores之间的数学核算并行化。你得到的加快取决于你所运用的GPU类型。我引荐个人用2080Ti,公司用V100。
乍一看,这或许会让你手足无措,但你真的只需求做两件事:1)移动你的模型到GPU, 2)每逢你运转数据经过它,把数据放到GPU上。
- #putmodelonGPU
- model.cuda(0)
- #putdataongpu(cudaonavariablereturnsacudacopy)
- xx=x.cuda(0)
- #runsonGPUnow
- model(x)
假如你运用Lightning,你什么都不必做,只需求设置Trainer(gpus=1)。
- #asklightningtousegpu0fortraining
- trainer=Trainer(gpus=[0])
- trainer.fit(model)
在GPU上进行练习时,要留意的首要作业是约束CPU和GPU之间的传输次数。
- #expensive
- xx=x.cuda(0)#veryexpensive
- xx=x.cpu()
- xx=x.cuda(0)
假如内存耗尽,不要将数据移回CPU以节约内存。在求助于GPU之前,测验以其他方法优化你的代码或GPU之间的内存散布。
另一件需求留意的作业是调用强制GPU同步的操作。铲除内存缓存便是一个比如。
0
- dataset=MNIST(root=self.hparams.data_root,traintrain=train,download=True)
- loader=DataLoader(dataset,batch_size=32,shuffle=True)
- forbatchinloader:
- x,y=batch
- model.training_step(x,y)
- ...
可是,假如运用Lightning,专一或许出现问题的当地是在界说Lightning Module时。Lightning会特别留意不去犯这类过错。
7. 16-bit 精度
16bit精度是将内存占用折半的惊人技能。大多数模型运用32bit精度数字进行练习。可是,最近的研讨发现,16bit模型也能够作业得很好。混合精度意味着对某些内容运用16bit,但将权重等内容保持在32bit。
要在Pytorch中运用16bit精度,请装置NVIDIA的apex库,并对你的模型进行这些更改。
1
- dataset=MNIST(root=self.hparams.data_root,traintrain=train,download=True)
- loader=DataLoader(dataset,batch_size=32,shuffle=True)
- forbatchinloader:
- x,y=batch
- model.training_step(x,y)
- ...
amp包会处理好大部分作业。假如梯度爆破或趋向于0,它乃至会缩放loss。
在lightning中,启用16bit并不需求修正模型中的任何内容,也不需求履行我上面所写的操作。设置Trainer(precision=16)就能够了。
2
- dataset=MNIST(root=self.hparams.data_root,traintrain=train,download=True)
- loader=DataLoader(dataset,batch_size=32,shuffle=True)
- forbatchinloader:
- x,y=batch
- model.training_step(x,y)
- ...
8. 移动到多个GPUs中
现在,作业变得十分风趣了。有3种(或许更多?)办法来进行多GPU练习。
分batch练习
A) 仿制模型到每个GPU中,B) 给每个GPU一部分batch
榜首种办法被称为“分batch练习”。该战略将模型仿制到每个GPU上,每个GPU取得batch的一部分。
3
- dataset=MNIST(root=self.hparams.data_root,traintrain=train,download=True)
- loader=DataLoader(dataset,batch_size=32,shuffle=True)
- forbatchinloader:
- x,y=batch
- model.training_step(x,y)
- ...
在lightning中,你只需求添加GPUs的数量,然后告知trainer,其他什么都不必做。
4
- dataset=MNIST(root=self.hparams.data_root,traintrain=train,download=True)
- loader=DataLoader(dataset,batch_size=32,shuffle=True)
- forbatchinloader:
- x,y=batch
- model.training_step(x,y)
- ...
模型散布练习
将模型的不同部分放在不同的GPU上,batch按次序移动
有时你的模型或许太大不能彻底放到内存中。例如,带有编码器和解码器的序列到序列模型在生成输出时或许会占用20GB RAM。在本例中,咱们期望将编码器和解码器放在独立的GPU上。
5
- dataset=MNIST(root=self.hparams.data_root,traintrain=train,download=True)
- loader=DataLoader(dataset,batch_size=32,shuffle=True)
- forbatchinloader:
- x,y=batch
- model.training_step(x,y)
- ...
关于这种类型的练习,在Lightning中不需求指定任何GPU,你应该把LightningModule中的模块放到正确的GPU上。
6
- dataset=MNIST(root=self.hparams.data_root,traintrain=train,download=True)
- loader=DataLoader(dataset,batch_size=32,shuffle=True)
- forbatchinloader:
- x,y=batch
- model.training_step(x,y)
- ...
两者混合
在上面的情况下,编码器和解码器依然能够从并行化操作中获益。
7
- dataset=MNIST(root=self.hparams.data_root,traintrain=train,download=True)
- loader=DataLoader(dataset,batch_size=32,shuffle=True)
- forbatchinloader:
- x,y=batch
- model.training_step(x,y)
- ...
运用多个GPU时要考虑的留意事项:
- 假如模型现已在GPU上了,model.cuda()不会做任何作业。
- 总是把输入放在设备列表中的榜首个设备上。
- 在设备之间传输数据是贵重的,把它作为最终的手法。
- 优化器和梯度会被保存在GPU 0上,因而,GPU 0上运用的内存或许会比其他GPU大得多
9. 多节点GPU练习
每台机器上的每个GPU都有一个模型的副本。每台机器取得数据的一部分,而且只在那部分上练习。每台机器都能同步梯度。
假如你现已做到了这一步,那么你现在能够在几分钟内练习Imagenet了!这并没有你幻想的那么难,可是它或许需求你对核算集群的更多常识。这些阐明假定你正在集群上运用SLURM。
Pytorch答应多节点练习,经过在每个节点上仿制每个GPU上的模型并同步梯度。所以,每个模型都是在每个GPU上独立初始化的,本质上独登时在数据的一个分区上练习,除了它们都从一切模型接纳梯度更新。
在高层次上:
- 在每个GPU上初始化一个模型的副本(保证设置种子,让每个模型初始化到相同的权重,不然它会失利)。
- 将数据集分割成子集(运用DistributedSampler)。每个GPU只在它自己的小子集上练习。
- 在.backward()上,一切副本都接纳到一切模型的梯度副本。这是模型之间仅有一次的通讯。
Pytorch有一个很好的笼统,叫做DistributedDataParallel,它能够帮你完成这个功用。要运用DDP,你需求做4的作业:
8
- dataset=MNIST(root=self.hparams.data_root,traintrain=train,download=True)
- loader=DataLoader(dataset,batch_size=32,shuffle=True)
- forbatchinloader:
- x,y=batch
- model.training_step(x,y)
- ...