清除对象后,为何仍在使用GPU中的内存?

alv*_*vas 9 python garbage-collection memory-leaks gpu pytorch

从零使用开始:

>>> import gc
>>> import GPUtil
>>> import torch
>>> GPUtil.showUtilization()
| ID | GPU | MEM |
------------------
|  0 |  0% |  0% |
|  1 |  0% |  0% |
|  2 |  0% |  0% |
|  3 |  0% |  0% |
Run Code Online (Sandbox Code Playgroud)

然后创建一个足够大的张量并占用内存:

>>> x = torch.rand(10000,300,200).cuda()
>>> GPUtil.showUtilization()
| ID | GPU | MEM |
------------------
|  0 |  0% | 26% |
|  1 |  0% |  0% |
|  2 |  0% |  0% |
|  3 |  0% |  0% |
Run Code Online (Sandbox Code Playgroud)

然后我尝试了几种方法来查看张量是否消失。

尝试1:分离,发送到CPU并覆盖变量

不,不行。

>>> x = x.detach().cpu()
>>> GPUtil.showUtilization()
| ID | GPU | MEM |
------------------
|  0 |  0% | 26% |
|  1 |  0% |  0% |
|  2 |  0% |  0% |
|  3 |  0% |  0% |
Run Code Online (Sandbox Code Playgroud)

尝试2:删除变量

不,这也不起作用

>>> del x
>>> GPUtil.showUtilization()
| ID | GPU | MEM |
------------------
|  0 |  0% | 26% |
|  1 |  0% |  0% |
|  2 |  0% |  0% |
|  3 |  0% |  0% |
Run Code Online (Sandbox Code Playgroud)

尝试3:使用torch.cuda.empty_cache()功能

似乎有效,但似乎还有一些挥之不去的开销...

>>> torch.cuda.empty_cache()
>>> GPUtil.showUtilization()
| ID | GPU | MEM |
------------------
|  0 |  0% |  5% |
|  1 |  0% |  0% |
|  2 |  0% |  0% |
|  3 |  0% |  0% |
Run Code Online (Sandbox Code Playgroud)

尝试4:也许清除垃圾收集器。

否,仍需5%

>>> gc.collect()
0
>>> GPUtil.showUtilization()
| ID | GPU | MEM |
------------------
|  0 |  0% |  5% |
|  1 |  0% |  0% |
|  2 |  0% |  0% |
|  3 |  0% |  0% |
Run Code Online (Sandbox Code Playgroud)

尝试5:尝试torch完全删除(好像在del x不工作时可以正常工作-_-)

不,不是... *

>>> del torch
>>> GPUtil.showUtilization()
| ID | GPU | MEM |
------------------
|  0 |  0% |  5% |
|  1 |  0% |  0% |
|  2 |  0% |  0% |
|  3 |  0% |  0% |
Run Code Online (Sandbox Code Playgroud)

然后我试图检查一下gc.get_objects(),看来里面还有很多奇怪的THCTensor东西……

知道为什么清除缓存后内存仍在使用吗?

Sta*_*czo 16

来自PyTorch 文档

内存管理

PyTorch 使用缓存内存分配器来加速内存分配。这允许快速内存释放而无需设备同步。但是,分配器管理的未使用内存仍将显示为在 nvidia-smi 中使用。您可以使用 memory_allocated()max_memory_allocated()来监视张量占用的内存,并使用memory_cached()和 来max_memory_cached() 监视缓存分配器管理的内存。调用 empty_cache()会释放 PyTorch 中所有未使用的缓存内存,以便其他 GPU 应用程序可以使用这些内存。但是,张量占用的 GPU 内存不会被释放,因此无法增加 PyTorch 可用的 GPU 内存量。

我将提到nvidia-smi 的部分加粗,据我所知,GPUtil 使用它。


小智 9

感谢分享!我遇到了同样的问题,我用你的例子来调试。基本上,我的发现是:

  • collect() 和empty_cache() 仅在删除变量后才起作用
  • del var +empty_cache()释放缓存分配的内存
  • del var +collect()仅释放分配的内存
  • 不管怎样,从 nvidia-smi 中仍然可以看到一些内存使用开销

下面是一些重现实验的代码:

    
import gc
import torch

def _get_less_used_gpu():
    from torch import cuda
    cur_allocated_mem = {}
    cur_cached_mem = {}
    max_allocated_mem = {}
    max_cached_mem = {}
    for i in range(cuda.device_count()):
        cur_allocated_mem[i] = cuda.memory_allocated(i)
        cur_cached_mem[i] = cuda.memory_reserved(i)
        max_allocated_mem[i] = cuda.max_memory_allocated(i)
        max_cached_mem[i] = cuda.max_memory_reserved(i)
    print(cur_allocated_mem)
    print(cur_cached_mem)
    print(max_allocated_mem)
    print(max_cached_mem)
    min_all = min(cur_allocated_mem, key=cur_allocated_mem.get)
    print(min_all)
    return min_all

x = torch.rand(10000,300,200, device=0)

# see memory usage
_get_less_used_gpu()
>{0: 2400000000, 1: 0, 2: 0, 3: 0}
>{0: 2401239040, 1: 0, 2: 0, 3: 0}
>{0: 2400000000, 1: 0, 2: 0, 3: 0}
>{0: 2401239040, 1: 0, 2: 0, 3: 0}
> *nvidia-smi*: 3416MiB

# try delete with empty_cache()
torch.cuda.empty_cache()
_get_less_used_gpu()
>{0: 2400000000, 1: 0, 2: 0, 3: 0}
>{0: 2401239040, 1: 0, 2: 0, 3: 0}
>{0: 2400000000, 1: 0, 2: 0, 3: 0}
>{0: 2401239040, 1: 0, 2: 0, 3: 0}
> *nvidia-smi*: 3416MiB

# try delete with gc.collect()
gc.collect()
_get_less_used_gpu()
>{0: 2400000000, 1: 0, 2: 0, 3: 0}
>{0: 2401239040, 1: 0, 2: 0, 3: 0}
>{0: 2400000000, 1: 0, 2: 0, 3: 0}
>{0: 2401239040, 1: 0, 2: 0, 3: 0}
> *nvidia-smi*: 3416MiB

# try del + gc.collect()
del x 
gc.collect()
_get_less_used_gpu()
>{0: **0**, 1: 0, 2: 0, 3: 0}
>{0: 2401239040, 1: 0, 2: 0, 3: 0}
>{0: 2400000000, 1: 0, 2: 0, 3: 0}
>{0: 2401239040, 1: 0, 2: 0, 3: 0}
> *nvidia-smi*: 3416MiB

# try empty_cache() after deleting 
torch.cuda.empty_cache()
_get_less_used_gpu()
>{0: 0, 1: 0, 2: 0, 3: 0}
>{0: **0**, 1: 0, 2: 0, 3: 0}
>{0: 2400000000, 1: 0, 2: 0, 3: 0}
>{0: 2401239040, 1: 0, 2: 0, 3: 0}
> *nvidia-smi*: **1126MiB**

# re-create obj and try del + empty_cache()
x = torch.rand(10000,300,200, device=0)
del x
torch.cuda.empty_cache()
_get_less_used_gpu()
>{0: **0**, 1: 0, 2: 0, 3: 0}
>{0: **0**, 1: 0, 2: 0, 3: 0}
>{0: 2400000000, 1: 0, 2: 0, 3: 0}
>{0: 2401239040, 1: 0, 2: 0, 3: 0}
> *nvidia-smi*: **1126MiB**
Run Code Online (Sandbox Code Playgroud)

尽管如此,这种方法仅适用于人们确切知道哪些变量保存着记忆的情况……我想,当人们训练深度学习模式时,情况并非总是如此,尤其是在使用第三方库时。


Ser*_*nko 5

看起来PyTorch的缓存分配器即使没有张量也保留一些固定数量的内存,并且此分配是由第一次CUDA内存访问触发的(torch.cuda.empty_cache()从缓存中删除未使用的张量,但缓存本身仍使用一些内存)。

即使有微小的1张量元素,后deltorch.cuda.empty_cache()GPUtil.showUtilization(all=True)报告作为用于一个巨大的张量GPU存储器的量是相同的(并且两个torch.cuda.memory_cached()torch.cuda.memory_allocated()返回零)。