技术背景

笔者在执行一个Jax的任务中,又发现了一个奇怪的问题,就是明明只分配了很小的矩阵空间,但是在多次的任务执行之后,显存突然就爆了。而且此时已经按照Jax的官方说明配置了XLA_PYTHON_CLIENT_PREALLOCATE这个参数为false,也就是不进行显存的预分配(默认会分配90%的显存空间以供使用)。然后在网上找到了一些类似的问题,比如参考链接中的1、2、3、4,都是在一些操作后发现未释放显存,这里提供一个实例问题和处理的思路,如果有更好的方案欢迎大家在评论区留言。

问题复现

在未执行任何GPU的任务时,我们可以看到此时nvidia-smi的输出如下:

Tue Dec 14 16:14:32 2021
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 470.42.01 Driver Version: 470.42.01 CUDA Version: 11.4 |
|-------------------------------+----------------------+----------------------+
| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |
| | | MIG M. |
|===============================+======================+======================|
| 0 Quadro RTX 4000 On | 00000000:03:00.0 On | N/A |
| 30% 43C P8 20W / 125W | 1260MiB / 7979MiB | 10% Default |
| | | N/A |
+-------------------------------+----------------------+----------------------+
| 1 Quadro RTX 4000 On | 00000000:A6:00.0 Off | N/A |
| 30% 34C P8 7W / 125W | 10MiB / 7982MiB | 0% Default |
| | | N/A |
+-------------------------------+----------------------+----------------------+ +-----------------------------------------------------------------------------+
| Processes: |
| GPU GI CI PID Type Process name GPU Memory |
| ID ID Usage |
|=============================================================================|
| 0 N/A N/A 1673 G /usr/lib/xorg/Xorg 110MiB |
| 0 N/A N/A 3015 G /usr/lib/xorg/Xorg 661MiB |
| 0 N/A N/A 3251 G /usr/bin/gnome-shell 132MiB |
| 0 N/A N/A 1142734 G ...AAAAAAAAA= --shared-files 64MiB |
| 0 N/A N/A 1337710 G ...AAAAAAAAA= --shared-files 80MiB |
| 0 N/A N/A 1371509 G ...369783.log --shared-files 63MiB |
| 0 N/A N/A 1506625 G ...AAAAAAAAA= --shared-files 89MiB |
| 1 N/A N/A 1673 G /usr/lib/xorg/Xorg 4MiB |
| 1 N/A N/A 3015 G /usr/lib/xorg/Xorg 4MiB |
+-----------------------------------------------------------------------------+

此时启动一个ipython的终端窗口,执行如下的Jax任务:

In [1]: import numpy as np

In [2]: import os
...: os.environ['CUDA_VISIBLE_DEVICES']='1'
...: os.environ["XLA_PYTHON_CLIENT_PREALLOCATE"] = "false" In [3]: from jax import numpy as jnp In [4]: a = np.ones(1000000) In [5]: b = jnp.array(a)

此时再次查看nvidia-smi的结果如下:

Tue Dec 14 16:18:26 2021
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 470.42.01 Driver Version: 470.42.01 CUDA Version: 11.4 |
|-------------------------------+----------------------+----------------------+
| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |
| | | MIG M. |
|===============================+======================+======================|
| 0 Quadro RTX 4000 On | 00000000:03:00.0 On | N/A |
| 30% 42C P8 20W / 125W | 1238MiB / 7979MiB | 10% Default |
| | | N/A |
+-------------------------------+----------------------+----------------------+
| 1 Quadro RTX 4000 On | 00000000:A6:00.0 Off | N/A |
| 30% 36C P0 35W / 125W | 114MiB / 7982MiB | 0% Default |
| | | N/A |
+-------------------------------+----------------------+----------------------+ +-----------------------------------------------------------------------------+
| Processes: |
| GPU GI CI PID Type Process name GPU Memory |
| ID ID Usage |
|=============================================================================|
| 0 N/A N/A 1673 G /usr/lib/xorg/Xorg 110MiB |
| 0 N/A N/A 3015 G /usr/lib/xorg/Xorg 661MiB |
| 0 N/A N/A 3251 G /usr/bin/gnome-shell 129MiB |
| 0 N/A N/A 1142734 G ...AAAAAAAAA= --shared-files 44MiB |
| 0 N/A N/A 1337710 G ...AAAAAAAAA= --shared-files 80MiB |
| 0 N/A N/A 1371509 G ...369783.log --shared-files 63MiB |
| 0 N/A N/A 1506625 G ...AAAAAAAAA= --shared-files 89MiB |
| 1 N/A N/A 1673 G /usr/lib/xorg/Xorg 4MiB |
| 1 N/A N/A 3015 G /usr/lib/xorg/Xorg 4MiB |
| 1 N/A N/A 1743467 C /usr/local/bin/python 101MiB |
+-----------------------------------------------------------------------------+

此时的结果还是比较符合我们的预期的,这个python的进程占用了101MB的空间。但是此时如果我们在ipython中把这个对象删除了:

In [6]: del b

In [7]: b
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-7-89e6c98d9288> in <module>
----> 1 b NameError: name 'b' is not defined

然后再次查看nvidia-smi的结果:

Tue Dec 14 16:21:12 2021
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 470.42.01 Driver Version: 470.42.01 CUDA Version: 11.4 |
|-------------------------------+----------------------+----------------------+
| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |
| | | MIG M. |
|===============================+======================+======================|
| 0 Quadro RTX 4000 On | 00000000:03:00.0 On | N/A |
| 30% 42C P5 21W / 125W | 1231MiB / 7979MiB | 0% Default |
| | | N/A |
+-------------------------------+----------------------+----------------------+
| 1 Quadro RTX 4000 On | 00000000:A6:00.0 Off | N/A |
| 30% 35C P8 7W / 125W | 114MiB / 7982MiB | 0% Default |
| | | N/A |
+-------------------------------+----------------------+----------------------+ +-----------------------------------------------------------------------------+
| Processes: |
| GPU GI CI PID Type Process name GPU Memory |
| ID ID Usage |
|=============================================================================|
| 0 N/A N/A 1673 G /usr/lib/xorg/Xorg 110MiB |
| 0 N/A N/A 3015 G /usr/lib/xorg/Xorg 662MiB |
| 0 N/A N/A 3251 G /usr/bin/gnome-shell 111MiB |
| 0 N/A N/A 1142734 G ...AAAAAAAAA= --shared-files 55MiB |
| 0 N/A N/A 1337710 G ...AAAAAAAAA= --shared-files 80MiB |
| 0 N/A N/A 1371509 G ...369783.log --shared-files 63MiB |
| 0 N/A N/A 1506625 G ...AAAAAAAAA= --shared-files 89MiB |
| 1 N/A N/A 1673 G /usr/lib/xorg/Xorg 4MiB |
| 1 N/A N/A 3015 G /usr/lib/xorg/Xorg 4MiB |
| 1 N/A N/A 1743467 C /usr/local/bin/python 101MiB |
+-----------------------------------------------------------------------------+

此时我们可以看到,虽然已经把对象给删除了,在python的程序中已然找不到这个对象,但是在显存中的数据并未被消除。而且如果一直不消除,这块显存就会一直占用在那里,直到python进程(此时作为该进程的一个守护进程)的结束。

解决思路

暂时还不清楚这个问题发生的机制,在一些特定场景下出现僵尸进程的问题似乎跟我复现的这个场景也有所不同。只是考虑到在python的进程结束之后,这一块的显存还是被成功释放了的,因此我考虑直接用进程的方法来解决这个显存分配和清空的方法,以下是一个基于进程实现的案例:

import os
os.environ['CUDA_VISIBLE_DEVICES']='1'
os.environ["XLA_PYTHON_CLIENT_PREALLOCATE"] = "false" import time
from multiprocessing import Pool
import numpy as np
from jax import numpy as jnp a = np.ones(1000000) def f(a):
b = jnp.array(a)
time.sleep(2)
print('Array b has been deleted!')
return True with Pool(1) as p:
res = p.map(f, [(a,)]) print ('Is jax array deleted successfully?\t{}'.format(res))
time.sleep(6)

在这个程序中,我们把要执行的相关任务,包含GPU矩阵的转化与分配,都放到了一个函数中,然后通过multiprocessing开启一个子进程,来执行这个任务,并且在任务中甚至不需要手动执行del这个删除的操作。这么一来,我们既可以实现对象的即时销毁,也通过进程控制的机制确保在显存中占用的位置被清空。如果进程执行中存在一些问题,还可以通过terminate的操作来直接杀死进程,同样也可以确保显存占用不会发生堆积的情况。程序的执行结果如下:

Array b has been deleted!
Is jax array deleted successfully? [True]

在程序执行的过程中我们也可以看到,在nvidia-smi中的显存占用,仅仅持续了2秒,也就是我们在函数内部设置的进程sleep参数。而在之后6秒的sleep时间中,这一块内存占用是被清空了的,这也就达到了我们最初的目的。当然,最重要的是,我们依然可以从函数中获取到返回值,这就确保后面有需要存储或者使用到的参数不被同步的销毁。需要注意的是,在同等条件下,如果不使用子进程来执行这个函数,而是直接使用res=f(a)的形式来执行,作为临时变量的b最终依然存在于显存之中,这是一个非常可怕的事情。

总结概要

在使用一些python的GPU模块,或者写CUDA时,有时会发现显存被无端占用的场景,即时执行了cudaFree()或者python的del操作,也无法消除这一块的显存占用。最终我们发现,可以通过额外开启一个子进程的方法来封装相关的操作,通过对进程的存活控制来实现对GPU显存占用的控制,有可能是一个临时规避问题的思路。

版权声明

本文首发链接为:https://www.cnblogs.com/dechinphy/p/gc.html

作者ID:DechinPhy

更多原著文章请参考:https://www.cnblogs.com/dechinphy/

打赏专用链接:https://www.cnblogs.com/dechinphy/gallery/image/379634.html

腾讯云专栏同步:https://cloud.tencent.com/developer/column/91958

参考链接

  1. https://blog.csdn.net/jzrita/article/details/80719297
  2. https://blog.csdn.net/xxs8457800/article/details/104307283
  3. https://jermine.vdo.pub/python/解决gpu显存未释放问题/
  4. https://blog.csdn.net/weixin_42317730/article/details/116786526?share_token=7ef0f7d6-6d68-4efb-995b-24517000ac11&tt_from=copy_link&utm_source=copy_link&utm_medium=toutiao_android&utm_campaign=client_share?=linux清理gpu内存,GPU内存在CUDA脚本执行后无法

关于python中显存回收的问题的更多相关文章

  1. Python中的垃圾回收与del语句

    python中的垃圾回收采用计数算法 一个对象如果被引用N次,则需要N次(即计算引用次数为零时)执行del 才能回收此对象. a = 100 b = a del a print(b) print(a) ...

  2. Python中的垃圾回收机制

    Python的垃圾回收机制 引子: 我们定义变量会申请内存空间来存放变量的值,而内存的容量是有限的,当一个变量值没有用了(简称垃圾)就应该将其占用的内存给回收掉,而变量名是访问到变量值的唯一方式,所以 ...

  3. 【转载】Python中的垃圾回收机制

    GC作为现代编程语言的自动内存管理机制,专注于两件事:1. 找到内存中无用的垃圾资源 2. 清除这些垃圾并把内存让出来给其他对象使用.GC彻底把程序员从资源管理的重担中解放出来,让他们有更多的时间放在 ...

  4. Python 中的垃圾回收机制--备忘

    GC作为现代编程语言的自动内存管理机制,专注于两件事:1. 找到内存中无用的垃圾资源 2. 清除这些垃圾并把内存让出来给其他对象使用.GC彻底把程序员从资源管理的重担中解放出来,让他们有更多的时间放在 ...

  5. Python 中的垃圾回收机制(转载)

    from: https://foofish.net/python-gc.html GC作为现代编程语言的自动内存管理机制,专注于两件事:1. 找到内存中无用的垃圾资源 2. 清除这些垃圾并把内存让出来 ...

  6. Python中的垃圾回收机制(转)

    原文:https://foofish.net/python-gc.html GC作为现代编程语言的自动内存管理机制,专注于两件事:1. 找到内存中无用的垃圾资源 2. 清除这些垃圾并把内存让出来给其他 ...

  7. Python 中的垃圾回收机制

    GC作为现代编程语言的自动内存管理机制,专注于两件事:1. 找到内存中无用的垃圾资源 2. 清除这些垃圾并把内存让出来给其他对象使用.GC彻底把程序员从资源管理的重担中解放出来,让他们有更多的时间放在 ...

  8. python中的垃圾回收机制及原理

    序言: 来一起看看: 不同于C/C++,像Python这样的语言是不需要程序员写代码来管理内存的,它的GC(Garbage Collection)机制 实现了自动内存管理.GC做的事情就是解放程序员的 ...

  9. 如何在Python中显式释放内存?

    根据Python官方文档,您可以强制垃圾收集器释放未引用的内存gc.collect().例: import gc gc.collect() 所属网站分类: python高级 > 综合&其 ...

随机推荐

  1. Effective C++ 总结笔记(六)

    七.模板与泛型编程 41.了解隐式接口和编译器多态 1.类和模板都支持接口和多态. 2.类的接口是显式定义的--函数签名.多态是通过虚函数在运行期体现的. 3.模板的接口是隐式的(由模板函数的实现代码 ...

  2. 部署一个支持Dapr 的Kubernetes APISIX Ingress

    在这篇文章中,我将展示如何创建一个 APISIX控制器,该控制器在 Kubernetes 集群中公开启用 Dapr 的应用程序. 本质上,APISIX控制器将配置相同的标准 Dapr annotati ...

  3. 体验.NET Core使用IKVM对接Java

    前言 与第三方对接最麻烦的是语言不同,因语言不同内置实现相关标准加密算法还是略微有所差异,对接单点登录场景再寻常不过,由于时间紧迫且对接方使用Java,所以留给我对接开发和联调的时间本就不多,于是乎, ...

  4. [loj3503]滚榜

    一个小问题:题意中关于$b_{i}$的顺序只需要单调不降即可,相同时可任意选择 考虑$i$优于$j$的条件,即$val_{i}\ge val_{j}+[i>j]$,并记$del_{i,j}=\m ...

  5. [hdu4747]Mex

    首先计算出以1为左端点的所有区间的mex,考虑删除左端点仍然维护这个序列:设当前删除点下一次出现在y,y~n的mex不变,从左端点到y的点中大于删除值的点要变成删除值,因为这个是不断递增的,所以是一段 ...

  6. [bzoj1927]星际竞速

    考虑没有爆发,那么相当于是带权最小不可交路径覆盖,由于只能从编号小的到编号大的,因此一定是DAG,而DAG的最小路径覆盖可以拆点并跑最大流,那么带权的只需要跑费用流即可(S向i连(1,0)的边,i'向 ...

  7. [loj2470]有向图

    参考ExtremeSpanningTrees,考虑优化整体二分时求$g_{i}\in \{w_{mid},w_{mid+1}\}$的最优解 对于$m=n-1$的问题,不需要去网络流,可以直接树形dp ...

  8. [atAGC007E]Shik and Travel

    二分枚举答案,判定答案是否合法 贪心:每一个叶子只能经过一遍,因此叶子的顺序一定是一个dfs序,即走完一棵子树中的所有叶子才会到子树外 根据这个贪心可以dp,设$f[k][l][r]$表示仅考虑$k$ ...

  9. sb 错误

    数组开小.很容易 \(2 \times 10^5\) 或 \(10^6\) 就开成 \(10^5\),或者各种变量的数据范围混用,\(m \leq 5\times 10^5\),结果只开到了 \(n\ ...

  10. AT695 マス目

    AT695 マス目 本题选自 DP 优化方法大杂烩 状压部分. 这个题很 nb.下文记 \(n=H\),\(m=W\). 对于每一列,如果只记录一个格子是否为黑色,那么发现它无法处理从右边绕到左边再绕 ...