GPU使用问题

监控 GPU 常用命令:nvidia-smi,nvidia-smi -l可以每五秒输出一次GPU信息 ;

watch --color -n1 gpustat -cpu  # 动态事实监控 GPU,linux会一直处于监测状态,ctrl+z中止;

GPU的内存占用率主要由两部分组成:

       一是优化器参数,模型自身的参数,模型中间每一层的缓存,都会在内存中开辟空间来进行保存,所以模型本身会占用很大一部分内存。模型自身的参数指的就是各个网络层的 Weight 和Bias,显存在模型加载完成之后就会被占用,有些层是有参数的,如CNN, RNN,有些层是无参数的, 如激活层, 池化层等。Pytorch 执行 model.to(device) , 模型就加载完毕了。

       二是batch size的大小,模型结构固定时,将batch size设置大,会充分利用GPU内存。

计算模型参数量

        import torch as t
        from torchsummary import summary
        rgb = t.randn(1,3,352,480).cuda()
        net = FCN(12).cuda()
        out = net(rgb)
        summary(net,input_size=(3,352,480),batch_size=1)

GPU使用率低

        训练模型时GPU的利用率有时会很低,而CPU的利用率却非常高。通常 CPU 进行数据读取和预处理,GPU 做模型的正向传播和反向传播。CPU数据读取跟不上(读到内存+多线程+二进制文件),而 GPU 处理速度太快,导致GPU的利用率不高。

        模型训练慢并不是因为显卡不行或者模型太大,而是在跑模型过程中有一些其他的操作导致速度很慢,尤其是文件的IO操作,这会导致GPU得不到连续性使用,整体速度特别慢。

        此时解决方法:

                · 关闭一些日志记录,减少日志IO操作频率。

                · NVIDA提供了DALI库,可将数据处理转移到GPU上。

        GPU内存占用通常是由模型的大小以及batch size的大小决定。如果GPU占用率很低,通常只需要改变batch size的大小。合理的batch size可以提高显存的效率和数据的处理速度,跑完一次epoch需要迭代的次数也会随之减少,并且训练过程中的loss震荡也会减小。但batch size太小会导致模型震荡且不收敛,batch size太大则会导致训练速度慢且模型不易收敛。

不是IO操作导致的GPU使用率低,可考虑如下办法:

        1、提高batch_size;

        2、提高模型输入尺寸;

        3、增加模型深度;

        4、使用DataLoader处理数据是可将 pin_memory设为 True,其会直接映射到GPU上,减少数据传输时间,也可调整 num_workers数量,通常为 2,4,8,16,过大反而可能会降低效率;

        优先提高batch_size, 其他方法会对模型结构产生影响。

显存不足问题解决

降低batch size

       适当降低batch size,则模型每层的输入输出会线性减少,batch_size 是训练神经网络中的重要超参数,该值决定了一次将多少数据送入神经网络训练。现存允许时,batch_size 越大越好,使显存尽可能占满, batch_size尽可能大。

选择更小的数据类型

       通常网络采用 32 位浮点数,如使用16 位的浮点数,显存占用量将接近呈倍数递减。

精简模型

       设计模型时适当精简模型,如原来两层的LSTM转为一层;原来使用LSTM, 现在使用GRU;减少卷积核数量;尽量少的使用 Linear ,因为全连接层参数较多,较少参数或则不用全连接层,使用全局平均池化替代等。

数据处理

       文本数据中,长序列会导致参数呈线性增加, 适当缩小序列长度可极大降低参数量。

total_loss

       loss 是包含梯度信息的tensor,正确求损失和的方式为:total_loss += loss.item()

释放不需要的张量和变量

       用 del 删除你不需要的张量和变量。

Relu 的 inplace 参数

       激活函数 Relu() 默认参数 inplace = Flase, 为True 时在通过relu() 计算得到的新值不会占用新的空间而是直接覆盖原来的值,可以节省一部分显存。

梯度累积

        在 Pytorch 中,执行 loss.backward() 时, 会为每个参数计算梯度,并将其存储在 paramter.grad 中,paramter.grad 是一个张量, 会累加每次计算得到的梯度,调用 optimizer.step() 进行梯度下降更新网络参数。而 batch size 与占用显存相关,有时候 batch size 不能设置太小,就可以使用梯度累加。

        通常每个batch_size使用 optimizer.zero_grad() 清空梯度,梯度累加本质上就是累加 若干个 batch_size 的梯度,即不是每个batch都清空梯度,而是根据累加的梯度更新网络参数,达到调整batch_size 的效果。在使用时,需要注意适当的扩大学习率。

        假设 batch size = 4 , accumulation steps = 8 , 梯度积累首先在前向传播的时候以 batch_size = 4 计算梯度,但不更新参数,将梯度积累下来,达到 accumulation steps 个 batch 再更新参数,等价于:真正的 batch_size = batch_size * accumulation_steps,梯度积累能很大程度上缓解GPU显存不足的问题,推荐使用。

梯度检查点

       梯度检查点是以时间换空间的方法,通过减少保存的激活值压缩模型占用空间,在计算梯度时必须重新计算没有存储的激活值。参考:陈天奇 Training Deep Nets with Sublinear Memory Cost

混合精度训练

       混合精度训练在单卡和多卡情况下都可以使用,通过cuda计算中的half2类型提升运算效率。一个half2类型中会存储两个FP16的浮点数,进行基本运算时可同时进行,因此 FP16 的期望速度是 FP32 的两倍。

分布式训练

       数据并行 Data Parallelism

       模型并行 Model Parallelism

多GPU并行训练
设置使用GPU:device = 'cuda' if torch.cuda.is_avaiable() else 'cpu'

DataParallel 和 DistributedDataParallel 两个类可用于GPU并行;

        以 DataParallel 为例:model = nn.DataParallel(model)

        在单卡上写好的 model 直接调用,别的都跟单卡形式一样,程序会自动把数据拆分放到所有已知的GPU上来运行,数据是直接从第一维拆开平均放到各个GPU上,相当于每个GPU放 batch_size / gpu_num 个样本。设置已知的GPU,可以在运行代码的 python 加上 CUDA_VISIBLE_DEVICES 参数,CUDA_VISIBLE_DEVICES=0,1,2,3 python example.py,如果要使用nohup的话,参数要加在nohup的前面,CUDA_VISIBLE_DEVICES=0,1,2,3 nohup python -u example.py >> nohup_output.log 2;如果不设置则默认为所有GPU,对GPU数量计数:torch.cuda.device_count() 代码。直接用 DataParallel 可能导致各卡空间不均衡的问题,建议使用 DistributedDataParallel。

参考:

模型训练时GPU利用率太低的原因及解决_python_脚本之家 (jb51.net)

优化GPU显存不足,提高GPU利用率_显存不足怎么解决方案_L_bloomer的博客-CSDN博客

深度学习PyTorch中GPU利用率较低,且模型训练速度很慢的问题总结与分析 - 哔哩哔哩 (bilibili.com)

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值