遇到OOM错误时如何有效清空GPU显存?
我太懂这种憋屈的感觉了——LLM模型好不容易加载进GPU,突然爆OOM,调用torch.cuda.empty_cache()完全没反应,最后只能硬着头皮重启GPU,半天的功夫全耗在等重启上!
下面是我踩过无数坑后总结的有效解决方案,从软清理到硬释放都有,亲测能解决大部分情况:
1. 先搞懂:为什么torch.cuda.empty_cache()没用?
这个函数不是“一键清显存”的魔法棒——它只会释放PyTorch已经不再持有引用的显存块。如果你的模型、张量还被Python变量(比如全局变量、Notebook历史单元格的变量)引用着,显存根本不会被释放,调用它自然没效果。
2. 软清理:先清引用,再清缓存
这是最常用的软解决方法,步骤不能乱:
- 第一步:手动删除所有和模型、推理相关的变量引用
- 第二步:强制触发Python垃圾回收,彻底销毁无引用的对象
- 第三步:再调用CUDA缓存清空
代码示例:
# 1. 删除模型、输入输出、中间张量的所有引用 del model del input_tensor del generation_outputs # 如果有多个模型/相关变量,全部删掉 del model_2, attention_masks # 2. 强制垃圾回收(必须导入gc模块) import gc gc.collect() # 3. 最后清空CUDA缓存 import torch torch.cuda.empty_cache()
⚠️ 特别提醒:如果是在Jupyter Notebook里,一定要检查之前的单元格有没有残留的模型变量!比如你在Cell 1加载了模型,Cell 5爆OOM,Cell 1的变量还活着,这时候必须先删掉那些历史变量,或者用%reset -f一键清空所有变量(但会清掉所有数据,谨慎使用)。
3. 用上下文管理器隔离模型(从根源减少引用泄漏)
把模型加载和推理逻辑放在函数或者上下文管理器里,函数执行完后,局部变量会被自动回收,从根源避免引用残留:
def run_llm_inference(model_path): # 模型在函数内是局部变量,函数结束后自动失去引用 model = AutoModelForCausalLM.from_pretrained( model_path, load_in_4bit=True, # 顺便提一句,4bit加载能大幅减少显存占用 device_map="auto" ) tokenizer = AutoTokenizer.from_pretrained(model_path) inputs = tokenizer("Hello world!", return_tensors="pt").to("cuda") outputs = model.generate(**inputs) return tokenizer.decode(outputs[0]) # 执行完函数,model的引用就被销毁了 result = run_llm_inference("your-model-path") # 再做一次收尾清理 gc.collect() torch.cuda.empty_cache()
4. 硬释放:杀掉占用显存的进程(比重启GPU快10倍)
如果软清理完全没用,那大概率是有顽固的进程占着显存。这时候不用重启GPU,直接杀掉对应的Python进程就行:
- 先通过
nvidia-smi找到占用显存的进程PID:
nvidia-smi
输出里会看到每个进程的PID、Process name(一般是python或python3)
- 杀掉对应PID的进程:
kill -9 <你的进程PID>
⚠️ 注意:别杀错其他重要进程!比如同事的训练进程或者系统关键进程,确认好再执行。
5. 预防大于治疗:从加载阶段减少显存占用
当然,最好的方法是从根源避免OOM:
- 用
load_in_4bit/load_in_8bit加载模型,显存占用直接砍半甚至更多 - 用
device_map="auto"让模型自动分配到GPU和CPU(如果显存不够) - 推理时用
batch_size=1,或者开启gradient_checkpointing(训练场景适用)
最后总结一下优先级:
→ 先试「软清理三部曲:del变量→gc.collect()→empty_cache()」
→ 不行就查Notebook历史变量,用%reset -f清空
→ 再不行就杀进程
→ 最后才考虑重启GPU
亲测这些方法能解决90%以上的显存无法释放问题,再也不用动不动重启GPU啦!




