PyTorch示例程序内存占用过高原因排查求助
注意:请谨慎运行以下代码,maxlen设置过高会耗尽电脑所有可用内存,导致系统卡顿甚至完全无响应,无法通过shell终止进程,只能重启电脑。
import numpy as np import collections import torch # q = collections.deque(maxlen=1500) # 占用约6.4GB # q = collections.deque(maxlen=3000) # 占用约12GB q = collections.deque(maxlen=5000) # 占用约18GB def f(): nparray = np.zeros([4,84,84], dtype=np.uint8) q.append(nparray) nparray1 = np.zeros([32,4,84,84], dtype=np.float32) tens = torch.tensor(nparray1, dtype=torch.float32) while True: f()
我正在调试这个内存占用极高的程序,通过top命令的VIRT列测量内存时,发现实际占用远超预期:比如将maxlen从1500增至3000,我计算的预期内存增长仅约403MB(计算方式:4 * 84 * 84 * 15000 / (1024**2)),但实际增长了6GB。
另外发现collections.deque和PyTorch张量的分配存在交互:注释掉两者中任意一项,内存占用就会恢复正常——比如注释张量创建行后,总内存仅约2GB。恳请帮忙分析原因。
核心原因解析
PyTorch内存池的惰性释放机制
PyTorch创建张量时会向系统申请连续内存块,即使张量局部引用消失,内存池也不会立即释放这块内存,而是留作后续张量复用。循环持续创建张量时,内存池会不断膨胀,直到耗尽系统可用内存。Deque持有numpy数组导致GC延迟
deque持续持有numpy数组,会让这些数组的引用计数始终不为0,Python垃圾回收(GC)无法主动回收它们。这会持续占用系统内存,导致PyTorch内存池无法有效复用已申请的内存块,只能不断向系统申请新内存,最终推高VIRT内存占用。两者交互放大内存问题
单独注释deque时,numpy数组在函数结束后失去引用,GC会及时回收,系统内存压力小,PyTorch内存池可以正常复用内存;单独注释张量时,没有持续的内存池膨胀,内存占用自然正常。但两者同时存在时,deque占用内存导致系统内存紧张,PyTorch无法复用内存,只能持续申请新内存,最终出现内存占用远超预期的情况。
验证与解决方向
- 手动触发GC:在循环中加入
import gc; gc.collect(),可部分缓解内存占用,但无法彻底解决PyTorch内存池的惰性释放问题。 - 调整PyTorch内存策略:如果使用GPU,可调用
torch.cuda.empty_cache()释放显存;CPU环境下可尝试设置torch.set_num_threads(1)减少内存开销,或使用torch.utils.checkpoint优化内存占用。 - 优化deque使用:限制
maxlen不要过大,或定期清理deque中的旧元素,释放numpy数组的引用,让GC可以回收内存,缓解系统内存压力。
内容的提问来源于stack exchange,提问作者Julian Francis

