Performance guide for Pytorch
Pytorch version: 0.4.0
Using CUDA in correct way:
- 设置torch.backends.cudnn.benchmark = True
使用benchmark以启动CUDNN_FIND自动寻找最快的操作,当计算图不会改变的时候(每次输入形状相同,模型不改变)的情况下可以提高性能,反之则降低性能
以上内容引用自:
perf improvements for depthwise convolutions by ngimel · Pull Request #3265 · pytorch/pytorchgithub.com- 另外,还可以采用确定性卷积:(相当于把所有操作的seed=0,以便重现,会变慢)
torch.backends.cudnn.deterministic
以下内容引用自:
Pytorch在训练过程中常见的问题 - Oldpan的个人博客oldpan.me添加torch.cuda.get_device_name和torch.cuda.get_device_capability实现如下功能。例:
>>>
如果设置torch.backends.cudnn.deterministic = True,则CuDNN卷积使用确定性算法
torch.cuda_get_rng_state_all并torch.cuda_set_rng_state_all引入,让您一次保存/加载随机数生成器的状态在所有GPU上
torch.cuda.emptyCache()释放PyTorch的缓存分配器中的缓存内存块。当与其他进程共享GPU时特别有用。
以下内容引用自:
PyTorch 有哪些坑/bug?www.zhihu.com训练模型个人的基本要求是deterministic/reproducible,或者说是可重复性。也就是说在随机种子固定的情况下,每次训练出来的模型要一样。之前遇到了两次不可重复的情况。第一次是训练CNN的时候,发现每次跑出来小数点后几位会有不一样。epoch越多,误差就越多,虽然结果大致上一样,但是强迫症真的不能忍。后来发现在0.3.0的时候已经修复了这个问题,可以用torch.backends.cudnn.deterministic = True
这样调用的CuDNN的卷积操作就是每次一样的了。
- More about using CUDA in Pytorch
预先分配内存空间:pin_memory + non_blocking async GPU training
为了防止多GPU同时读取内存导致blocking,non_blocking需要对train data设置,否则,0.4.0版本中的DataParallel会自动尝试用async GPU training,更多信息请查看以下例子和文档:
- https://github.com/pytorch/examples/blob/master/imagenet/main.py#L95
- CUDA semantics - PyTorch master documentation
用Variable:
- Variable() volatile=True
请检查您的当前版本是否已经默认variable?print一下emun出来的data看看
用DistributedDataParallel代替DataParallel
========================== 这一段正在修改中,请跳过
Pytorch引入了一个新的函数model = torch.nn.parallel.DistributedDataParallel(model)为的就是支持分布式模式
不同于原来在multiprocessing中的model = torch.nn.DataParallel(model,device_ids=[0,1,2,3]).cuda()函数,DataParallel只是实现了在单机上的多GPU训练,根据官方文档的说法,甚至在单机多卡的模式下,新函数DistributedDataParallel表现也会优于旧函数。
具体设置其实是比较复杂的,可以参考这个官方例子:https://github.com/pytorch/examples/blob/master/imagenet/main.py
实际上,在使用DistributedDataParallel的时候,pytorch会在每张卡上面启动一个训练worker,并默认通过NCCL后端进行分布式训练。
我感觉主要步骤大概有这几步,你最好还是套用这个官方的例子修改成你自己的:
- 计算总共的GPU数量
- 启动各个训练进程
torch.multiprocessing.spawn(你的主训练进程函数, 第几个进程)
3. 在每个训练进程中, 注册到主训练进程上: dist.init_process_group(backend=args.dist_backend, init_method=args.dist_url,world_size=args.world_size, rank=args.rank)
4. 在每个训练进程中,用DistributedDataParallel包装模型,model = torch.nn.parallel.DistributedDataParallel(model, device_ids=[args.gpu])
请参考如下修改方式,以便使用新函数:
parser
========================好了可以开始看了
使用比Adam更快的优化器
- SGD with Momentum :该优化器在多项式时间内的收敛性已经明确被证明,更不用说所有的参数都已经像您的老朋友一样熟悉了
- 【暂时不可用】使用AdamW or Adam with correct weight decay:
因为Adam在优化过程中有一个L2正则化参数,但在当前版本的Pytorch中,L2正则化没有根据学习率进行归一化,AdamW论文中提出的Adam修改方案解决了这一问题并证明收敛更快,而且适用于cosine学习率衰减等:
Fixing Weight Decay Regularization in Adam: https:// arxiv.org/abs/1711.0510 1
DECOUPLED WEIGHT DECAY REGULARIZATION: https:// arxiv.org/pdf/1711.0510 1.pdf
除adam外,多数常用优化器中都可应用。包括:adagrad,adadelta,adamax,asgd,rms_prop,以及大家的老朋友SGD。
相关代码正在等待审核和合并到pytorch,因此目前还不可用。相关pull request请查看:
Decoupled Weight Decay Regularization in optimizers (added adamw and sgdw among others)github.com尝试Nvidia Apex 16位浮点数扩展
Apex (A PyTorch Extension)nvidia.github.io需要:
- CUDA9
- Python 3
- Pytorch 0.4.0
我还没试过,有试过的朋友麻烦发表一下感想。
按照自己的数据集,试一下dataloader中的参数
如果你的选项刚好是最坏情况,优化这个有可能达到2倍左右的性能提升(经验值哈),先解释一下DataLoader中其中两个参数:
- num_worker:
数据集加载的时候,控制用于同时加载数据的线程数(默认为0,即在主线程读取) - pin_memory:
是否提前申请CUDA内存(默认为False,但有说法除非数据集很小,否则在N卡上推荐总是打开)
我自己的经验是:
- 对于num_worker:
存在最优值,你会看到运行的时候pytorch会新建恰等于这个值的数据读取线程,我猜,线程多于必要的时候,数据读取线程返回到主线程反而会因为线程间通信减慢数据。因此大了不好小了也不好。建议把模型,loss,优化器全注释了只跑一下数据流速度,确定最优值 - 对于pin_memory:
在MNIST这样的小数据集上好像是关闭比较好,到底多小算小说不清楚,建议自己试一下。 - 总之官方的默认值很有可能不是最好的,建议自己多试试。
我用MNIST测试的例子代码如下:
import torch
from torchvision import datasets, transforms
import time
if __name__ == '__main__':
use_cuda = torch.cuda.is_available()
for num_workers in range(0,50,5): # 遍历worker数
kwargs = {'num_workers': num_workers, 'pin_memory': False} if use_cuda else {}
train_loader = torch.utils.data.DataLoader(
datasets.MNIST('./data', train=True, download=True,
transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])),
batch_size=64, shuffle=True, **kwargs)
start = time.time()
for epoch in range(1, 5):
for batch_idx, (data, target) in enumerate(train_loader): # 不断load
pass
end = time.time()
print("Finish with:{} second, num_workers={}".format(end-start,num_workers))
系统详情:
- CUDA9
- Python 3
- Pytorch 0.4.1
- Ubuntu 16.04
- Nvidia TitanXp
- 逻辑CPU数:32
结果如下:
- 当pin_memory == True:
Finish with:30.154743432998657, num_workers=0 #默认设置
Finish with:7.530909538269043, num_workers=5
Finish with:6.491116762161255, num_workers=10
Finish with:6.484131813049316, num_workers=15
Finish with:6.974432706832886, num_workers=20
Finish with:8.730116128921509, num_workers=25
Finish with:8.396393299102783, num_workers=30
Finish with:9.639503240585327, num_workers=35
Finish with:9.779307842254639, num_workers=40
Finish with:10.46692681312561, num_workers=45
- 当pin_memory == False:
Finish with:25.25737738609314, num_workers=0 #默认设置
Finish with:6.703955173492432, num_workers=5
Finish with:4.469067573547363, num_workers=10
Finish with:4.783797025680542, num_workers=15
Finish with:3.8453176021575928, num_workers=20
Finish with:3.993709087371826, num_workers=25
Finish with:4.045541048049927, num_workers=30
Finish with:4.440596580505371, num_workers=35
Finish with:4.228761434555054, num_workers=40
Finish with:4.400238513946533, num_workers=45
可以看到,在MNIST这样的小数据集上,pin_memory关闭比较好。而且,num_workers需要调节,除了默认情况外,最快和最慢是有一定差距的,建议在自己的代码上只跑数据读取这一块,确定这两个参数的最优值。
对于Windows用户,特别地,在windows平台上数据读取参数如何设置的问题,pytorch的github上也有人提issue。建议保持观察。