显存爆了十次之后,我终于看懂了 OOM 的四张面孔
我的生图推理容器(z-image,跑在我的 4090 主机上),在稳定之前,前前后后 OOM 了不下十次。
每次 OOM 之后,我都逼自己回答同一个问题:
这次和上次,是不是同一个原因?
答案是四次"不是"。
四类触发链收敛出来之后,架构重做了一遍,至今零复发。
这篇把四张面孔全摆出来。
面孔一:加载策略型——还没干活,显存先满
最简单粗暴的一类:模型权重加载策略不当,服务一起动显存吃满,第一个请求还没来,系统已经站在悬崖边。
防御思路:加载策略必须显式选择——全量常驻(快但占满)、按需加载(省但首请求慢)、分层驻留。
没有默认答案,但必须是个决策,不能是"框架默认"。
面孔二:队列旁路型——绕过调度的并发(最隐蔽)
调度队列管得好好的,但某条"临时调试用"的入口可以绕过队列直接发起推理。
两个推理同时上 GPU,各自的峰值需求叠加,OOM。
临时入口活成了永久后门。
这条事故催生了我集群里现在最硬的纪律:任何绕过调度队列的路径都被禁止,写进审计规则,定期检查。
队列的意义就是把并发峰值钳在显存预算之内——旁路一开,预算就是废纸。
面孔三:碎片累积型——单看都合法,加起来爆了
最狡猾的一类:每次推理的显存申请都在预算内,但高频申请/释放让显存碎片化。
总余量看着够,连续的大块申请失败。
日志特征:偶发、与负载量弱相关、重启后消失。
防御:推理进程按次生命周期管理(下面架构节详说),天然规避长周期碎片累积;必须常驻的服务,安排主动重启窗口清零碎片。
显存和房间一样,不打扫就会堆满意想不到的东西。
面孔四:进程内泄漏型——最深的潜伏者
排查最久的一类:推理进程处理完任务不退出、驻留常驻,框架的某条路径会在进程内留下不释放的显存。
单次几 MB,肉眼不可见。
几百次任务之后,显存被无声吃穿。
定位它的关键动作是画显存曲线:把每次任务前后的水位记录下来,看到"任务结束后回不到基线"的锯齿状抬升——泄漏一目了然。
架构答案:权重占位符 + 唯一记账人
四张面孔逼出来两个架构决策。
第一,容器只做权重占位符。z-image 容器启动后只跑 sleep infinity——不跑任何常驻推理服务,只负责把模型权重挂载到位。真正的推理由调度器按次在容器内拉起独立进程执行,任务结束进程即退出,显存随进程生命周期归零。碎片和泄漏无处累积。
第二,显存必须有唯一的记账人。所有 GPU 任务必须经过同一个调度队列:统一记账、统一排队、统一拒绝超预算的请求。OOM 的本质不是显存不够,是显存预算没有唯一的记账人——每个组件都以为自己有权限用,就没有人的请求会被真正约束。
方法论价值(抽象一层)
三条可迁移:同类事故重复发生,说明你在治症状没治触发链——逐次逼问"这次和上次是否同一个原因",直到收敛出完整类别清单;稀缺资源的本质要求是唯一记账人,多入口即无预算;"每次使用独立生命周期"是对抗一切累积型故障的通用解。
这套思路的源头纪律见 Layer 0 控制图;两台设备的性能对比见 4090 对阵 Thor。
← 返回博客列表 · 相关阅读:4090 对阵 Thor · Layer 0 控制图