Pytorch显存分配机制与显存占用分析方法

9b2ca92fa7c0d046b0c53a7ebad8c91c.png

文 | Connolly@知乎(已授权)
源 | 极市平台

作者最近两年在研究分布式并行,经常使用PyTorch框架。一开始用的时候对于PyTorch的显存机制也是一知半解,连蒙带猜的,经常来知乎上来找答案,那么我就吸收大家的看法,为PyTorch的显存机制做个小的总结吧。

实验环境:
OS: Ubuntu18.04
python: 3.7.4
PyTorch: 1.9.1
GPU: V100

99682211b37abb2873355cf64dc54904.png一、理论知识78b1cb7e93eba04a865e3dd2eabad626.png

1. 深度学习训练过程

开门见山的说,PyTorch在进行深度学习训练的时候,有4大部分的显存开销,分别是模型参数(parameters),模型参数的梯度(gradients),优化器状态(optimizer states)以及中间激活值(intermediate activations) 或者叫中间结果(intermediate results)。

为了后面显存分析阐述的方便,我将深度学习的训练定义4个步骤:

1.模型定义:定义了模型的网络结构,产生模型参数;
while(你想训练):2.前向传播:执行模型的前向传播,产生中间激活值;3.后向传播:执行模型的后向传播,产生梯度;4.梯度更新:执行模型参数的更新,第一次执行的时候产生优化器状态。

在模型定义完之后,2~4循环执行。

2. 前向传播

拿Linear层(或者叫Dense层,前馈神经网络,全连接层等等...)举例:假设他的权重矩阵为W,偏置向量为b,那么他的前向计算过程就是:

这里的X为该层的输入向量,Y为输出向量(中间激活值)

3. 后向传播(反向传播)

参考了这篇文章:

题目:《神经网络反向传播的数学原理》
链接:
https://zhuanlan.zhihu.com/p/22473137

后向传播回来了一个第l+1层的输出误差矩阵 ,用以计算该层的梯度和输入误差

402 Payment Required

4. 梯度更新

接下来就是利用W_diff和b_diff进行更新了:

402 Payment Required

当然使用Adam优化器的时候,实际的更新过程并没有上面的这么简单。目前用的最多的是AdamW,可以看看这篇文章。

但是使用这一类优化器,也会带来额外的显存开销。对于每一个参数,Adam都会为它准备对应的2个优化器状态,分别是动量(momentum)和方差(variance),用以加速模型的训练。

58ac331ce9864639c3fa271ae6f3aeb0.png二、显存分析与Torch机制e12cf5032c2325ccfa33d52337552646.png

2.1. 分析方法

(1) No Nvidia-smi

我看很多人现在还在用nvidia-smi来看pytorch的显存占用,盯着跳来跳去的torch缓存区分析真的不累吗。

而且PyTorch是有缓存区的设置的,意思就是一个Tensor就算被释放了,进程也不会把空闲出来的显存还给GPU,而是等待下一个Tensor来填入这一片被释放的空间。

有什么好处?进程不需要重新向GPU申请显存了,运行速度会快很多,有什么坏处?

他不能准确地给出某一个时间点具体的Tensor占用的显存,而是显示的已经分配到的显存缓冲区和torch在创建cuda进程时所需开销的和,也就是reserved_memory和torch context显存之和

这也是令很多人在使用PyTorch时对显存占用感到困惑的罪魁祸首。

(2) torch.cuda is all you need.

在分析PyTorch的显存时候,一定要使用torch.cuda里的显存分析函数,我用的最多的是torch.cuda.memory_allocated()和torch.cuda.max_memory_allocated(),前者可以精准地反馈当前进程中torch.Tensor所占用的GPU显存(注意是只包括torch.Tensor),后者则可以告诉我们到调用函数为止所达到的最大的显存占用字节数。

还有像torch.cuda.memory_reserved()这样的函数则是查看当前进程所分配的显存缓冲区是多少的。

非~常~好~用

fb0b6f9c0f2005b8b3e8256cad4347ba.jpeg
▲Torch 官方文档

2.2. PyTorch context开销

-----之前没有提到PyTorch context的开销,做个补充...

我注意到有很多同学在做显存分析的时候是为了在训练的时候可以把卡的显存用满,这个之前没有考虑到呢。其实PyTorch context是我们在使用torch的时候的一个大头开销。

主要参考的是论坛里的这篇讨论:

How do I create Torch Tensor without any wasted storage space/baggage?

https://link.zhihu.com/?target=https%3A//discuss.pytorch.org/t/how-do-i-create-torch-tensor-without-any-wasted-storage-space-baggage/131134

什么是PyTorch context? 其实官方给他的称呼是CUDA context,就是在第一次执行CUDA操作,也就是使用GPU的时候所需要创建的维护设备间工作的一些相关信息。如下图所示

579b09fa38d6d369d2193348c7492321.jpeg

这个值跟CUDA的版本,pytorch的版本以及所使用的设备都是有关系的。目前我在ubuntu的torch1.9上测过RTX 3090和V100的context 开销。其中3090用的CUDA 11.4,开销为1639MB;V100用的CUDA 10.2,开销为1351MB。

感兴趣的同学可以在shell中执行下面这两行代码,然后用nvidia-smi去看看自己的环境里context的大小。然后用总大小减去context的大小再做显存分析。

import torch
temp = torch.tensor([1.0]).cuda()

我估计会有人问怎么去减小这个开销...官方也给了一个办法,看看自己有哪些cuda依赖是不需要的,比如cuDNN,然后自己重新编译一遍PyTorch。编译的时候把对应的包的flag给设为false就好了。我是还没有试过,要搭编译的环境太难受了,而且还要经常和库做更新。

2.3 Torch显存分配机制

在PyTorch中,显存是按页为单位进行分配的,这可能是CUDA设备的限制。就算我们只想申请4字节的显存,CUDA也会为我们分配512字节或者1024字节的空间。

2.4 Torch显存释放机制

在PyTorch中,只要一个Tensor对象在后续不会再被使用,那么PyTorch就会自动回收该Tensor所占用的显存,并以缓冲区的形式继续占用显存。要是实在看缓冲区不爽的话,也可以用torch.cuda.empty_cache()把它归零,但是程序速度会变慢哦

6a6afe1ea5a4295085b2e2eccd547169.png3 训练过程显存分析cb43aac012b09f7cfe1c6a5b13e7b113.png

为了让大家方便理解,我这里用torch.nn.Linear(1024, 1024, bias=False) 来做例子。

为了省事,loss函数则直接对输出的样本进行求和得到。没办法,想直接执行loss.backward()的话,loss得是标量才行呢。示例代码:

import torchmodel = torch.nn.Linear(1024,1024, bias=False).cuda() 
optimizer = torch.optim.AdamW(model.parameters())
inputs = torch.tensor([1.0]*1024).cuda() # shape = (1024)
outputs = model(inputs) # shape = (1024)
loss = sum(outputs) # shape = (1)
loss.backward()
optimizer.step()

3.1 模型的定义

结论:显存占用量约为参数量乘以4

import torchmodel = torch.nn.Linear(1024,1024, bias=False).cuda() 
print(torch.cuda.memory_allocated())

打印出来的数值为4194304,刚好等于1024×1024×4。

3.2 前向传播过程

结论:显存增加等于每一层模型产生的结果的显存之和,且跟batch_size成正比。

inputs = torch.tensor([1.0]*1024).cuda() # shape = (1024)  memory + 4096
outputs = model(inputs) # memory + 4096

代码中,outputs为产生的中间激活值,同时它也恰好是该模型的输出结果。在执行完这一步之后,显存增加了4096字节。(不算inputs的显存的话)。

3.3 后向传播过程

后向传播会将模型的中间激活值给消耗并释放掉掉,并为每一个模型中的参数计算其对应的梯度。在第一次执行的时候,会为模型参数分配对应的用来存储梯度的空间。

loss = sum(outputs) # memory + 512(torch cuda分配最小单位)
temp = torch.cuda.memory_allocated()
loss.backward()
print(torch.cuda.memory_allocated() - temp) # 第一次增加4194304

第一次执行时显存增加:4194304字节 - 激活值大小;第二次以后执行显存减少:激活值大小;Note:由于这个中间激活值被赋给了outputs,所以后面在后向传播的时候会发现,这个outputs的显存没有被释放掉。

但是当层数变深的时候,就能明显看到变化了。为了让大家看到变化,再写一段代码~

import torch# 模型初始化
linear1 = torch.nn.Linear(1024,1024, bias=False).cuda() # + 4194304
print(torch.cuda.memory_allocated())
linear2 = torch.nn.Linear(1024, 1, bias=False).cuda() # + 4096
print(torch.cuda.memory_allocated())# 输入定义
inputs = torch.tensor([[1.0]*1024]*1024).cuda() # shape = (1024,1024) # + 4194304
print(torch.cuda.memory_allocated())# 前向传播
loss = sum(linear2(linear1(inputs))) # shape = (1) # memory + 4194304 + 512
print(torch.cuda.memory_allocated())# 后向传播
loss.backward() # memory - 4194304 + 4194304 + 4096
print(torch.cuda.memory_allocated())# 再来一次~
loss = sum(linear2(linear1(inputs))) # shape = (1) # memory + 4194304  (512没了,因为loss的ref还在)
print(torch.cuda.memory_allocated())
loss.backward() # memory - 4194304
print(torch.cuda.memory_allocated())

3.4 参数更新

optimizer.step() # 第一次增加8388608,第二次就不增不减了哦

第一次执行时,会为每一个参数初始化其优化器状态,对于这里的AdamW而言,每一个参数需要4*2=8个字节。

第二次开始,不会再额外分配显存。显存开销:第一次: 增加8388608字节第二次及以后: 无增减3.5 Note由于计算机计算的特性,有一些计算操作在计算过程中是会带来额外的显存开销的。

但是这种开销在torch.memory_allocated中是不能被察觉的。

比如在AdamW在进行某一层的更新的时候,会带来2倍该层参数量大小的临时额外开销。这个在max_memory_allocated中可以看到。在本例中就是8388608字节。

09f1a787a70794894510c33016a56128.jpeg后台回复关键词【入群

加入卖萌屋NLP、CV、搜广推与求职讨论群

0a9b97f6496c5f2500864231a579b9aa.png

[1]http://jwc.hnu.edu.cn/info/1185/10103.htm

[2]https://www.zhihu.com/question/547254904

[3]https://www.thepaper.cn/newsDetail_forward_19313268

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/477051.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring Cloud Stream 使用延迟消息实现定时任务(RabbitMQ)

应用场景 我们在使用一些开源调度系统(比如:elastic-job等)的时候,对于任务的执行时间通常都是有规律性的,可能是每隔半小时执行一次,或者每天凌晨一点执行一次。然而实际业务中还存在另外一种定时任务&am…

LeetCode 1380. 矩阵中的幸运数(set)

1. 题目 给你一个 m * n 的矩阵,矩阵中的数字 各不相同 。请你按 任意 顺序返回矩阵中的所有幸运数。 幸运数是指矩阵中满足同时下列两个条件的元素: 在同一行的所有元素中最小在同一列的所有元素中最大 示例 1: 输入:matrix …

GARFIELD@10-07-2004

tit for tat转载于:https://www.cnblogs.com/rexhost/archive/2004/10/07/49560.html

DeepMind 发了篇论文,把我看笑了

文 | severus近日,曾开发出举世瞩目的 AlphaGo 的 DeepMind,在 ArXiv 上发表了一篇文章,名为:Meaning without reference in large language models文中提到,大参数规模的语言模型是已经具备了部分类人智能的&#xff…

Spring Cloud Stream消费失败后的处理策略(三):使用DLQ队列(RabbitMQ)

应用场景 前两天我们已经介绍了两种Spring Cloud Stream对消息失败的处理策略: 自动重试:对于一些因环境原因(如:网络抖动等不稳定因素)引发的问题可以起到比较好的作用,提高消息处理的成功率。自定义错误…

LeetCode 1382. 将二叉搜索树变平衡(中序遍历+二分递归)

1. 题目 给你一棵二叉搜索树,请你返回一棵 平衡后 的二叉搜索树,新生成的树应该与原来的树有着相同的节点值。 如果一棵二叉搜索树中,每个节点的两棵子树高度差不超过 1 ,我们就称这棵二叉搜索树是 平衡的 。 如果有多种构造方…

电影:『新警察故事』

【电影名称】:『新警察故事』 【主 演】: 成龙 谢霆锋 杨采妮 蔡卓妍 吴彦祖  【导 演】: 陈木胜 【内容简介】:《新警察故事》是成龙英皇电影公司的处女作,投资超过1亿6000万港元,请来香港顶尖电影…

NLP顶级赛事LIC2022霸榜经验分享!

语言是人类传递信息最重要的媒介,让机器理解语言并进行交互是人工智能的重要挑战。为推动语言与智能领域的技术发展和应用,中国中文信息学会、中国计算机学会和百度公司连续五年联合举办“语言与智能技术竞赛”,为中文NLP研究者和开发者提供同…

Spring Cloud Zuul中使用Swagger汇总API接口文档

有很多读者问过这样的一个问题:虽然使用Swagger可以为Spring MVC编写的接口生成了API文档,但是在微服务化之后,这些API文档都离散在各个微服务中,是否有办法将这些接口都整合到一个文档中?之前给大家的回复都只是简单的…

LeetCode 1381. 设计一个支持增量操作的栈(deque/数组)

1. 题目 请你设计一个支持下述操作的栈。 实现自定义栈类 CustomStack : CustomStack(int maxSize):用 maxSize 初始化对象,maxSize 是栈中最多能容纳的元素数量,栈在增长到 maxSize 之后则不支持 push 操作。void push(int x)…

GARFIELD@10-31-2004

apprentice转载于:https://www.cnblogs.com/rexhost/archive/2004/10/31/59013.html

Spring Cloud构建微服务架构:分布式服务跟踪(入门)【Dalston版】

通过之前的N篇博文介绍,实际上我们已经能够通过使用它们搭建起一个基础的微服务架构系统来实现我们的业务需求了。但是,随着业务的发展,我们的系统规模也会变得越来越大,各微服务间的调用关系也变得越来越错综复杂。通常一个由客户…

有哪些值得计算机专业学生加入的国企?

文 |重庆搬砖喵知乎知乎上最近有个问题很火:有哪些值得计算机专业学生加入的国企?这个问题确实很应今年秋招的景,于是转载了知乎答主重庆搬砖喵 的高赞回答分享给大家。原回答链接:https://www.zhihu.com/question/285730093/answ…

上海著名综合性商厦一览 (1)

上海著名综合性商厦一览 jxjb 2004-10-29 11:53:21 发表于搜狐焦点上海房地产网-谈房论市-东方康洛论坛 主要包括:第一百货东楼 华联商厦 置地广场 友谊欧洲商城 东方商厦 港汇广场 汇金百货 梅龙镇广场 中环广场二百永新 正大广场 上海第一八佰伴友谊南方商城…

LeetCode 1383. 最大的团队表现值(贪心,优先队列,难)

1. 题目 公司有编号为 1 到 n 的 n 个工程师,给你两个数组 speed 和 efficiency ,其中 speed[i] 和 efficiency[i] 分别代表第 i 位工程师的速度和效率。 请你返回由最多 k 个工程师组成的 ​​​​​​最大团队表现值 ,由于答案可能很大&am…

程序员坐牢了,会被安排去写代码吗?

文 | 无念源 | 知乎今天给大家分享一篇有意思的爽文,但也是根据多年之前一个真实报道改编而来的。本文字数较多,建议先收藏,上下班路上、带薪上厕所、浑水摸鱼时再慢慢看~本故事纯属虚构请大家不要随意模仿,后果自负!因…

Dubbo将积极适配Spring Cloud生态,Spring Cloud体系或将成为微服务的不二选择!

2016年,我在博客中发表过一篇《微服务架构的基础框架选择:Spring Cloud还是Dubbo?》获得了很大的阅读量和转载量。在这篇文章中,我主要对比了Spring Cloud与Dubbo所具备的能力,并阐述了个人推崇Spring Cloud的原因。但…

Java sdk及tomcat安装设置

在安装好Java SDK后,还需要设置一些系统变量,系统变量的设置可以在系统属性-高级-环境变量中进行 JAVA_HOMEJAVA安装目录 CLASSPATH… 库路径,可以是目录或jar文件,如C:\j2sdk1.4.0_01\lib\dt.jar;d:\java&#xf…

剑指Offer - 面试题51. 数组中的逆序对(归并排序,求逆序对)

1. 题目 在数组中的两个数字&#xff0c;如果前面一个数字大于后面的数字&#xff0c;则这两个数字组成一个逆序对。输入一个数组&#xff0c;求出这个数组中的逆序对的总数。 示例 1: 输入: [7,5,6,4] 输出: 5限制&#xff1a; 0 < 数组长度 < 50000来源&#xff1a;力…

【小马哥】Spring Cloud系列讲座

这里推荐一个不错的Spring Cloud系列讲座&#xff0c;讲师简介如下&#xff1a; 小马哥&#xff0c;阿里巴巴技术专家&#xff0c;从事十余年Java EE 开发&#xff0c;国内微服务技术讲师。目前主要负责微服务技术推广、架构设计、基础设施、迁移等。重点关注云计算、微服务以及…