探索CUDA out of 如何释放GPU显存是memory背后的原因?
时间:2025-06-24 11:57:33 来源:新华社
【字体:  

目录。

  • 1 问题背景。
  • 2 问题探索。
    • 2.1 CUDA固有显存。
    • 2.2 激活和失活显存。
    • 2.3 释放GPU显存。
  • 3 问题总结。
  • 4 告别bug。

1 问题背景。

研究过深度学习的学生,一定熟悉以下CUDA显存溢出错误。

RuntimeError: CUDA out of memory. Tried to allocate 916.00 MiB (GPU 0; 6.00 GiB total capacity; 4.47 GiB already allocated; 186.44 MiB free; 4.47 GiB reserved in total by PyTorch)。

本文探讨了CUDA的内存管理机制,并对问题的解决方法进行总结。

在这里插入图片描述

本文探讨了CUDA的内存管理机制,并总结了这个问题的解决方案。

2 问题探索。

2.1 CUDA固有显存。在实验开始前,先清空环境󿀌终端输入。

在这里插入图片描述
nvidia-smi。

接下来,向GPU存储一个小的张量。import。 torchdevice。=torch。.。device。(。'cuda'if。torch。.。cuda。.。is_available。(。)。else。'cpu')。torch。.。randn。(。(。2。,3。)。,device。=device。

)。占用显存的情况如下c;共计。

在这里插入图片描述
448M。

当我们增加张的尺寸࿰时c;例如。torch。.。randn。(。(。200。,300。,200。,20。)。,device。=device。

)。此时GPU占用也随之上升c;共计。

在这里插入图片描述

1362M。这表明:GPU显存占用率与存储的数据尺寸成正相关,显存占用的数据越多。

,这其实是废话,但是这句话反过来:数据占用越小,显存越小吗?做个实验。torch。.。randn。(。(。1。,1。)。,device。=device。

)。仍然占用。

在这里插入图片描述
448M。事实上,这是因为CUDA运行时,其固件将占用一定的显存,在本机软硬件环境下。448M。,不同的CUDA版本或显卡型号固件显存不同。换句话说,#xff0c;只要使用GPU,至少会占。 x x。x。显存,而且这部分显存无法释放。

2.2 激活和失活显存。

  • 给出以下代码󿀌哪一个会报错࿱?f;
    代码A。 x1。=torch。.。randn。(。(。200。,300。,200。,20。)。,device。=device。)。 x2。=torch。.。randn。(。(。200。,300。,200。,20。)。,device。=device。)。 x3。=torch。.。randn。(。(。200。,300。,200。,20。)。,device。=device。)。 x4。=torch。.。randn。(。(。200。,300。,200。,20。)。,device。=device。)。 x5。=torch。.。randn。(。(。200。,300。,200。,20。)。,device。=device。)。 x6。=torch。.。randn。(。(。200。,300。,200。,20。)。,device。=device。
  • )。
    代码B。 x。=torch。.。randn。(。(。200。,300。,200。,20。)。,device。=device。)。 x。=torch。.。randn。(。(。200。,300。,200。,20。)。,device。=device。)。 x。=torch。.。randn。(。(。200。,300。,200。,20。)。,device。=device。)。 x。=torch。.。randn。(。(。200。,300。,200。,20。)。,device。=device。)。 x。=torch。.。randn。(。(。200。,300。,200。,20。)。,device。=device。)。 x。=torch。.。randn。(。(。200。,300。,200。,20。)。,device。=device。

)。答案可以猜到,代码A报错了,显存为CUDA。激活机制。有关。CUDA目前的数据空间可以看作是队列,队列中有两种内存。激活内存(Activate Memory。)和。失活内存(Unactivate Memory)。

。当一个内存不再被变量引用时,,内存由激活内存转变为失活内存,但它仍然存在于这个数据队列中。

接下来,添加了一个新的数据,CUDA将释放部分失活内存,用于存储新数据。如果新数据占用的空间大于队列中的所有失活内存,从显存到队列࿰,再申请一部分空间c;相当于队列的容量扩展;如果新数据占用空间约等于队列中的失活内存,然后CUDA显存的占用率几乎保持不变。

可以实验证󿀌运行。 x。=torch。.。randn。(。(。200。,300。,200。,20。)。,device。=device。)。 x。=torch。.。randn。(。(。200。,300。)。,device。=device。

)。显存占用为。1364M。

,单独运行。 x。=torch。.。randn。(。(。200。,300。,200。,20。)。,device。=device。

)。的。1362M。

相比相似󿀌但当新数据占用的空间大于队列中的所有失活内存时。 x。=torch。.。randn。(。(。200。,300。,200。,20。)。,device。=device。)。 x。=torch。.。randn。(。(。300。,300。,300。,20。)。,device。=device。

)。显存占用率飙升至。3422M。当数据队列达到一定阈值时,,CUDA触发垃圾回收机制,清理失活的内存。

上述实验解释了深度学习中非常常见的代码。for。images。, labels。in。train_bar。:。images。, labels。=images。.。to。(。config。.。device。)。,labels。.。to。(。config。.。device。)。# 梯度清零。opt。.。zero_grad。(。)。# 正向传播。 outputs。=model。(。images。)。# 计算损失。 loss。=F。.。cross_entropy。(。outputs。,labels。)。# 反向传播。loss。.。backward。(。)。# 模型更新。opt。.。step。(。

)。

为什么能保持GPU显存不变?本质上,这就是上述代码B的执行过程。

2.3 释放GPU显存。

操作下面的命令可以手动清理GPU数据队列中的失活内存。torch。.。cuda。.。empty_cache。(。

)。

需要注意的是,,上述命令可能需要多次才能释放空间,比如。 x。=torch。.。randn。(。(。200。,300。,200。,20。)。,device。=device。)。 x。=torch。.。randn。(。(。200。,300。,200。,20。)。,device。=device。)。 x。=torch。.。randn。(。(。200。,300。,200。,20。)。,device。=device。)。 x。=torch。.。randn。(。(。200。,300。,200。,20。)。,device。=device。)。 x。=torch。.。randn。(。(。200。,300。,200。,20。)。,device。=device。)。 x。=torch。.。randn。(。(。200。,300。,200。,20。)。,device。=device。)。 x。=

1。此时。x。指向了。int。类型,因此,GPU数据队列中的空间没有被变量引用,说明所有的队列都是失活内存,但此时运行。nvidia-smi。仍有。2278M。占用,进一步运行。torch.cuda.empty_cache()。然后可以恢复。448M。

基本占用-虽然GPU上没有数据,但是固件已经开始运行󿀌因此,占用不能释放。

3 问题总结。关于CUDA GPU。显存管理。

  • 总结:
  • GPU显存占用率与存储的数据尺寸成正相关,显存占用的数据越多。只要使用GPU,至少会占。 x x。x。
  • 显存,而且这部分显存无法释放。
  • 当一个内存不再被变量引用时,,内存由激活内存转变为失活内存,但它仍然存在于这个数据队列中。
  • 当数据队列达到一定阈值时,,CUDA触发垃圾回收机制,清理失活的内存。运行。torch.cuda.empty_cache()。

失活的内存可以手动清理。

  • 然后根据上述理论�可以得到相应的问题解决方案。调小。

    batch_size。

  • 本质上是为了防止GPU数据队列向显存申请的空间大于显存本身。

    检查GPU中是否有数据在未发布的情况下继续存储。

    例如: app。=[。]。for。 _。in。range。(。1000。)。:。app。.。append。(。torch。.。randn。(。(。200。,300。,200。,20。)。,device。=device。)。

    )。这里。append。函数相当于获得张量。torch.randn((200, 300, 200, 20), device=device)。

  • 存储在列表中的副本,因此,每次存储的张量都会被隐式引用,GPU继续增加激活内存而不释放,导致崩溃。在测试阶段和验证阶段之前插入代码。

    with torch.no_grad()。

原理是不计算梯度,因此,不需要GPU加速运算�不要在数据队列中添加数据。

4 告别bug。本文收录了《告别bug》专栏,本专栏记录了人工智能领域的各种bug,以备复习,文章形式:问题背景 + 问题探索 + 问题解决。


,订阅栏目+关注博主后,可以通过下面的名片联系我,进入AI技术交流小组帮助解决问题。 🔥更多精彩的专栏。

  • 从入门到精通ROS。
  • Pytorch深度学习实战。
  • 《机器学习强基计划》。
  • 《体育规划实战精讲》。

…。
👇源码获取 · 技术交流 · 抱团学习 · 咨询分享 请联系👇

[责任编辑:百度一下]
检察日报数字报 | 正义网 |
Copyrights©最高人民检察院 All Rights Reserved.