4、推理时显存溢出(OOM):batch size过大、分辨率过高、内存泄漏排查
显存溢出,圈里人常说的 OOM(Out of Memory),是 YOLO 实时流推理里最让人头疼的问题之一。你想想看,摄像头画面正推着,突然程序崩了,终端里跳出个红色报错——那种感觉,我经历过太多次了。
其实 OOM 的原因并不复杂,无非就是显存装不下当前的计算量。但排查起来,往往需要一点耐心。我个人习惯把 OOM 分成三类:配置不当型、输入过载型、内存泄漏型。咱们一个一个说。
4.1 配置不当:batch size 的陷阱
很多人以为 batch size 越大推理越快,这个想法在训练阶段没错,但在实时流推理里,它是个大坑。
YOLO 推理时,batch size 决定了一次送入 GPU 的图片数量。如果你设了 batch=8,每张图 640×640,那显存占用就是单张的 8 倍。我见过一个项目,同事把训练脚本的 batch=32 直接搬到了推理脚本里,结果 8GB 显存的卡瞬间爆掉。
这里有个简单的估算公式:
显存占用 ≈ batch_size × (输入分辨率宽 × 输入分辨率高 × 3 × 4) × 模型系数
模型系数取决于 YOLO 版本和模型大小。YOLOv8s 大约 1.2,YOLOv8x 能到 2.5 以上。你可以先跑一次小 batch 看看占用,再往上加。
4.2 分辨率过高:不是越大越好
YOLO 默认输入分辨率是 640×640。很多人觉得分辨率越高检测越准,于是改成 1280×1280,甚至 1920×1920。结果呢?显存直接翻 4 倍甚至 9 倍。
为什么会这样?因为分辨率是平方级影响显存的。从 640 提到 1280,像素数变成 4 倍,显存占用也差不多 4 倍。再加上特征图也跟着膨胀,实际占用可能更高。
| 输入分辨率 | 像素数(相对 640) | 显存占用估算(batch=1) | 适用场景 |
|---|---|---|---|
| 320×320 | 0.25x | 约 0.3 GB | 低功耗设备、快速检测 |
| 640×640 | 1x | 约 1.0 GB | 通用场景 |
| 1280×1280 | 4x | 约 3.5-4.5 GB | 小目标检测、高精度需求 |
| 1920×1920 | 9x | 约 8-10 GB | 慎用,仅高端显卡 |
4.3 内存泄漏:最隐蔽的杀手
配置和分辨率的问题,改一下参数就能解决。但内存泄漏不一样——它不会一开始就爆,而是跑着跑着,显存一点点涨上去,直到撑不住。
我曾经维护过一个实时监控系统,摄像头 24 小时运行。前 2 小时一切正常,第 3 小时开始卡顿,第 5 小时直接 OOM 崩溃。查了两天才发现,是推理循环里有个 tensor 没释放。
常见的泄漏原因有:
- 每次推理都创建新的 tensor,但没有 detach()——计算图越积越大
- 视频帧队列无限增长——处理速度跟不上推流速度
- 日志或可视化代码里保留了历史帧引用——垃圾回收不了
- 多线程中显存上下文没正确切换——每个线程都在吃显存
排查泄漏,我一般用这招:
import torch
# 在推理循环里打印显存变化
print(f"allocated: {torch.cuda.memory_allocated() / 1024**3:.2f} GB")
print(f"cached: {torch.cuda.memory_reserved() / 1024**3:.2f} GB")
如果 allocated 持续增长,说明有泄漏。如果 allocated 稳定但 cached 在涨,那是 PyTorch 的缓存机制,不用太担心。
- 在推理循环前后各打一次显存占用
- 如果每次循环后 allocated 都增加,检查 tensor 是否 detach
- 检查是否有全局列表或字典在累积推理结果
- 用
torch.cuda.empty_cache()手动清理缓存(治标不治本) - 最终方案:用
with torch.no_grad()包裹推理代码,切断计算图
4.4 知识结构图:OOM 排查全景
下面这张图总结了 OOM 的三大原因和对应的排查路径,你可以把它当作调试时的快速参考。
4.5 实战排查清单
最后,我整理了一份排查清单。下次遇到 OOM,按这个顺序走一遍,基本能定位问题。
- 检查当前显存占用:
nvidia-smi看一眼,确认是不是真的爆了 - 降低 batch size:直接改成 1,看还爆不爆
- 降低输入分辨率:从 640 降到 320,看显存占用变化
- 检查推理代码:有没有
with torch.no_grad()?有没有 detach? - 检查视频帧队列:队列长度是不是在无限增长?
- 加显存监控日志:每 100 帧打印一次,观察趋势
- 尝试半精度推理:
model.half()能省一半显存