PyTorch训练中断后GPU显存未释放是因Python进程僵死导致CUDA上下文未销毁,需用fuser定位并kill对应PID;torch.cuda.empty_cache()对此无效,因其仅清理PyTorch缓存而非进程级显存。

PyTorch训练中断后GPU显存没释放,nvidia-smi还显示占用
这是典型进程残留导致的显存卡死,不是PyTorch本身没清理,而是Python进程(或子进程)僵死在后台,GPU上下文没被销毁。直接杀进程最有效,但不能瞎杀——得先定位谁在占显存。
用 fuser 查进程ID:fuser -v /dev/nvidia0(按实际GPU编号改,比如/dev/nvidia1)
输出里看 PID 列,重点关注 python 或 torch 相关的行。
- 如果只看到一个 PID,
kill -9 PID基本就清掉了 - 如果看到多个 PID,优先 kill 主训练进程(通常是启动脚本的那个),再看显存是否释放;没释放再逐个 kill 子进程(如 dataloader worker)
- 别直接
killall python—— 可能误杀 Jupyter、tensorboard 等其他服务
为什么 torch.cuda.empty_cache() 没用
torch.cuda.empty_cache() 只回收 PyTorch 自己管理的缓存显存(比如中间 tensor 占的 cache),不碰 GPU 上的进程级上下文。一旦 Python 进程僵死,这个函数根本执行不到,或者执行了也无效——因为显存被操作系统级的进程锁住了。
- 它对“程序正常退出但显存没归还”场景有用(比如反复创建大模型没删引用)
- 对“Ctrl+C 中断后卡住”“Jupyter kernel crash 后残留”完全无效
- 调用后
nvidia-smi显存使用量不变 ≠ 函数失败,只是说明问题不在 PyTorch 缓存层
自动清理:Python 脚本里加 atexit 和信号捕获
预防比抢救重要。在训练脚本开头加几行,让中断时尽量优雅收尾:
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
import atexit
import signal
import torch
def cleanup():
if torch.cuda.is_available():
torch.cuda.empty_cache()
atexit.register(cleanup)
def signal_handler(sig, frame):
cleanup()
exit(0)
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
-
atexit保证正常退出时清 cache -
SIGINT(Ctrl+C)和SIGTERM(kill PID)都会触发 cleanup - 注意:对 SIGKILL(
kill -9)无响应,所以还是得靠fuser手动收尾 - 多进程 dataloader 下,主进程 cleanup 无法释放 worker 占的显存,得额外设
num_workers=0测试阶段排障
常见错误现象和对应检查点
看到显存不释放,先别急着 fuser,按顺序排查:
立即学习“Python免费学习笔记(深入)”;
-
nvidia-smi里Processes列为空,但显存占用高 → 很可能是 CUDA 上下文没释放,重启nvidia-persistenced或重启 GPU 驱动(sudo systemctl restart nvidia-persistenced) - 显存占用低但
torch.cuda.memory_allocated()返回值很大 → 是 PyTorch 缓存问题,empty_cache()有用 -
fuser查不到 PID,但nvidia-smi显示占用 → 检查是否用了 WSL2,WSL2 的 GPU 支持有已知残留 bug,需升级内核或换原生 Linux - Jupyter notebook kernel 重启后显存还在 → kernel 没真正退出,用
jupyter kernelspec list找路径,删掉对应 runtime 目录下的kernel-*.json
GPU 显存残留本质是进程生命周期和 CUDA 上下文解耦的问题,工具只是补救手段,关键在理解谁持有显存、何时释放、为何卡住。

















