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

31

从零开始使用:

>>> 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:分离(detach),发送到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 内存,则可以尝试此方法来清除内存。 - akshayk07
3个回答

17
似乎PyTorch的缓存分配器即使没有张量也会保留一定数量的内存,而第一个CUDA内存访问会触发此分配(torch.cuda.empty_cache()从缓存中删除未使用的张量,但缓存本身仍然会使用一些内存)。
即使有一个只有1个元素的小张量,在执行deltorch.cuda.empty_cache()后,GPUtil.showUtilization(all=True)报告的GPU内存使用量与巨大张量的使用量完全相同(torch.cuda.memory_cached()torch.cuda.memory_allocated()都返回零)。

15

来自PyTorch文档

内存管理

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

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


Torch 可能还会为内部系统/管理器(可能是其分配器)等事物分配内存。 - Tiphaine
关于empty_cache()的句子怎么样?对我来说,它清晰地表明它确实释放了内存,也就是说,它以“nvidia-smi-free”的方式免费。 - BlameTheBits
确实如此,但不幸的是,它也声明仅释放由PyTorch视为未使用的内存...这可能是那个顽固的5%。 - Stanowczo
是的。正如@Tiphaine所建议的那样,可能还有一些内部问题。但这5%的OP大约占用了半个Gigabyte!然而,文档只提到了“未使用的缓存内存”和“张量占用的GPU内存”,而这5%似乎都不属于这两种情况。 - BlameTheBits
请注意,cuda和torch为内核分配了一些内存,因此即使您在GPU上创建非常小的张量,它仍然可能占用高达1.5GB的GPU内存:https://github.com/pytorch/pytorch/issues/12873#issuecomment-482916237。这个内存不被认为是由pytorch的分配器分配或保留的。 - chiragjn
memory_cached()max_memory_cached()现在被称为memory_reserved()max_memory_reserved() - Maikefer

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

然而,这种方法只适用于知道确切哪些变量正在占用内存的情况...当训练深度学习模型时并非总是如此,尤其是使用第三方库时。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接