【问题标题】: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清除内存。

标签: python memory-leaks garbage-collection gpu pytorch


【解决方案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**
    

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

    【讨论】:

      猜你喜欢
      • 2022-10-05
      • 2013-05-11
      • 1970-01-01
      • 2022-01-10
      • 1970-01-01
      • 1970-01-01
      • 2022-12-15
      • 1970-01-01
      • 2017-02-07
      相关资源
      最近更新 更多