11 · 设备、显存与卸载
显存管理不是在 OOM 后调用一次 empty_cache()。ComfyUI 要在多种后端、多个模型和不同 dtype 下,持续决定“谁现在需要、谁可以部分卸载、给推理临时张量预留多少、CPU RAM 是否也快耗尽”。
三种内存不要混成一个数字
VRAM / device memory GPU 等执行设备上的权重、激活与临时张量
CPU RAM offload 权重、缓存中间结果、文件读取
pinned RAM 可更快异步传输,但受注册/锁页预算约束把权重从 GPU 卸到 CPU 释放了 VRAM,却增加 RAM 压力;把 CPU 权重 pin 住提升传输速度,又减少系统可自由回收的内存。第 08 章的 RAM cache 和本章 model management 必须联动。
设备抽象
model_management.py 在启动时识别可用后端与参数,统一返回 torch.device 或对应包装。该快照覆盖的路径包括:
- CUDA / NVIDIA;
- ROCm;
- Apple MPS;
- DirectML;
- Intel XPU;
- Ascend NPU;
- MLU;
- CPU fallback。
不同设备不仅名字不同:空闲内存查询、支持 dtype、non_blocking copy、缓存清理、pinned memory 和 attention backend 都可能不同。节点应请求全局 device policy,而不是写死 cuda:0。
VRAMState
运行时把策略归纳为 HIGH_VRAM、NORMAL_VRAM、LOW_VRAM、NO_VRAM、DISABLED 等状态,结合 CLI 强制选项和设备能力。
| 状态 | 直觉 |
|---|---|
| HIGH_VRAM | 尽量保留完整模型,减少搬运 |
| NORMAL_VRAM | 在模型驻留与推理余量间平衡 |
| LOW_VRAM | 只给部分模块预算,更多按需 offload |
| NO_VRAM | 极小设备驻留,强依赖 CPU/offload |
| DISABLED | CPU 等无独立 VRAM 的设备语义 |
状态不是“实际还剩多少”的替代;每次加载仍查询实时 free memory。
预留内存
代码定义额外 reserved VRAM,Windows 因共享显存行为预留更多,用户可用 --reserve-vram 覆盖。minimum_inference_memory() 还给激活和临时计算保留约 0.8 GiB 加额外 reserve。
若只按权重大小把显存塞满,第一次 forward 仍会因 attention、latent 或 kernel workspace OOM。预留是权重装载与实际可运行之间的安全间隔。
LoadedModel:物理驻留记录
LoadedModel 用 weakref 指向 ModelPatcher,并记录:
- 目标 device;
- 实际基础 model 的弱引用;
- 当前是否被本 Prompt 使用;
- finalizer;
- parent patcher 切换关系。
它提供三个不同数字:
model_memory() 总模型大小
model_loaded_memory() 目标 device 当前已加载大小
model_offloaded_memory()总大小 - 已加载大小为什么用 weakref
全局 current_loaded_models 不应仅因登记而让上游 Prompt cache 已淘汰的 patcher 永远存活。对象消失时 finalizer 可清理物理登记;clone 有 parent 时还可切换引用。
load_models_gpu() 的七个阶段
comfy/model_management.pyL901-1016多模型去重、预算、卸载与装载主流程。
1. 去重并纳入 patch dependencies
按输入顺序去重目标 patchers,同时加入 model_patches_models() 返回的相关模型。使用 insertion-ordered dict 而不是 set,避免跨运行随机改变装载顺序。
2. 识别已加载对象
为每个目标构造 LoadedModel 视图,在 current_loaded_models 中寻找同一 patcher。已存在则标记 currently_used。
3. 移除不兼容 clone
共享底层 model 的另一 clone 可能带不同 patches。加载新变体前从当前列表移除冲突 clone,并 detach 旧 patch 状态,避免共享权重同时处于两种逻辑版本。
4. 汇总每个 device 的需求
按设备累计:
total_memory_required = 尚未驻留部分
total_pins_required = 非 dynamic 模型的总权重 pin 需求汇总后统一 free,比逐模型各自抢内存更稳定。
5. 先释放空间
对每个非 CPU device 请求:
required × 1.1 + max(minimum inference, requested extra) + reserved不足时 free_memory() 从旧 LoadedModels 中选择可卸载对象。
6. 计算 low-VRAM 预算
NORMAL/LOW VRAM 且非 force full load 时:
current_free = device free + this model already loaded
budget = min(可用比例, 扣除最小推理余量后的空间)NO_VRAM 给极小非零预算,让 Patcher 进入按模块/按权重路径而非误判为“无限预算”。
7. patch + load 并登记
LoadedModel.model_load(budget) 把 patch metadata 移到正确 device/dtype,再调用 patcher partial load。日志分别报告 RAM MiB 与 VRAM MiB,最后把对象插到当前 loaded 列表前方。
free_memory() 怎样挑对象
候选排除 keep_loaded 与 dead model,标记不再 currently used,并按以下信息排序:
- 已 offload 大小;
- Python 引用计数;
- 总模型大小;
- 当前 loaded list 位置。
它先计算还需释放多少;若小于模型已加载量,尝试 partially_unload(),释放够就保留 LoadedModel 记录。否则完全 detach 并从列表移除。
dynamic 模型特例
当本次所有目标都是 dynamic patcher 时,不为另一个 dynamic 模型粗暴卸载 dynamic 模型;它们可通过按需内存控制自行释放。这避免两套管理器互相抖动。
smart memory
默认策略只释放达到目标所需的空间,而不是每次切换都卸载全部模型。DISABLE_SMART_MEMORY 则更激进,在 Prompt 结束时可卸载所有模型。
取舍:
| 策略 | 优点 | 代价 |
|---|---|---|
| smart | 重复工作流快、减少 PCIe 搬运 | 长生命周期、更复杂状态 |
| disable smart | 行为更易观察、释放彻底 | 每次重新装载,吞吐下降 |
dtype 决策
unet_dtype() 综合设备能力、模型参数量、受支持 dtype、权重 dtype 与用户开关。should_use_fp16() 等函数包含不同后端的能力和性能例外。
更低位宽并非永远更省或更快:
- 某设备不原生支持,需要频繁 cast;
- 某些算子数值不稳定;
- 量化权重还需要 scale/metadata;
- 参数省下的显存可能被 cast buffer 抵消。
运行时让 model config 提供允许集合,再让 device policy 选择,而不是节点自行决定。
pinned memory 与异步搬运
CPU offload 的瓶颈可能从容量变成传输。pinned memory 允许更有效的 H2D copy,但 OS 可锁页资源有限。代码维护 pin budget / registry,并在 RAM pressure 时允许 free_pins()。
在支持的设备路径中,还会用独立 stream 进行非阻塞 offload/load,并在正确同步点等待。异步 copy 的正确性不只取决于 non_blocking=True,还要保证源 buffer 生命周期与 stream 依赖。
RAM cache 与模型内存联动
一次节点完成后若系统 RAM 不足:
这说明“低显存”不能只观察 GPU。大量 latent/image 缓存在 CPU,同样会让进程被系统 OOM killer 终止。
OOM 恢复
执行器捕获 model management 识别的 OOM:
- 记录内存摘要;
- 添加 batch size 等用户提示;
unload_all_models()清理 loaded 状态;- 把结构化 execution error 发给客户端。
它不能让已失败的 kernel 自动继续,也不会自动降低图像尺寸重试。清理的目标是让下一次 Prompt 不继承破损状态。
常见误区
“模型文件 8 GB,所以 8 GB GPU 足够”
还需要激活、attention、latent、VAE、CLIP、patch/cast buffers 和保留余量;文件量化/压缩大小也不等于加载后大小。
“offload 就是删掉模型”
部分卸载保留 patcher、模块结构和 CPU 权重,只减少执行设备驻留。完全 detach 才更接近卸载逻辑状态。
“empty_cache 能释放所有显存”
PyTorch cache 只释放 allocator 未使用块;仍被 tensor/model 引用的内存不会消失。必须先卸载或删除真实引用。
“节点里手工 .to(device) 更直接”
它绕过 LoadedModel、预算、clone 冲突、dtype、pins 和 partial unload。除纯临时小 tensor 外,模型节点应走统一管理。
调优顺序
1. 确认实际设备与 backend
2. 看日志中的 model total / RAM / VRAM loaded
3. 确认推理 dtype 与 manual cast
4. 区分权重 OOM、采样激活 OOM、VAE decode OOM
5. 降 batch / 分辨率 / tile,观察 minimum inference headroom
6. 再选择 lowvram / reserve-vram / disable smart memory
7. 同时看系统 RAM 与 pinned memory,而非只看 nvidia-smi可迁移的设计方法
- 资源状态用“总量 / 已加载 / 已卸载”连续模型,不用布尔 loaded。
- 为执行临时空间预留 headroom,不能把权重塞到 100%。
- 多资源池联动回收:VRAM、RAM、pinned RAM 互相转移压力。
- 用弱引用/最终器让资源登记不反向制造内存泄漏。
- 平台差异集中在 policy 层,业务节点只请求能力。