这不是真正的内存泄漏,而是多进程 fork、Python 引用机制与序列化开销共同导致 RSS 持续增长;random_split 强引用原始数据、__getitem__ 返回 list 触发 pickle 堆积、pin_memory=True 被 nvidia-smi 误计为 GPU 显存是主因。

这不是真正的内存泄漏,而是多进程 fork + Python 引用机制 + 序列化开销共同导致的 RSS 持续增长;你看到的“越训越卡”,大概率是 random_split 强引用原始数据、__getitem__ 返回 list 触发 pickle 堆积、或 pin_memory=True 被 nvidia-smi 误计为 GPU 显存。
random_split 怎么偷偷复制全部数据?
它返回的 Subset 不做数据切片,只存索引列表和对原始 Dataset 的强引用。一旦你的 self.image_paths = [] 是 Python list,每个 worker 进程 fork 时都会复制整个 list —— 即使只用其中 2% 的样本。
- Linux 下 fork 后引用计数翻倍,Copy-on-Write 失效,所有 worker 都持有独立副本
- 用
np.array(self.image_paths, dtype='U256')替换list,可让 fork 保持共享视图 - 更彻底的做法:不用
random_split,直接按索引范围构造两个新Dataset实例,彻底切断引用链
__getitem__ 返回 list 为什么让内存卡住?
worker 进程要把 batch 传给主进程,必须走 multiprocessing.Queue,而 queue 依赖 pickle。Python list 序列化时会反复创建临时对象,尤其当元素是长字符串(如路径)时,GC 来不及回收,RSS 就堆上去了。
- 错误写法:
return [img_array, label_str]或return self.labels[idx](若self.labels是list) - 正确写法:把所有元数据转成
np.ndarray,__getitem__返回np.ndarray或torch.tensor - 如果必须用字符串,考虑用
struct.pack编码为 bytes,或统一转为固定长度np.array(dtype='U256')
pin_memory=True 怎么让 nvidia-smi 显示显存爆炸?
它不占 GPU 显存,但会在 CPU 端分配大量锁页内存(pinned memory),而 NVIDIA 驱动层会把这部分计入 nvidia-smi 的 “GPU Memory” 字段。实际 torch.cuda.memory_allocated() 可能只有几百 MB。
- 每个 worker 进程都分配独立 pinned buffer,默认
prefetch_factor=2,意味着预取 2 个 batch,内存占用直接翻倍 - 临时解法:
prefetch_factor=1+num_workers=2,多数场景吞吐下降不到 10%,但 RSS 峰值降 40%+ - 进阶控制:Linux 下加
torch.multiprocessing.set_sharing_strategy('file_system'),避免 fork 复制 pinned buffer
真正棘手的是这些现象混合出现:一个 random_split 引用大 list,加上 __getitem__ 返回 list,再叠上 pin_memory=True 和默认 prefetch_factor,三者叠加会让 RSS 在前 5 个 epoch 就涨到不可收拾。排查时别只盯着单点,要一起看。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











