Pytorch 提速指南

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.cuda.get_device_name(0)
    'Quadro GP100'
    >>> torch.cuda.get_device_capability(0)
    (6, 0)

如果设置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《Pytorch 提速指南》

训练模型个人的基本要求是deterministic/reproducible,或者说是可重复性。也就是说在随机种子固定的情况下,每次训练出来的模型要一样。之前遇到了两次不可重复的情况。第一次是训练CNN的时候,发现每次跑出来小数点后几位会有不一样。epoch越多,误差就越多,虽然结果大致上一样,但是强迫症真的不能忍。后来发现在0.3.0的时候已经修复了这个问题,可以用torch.backends.cudnn.deterministic = True

这样调用的CuDNN的卷积操作就是每次一样的了。

  • More about using CUDA in Pytorch

Pytorch v0.3.0版本发布–pytorch性能优化提速,支持ONNX,高阶梯度以及SparseAdam优化器www.ptorch.com《Pytorch 提速指南》

预先分配内存空间:pin_memory + non_blocking async GPU training

为了防止多GPU同时读取内存导致blocking,non_blocking需要对train data设置,否则,0.4.0版本中的DataParallel会自动尝试用async GPU training,更多信息请查看以下例子和文档:

用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后端进行分布式训练。

我感觉主要步骤大概有这几步,你最好还是套用这个官方的例子修改成你自己的:

  1. 计算总共的GPU数量
  2. 启动各个训练进程

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.add_argument('--dist-url', default='tcp://172.27.149.6:7777', type=str,
                        help='url used to set up distributed training')
    parser.add_argument('--dist-backend', default='gloo', type=str,
                        help='distributed backend')
    import torch.distributed as dist
    import torch.utils.data.distributed
    dist.init_process_group(backend='gloo, init_method='tcp://172.27.149.6:7777',  world_size=args.world_size)  torch.utils.data.distributed.DistributedSampler(train_dataset)  , sampler=train_sampler

========================好了可以开始看了

使用比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.05101

DECOUPLED WEIGHT DECAY REGULARIZATION:
https://arxiv.org/pdf/1711.05101.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《Pytorch 提速指南》

尝试Nvidia Apex 16位浮点数扩展

Apex (A PyTorch Extension)nvidia.github.io

需要:

  1. CUDA9
  2. Python 3
  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))

系统详情:

  1. CUDA9
  2. Python 3
  3. Pytorch 0.4.1
  4. Ubuntu 16.04
  5. Nvidia TitanXp
  6. 逻辑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。建议保持观察。

参考其他优化建议

https://www.sagivtech.com/2017/09/19/optimizing-pytorch-training-code/www.sagivtech.com

    原文作者:云梦
    原文地址: https://zhuanlan.zhihu.com/p/39752167
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞