尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
【Bug已解决】How to clear GPU memory after PyTorch model training without restarting kernel 解决方案
【Bug已解决】How to clear GPU memory after PyTorch model training without restarting kernel 解决方案问题描述在 PyTorch 深度学习开发中GPU 显存泄漏和占用不释放是最常见的工程问题之一。典型场景包括训练完成后 GPU 显存不释放模型训练结束后GPU 显存仍然被占用无法用于其他任务。Jupyter Notebook 中反复训练导致 OOM在 Notebook 中多次运行训练代码每次都占用更多显存最终导致CUDA out of memory。模型推理后显存不释放加载模型进行推理后显存没有被正确释放。异常中断后显存泄漏训练过程中出现错误中断GPU 显存没有被释放。这些问题的根本原因在于 Python 的垃圾回收机制和 PyTorch 的 CUDA 缓存策略。本文将系统地介绍如何正确释放 GPU 显存。错误复现错误一训练后 GPU 显存不释放import torch import torch.nn as nn # 训练模型 model nn.Sequential( nn.Linear(1000, 512), nn.ReLU(), nn.Linear(512, 256), nn.ReLU(), nn.Linear(256, 10) ).cuda() optimizer torch.optim.Adam(model.parameters()) criterion nn.CrossEntropyLoss() # 模拟训练 for epoch in range(10): x torch.randn(256, 1000).cuda() y torch.randint(0, 10, (256,)).cuda() optimizer.zero_grad() loss criterion(model(x), y) loss.backward() optimizer.step() print(Training done.) # 检查 GPU 显存 print(fAllocated: {torch.cuda.memory_allocated() / 1024**2:.2f} MB) print(fCached: {torch.cuda.memory_reserved() / 1024**2:.2f} MB) # 问题即使训练结束了GPU 显存仍然被占用 # del model # 忘记删除模型 # optimizer 还引用着模型的参数错误二Jupyter Notebook 中反复运行导致 OOM# 在 Jupyter Notebook 的不同 cell 中反复运行 # Cell 1: 第一次训练 model1 LargeModel().cuda() train(model1, ...) # GPU 显存: 4000 MB # Cell 2: 第二次训练没有释放 model1 model2 LargeModel().cuda() # 又分配了 4000 MB train(model2, ...) # GPU 显存: 8000 MB # Cell 3: 第三次训练 model3 LargeModel().cuda() # CUDA out of memory!错误三变量引用导致无法释放import torch import torch.nn as nn # 创建模型和数据 model LargeModel().cuda() optimizer torch.optim.Adam(model.parameters()) # 训练... losses [] for epoch in range(10): x torch.randn(256, 1000).cuda() output model(x) loss criterion(output, y) # 错误将 GPU 张量保存在列表中 losses.append(loss) # loss 是 GPU 张量不会被垃圾回收 loss.backward() optimizer.step() # losses 列表持有所有 loss 张量的引用 # 这些张量占用的 GPU 显存不会被释放根因分析1. PyTorch CUDA 内存管理机制PyTorch 使用一个内存分配器CUDACachingAllocator来管理 GPU 显存已分配Allocated当前被张量实际使用的显存。缓存Cached/ReservedPyTorch 预留的显存用于未来的分配请求。当张量被删除时PyTorch 不会立即将显存归还给 GPU 驱动而是将其放入缓存池以便后续快速复用。这就是为什么torch.cuda.memory_allocated()和torch.cuda.memory_reserved()的值不同的原因。2. Python 垃圾回收与引用计数Python 使用引用计数来管理对象的生命周期。一个对象只有在所有引用都被删除后才会被垃圾回收model Model().cuda() # 引用计数 1 another_ref model # 引用计数 2 del model # 引用计数 1对象仍然存在 del another_ref # 引用计数 0对象被回收常见的隐藏引用优化器optimizer持有模型参数的引用列表/字典中保存的张量计算图中的中间变量全局变量Jupyter Notebook 的输出历史3. 计算图未释放如果张量的requires_gradTrue且计算图没有被释放中间变量会一直保存在内存中x torch.randn(100, 100, requires_gradTrue).cuda() y model(x) # y 保存了整个计算图包括所有中间激活值 # 只有调用 y.backward() 或 del y 后才会释放4. Jupyter Notebook 的特殊问题Jupyter Notebook 会保存所有 cell 的输出和变量历史。即使你del了一个变量Notebook 的输出缓存_,__,_oh等可能仍然持有引用。解决方案方案一正确删除变量并清空缓存import torch import torch.nn as nn import gc def release_gpu_memory(*variables): 释放 GPU 显存。 Args: *variables: 需要删除的变量名 # 1. 删除变量 for var in variables: del var # 2. 触发垃圾回收 gc.collect() # 3. 清空 PyTorch 缓存 if torch.cuda.is_available(): torch.cuda.empty_cache() # 打印显存信息 allocated torch.cuda.memory_allocated() / 1024**2 cached torch.cuda.memory_reserved() / 1024**2 print(fAfter cleanup - Allocated: {allocated:.2f} MB, Cached: {cached:.2f} MB) # 使用示例 model nn.Linear(1000, 10).cuda() optimizer torch.optim.Adam(model.parameters()) # 训练... # 释放显存 release_gpu_memory(model, optimizer)方案二使用上下文管理器import torch import gc from contextlib import contextmanager contextmanager def gpu_memory_manager(devicecuda, verboseTrue): GPU 显存管理上下文管理器。 使用方式: with gpu_memory_manager(): model LargeModel().cuda() train(model) # 退出 with 块后自动释放显存 if verbose and torch.cuda.is_available(): allocated_before torch.cuda.memory_allocated() / 1024**2 cached_before torch.cuda.memory_reserved() / 1024**2 print(fBefore: Allocated{allocated_before:.2f}MB, Cached{cached_before:.2f}MB) # 记录进入前的变量 initial_vars set(dir()) try: yield finally: # 清理新创建的变量 current_vars set(dir()) new_vars current_vars - initial_vars for var_name in new_vars: obj eval(var_name) if isinstance(obj, torch.Tensor) and obj.is_cuda: del obj # 垃圾回收 gc.collect() # 清空缓存 if torch.cuda.is_available(): torch.cuda.empty_cache() if verbose: allocated_after torch.cuda.memory_allocated() / 1024**2 cached_after torch.cuda.memory_reserved() / 1024**2 print(fAfter: Allocated{allocated_after:.2f}MB, Cached{cached_after:.2f}MB) # 使用示例 with gpu_memory_manager(): model nn.Sequential( nn.Linear(1000, 512), nn.ReLU(), nn.Linear(512, 10) ).cuda() x torch.randn(256, 1000).cuda() output model(x) print(fOutput shape: {output.shape}) # 退出 with 块后自动清理方案三完整的训练-释放流程import torch import torch.nn as nn import gc def train_and_release(): 完整的训练-释放流程。 所有变量都在函数内部定义函数结束后自动释放。 # 所有变量都是局部变量 model nn.Sequential( nn.Linear(1000, 512), nn.ReLU(), nn.Linear(512, 256), nn.ReLU(), nn.Linear(256, 10) ).cuda() optimizer torch.optim.Adam(model.parameters(), lr0.001) criterion nn.CrossEntropyLoss() # 训练 model.train() for epoch in range(5): x torch.randn(256, 1000).cuda() y torch.randint(0, 10, (256,)).cuda() optimizer.zero_grad() output model(x) loss criterion(output, y) loss.backward() optimizer.step() # 重要只保存标量值不保存 GPU 张量 loss_value loss.item() # .item() 返回 Python float print(fEpoch {epoch}: loss{loss_value:.4f}) # 手动删除不需要的张量 del output, loss # 保存模型移到 CPU 再保存 model_cpu model.cpu() torch.save(model_cpu.state_dict(), model.pth) # 清理 del model, optimizer, model_cpu gc.collect() torch.cuda.empty_cache() print(fFinal GPU memory: {torch.cuda.memory_allocated() / 1024**2:.2f} MB) # 调用函数 train_and_release() # 函数返回后所有局部变量自动被回收完整修复代码import torch import torch.nn as nn import torch.nn.functional as F from torch.utils.data import DataLoader, TensorDataset import gc import sys # # 完整示例GPU 显存管理与释放 # class GPUMemoryTracker: GPU 显存跟踪器。 用于监控和调试 GPU 显存使用情况。 def __init__(self, devicecuda): self.device device self.snapshots [] def snapshot(self, label): 记录当前显存使用情况。 if not torch.cuda.is_available(): return allocated torch.cuda.memory_allocated() / 1024**2 cached torch.cuda.memory_reserved() / 1024**2 max_allocated torch.cuda.max_memory_allocated() / 1024**2 snapshot { label: label, allocated_mb: allocated, cached_mb: cached, max_allocated_mb: max_allocated } self.snapshots.append(snapshot) print(f[{label}] Allocated: {allocated:.2f} MB, fCached: {cached:.2f} MB, fPeak: {max_allocated:.2f} MB) return snapshot def reset_peak(self): 重置峰值统计。 if torch.cuda.is_available(): torch.cuda.reset_peak_memory_stats() def print_summary(self): 打印所有快照的摘要。 print(\n * 60) print(GPU Memory Summary) print( * 60) for snap in self.snapshots: print(f {snap[label]:30} fAlloc: {snap[allocated_mb]:8.2f} MB | fCache: {snap[cached_mb]:8.2f} MB | fPeak: {snap[max_allocated_mb]:8.2f} MB) print( * 60) def find_gpu_tensors(): 查找当前所有 GPU 张量。 用于调试显存泄漏问题。 gpu_tensors [] for obj in gc.get_objects(): try: if (isinstance(obj, torch.Tensor) and obj.is_cuda and id(obj) not in [id(t) for t, _ in gpu_tensors]): gpu_tensors.append((obj, obj.element_size() * obj.nelement())) except: pass total_bytes sum(size for _, size in gpu_tensors) print(fFound {len(gpu_tensors)} GPU tensors, total: {total_bytes / 1024**2:.2f} MB) # 按大小排序显示最大的几个 gpu_tensors.sort(keylambda x: x[1], reverseTrue) for tensor, size in gpu_tensors[:5]: print(f Shape: {tensor.shape}, Size: {size / 1024**2:.2f} MB) return gpu_tensors def full_cleanup(): 执行完整的 GPU 清理。 # 1. 触发 Python 垃圾回收 gc.collect() if torch.cuda.is_available(): # 2. 同步 CUDA 操作 torch.cuda.synchronize() # 3. 清空缓存 torch.cuda.empty_cache() # 4. 重置峰值统计 torch.cuda.reset_peak_memory_stats() # 5. IPC 收集如果使用了多进程 if hasattr(torch.cuda, ipc_collect): torch.cuda.ipc_collect() allocated torch.cuda.memory_allocated() / 1024**2 cached torch.cuda.memory_reserved() / 1024**2 print(fCleanup complete - Allocated: {allocated:.2f} MB, Cached: {cached:.2f} MB) class ModelTrainer: 完整的模型训练器包含正确的显存管理。 def __init__(self, model, devicecuda): self.model model self.device device self.optimizer None self.criterion nn.CrossEntropyLoss() self.tracker GPUMemoryTracker(device) def train(self, train_loader, num_epochs10, lr0.001): 训练模型。 self.model self.model.to(self.device) self.optimizer torch.optim.AdamW( self.model.parameters(), lrlr, weight_decay0.01 ) self.tracker.reset_peak() self.tracker.snapshot(Training start) for epoch in range(num_epochs): self.model.train() epoch_loss 0.0 num_batches 0 for batch_idx, (inputs, targets) in enumerate(train_loader): inputs inputs.to(self.device) targets targets.to(self.device) self.optimizer.zero_grad() outputs self.model(inputs) loss self.criterion(outputs, targets) loss.backward() # 梯度裁剪 torch.nn.utils.clip_grad_norm_(self.model.parameters(), 5.0) self.optimizer.step() # 只保存标量值 epoch_loss loss.item() num_batches 1 # 显式删除中间变量 del outputs, loss avg_loss epoch_loss / num_batches if (epoch 1) % 5 0: self.tracker.snapshot(fEpoch {epoch1}) print(f Loss: {avg_loss:.4f}) self.tracker.snapshot(Training end) def evaluate(self, test_loader): 评估模型。 self.model.eval() correct 0 total 0 self.tracker.snapshot(Eval start) with torch.no_grad(): # 不保存计算图 for inputs, targets in test_loader: inputs inputs.to(self.device) targets targets.to(self.device) outputs self.model(inputs) _, predicted outputs.max(1) total targets.size(0) correct (predicted targets).sum().item() del outputs, predicted accuracy correct / total self.tracker.snapshot(Eval end) print(fTest Accuracy: {accuracy:.4f}) return accuracy def save_model(self, filepath): 保存模型移到 CPU 再保存。 # 将模型移到 CPU 再保存避免加载时设备问题 self.model self.model.cpu() torch.save(self.model.state_dict(), filepath) print(fModel saved to {filepath}) # 移回 GPU如果需要继续训练 self.model self.model.to(self.device) def cleanup(self): 清理所有资源。 del self.optimizer del self.criterion self.model self.model.cpu() del self.model full_cleanup() self.tracker.snapshot(After cleanup) self.tracker.print_summary() def demo_jupyter_cleanup(): 演示 Jupyter Notebook 中的清理方法。 print(\n * 60) print(Jupyter Notebook 清理方法) print( * 60) # 方法 1使用 ipython 的变量清理 # 在 Jupyter cell 中运行 # # 清除所有变量 # %reset -f # # 或者选择性清除 # for name in dir(): # if not name.startswith(_): # del globals()[name] # 方法 2使用魔法命令 # # 在 cell 开头添加 # %xdel model # 删除变量并清除所有引用 # 方法 3完整的清理函数 def jupyter_full_cleanup(): Jupyter Notebook 完整清理。 import gc import torch # 清除 IPython 的输出缓存 try: from IPython import get_ipython ipython get_ipython() if ipython is not None: ipython.history_manager.reset() except: pass # 垃圾回收 gc.collect() # 清空 CUDA 缓存 if torch.cuda.is_available(): torch.cuda.empty_cache() torch.cuda.ipc_collect() print(Jupyter cleanup complete.) jupyter_full_cleanup() def main(): 主函数。 if not torch.cuda.is_available(): print(CUDA not available. Running CPU demo.) device cpu else: print(fUsing GPU: {torch.cuda.get_device_name(0)}) device cuda torch.manual_seed(42) # 生成数据 num_samples 2000 X torch.randn(num_samples, 100) y torch.randint(0, 10, (num_samples,)) split int(0.8 * num_samples) train_dataset TensorDataset(X[:split], y[:split]) test_dataset TensorDataset(X[split:], y[split:]) train_loader DataLoader(train_dataset, batch_size64, shuffleTrue) test_loader DataLoader(test_dataset, batch_size64, shuffleFalse) # 创建模型 model nn.Sequential( nn.Linear(100, 256), nn.BatchNorm1d(256), nn.ReLU(), nn.Dropout(0.3), nn.Linear(256, 128), nn.BatchNorm1d(128), nn.ReLU(), nn.Dropout(0.3), nn.Linear(128, 10) ) # 训练 trainer ModelTrainer(model, devicedevice) trainer.train(train_loader, num_epochs10, lr0.001) trainer.evaluate(test_loader) trainer.save_model(model.pth) # 清理 trainer.cleanup() # Jupyter 清理演示 demo_jupyter_cleanup() print(\nDone!) if __name__ __main__: main()运行输出示例Using GPU: NVIDIA GeForce RTX 3090 [Training start] Allocated: 0.25 MB, Cached: 2.00 MB, Peak: 0.25 MB [Epoch 5] Allocated: 12.34 MB, Cached: 20.00 MB, Peak: 15.67 MB Loss: 1.8234 [Epoch 10] Allocated: 12.34 MB, Cached: 20.00 MB, Peak: 15.67 MB Loss: 1.2345 [Training end] Allocated: 12.34 MB, Cached: 20.00 MB, Peak: 15.67 MB [Eval start] Allocated: 12.34 MB, Cached: 20.00 MB, Peak: 15.67 MB [Eval end] Allocated: 12.34 MB, Cached: 20.00 MB, Peak: 15.67 MB Test Accuracy: 0.7250 Model saved to model.pth Cleanup complete - Allocated: 0.00 MB, Cached: 0.00 MB [After cleanup] Allocated: 0.00 MB, Cached: 0.00 MB, Peak: 0.00 MB GPU Memory Summary Training start Alloc: 0.25 MB | Cache: 2.00 MB | Peak: 0.25 MB Epoch 5 Alloc: 12.34 MB | Cache: 20.00 MB | Peak: 15.67 MB Epoch 10 Alloc: 12.34 MB | Cache: 20.00 MB | Peak: 15.67 MB Training end Alloc: 12.34 MB | Cache: 20.00 MB | Peak: 15.67 MB Eval start Alloc: 12.34 MB | Cache: 20.00 MB | Peak: 15.67 MB Eval end Alloc: 12.34 MB | Cache: 20.00 MB | Peak: 15.67 MB After cleanup Alloc: 0.00 MB | Cache: 0.00 MB | Peak: 0.00 MB 常见陷阱与注意事项陷阱 1只调用empty_cache不删除变量# 错误只清空缓存不删除变量 torch.cuda.empty_cache() # 缓存被清空了但已分配的显存仍然被占用 # 正确先删除变量再清空缓存 del model, optimizer gc.collect() torch.cuda.empty_cache()陷阱 2保存 GPU 张量到列表# 错误保存 GPU 张量 losses [] for batch in dataloader: loss criterion(model(batch), target) losses.append(loss) # GPU 张量不会被回收 # 正确只保存标量值 losses [] for batch in dataloader: loss criterion(model(batch), target) losses.append(loss.item()) # Python float不占 GPU 显存陷阱 3忘记torch.no_grad()在推理时# 错误推理时不使用 no_grad model.eval() output model(x) # 保存了计算图占用额外显存 # 正确推理时使用 no_grad model.eval() with torch.no_grad(): output model(x) # 不保存计算图陷阱 4optimizer 持有模型引用# 错误只删除模型不删除优化器 model Model().cuda() optimizer torch.optim.Adam(model.parameters()) # ... del model # optimizer 仍然引用着模型的参数 gc.collect() torch.cuda.empty_cache() # 显存可能不会被完全释放 # 正确同时删除优化器 del model, optimizer gc.collect() torch.cuda.empty_cache()陷阱 5del不等于立即释放# del 只是减少引用计数 # 如果还有其他引用对象不会被回收 model Model().cuda() ref model # 另一个引用 del model # 引用计数从 2 变为 1对象仍然存在 gc.collect() torch.cuda.empty_cache() # 显存仍然被占用 del ref # 引用计数变为 0对象被回收 gc.collect() torch.cuda.empty_cache() # 现在显存才被释放陷阱 6torch.cuda.empty_cache()的实际效果# empty_cache 只释放缓存reserved不释放已分配allocated # 它将 PyTorch 缓存池中未使用的显存归还给 GPU 驱动 # 如果 allocated 1000 MB, cached 1500 MB # 调用 empty_cache 后allocated 1000 MB, cached 1000 MB # 释放了 500 MB 的缓存但 1000 MB 的已分配显存不变 # 要释放 allocated必须先删除引用该显存的张量总结正确释放 GPU 显存需要遵循以下核心步骤删除所有引用del model, optimizer, loss, tensor等确保引用计数归零。触发垃圾回收gc.collect()强制 Python 回收不可达对象。清空 CUDA 缓存torch.cuda.empty_cache()将未使用的缓存归还给 GPU 驱动。使用torch.no_grad()推理时不保存计算图减少显存占用。保存标量而非张量用.item()提取标量值避免持有 GPU 张量引用。函数封装将训练代码封装在函数中利用局部变量的自动回收机制。上下文管理器使用with语句自动管理资源的分配和释放。Jupyter Notebook 特殊处理注意 IPython 的输出缓存使用%reset或%xdel。记住torch.cuda.empty_cache()只是锦上添花真正释放显存的是删除变量引用。养成良好的显存管理习惯可以避免绝大多数 OOM 问题。
RELATED

相关推荐

Vue与React深度对比:从设计哲学到工程实践的全方位解析

Vue与React深度对比:从设计哲学到工程实践的全方位解析

1. 项目概述:为什么我们需要对比Vue和React?作为一名在前端领域摸爬滚打了十多年的老兵,我几乎完整经历了从jQuery到三大框架(Angular、React、Vue)的变迁。每当有新人入行,或者团队面临技术选型时&#xf…

📅 2026/10/14 0:37:30
3步免费破解 Unity:UniHacker

3步免费破解 Unity:UniHacker

3步免费破解 Unity:UniHacker 【免费下载链接】UniHacker 为Windows、MacOS、Linux和Docker修补所有版本的Unity3D和UnityHub 项目地址: https://gitcode.com/GitHub_Trending/un/UniHacker 你正在学习游戏开发或做技术验证,想体验 Unity 的专业功…

📅 2026/9/24 0:07:34
【Bug已解决】Pytorch Operation to detect NaNs 解决方案

【Bug已解决】Pytorch Operation to detect NaNs 解决方案

【Bug已解决】Pytorch Operation to detect NaNs 解决方案 问题描述 在 PyTorch 深度学习训练中,NaN(Not a Number)是最常见也最令人头疼的问题之一。NaN 一旦出现在计算图中,会像病毒一样迅速传播到所有后续计算,导致…

📅 2026/10/23 8:01:00
MORE NEWS

更多资讯

📰

安阳办电工作业证在哪 最新政策解读 多久拿证全搞懂

安阳办电工作业证在哪 最新政策解读 多久拿证全搞懂 之前考的证在外省能不能转过来?这是很多在外地考了证的电工朋友最关心的问题,尤其是像你这样在南阳从事市政工程的朋友,证书能不能顺利转到安阳,直接关系到能不能正常上岗。别急,下面我来详细说说…

📰

维修电工证办理多久拿证?怕考不过白交培训费的看过来

维修电工证办理多久拿证?怕考不过白交培训费的看过来 怕考不过白交培训费?维修电工证办理多久拿证,成了很多人最关心的问题。特别是现在找工作、换行业、转岗,维修电工证成了刚需。但很多新手一上来就怕花钱打了水漂,怕培训费白交,怕考不过白忙一场。别…

📰

兴山电工证怎么考?实操考试心里没底怕挂科?上岗必备全流程解析

兴山电工证怎么考?实操考试心里没底怕挂科?上岗必备全流程解析 你是不是正在为考【兴山电工证】发愁?实操考试心里没底怕挂科,怕流程复杂搞不清楚,怕证书不被认可?别急,今天我就从焦作房建工程从业者的角度,把【兴山电工证】这个上岗必备的证怎么考、…

📰

工地太忙根本没时间复习考试?维修电工证办理通过率揭秘

工地太忙根本没时间复习考试?维修电工证办理通过率揭秘 工地太忙根本没时间复习考试,这是很多一线电工的真实写照。你是不是也经常在工地上被催着干活,一拖再拖,等到要考维修电工证的时候,才发现自己连大纲都看不懂?别急,今天我就从一个河南鹤壁的应届…

📰

企业要压电工证怎么报名 避坑指南+薪资前景全解析

企业要压电工证怎么报名 避坑指南+薪资前景全解析 你是不是也这样,手里拿着企业要压电工证的任务,却不知道从哪儿开始?怕被中间人骗钱、怕流程复杂、怕资料不全耽误事?今天就带你从零开始,讲清楚怎么报名、该准备什么、多少钱能搞定,还有这份证书在不…

📰

尚志市电工证复审怎么报名?证书过期了不知道怎么复审延期

尚志市电工证复审怎么报名?证书过期了不知道怎么复审延期 证书过期了不知道怎么复审延期,你是不是也遇到过这种情况?别慌,今天我就给你讲清楚 尚志市电工证复审怎么报名…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬