ComfyUI 报 CUDA OOM 时的排查顺序

2026-08-09

跑图跑到一半,控制台吐出一串 torch.cuda.OutOfMemoryError,队列红掉。这时候最容易做的事,是去搜一条命令行参数贴上去重启——十有八九搜到的是 --lowvram。坏消息是,在当前版本的 ComfyUI 上,这条参数在默认配置下什么都不做。所以这篇不给你一串万能参数,给你一个顺序:先看日志确认自己在什么状态,再决定动哪个开关。

先说一个可以让你安心的事实:在 comfy/model_management.py(v0.31.0)里,源码定义了 OOM_EXCEPTION = torch.cuda.OutOfMemoryError(在没有 CUDA 的路径上会退化成 Exception),并在若干处用 isinstance(e, OOM_EXCEPTION) 做判断。也就是说,OOM 在 ComfyUI 里不是一个漏出来的裸异常,官方有专门的捕获路径。具体在捕获之后会做哪些降级动作,源码我们没有摘全,这里不展开——你只需要知道,一次 OOM 报错并不等于”这套配置根本跑不了”,它更像是某一步的分配请求撞上了当前的显存边界。

下面的所有结论都以 ComfyUI v0.31.0(2026-08-08)的 comfy/cli_args.pycomfy/model_management.py 为准。ComfyUI 大约每两周一个版本,参数和默认值都会变,读到这篇时请以 python main.py --help 的实际输出为准。

第一步:把启动日志前几行读完,别急着改参数

排查显存问题的第一个可执行动作,是把终端往上翻到进程刚启动的位置,找这几行(都是源码里的原始格式串):

日志文案你要从中读到什么
Total VRAM {:0.0f} MB, total RAM {:0.0f} MBComfyUI 认到的显存与内存总量,先确认它认到的卡是不是你以为的那张
Set vram state to: {vram_state.name}当前 VRAM 状态机取值
Enabled pinned memory {}(单位 MB)pinned memory 是否启用、上限多少
Using async weight offloading with {} streams异步权重卸载是否启用、几个流
Requested to load {模型类名}哪个模型开始加载

Set vram state to: 后面那个名字,来自源码里的枚举 VRAMStateDISABLED / NO_VRAM / LOW_VRAM / NORMAL_VRAM / HIGH_VRAM / SHARED。源码给这几项的注释很短——NO_VRAM 是”显存极低,打开所有省显存的选项”,SHARED 是”没有独立显存,CPU 与 GPU 共享内存,但模型仍然需要在两者之间搬”。每个状态下具体会做哪些动作,注释没写,我也不替它编。

这一行的实际用处是校对预期:源码给 SHARED 的注释是「没有独立显存」,所以如果你自认为在用一张独显、日志却给你 SHARED,那要先去查设备而不是查参数。同样,如果这一行的取值和你的启动命令对不上(比如你什么显存参数都没加,它却不是你预期的那个状态),先去翻启动脚本、.bat 和桌面快捷方式,看看是不是有旧参数在替你传——ComfyUI 具体依据什么条件落到哪个状态,源码注释里没写,这里不替它推断。

Requested to load 这一行同样重要——它告诉你 OOM 是发生在加载哪个模型的时候。加载阶段炸和采样阶段炸,是两类问题,别混着治。

第二步:确认 dynamic VRAM 现在是开还是关

这是整条排查链上最容易搞错、也最值得先搞清的一环。comfy/cli_args.py(v0.31.0)里的判定函数是这样的:

def enables_dynamic_vram():
    if args.enable_dynamic_vram:
        return True
    return not args.disable_dynamic_vram and not args.highvram and not args.gpu_only and not args.novram and not args.cpu

翻成人话,有两条直接结论:

一、你什么参数都不加时,dynamic VRAM 是开着的。--lowvram 的 help 原文就写着「如果启用了 dynamic vram,这个选项不做任何事」——它只在没用 dynamic VRAM 的情况下,让文本编码器跑在 CPU 上。所以老教程里那句”显存小就加 --lowvram”,在当前版本已经不成立。你加了它、然后觉得”好像好点了”,大概率是别的变量在起作用。

二、--highvram--gpu-only--novram--cpu 这四个开关会顺带把 dynamic VRAM 关掉。 这是它们各自 help 文本之外的连带效果,最坑的一种情况是:有人为了”让模型留在显存里提速”加了 --highvram,结果同时退回了基于估算的模型加载,然后遇到 OOM,再回头加 --lowvram 试图补救——两条参数还是互斥组里的,加不上。

所以第二步的可执行动作很简单:把你启动命令里的显存类参数全部去掉,用裸的 python main.py 复现一次。如果裸启动不 OOM、加了参数才 OOM,那么问题在你的参数组合,不在显存容量。这一步比任何参数调优都值钱。

顺带一提,社区里确实有人希望把这个开关持久化下来:issue #14029 反映了「强烈建议永久保存 --disable-dynamic-vram」这一诉求,创建于 2026-05-21,标签为 Feature,截至 2026-08-09 仍为 open。我们没有读取该 issue 的正文与评论,这里只说明”有这么个诉求存在”,不代表官方的取舍。

第三步:分清 --reserve-vram--vram-headroom

这两个参数长得像,语义完全不同,混用是很多人越调越糟的原因。

  • --reserve-vram GB,默认 None。help 说的是”给操作系统/其它软件保留的显存量”,并注明默认会按操作系统保留一定量
  • --vram-headroom GB,默认 0。help 说的是”让 DynamicVRAM 在默认之上额外保持的空闲显存”,并特意点明:ComfyUI 会尽量保持这么多显存完全空闲,即使是被其它应用占用的显存也计入

区别在两处。第一,--reserve-vram 是覆盖式的(下面会看到,它给的值直接顶掉源码里的默认常量),--vram-headroom 是叠加式的——help 原话是”在默认之上额外”,也就是它不动默认值,只在其上再加一层。第二,--vram-headroom 的计数口径是”完全空闲”,而且 help 特意点了一句「即使是被其它应用占用的显存也计入」。这句话在实际配置时的确切算法,help 只给了这一句,我们没有摘到对应源码,不替它推断;你只需要记住一个实用结论:当你机器上还有浏览器、别的推理进程在吃显存时,--vram-headroom 的行为和”纯净环境下留出这么多”是不一样的,所以别把别人在空机器上调出来的值直接抄到自己开着一堆程序的机器上。

那句”默认会按操作系统保留一定量”具体是多少,源码里有常量:EXTRA_RESERVED_VRAM 默认是 400 * 1024 * 1024,即 400MB;Windows 上更高,是 600 * 1024 * 1024(600MB),源码注释原文写的是 #Windows is higher because of the shared vram issue;另外在某条件下还会再 += 100 * 1024 * 1024。而一旦你显式指定了 --reserve-vram,这个值就直接变成 args.reserve_vram * 1024**3——注意单位是 GB,而且默认值不再参与,完全由你接管。

这带来一个很实际的提醒:如果你在 Windows 上照着某篇 Linux 教程写 --reserve-vram 0.2,你实际上是把系统默认留的 600MB 砍到了 200MB。省下来的那点显存,换来的可能是更早的一次 OOM,或者别的程序开始抢。同一张卡在 Windows 上默认被多留 200MB 是硬事实,但别把它外推成”Windows 显存利用率低百分之多少”这类结论——源码只给了常量和一句注释,没给任何量化对比。

处置:一次只动一个开关,并且记住去哪儿验证

按上面三步定位完,可选的动作大致是:如果确定是别的程序在抢显存,用 --reserve-vram 把保留量往上给;如果是 dynamic VRAM 的行为不合你的机器,用 --disable-dynamic-vram 退回基于估算的模型加载(help 原文:estimate based model loading),或者反过来用 --enable-dynamic-vram 在默认不启用的系统上强开。相关的还有 --disable-async-offload(关掉异步权重卸载,NVIDIA 上默认是启用的)、--disable-pinned-memory--disable-nvml-pressure(让 DynamicVRAM 的内存压力判断走 CUDA 而不是 NVML)。

一个组合示例:

python main.py --reserve-vram 1.5 --verbose DEBUG

以上为按官方参数语义组合的示例,未逐项实测,以官方文档与 --help 输出为准。

处置之后怎么验证? 不要靠”感觉不炸了”,去看这三处:

  1. 重启后启动日志里的 Set vram state to:Enabled pinned memory 有没有随你的改动变化。改了参数但这两行纹丝不动,说明你的参数根本没传进去(Windows 上从桌面快捷方式或 .bat 启动时特别常见)。
  2. --verbose DEBUG 重跑一次同一个工作流。--verbose 的合法等级是 DEBUG / DETAIL / INFO / WARNING / ERROR / CRITICAL,不带值时等价于 DEBUG,控制台默认是 INFO。看 Requested to load 之后是不是走到了更远的位置。
  3. 复现路径要固定:同一个工作流、同一批参数、同样是冷启动后第一次跑。ComfyUI 的执行模型是”只有相对上次执行发生变化的部分才会被执行”(README「Notes」章节),你第二次点运行时可能压根没重跑那个爆掉的节点,那不叫修好了。

什么情况说明不是显存不够

这一节才是这篇文章相对”报错大全”的价值所在。以下几类现象,往上堆显存参数是白费力气:

一、告警里说的是 RAM 和 pin,不是 VRAM。 pinned memory 的上限是内存侧的常量:Windows 上是 ram * 0.40(源码注释写 # Windows limit is apparently 50%),其它系统的算式还会把磁盘 swap 总量算进来,并留出 4GB / 16GB 两道余量。源码里有两条对应告警——Could not get amount of swap memory on system.Could not read Windows swap usage; falling back to RAM-pressure pin eviction: %s——说明”拿不到 swap 信息”是官方预期内的情况,会退回按 RAM 压力做 pin 淘汰。另外 v0.31.0(2026-08-08)的 release note 有一条「Don’t pin too much memory on Linux systems with no swap partition.」(PR #15266)。所以:如果你在 Linux 上没建 swap 分区、跑的又是早于 v0.31.0 的版本,那么在把账算到显卡头上之前,先把 RAM 侧的 pin 策略排除掉——升到 v0.31.0(2026-08-08)或以上,或者临时加 --disable-pinned-memory 再跑一次。这条 release note 只说明官方在这个版本改了无 swap 分区时的 pin 行为,并没有说你遇到的每一次内存问题都是它,所以它是一个”先排除”的对象,不是一个现成结论。

二、日志里出现内存泄漏提示。 源码文案是 Potential memory leak detected with model {类名}, doing a full garbage collect, ...,更严重时是 WARNING, memory leak with model {类名}. Please make sure it is not being referenced from somewhere.。官方给的方向是”避免循环引用、检查还有谁在引用它”,这几乎总是指向某个自定义节点持有了模型引用。这时候该做的是二分排查:用 --disable-all-custom-nodes 裸启动跑一遍,不炸就说明是节点的问题,再用 --whitelist-custom-nodes 一点点放回来。加显存参数对这类问题没有意义。

三、你用的是 --cache-lru 它的 help 直接写明”可能占用更多 RAM/VRAM”。如果你为了跳过重复计算加了这个,OOM 的账要先算到它头上——把它去掉,回到默认的 --cache-ram(RAM 压力缓存),或者临时用 --cache-none(代价是每次运行都重新执行每个节点)对照一次。

四、你撞上的可能是一次版本回归,而不是配置问题。 官方仓库 issue #15255 反映了「Dynamic VRAM streaming crashes all generations with HostBuffer.read_file_slice failed → CUDA OOM」这一现象,标题里作者自己标注为「regression after Aug 3 2026 update」,该 issue 创建于 2026-08-03,标签为 Bug,截至 2026-08-09 仍为 open。另一条是 issue #14340,反映「VRAM OOM on Linux with large singular allocation but should be within limits」,创建于 2026-06-08,标签为 Potential Bug,截至 2026-08-09 仍为 open。

关于这两条要说清楚三件事:其一,Potential Bug 的字面意思是”疑似 bug”,不是官方已确认;其二,它们目前都是 open 状态,不存在”已修复”这回事;其三,我们没有读取这两条 issue 的正文与评论,所以这里不提供任何来自其中的复现步骤、根因或社区绕行方案。它们在这篇里的唯一用途是:当你的现象与标题高度吻合、且你已经按前三步排除了配置因素时,把”这是一个还没关闭的已知反馈”纳入考虑,别再一个人跟参数死磕。真要跟进,去仓库看 issue 本身。

最后一句实话:显存这块,ComfyUI 的机制在最近几个版本里改动很密集(v0.23.0 的多线程磁盘加载、v0.30.0 的 pinning 与 MRU 权重加载、v0.31.0 的 swap 修正),跨版本抄参数是这类问题的主要来源。排查前先确认自己的版本号,比什么都重要。

延伸阅读


本文依据 ComfyUI 官方仓库(github.com/Comfy-Org/ComfyUI)的 README、comfy/cli_args.py、 release notes 与官方安全公告整理,核对日 2026-08-09,对应版本 v0.31.0; 文中引用的 issue 状态为该日期的快照。本文内容为官方文档与源码口径,非本机实测。 参数、默认值与功能随版本变动,请以官方文档与 python main.py --help 的实际输出为准。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。