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

Posted

技术标签:

【中文标题】为啥清除对象后GPU中的内存仍在使用中?【英文标题】:Why is the memory in GPU still in use after clearing the object?为什么清除对象后GPU中的内存仍在使用中? 【发布时间】:2019-12-21 02:31:01 【问题描述】:

从零使用开始:

>>> import gc
>>> import GPUtil
>>> import torch
>>> GPUtil.showUtilization()
| ID | GPU | MEM |
------------------
|  0 |  0% |  0% |
|  1 |  0% |  0% |
|  2 |  0% |  0% |
|  3 |  0% |  0% |

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

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

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

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

不,不起作用。

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

尝试2:删除变量

不,这也不起作用

>>> del x
>>> GPUtil.showUtilization()
| ID | GPU | MEM |
------------------
|  0 |  0% | 26% |
|  1 |  0% |  0% |
|  2 |  0% |  0% |
|  3 |  0% |  0% |

尝试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% |

尝试 4: 可能清除垃圾收集器。

不,5% 仍在被占用

>>> gc.collect()
0
>>> GPUtil.showUtilization()
| ID | GPU | MEM |
------------------
|  0 |  0% |  5% |
|  1 |  0% |  0% |
|  2 |  0% |  0% |
|  3 |  0% |  0% |

尝试 5: 尝试完全删除 torch(就好像 del x 不起作用时那样可行 -_- )

不,它没有...*

>>> del torch
>>> GPUtil.showUtilization()
| ID | GPU | MEM |
------------------
|  0 |  0% |  5% |
|  1 |  0% |  0% |
|  2 |  0% |  0% |
|  3 |  0% |  0% |

然后我尝试检查gc.get_objects(),看起来里面仍然有很多奇怪的THCTensor 东西......

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

【问题讨论】:

首先,使用nvidia-smi确认哪个进程正在使用GPU内存。那里的进程 id pid 可用于查找进程。如果没有显示任何进程但GPU内存仍在使用,您可以尝试this method清除内存。 【参考方案1】:

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

即使是很小的 1 元素张量,在 deltorch.cuda.empty_cache() 之后,GPUtil.showUtilization(all=True) 报告的 GPU 内存量与用于巨大张量的完全相同(torch.cuda.memory_cached()torch.cuda.memory_allocated() 都返回零)。

【讨论】:

【参考方案2】:

来自PyTorch docs:

内存管理

PyTorch 使用缓存内存分配器来加速内存 分配。这允许在没有设备的情况下快速释放内存 同步。但是,分配器管理的未使用内存 仍会显示为在 nvidia-smi 中使用。您可以使用 memory_allocated()max_memory_allocated() 监控内存 被张量占用,并使用memory_cached()max_memory_cached() 监控缓存分配器管理的内存。打电话 empty_cache() 从 PyTorch 中释放所有未使用的缓存内存,以便 这些可以被其他 GPU 应用程序使用。但是占用的GPU 张量的内存不会被释放,所以它不能增加数量 可用于 PyTorch 的 GPU 内存。

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

【讨论】:

Torch 也可能为内部系统/管理器之类的东西分配内存。可能是它的分配器。 关于empty_cache()的那句话呢?对我来说,这显然听起来像是真的释放了内存,也就是说它也以“nvidia-smi-free”的方式免费。 确实如此,但不幸的是,它也声明它只释放 pytorch 认为未使用的内存......这可能是这个挥之不去的 5%。 是的。正如@Tiphaine 所建议的那样,必须有一些内部的东西。但是这 5% 的 OP 大约是 0.5GB!再一次,文档只知道“未使用的缓存内存”和“张量占用的 GPU 内存”,这 5% 似乎都不属于。 还要注意cuda和torch为内核分配了一些内存,所以即使你在gpu上做一个非常小的张量,它仍然可以占用1.5GB的GPU内存:github.com/pytorch/pytorch/issues/12873#issuecomment-482916237这个内存不是被 pytorch 的分配器视为已分配或保留【参考方案3】:

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

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**

尽管如此,这种方法仅适用于确切知道哪些变量持有内存的情况......我猜在训练深度学习模式时并非总是如此,尤其是在使用第三方库时。

【讨论】:

以上是关于为啥清除对象后GPU中的内存仍在使用中?的主要内容,如果未能解决你的问题,请参考以下文章

为啥清除对象的内容不会释放内存?

调用 intern() 方法后,内存中的 new String() 对象何时被清除

如何清除Tensorflow-Keras GPU内存?

Java垃圾回收算法

JVM垃圾收集算法(标记-清除复制标记-整理)

如何从 SQL Server 内存中清除 SqlDependency?