CVHub手把手帮你榨干GPU的显存

简介: CVHub手把手帮你榨干GPU的显存

引言

Out Of Memory, 一个炼丹师们熟悉得不能再熟悉的异常,其解决方法也很简单,减少输入图像的尺寸或者Batch Size就好了。但是,且不说输入尺寸对模型精度的影响,当BatchSize过小的时候网络甚至无法收敛的。

下图来源知乎:

深度学习中的batch的大小对学习效果有何影响?

6a1ad542f3a8223219f3dd8c58bf6d82.png

作者使用LeNet在MNIST数据集上进行测试,验证不同大小的BatchSize对训练结果的影响。我们可以看到,虽然说BatchSize并不是越大越好,但是过小的BatchSize的结果往往更差甚至无法收敛。


因此本文将会介绍如何在不减少输入数据尺寸以及BatchSize的情况下,进一步榨干GPU的显存。

什么在占用显存

显存主要是被以下三部分内容占用:1、网络模型,2、模型计算的过程中的中间变量,3、框架自身的显存开销。

网络模型的占用的显存主要是来自于所有有参数的层,包括:卷积、全连接、BN等;而不占用显存的有:激活函数、池化层以及Dropout等。

计算过程中产生的显存主要有:优化器、中间过程的特征图、backward过程产生的参数

而框架自身的显存开销一般不大,并且我们也不好优化,所以我们只能考虑从前面两点对显存进行优化,针对这两个部分,本文接下来将会介绍常用的显存占用优化策略。

模型显存优化

尽量使用Inplace

在PyTorch中,inplce操作指的是改变一个tensor值的时候,不经过复制操作而是直接在原来的内存上修改它的值,也就是原地操作。基本上,所有提供inplace参数的操作都可以使用inplace,并且官方文档也说了,如果你使用了inplace operation而没有报错的话,那么你可以确定你的梯度计算是正确的。


335e3e53f71c93e3c869aa02b94af75f.png


pytorch中所有inplace操作一般都是以_为后缀,如tensor.add_()、tensor.scatter_()等。除了自带的一些函数提供inplace操作外,一些运算法也存在inplace操作。如上图展示的是两个向量相加操作使用inplace与否的区别,但是要注意写法:


x = x+y属于case 1

x += y属于case2

同理, *=也是inplace操作


尽量少产生中间结果

下面两份代码,效果是一样的,但是占用显存却是不一样的。

不推荐写法

def forward(self, x):
  out_1 = self.conv_1(x)
  out_2 = self.conv_2(out_1)
  out_3 = self.conv_3(out_2)
  return out_3

推荐写法

def forward(self, x):
  x = self.conv_1(x)
  x = self.conv_2(x)
  x = self.conv_3(x)
  return x


不需要的中间变量尽可能的都是用一个变量来代替,因为这些变量都是会占用显存的。因此,网络中如果存在一些较长的连接(比如第10层的网络需要使用来自网络第一层的输出结果),这部分的特征图就会一直占用显存。

不使用过大全连接


相比于卷积的参数,全连接的参数量可就大多了。因为卷积只是一个局部的连接,而全连接则是一个全局的连接。举个栗子:


卷积的参数只与输出的通道数、卷积核大小相关。在不考虑偏置的情况下,卷积核大小为3,输入通道为32,输出通道数为64的时候,参数量大小为3 ∗ 3 ∗ 32 ∗ 64 = 18432

image.png

所以一般来说,特征图比较大的时候,直接用全连接显卡会直接冒烟。因此往往只能在深层或者特征进行压缩之后才能够使用全连接。比如像SENet中,就是先将特征图使用GAP(Global Average Pooling)之后,才使用全连接,并且在全连接的中间层还是用了一定的压缩倍率。


亦或者可以像ECA-Net那般,不使用全连接,采用邻域连接的方式来减少计算量。


计算过程优化

使用checkpoint


PyTorch在0.4版本后推出了一个新功能,可以将一个模型的计算过程分为两半。也就是说,如果一个模型训练需要占用的显存太大,可以先计算网络的一半,保存后半部分所需要的中奖结果,再计算后半部分。当然,这样的操作显然是一个牺牲时间换空间的方法没,其使用方式如下:


# 常规写法
def forward(self, x):
  x = self.conv_1(x)
  x = self.conv_2(x)
  x = self.conv_3(x)
  return x
# 引入checkpoint
from torch.utils.checkpoint import checkpoint
def forward(self, x):
  x = checkpoint(self.conv_1(x), x)
  x = checkpoint(self.conv_2(x), x)
  x = checkpoint(self.conv_3(x),x)
  return x

梯度累加

大多数情况下,其实我们降低显存就是为了获得更大的Batchsize,因此使用gradient accumulation(梯度累加)也可以达到类似的效果。一般来说,我们使用pytorch写网络的训练过程主要是下面这个流程:


for i in range(epochs):
        optimizer.zero_grad()                   # 梯度清零
        outputs = network(input)                   # 正向传播
        loss = criterion(output, label)       # 计算损失
        loss.backward()                         # 反向传播,计算梯度
        optimizer.step()                        # 更新参数

而梯度累加的代码则只需要多一步:

for i in range(epochs):
        optimizer.zero_grad()                   # 梯度清零
        outputs = network(input)                   # 正向传播
        loss = criterion(output, label)/accumulation_steps
        if (i+1) % accumulation_steps == 0: 
            optimizer.step()                    # 更新参数
            optimizer.zero_grad()               # 梯度清零

通过这种方法能够比较简单的在有限的内存下模拟更大batchsize 的效果,并且效果也比较接近。

降低计算精度


PyTorch中,所有Tensor默认的精度都是FP32,也就是说每一个浮点型参数都需要占用32bit的显存。因此,如果直接把精度降低到FP16,那理论上直接就能减少一半的显存占用。那么,古尔丹,代价是什么呢?代价就是,在反向传播的过程中,大多数更新值都非常小但不为零。反向传播的舍入误差可以把这些数字变成0或者nans,使得梯度更新不准确,影响网络的收敛。ICLR2018论文中Mixed Precision Training发现,使用FP16进行训练的网络约有5%的梯度都会被“吞掉”。


4246534d78798cf330670b5f0c4711f7.png

在PyTorch1.6之前,降低训练进度普遍使用的都是NVIDIA提供的apex库。而在1.6版本之后,PyTorch推出了AMP(Automatic mixed precision),自动混合精度训练。这套技术并不是简单的将所有的参数降低精度,而是根据不同向量的不同操作对于误差的敏感程度来决定其使用的是FP16还是FP32。其使用起来也十分简单,下面是一个简单的例子,代码参考知乎:

from torch.cuda.amp import autocast,GradScaler
# 创建model,默认是torch.FloatTensor
model = Net().cuda()
optimizer = optim.SGD(model.parameters(), ...)
# 在训练最开始之前实例化一个GradScaler对象
scaler = GradScaler()
for epoch in epochs:
    for input, target in data:
        optimizer.zero_grad()
        # 前向过程(model + loss)开启 autocast
        with autocast():
            output = model(input)
            loss = loss_fn(output, target)
        # Scales loss. 为了梯度放大.
        scaler.scale(loss).backward()
        # scaler.step() 首先把梯度的值unscale回来.
        # 如果梯度的值不是 infs 或者 NaNs, 那么调用optimizer.step()来更新权重,
        # 否则,忽略step调用,从而保证权重不更新(不被破坏)
        scaler.step(optimizer)
        # 准备着,看是否要增大scaler
        scaler.update()


终极解决办法

加钱

写在最后

如果您也对人工智能和计算机视觉全栈领域感兴趣,强烈推荐您关注有料、有趣、有爱的公众号『CVHub』,每日为大家带来精品原创、多领域、有深度的前沿科技论文解读及工业成熟解决方案!欢迎添加小编微信号:cv_huber,一起探讨更多有趣的话题!


相关实践学习
在云上部署ChatGLM2-6B大模型(GPU版)
ChatGLM2-6B是由智谱AI及清华KEG实验室于2023年6月发布的中英双语对话开源大模型。通过本实验,可以学习如何配置AIGC开发环境,如何部署ChatGLM2-6B大模型。
目录
相关文章
|
10月前
|
人工智能 并行计算 Linux
斯坦福黑科技让笔记本GPU也能玩转AI视频生成!FramePack:压缩输入帧上下文长度!仅需6GB显存即可生成高清动画
斯坦福大学推出的FramePack技术通过压缩输入帧上下文长度,解决视频生成中的"遗忘"和"漂移"问题,仅需6GB显存即可在普通笔记本上实时生成高清视频。
2509 19
斯坦福黑科技让笔记本GPU也能玩转AI视频生成!FramePack:压缩输入帧上下文长度!仅需6GB显存即可生成高清动画
|
TensorFlow 算法框架/工具 异构计算
GPU 显存释放
GPU 显存释放
721 1
|
异构计算 算法框架/工具 TensorFlow
|
4月前
|
人工智能 算法 调度
阿里云ACK托管集群Pro版共享GPU调度操作指南
本文介绍在阿里云ACK托管集群Pro版中,如何通过共享GPU调度实现显存与算力的精细化分配,涵盖前提条件、使用限制、节点池配置及任务部署全流程,提升GPU资源利用率,适用于AI训练与推理场景。
457 1
|
4月前
|
人工智能 城市大脑 运维
喜讯!阿里云国产异构GPU云平台技术荣获“2025算力中国·年度重大成果”
2025年8月23日,在工业和信息化部新闻宣传中心、中国信息通信研究院主办的2025中国算力大会上,阿里云与浙江大学联合研发的“国产异构GPU云平台关键技术与系统”荣获「算力中国·年度重大成果」。该评选旨在选拔出算力产业具有全局性突破价值的重大成果,是业内公认的技术创新“风向标”。
575 0
|
9月前
|
存储 机器学习/深度学习 数据库
阿里云服务器X86/ARM/GPU/裸金属/超算五大架构技术特点、场景适配参考
在云计算技术飞速发展的当下,云计算已经渗透到各个行业,成为企业数字化转型的关键驱动力。选择合适的云服务器架构对于提升业务效率、降低成本至关重要。阿里云提供了多样化的云服务器架构选择,包括X86计算、ARM计算、GPU/FPGA/ASIC、弹性裸金属服务器以及高性能计算等。本文将深入解析这些架构的特点、优势及适用场景,以供大家了解和选择参考。
1345 61
|
存储 机器学习/深度学习 人工智能
2025年阿里云GPU服务器租用价格、选型策略与应用场景详解
随着AI与高性能计算需求的增长,阿里云提供了多种GPU实例,如NVIDIA V100、A10、T4等,适配不同场景。2025年重点实例中,V100实例GN6v单月3830元起,适合大规模训练;A10实例GN7i单月3213.99元起,适用于混合负载。计费模式有按量付费和包年包月,后者成本更低。针对AI训练、图形渲染及轻量级推理等场景,推荐不同配置以优化成本和性能。阿里云还提供抢占式实例、ESSD云盘等资源优化策略,支持eRDMA网络加速和倚天ARM架构,助力企业在2025年实现智能计算的效率与成本最优平衡。 (该简介为原文内容的高度概括,符合要求的字符限制。)
|
10月前
|
存储 机器学习/深度学习 算法
阿里云X86/ARM/GPU/裸金属/超算等五大服务器架构技术特点、场景适配与选型策略
在我们选购阿里云服务器的时候,云服务器架构有X86计算、ARM计算、GPU/FPGA/ASIC、弹性裸金属服务器、高性能计算可选,有的用户并不清楚他们之间有何区别。本文将深入解析这些架构的特点、优势及适用场景,帮助用户更好地根据实际需求做出选择。
|
边缘计算 调度 对象存储
部署DeepSeek但IDC GPU不足,阿里云ACK Edge虚拟节点来帮忙
介绍如何使用ACK Edge与虚拟节点满足DeepSeek部署的弹性需求。

热门文章

最新文章