PyTorch训练循环中显存持续上涨,调用torch.cuda.empty_cache()无效的问题求助
PyTorch训练循环中显存持续上涨,调用torch.cuda.empty_cache()无效的问题求助
老哥我太懂你这种显存越跑越高最后崩掉的痛苦了!看了你的代码,问题根源其实出在Dataset的__getitem__方法里直接在GPU上创建张量,再加上DataLoader的多进程加载机制,导致显存泄漏根本没法通过主进程的del和empty_cache()解决。
问题具体原因:
你在__getitem__里写了image = torch.tensor(image).cuda()和label = torch.tensor(label).cuda(),而DataLoader设置了num_workers=4——这会启动4个子进程来并行加载数据。每个子进程都会独立地在GPU上分配内存创建张量,但这些子进程的显存空间是和主进程隔离的!你在主循环里调用的del和torch.cuda.empty_cache()只能清理主进程的GPU内存,完全管不到子进程里的那些张量,它们会一直占着显存不释放,每加载一批数据就多占一点,最后自然就OOM了。
解决方法:
直接把GPU张量的创建逻辑移到主进程里,Dataset只负责加载CPU张量,修改后的代码大概是这样:
import torch from torch.utils.data import Dataset, DataLoader import numpy as np import gc # 加上垃圾回收模块 class CustomDataset(Dataset): def __init__(self, data_paths): self.data_paths = data_paths def __len__(self): return len(self.data_paths) def __getitem__(self, idx): # 这里只返回CPU张量,不要碰GPU image = np.load(self.data_paths[idx]['image']).astype(np.float32) label = np.load(self.data_paths[idx]['label']).astype(np.int64) image = torch.tensor(image) label = torch.tensor(label) return image, label data_paths = [{'image': f'img_{i}.npy', 'label': f'label_{i}.npy'} for i in range(10000)] dataset = CustomDataset(data_paths) # pin_memory=True可以保留CPU内存页锁,加速后续GPU转移,这个没问题 dataloader = DataLoader(dataset, batch_size=32, num_workers=4, pin_memory=True) device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') for epoch in range(10): for batch in dataloader: images, labels = batch # 主进程里把张量移到GPU images = images.to(device, non_blocking=True) labels = labels.to(device, non_blocking=True) output = images.mean() loss = output.sum() loss.backward() # 清理时加上垃圾回收 del images, labels, loss, output torch.cuda.empty_cache() gc.collect() # 触发Python垃圾回收,确保CPU上的引用被彻底清理
额外补充几个小细节:
- 你之前试的
num_workers=0没用,是因为即使单进程,在__getitem__里创建的GPU张量,DataLoader内部的逻辑也可能导致引用没被正确释放,还是不如在主进程移GPU靠谱。 .detach()在这里没用,因为问题根本不是计算图的引用,而是子进程的GPU张量泄漏。- 以后记住:永远不要在DataLoader的worker进程(也就是Dataset的__getitem__里)直接操作GPU,所有GPU相关的操作都放在主进程的训练循环里。
备注:内容来源于stack exchange,提问作者Andrew3553




