先说一个我这两年反复验证过的结论:ComfyUI 里跑出一张好看图,和跑出一批稳定可用的图,中间隔着一整套工程。前者靠运气和审美,后者靠结构、参数和预算。很多人第一次把多 ControlNet 叠起来,看到画面既像参考图又像提示词,激动了半小时;然后在批量出第 7 张图时遇到 CUDA out of memory,或者在第 30 张时发现 seams(接缝)横在人物脸上,于是整个工作流报废。
工业级的门槛从来不是画质,而是可复现。同一份工作流、同一批种子、同一组参考图,今天跑和下周跑要能得到同一批图;别人接手你的工作流,也得能跑出同样的结果。做到这一步之后,第二个门槛是可批量:不是一张一张手动点,而是能挂着跑一晚上,第二天早上收图。第三个门槛才是不炸显存:8GB 卡跑 SDXL 加两个 ControlNet 本来就是奢望,但如果学会把 Tiled VAE 解码安排对位置,很多场景是可以跑通的。
这篇文章不讲”我用了什么神仙 ComfyfyUI 插件”,而是把多 ControlNet 的取舍、IP-Adapter 的权重博弈、Tiled VAE 的踩坑、批量渲染三种模式的对比,以及按显卡型号算的显存预算,一条条写清楚。你会发现,你以为在等采样,其实在等 VAE 解码——这个认知一旦建立,整套调优思路都要改写。
工业级工作流的三条评判标准
先定义清楚什么叫”工业级”。我在给团队做 ComfyUI 工作流评审时,只看三件事。任何一条不达标,这个工作流就不能进生产。
标准一:可复现,而不是”大概长这样”
可复现的意思是:一份 workflow JSON、一份模型清单(含版本哈希)、一组固定的 seed 与采样器参数,换个人、换台机器、换一天,出来的图在感知层面一致。这里最容易出问题的地方有三个。
模型版本漂移
同一个 Civitai 上叫 “XXL Realistic v3” 的模型,作者会悄悄更新 v3.1 但不改文件名。你以为复现了,实际上模型权重已经变了。解决办法是建一个 models_manifest.txt,记录每个模型的 SHA256 前 12 位,每次批量跑之前核对一次。
随机源不确定
ComfyUI 的 KSampler 节点有 seed 和 control_after_generate 两个字段。批量时如果你用了 randomize,种子就失控了。工业流必须用 fixed,或者用 increment 配合一个可追溯的初始种子,比如第 N 张图的 seed = 20241105 + N。
自定义节点版本
ControlNet 的叠加顺序、IP-Adapter 的 weight_type、Tiled VAE 的 tile 参数,都依赖具体节点实现。ComfyUI 本体版本和 ComfyUI_IPAdapter_plus、ComfyUI-Advanced-ControlNet、ComfyUI-Manager 的版本必须写进环境说明。我见过因为 IPAdapter Plus 从 v1 升级到 v2,weight_type 默认值从 linear 改成 ease in,整批图风格偏了一档的真实事故。
标准二:可批量,而不是”一张一张精修”
可批量意味着工作流里没有任何”人工介入点”。这里的介入点包括:手动遮罩、手动调权重、手动换参考图、手动挑种子。工业流要么把这些变量参数化(用 CSV 驱动),要么彻底固化成常量。中间状态最要命——半自动的工作流跑到 200 张时,人会崩溃,机器不会。
标准三:显存可控,而不是”跑到一半才知道炸”
可控的显存不是”我的卡够大”,而是我知道峰值出现在哪个环节、峰值是多少、余量还有多少。多 ControlNet 每加一个就是 1.5-2.5GB 的固定开销,IP-Adapter 再加 0.8-1.5GB,Tiled VAE 解码则能把最后那一下峰值压到一半以下。这些数字必须能在跑之前算出来,而不是靠 OOM 报错来教你。
| 评判维度 | 不合格的表现 | 达标的做法 | 验收方式 |
|---|---|---|---|
| 可复现 | 换个 seed 效果就崩,模型靠文件名识别 | JSON + manifest + 固定 seed 策略 | 换机器重跑 10 张,感知一致率 ≥ 90% |
| 可批量 | 每张图都要手动改参数 | 参数全部外置到 CSV / API | 无人值守连跑 4 小时不中断 |
| 显存可控 | 换个分辨率就 OOM | 峰值预算表 + Tiled VAE 兜底 | 最坏组合下峰值 ≤ 显存 85% |
多 ControlNet 的”2+1 原则”:为什么叠三个就开始失控
先说成本。每叠加一个 ControlNet,显存多消耗约 1.5-2.5GB(取决于模型是 SD1.5 还是 SDXL/FLUX 版本),每步多 200-400ms。20 步采样,一个 ControlNet 就是多 4-8 秒;三个 ControlNet 加两个 IP-Adapter,单张图的时间可以从 12 秒涨到 45 秒,显存从 6GB 涨到 14GB。这还是不出错的情况。
叠加的边际效应是递减的
第一个 ControlNet 带来的是”结构可信”,收益最大。第二个 ControlNet 带来的是”细节纠正”,收益中等。第三个开始,各个控制信号之间开始打架:Canny 说这里要有一条硬边,Depth 说这里是平的,OpenPose 说手臂在左边。CFG 引导会在这几个约束之间反复拉扯,结果就是画面局部糊掉、肢体断裂、或者干脆忽略其中一个。你在权重上做的所有微调,本质上是在给这几个信号排优先级。
2+1 原则的具体分工
我的建议固定成”一个结构引导 + 一个风格引导”,最多再加一个补充。结构引导负责构图和姿态,风格引导负责材质、色调、光影,补充位留给那些”缺了会明显出错”的东西,比如手部 OpenPose 或者文字区域的 Canny。
结构引导位:Depth / OpenPose / Lineart
这一位决定”东西在哪”。做产品图用 Depth(深度)最稳,做人物姿势用 OpenPose,做插画线稿用 Lineart 或 Canny。这一位的权重通常给到 0.7-0.9,因为构图错了后面全白搭。
风格引导位:SoftEdge / Tile / 参考色块
这一位决定”长什么样”。SoftEdge 适合保留参考图的氛围但允许重绘细节,Tile 适合材质迁移和超分场景,参考色块适合控制整体色调。权重给 0.4-0.6,太高会把结构位压掉。
补充位:最多一个,且要”只在必要时开”
补充位应该是可开关的。比如某批图需要严格控制手部,就临时挂一个 OpenPose 手部模型(权重 0.6-0.8);不需要的时候直接旁路掉,而不是设成 0.01 混着跑。很多事故来自”留着一个权重 0.05 的 ControlNet 忘关”,它不参与构图,但占显存、拖时间,还可能在局部产生奇怪纹理。
ControlNet 数量与显存 / 耗时对照
| 叠加组合 | 模型规模 | 额外显存增量 | 单步耗时增量 | 1024×1024 单图总耗时参考 | 建议 |
|---|---|---|---|---|---|
| 无 ControlNet | SDXL | — | — | 约 9-11s | 自由发挥 |
| 1 个(结构) | SDXL | +1.5-2GB | +200-300ms | 约 14-18s | 推荐起点 |
| 2 个(结构+风格) | SDXL | +3-4.5GB | +400-700ms | 约 20-26s | 2+1 的标准形态 |
| 3 个 | SDXL | +4.5-7GB | +600-1100ms | 约 27-35s | 仅在必要时,需降分辨率 |
| 2 个 + IP-Adapter ×1 | SDXL | +4-6GB | +500-800ms | 约 24-30s | 商业图常用组合 |
| 3 个 + IP-Adapter ×2 | SDXL | +6-9GB | +800-1400ms | 约 33-45s | 不推荐,信号冲突明显 |
| 2 个(结构+风格) | FLUX.1 量化 | +3-5GB | +500-900ms | 约 30-50s | 需要 12GB 以上 |
IP-Adapter 与 ControlNet 的权重博弈:谁该压谁
IP-Adapter 和多 ControlNet 放在一起,本质上是两套不同维度的约束在抢同一张大图的解释权。ControlNet 是空间约束,”这里必须有一条线”;IP-Adapter 是语义/风格约束,”整体必须像这张参考图”。权重配置不当的时候,两者会互相抵消。
IP-Adapter 权重建议 0.5-0.8,高了会压掉结构
IP-Adapter 的权重建议 0.5-0.8,过高会压过结构控制。这是经验值里最容易被忽视的一条。很多人第一次用 IP-Adapter,看到参考图相似度不够,就往上调到 1.0 甚至 1.5,结果 IP-Adapter 把整个画面拉向参考图的构图,ControlNet 的 Depth 就被无视了——你要的姿势没了,脸也糊了,但色调确实像了。
为什么 IP-Adapter 权重会”抢戏”
IP-Adapter 的注入方式是在 cross-attention 里加一路图像特征,它对全局的影响是”弥散”的,不像 ControlNet 那样精准到像素位置。所以权重一高,它会在所有位置施加影响,包括那些 ControlNet 已经定义了结构的地方。两个信号在同一区域叠加,更”强势”的那个赢。IP-Adapter 的 CLIP vision 特征通常强势,于是结构被覆盖。
验证办法:关掉 IP-Adapter 对比一次
调权重时最有效的方法是:先把 IP-Adapter 关掉,确认 ControlNet 的结构稳住了;再打开 IP-Adapter,从 0.5 开始往上爬,每 0.1 一组,找出”风格明显接近参考图但结构还没有变形”的那个点。通常这个点在 0.6-0.7 之间。
权重配置表:按场景给建议
| 场景 | 结构 ControlNet | 风格 ControlNet | IP-Adapter | 说明 |
|---|---|---|---|---|
| 电商产品换背景 | Depth 0.85 | — | 主体参考 0.55 | 结构必须保死,风格交给提示词 |
| 人物风格迁移 | OpenPose 0.8 | SoftEdge 0.45 | 风格参考 0.65 | 姿态优先,风格辅助 |
| 插画上色 | Lineart 0.9 | — | 风格参考 0.7 | 线稿绝对不能动 |
| 品牌借势海报 | Depth 0.7 | Tile 0.5 | 主体 0.6 + 风格 0.5 | 两个 IP-Adapter 已是上限 |
| 建筑效果图 | MLSD 0.85 | Depth 0.5 | 风格参考 0.5 | 线条硬约束,材质软约束 |
| 真人写真复刻 | OpenPose 0.75 | — | FaceID 0.75 | FaceID 权重可稍高,但不超过 0.8 |
两个 IP-Adapter 的叠加技巧
当你需要同时参考”主体”和”风格”时,用两个 IP-Adapter(一个走 ip-adapter-plus 走主体,一个走风格)。这时候最关键的是分段:主体参考的 start_at 设在 0,end_at 设在 0.5-0.6,让它在构图阶段起作用;风格参考的 start_at 设在 0.3 之后。这样两路各管一段,冲突最小。
别用 1.0 权重去”补偿”相似度
常见错误是把主体权重设为 1.0 追求高相似度。实际效果是背景、姿态、光影全部被主体参考图绑架,你要的构图全没了。正确的做法是把权重压在 0.7 以内,剩下的相似度用 ip-adapter-plus-face 这种专用模型补,而不是靠调数值。
Tiled VAE 解码:把最后那一下峰值压到一半以下
这一节是整篇文章里最”值钱”的部分,因为它能救回一批原本跑不通的机器。
先说清楚一件事:你以为在等采样,其实在等 VAE 解码
跑过 ComfyUI 的人都有体感:进度条到 100% 之后还要再等好几秒,图才出来。那几秒就是 VAE 解码。性能观测里一个很反直觉的数字是:整体耗时的 40-60% 落在 VAE 解码与放大模型上,而不是主模型采样。尤其是 2048 以上分辨率、或者接了放大模型(4x-UltraSharp、RealESRGAN)的场景,VAE 解码的耗时和显存峰值都会超过采样本身。
为什么 VAE 解码这么贵
以 1024×1024 为例,latent 是 128×128×4,解码后是 1024×1024×3。中间的特征图会膨胀到 [1, 512, 1024, 1024] 这个量级,单精度下就是 2GB 的中间张量,还只是一层。从 1024×1024 升到 2048×2048,激活值近似平方增长——4 倍面积,中间张量也接近 4 倍。这就是为什么 8GB 卡跑 1024 还行,跑 2048 必炸。
Tiled VAE Decode 的参数:512 / 64 是常用起点
Tiled VAE Decode 的做法是把大图切成若干 tile,逐块解码,再把它们拼回去。tile size 512、overlap 64 是常用起点。512 的 tile 在单块解码时中间张量只有 512²,显存需求降到原来的 1/4 左右,加上重叠区裁剪,整体峰值能压到一半以下。
tile size 怎么选
tile size 不是越小越好。太小(比如 256)会导致接缝数量变多,每块边缘的上下文不足,越容易出色差和结构断裂;同时 tile 数量增多,像素级拼接的 CPU 开销上升,总耗时可能翻倍。512 是个平衡点,1024 适合显存宽裕的卡(16GB 以上)且要求无缝的场景。
overlap 怎么选
overlap 64 是常用起点。它的作用是让相邻 tile 有一段重叠区,解码后用加权融合(feather blending)把接缝抹掉。overlap 越大,融合越平滑,但计算量也越大。一般规律:内容越平滑(天空、水面),overlap 越小越好;内容越复杂(头发、树叶、细纹理),overlap 需要给到 96 甚至 128。
用 Load Checkpoint 的高级设置开 enable_vae_tiling
很多人不知道,ComfyUI 的 Load Checkpoint 节点有一个”高级”折叠区,里面可以直接开启 enable_vae_tiling。勾上之后,VAE Decode 时自动分块,不需要单独接 Tiled VAE Decode 节点。这个开关对显存紧张的场景非常省事,但缺点是参数不可调,用的是默认 tile size。
如果你需要精细控制(比如人物特写要 1024 的 tile 避免接缝),就用独立的 VAE Decode (Tiled) 节点,或者在 comfyui-tiledvae 这类插件里用更细的控制。两者不要同时开,会重复分块,反而更慢。
踩坑一:接缝——不是 tile 不够小,是融合不对
接缝最常见的表现是:图里出现一条条等距的浅色/深色竖线,或者拼接处有细密网格。原因通常不是 tile 参数,而是:
- 没开 overlap:tile 之间硬拼接,边缘特征不连续。
- overlap 但融合方式选错:部分节点的 blend 模式是
average而不是feather,边界会有半透明过渡带。 - VAE 精度不一致:tile 解码用了 fp16 而主解码是 fp32,色值会漂移零点几个百分点,在接缝处肉眼可见。
解决办法:确认 overlap ≥ 64,确认融合模式是渐入渐出,确认整个管线 vae dtype 一致(要么全 fp16,要么全 fp32)。如果接缝仍存在,把 tile size 提到 768 试一次——很多情况下接缝并不是 tile 太小造成,而是 tile 划分刚好切在关键结构上。
踩坑二:色差——拼回去的图整体偏色
另一个高频问题是:Tiled 解码后,整张图相比未分块解码偏亮或者偏暖。原因是每块 tile 独立做归一化,块的统计分布有微小差异,融合后累加成整体偏移,尤其在大面积渐变(天空、背景)上明显。
缓解方法有三条,按成本从低到高:
1. 用后处理做一次整体校色
在 VAE Decode 之后接一个 Color Correct 或者 Level 节点,以其中一块未受影响的区域做参考。实操里更简单:用 Image Color Match 比对一张未分块解码的低分辨率预览图。
2. tile 之间保留量化后的相同统计
部分 Tiled VAE 实现支持 “normalize per tile” 关闭,改成全局统计后再分块,色差会明显降低。代价是显存略高一点。
3. 只在最后一步开 Tiled
最稳妥的做法:采样、预览、试色全部用普通解码(小分辨率),只有最终出图那一步开 Tiled VAE Decode。这样色差只影响最终一张,而且有前一张作为参考可以校色。
Tiled VAE 的节点连接方式
# ComfyUI 中 Tiled VAE Decode 的两种接法
# 方案 A:Load Checkpoint 高级设置开启(省事,参数不可调)
Load Checkpoint
└─ advanced → enable_vae_tiling = true
└─ VAE → VAE Decode → Save Image
# 方案 B:独立 Tiled 节点(推荐,参数可控)
KSampler ── LATENT ──┐
├── VAE Decode (Tiled) ── IMAGE ── Save Image
VAE Loader ── VAE ───┘
tile_size = 512
overlap = 64
temporal_size = 64 # 仅视频模型使用
temporal_overlap = 8
# 注意:方案 A 与方案 B 不要同时开启,会重复分块导致更慢
批量渲染的三种模式:batch_size、Queue Batch、多实例
批量渲染的问题不在”能不能跑”,在”跑多久、稳不稳、好不好追”。三种模式各有适用场景,混用会立刻踩坑。
模式一:batch_size(同一个采样里多张)
batch_size 是把多张图塞进同一次采样。速度最快,因为模型权重只加载一次,GPU 的利用率最高。但它有三个硬性限制。
所有输入图尺寸必须一致
ControlNet 批处理要求:所有输入图尺寸一致,batch 数必须等于 latent batch 数。这意味着你的 ControlNet 输入预处理器要输出同尺寸的图,而且每个 ControlNet 的 batch 都要和 Empty Latent 的 batch 对齐。只要你一个 ControlNet 输入了 4 张而另一个只输入 1 张,整个采样就会报错或者悄悄只处理第一张。
显存线性增长
batch_size 每翻一倍,latent 和激活值翻倍。8GB 卡跑 SDXL 加两个 ControlNet,batch_size = 4 基本到顶。这不是可以靠 Tiled VAE 解决的问题,因为瓶颈在采样阶段的激活值。Tiled 只能压解码那一段。
所有图共享同一个提示词
batch_size 的几张图只能共享同一个 prompt 和同一个 ControlNet 输入组。它适合”同一构图多个 seed 挑一张”,不适合”一批不同的产品图”。
模式二:Queue Batch(队列级批处理)
Queue Batch / Batch count 是把同一份工作流丢进队列跑 N 次。它不共享显存,每次都是干净的采样周期,更稳定但更慢。适合不同 seed 和不同提示词的场景。ComfyUI 界面上的 “Queue” 面板可以设 batch count,或者用 /prompt API 循环调用。
Queue Batch 的优点
每次执行都是独立的,一张图失败不影响其他图;显存峰值固定,不会因为 batch 变大而飘;可以在某张图 OOM 后自动降级(比如把 tile size 从 1024 降到 512 重跑)。这些特性对无人值守跑一晚非常重要。
Queue Batch 的缺点
模型在这几张图之间会被反复加载卸载(除非 ComfyUI 的模型缓存能用上),总吞吐低于 batch_size。经验上是 batch_size 的 60-75%。
模式三:多实例(多进程 / 多 ComfyUI)
起多个 ComfyUI 实例,每个占一张卡或者同一张卡的显存分区,各自消费队列。多 GPU 对单图生成无效(采样严格串行),但能线性提升批量吞吐。两台 4090 跑同一个工作流,可以把一整晚的批量任务时间砍半。
多实例的正确姿势
不要在同一个 GPU 上起两个实例:显存会被各占一半,单实例的 batch_size 从 8 掉到 3,总吞吐反而更低。正确做法是每个实例绑一张卡,用 CUDA_VISIBLE_DEVICES 隔离,再在前面加一个简单的队列分发器。
# 多实例启动与环境变量(Linux / Windows 通用)
# 实例 1 绑定 GPU 0
CUDA_VISIBLE_DEVICES=0 python main.py \
--port 8188 --listen 0.0.0.0 \
--force-fp16 \
--extra-model-paths-config models_gpu0.yaml
# 实例 2 绑定 GPU 1
CUDA_VISIBLE_DEVICES=1 python main.py \
--port 8189 --listen 0.0.0.0 \
--force-fp16 \
--extra-model-paths-config models_gpu1.yaml
# 用一个极简分发器把任务均匀派给两个实例
python - <<'PY'
import json, random, urllib.request, pathlib
NODES = ["http://127.0.0.1:8188/prompt", "http://127.0.0.1:8189/prompt"]
wf = json.loads(pathlib.Path("workflow_api.json").read_text(encoding="utf-8"))
for i in range(400):
w = json.loads(json.dumps(wf))
for k, v in w.items():
if v.get("class_type") == "KSampler":
v["inputs"]["seed"] = 20241105 + i
body = json.dumps({"prompt": w}).encode("utf-8")
req = urllib.request.Request(
random.choice(NODES), data=body,
headers={"Content-Type": "application/json"})
urllib.request.urlopen(req).read()
PY
三种模式的对比表
| 模式 | 显存特征 | 吞吐 | 稳定性 | 适用场景 | 坑 |
|---|---|---|---|---|---|
| batch_size | 线性增长,峰值高 | 最高(基准 100%) | 中,一张失败全批失败 | 同构图多 seed 筛选 | 输入图尺寸与 batch 数必须严格对齐 |
| Queue Batch | 固定峰值,不飘 | 约 60-75% | 高,失败可独立重试 | 不同提示词、不同 seed 的正式出图 | 模型反复加载,需要缓存策略 |
| 多实例(多 GPU) | 每卡独立预算 | 近似线性叠加 | 高,单实例故障不影响其他 | 长夜批量、多项目并行 | 同卡多实例会互相挤占显存 |
显存预算表:按显卡型号给配置
这张表是整篇文章最常被截图的部分。原理很简单:batch_size 和显存的关系近似线性,多 ControlNet 是固定开销,Tiled VAE 是兜底。先看通用参考:8GB→batch_size 1-2,12GB→2-4,16GB→4-8,24GB→8-16。
| 显卡 | 显存 | 推荐基础模型 | batch_size | 可叠加 ControlNet | 是否开 Tiled VAE | 备注 |
|---|---|---|---|---|---|---|
| RTX 3060 8G | 8GB | SD1.5 | 1-2 | 最多 2 个 | 必须开 | SDXL 需 –lowvram,速度慢 |
| RTX 4060 8G | 8GB | SD1.5 / SDXL 轻量 | 1-2 | 最多 2 个 | 必须开 | SDXL + 2 ControlNet 已接近上限 |
| RTX 3060 12G | 12GB | SDXL | 2-4 | 2-3 个 | 建议开 | 性价比最高的 SDXL 入门卡 |
| RTX 4070 12G | 12GB | SDXL / FLUX 量化 | 2-4 | 2 个 + IP-Adapter 1 个 | 建议开 | FLUX 量化跑 1024 可接受 |
| RTX 4070 Ti S 16G | 16GB | SDXL / FLUX 量化 | 4-8 | 3 个 | 2048 时开 | 批量渲染的主力配置 |
| RTX 4080 S 16G | 16GB | SDXL / FLUX.1-dev fp8 | 4-8 | 3 个 + IP-Adapter 1 个 | 2048 时开 | 单卡生产力分水岭 |
| RTX 4090 24G | 24GB | SDXL / FLUX.1-dev | 8-16 | 3 个 + IP-Adapter 2 个 | 仅 4K 放大时开 | 24G 是 SDXL 工业流的舒适区 |
| RTX 5090 32G | 32GB | FLUX.1-dev fp16 | 12-20 | 4 个以内 | 仅 4K 放大时开 | Blackwell 可用 –fp8_e4m3fn-unet,并有 FP4 |
| A6000 / L40S 48G | 48GB | FLUX 全精度 | 16-24 | 信号冲突比显存更早成为瓶颈 | 大分辨率放大时开 | 多 ControlNet 的边际收益已很低 |
模型的最低门槛与舒适门槛
买卡或者决定跑什么模型时,先对一下这三组数字:SD1.5 最低 4GB / 舒适 6GB;SDXL 最低 6GB / 舒适 8GB;FLUX.1 量化 8GB / 舒适 12GB。注意这是”单模型单 ControlNet”的门槛,多 ControlNet 每加一个要另算 1.5-2.5GB。
启动参数的用法
门槛不够的时候,靠启动参数救。–lowvram 把模型分块搬到 CPU,能跑但会慢 3-5 倍;–medvram 折中;–novram 几乎完全走 CPU,只适合调试。–force-fp16 让模型以半精度加载,显存省近一半,但部分 VAE 在 fp16 下会出 NaN,需要配合 --fp16-vae 或者手动指定 VAE 精度。
# 不同显存档位的推荐启动参数
# 8GB:SDXL 勉强,必须分块 + 半精度
python main.py --lowvram --force-fp16 --use-split-cross-attention
# 12GB:SDXL 主战场,可用 FP8 的 UNet 进一步省显存
python main.py --medvram --force-fp16 --fp8_e4m3fn-unet
# 16GB:SDXL 全速,FLUX 量化可用
python main.py --force-fp16 --fp8_e4m3fn-unet
# 24GB 及以上:不加显存参数,只固定注意力后端
python main.py --force-fp16 --use-pytorch-cross-attention
# Blackwell(50 系):可启用 FP4 / FP8 的 UNet 路径
python main.py --force-fp16 --fp8_e4m3fn-unet --fast
# 常见组合说明
# --lowvram 把模型分块搬到 CPU,能跑但慢 3-5 倍
# --medvram 折中方案,适合 12GB
# --novram 几乎全走 CPU,仅调试用
# --force-fp16 模型半精度加载,显存近减半
# --fp8_e4m3fn-unet UNet 用 FP8 存储,Blackwell 上还有 FP4 路径
注意力后端:0.3.x 之后的默认变化
ComfyUI 0.3.x 起默认 sdp_attention(PyTorch 的 scaled dot-product attention),部分自定义节点会强制 xformers 或 split_attention。这带来的问题是:同一份工作流在不同机器上可能走了不同的注意力实现,速度差 10-20%,极端情况下个别节点的 mask 形状会不兼容,直接报错。
工业流的做法是固定后端:启动时显式指定 --use-pytorch-cross-attention,并在文档里写清楚。如果某个 ControlNet 节点强制 xformers(这在 Advanced-ControlNet 里偶有出现),要么换等价的官方节点,要么接受这个节点走 xformers,但要单独记在案。
完整工作流结构:节点连接一张图说清
下面是多 ControlNet组合的标准形态——Depth 作结构、SoftEdge 作风格、IP-Adapter 参考主体,末端Tiled VAE 解码。命名里带空格是刻意的:在日志里搜 “多 ControlNet” 能一眼定位所有相关节点。
节点连接清单
# 节点连接清单(文本版,可直接对照搭建)
[Checkpoint Loader]
MODEL ──► [ControlNet Apply Advanced] ──► [ControlNet Apply Advanced] ──► [IPAdapter Advanced] ──► KSampler
CLIP ──► CLIP Text Encode (Positive / Negative)
VAE ──► VAE Decode (Tiled)
[Load Image: depth_ref.png]
└─► [Depth Anything V2 Preprocessor] ──► [ControlNet Apply Advanced] (depth_sdxl, strength 0.85, 0.0-1.0)
[Load Image: style_ref.png]
└─► [Canny / SoftEdge Preprocessor] ──► [ControlNet Apply Advanced] (softedge_sdxl, strength 0.45, 0.2-0.8)
[Load Image: subject_ref.png]
└─► [IPAdapter Unified Loader] (ip-adapter-plus_sdxl_vit-h.safetensors) ──► IPAdapter Advanced
weight 0.62 / weight_type linear / start_at 0.0 / end_at 0.9
[Empty Latent Image] 1024 × 1024 / batch_size 4
└─► KSampler (dpmpp_2m / karras / 28 steps / cfg 5.5 / seed fixed)
KSampler ── LATENT ──► [VAE Decode (Tiled)] tile 512 / overlap 64 ──► Save Image
等效的 API 格式片段
{
"4": {
"class_type": "CheckpointLoaderSimple",
"inputs": { "ckpt_name": "sd_xl_base_1.0.safetensors" }
},
"12": {
"class_type": "ControlNetApplyAdvanced",
"inputs": {
"positive": ["6", 0], "negative": ["7", 0],
"control_net": ["10", 0], "image": ["21", 0],
"strength": 0.85, "start_percent": 0.0, "end_percent": 1.0
}
},
"13": {
"class_type": "ControlNetApplyAdvanced",
"inputs": {
"positive": ["12", 0], "negative": ["12", 1],
"control_net": ["11", 0], "image": ["22", 0],
"strength": 0.45, "start_percent": 0.2, "end_percent": 0.8
}
},
"14": {
"class_type": "IPAdapterAdvanced",
"inputs": {
"model": ["13", 2], "ipadapter": ["15", 0], "image": ["23", 0],
"weight": 0.62, "weight_type": "linear",
"start_at": 0.0, "end_at": 0.9, "unfold_batch": false
}
},
"9": {
"class_type": "VAEDecodeTiled",
"inputs": {
"samples": ["3", 0], "vae": ["4", 2],
"tile_size": 512, "overlap": 64, "temporal_size": 64, "temporal_overlap": 8
}
}
}
为什么这样接线
顺序上,ControlNet 在前、IP-Adapter 在后。这样 IP-Adapter 看到的是已经被结构约束过的模型,它做风格注入时是在”有结构的图”上着色,而不是先自由生成再套结构。反过来接,结构会明显被风格冲掉。
另一个细节是每个 ControlNet 独立的正负文本输入。ControlNetApplyAdvanced 需要 positive/negative 两个输入,串联时要把前一个节点的输出接进后一个,而不是都接原始 CLIP 编码。漏接会导致后面那个 ControlNet 的强度被前面覆盖。
性能调优检查清单
下面这份清单是我每次接手一个新工作流都会过一遍的,顺序按”收益/成本”排序。前面的项基本免费,后面的项需要改架构。
第一层:零成本,立刻能改
- 确认 ControlNet 数量 ≤ 3,且其中一个可旁路。每次出图的组合要写进 manifest。
- IP-Adapter 权重 ≤ 0.8,从 0.6 起调,每次只动一个变量。
- 打开 VAE Tiled,512 / 64 起步,根据内容类型微调 overlap。
- 固定 seed 策略,batch 里禁止用 randomize。
- 固定注意力后端,启动参数写明 –use-pytorch-cross-attention 或等价项。
- 确认所有 ControlNet 输入图尺寸一致,且 batch 数与 latent batch 数对齐。
第二层:需要一点调试
- 看时间分布:把采样时间、VAE 解码时间、放大时间分开记录。如果 VAE+放大超过一半,先优化解码。
- 降分辨率再放大:直接从 1024 生成再放大,比直接跑 2048 省 60% 以上。2048 的激活值是 1024 的近 4 倍。
- 预处理器缓存:Depth/Canny 的预处理结果固定,提前算好存盘,别每次重跑。
- 模型常驻:Queue Batch 场景下确认模型缓存生效,避免反复从磁盘加载。
- batch_size 找拐点:从 1 往上加,直到单张图平均耗时不再下降。这个点通常就是显存拐点。
第三层:需要改架构
- 多 GPU 分发:两张卡起两个实例,前面加队列分发器,吞吐近似翻倍。
- 分阶段管线:低分辨率出构图 → 锁定种子 → 高分辨率细化 → Tiled VAE 出图。这是工业流的标准三段式。
- 失败降级策略:tile 从 1024 降到 512,batch 从 8 降到 4,自动重试。
- 日志与断点续跑:每张图记录 seed、参数哈希、输出路径,中断后从断点继续。
一个容易被忽略的调优点:放大模型
放大模型(ESRGAN 系列)的显存占用和耗时经常被低估。4x-UltraSharp 对 1024 的图做一次 2 倍放大,中间张量在 2048×2048 量级,单次可能吃掉 4-6GB。如果走到这里才 OOM,前面所有的显存预算全部白算。解决办法是把放大模型也放进 Tiled 流程,或者使用专门的分块放大节点。
第二个:CFG 和步数不是靠感觉调的
多 ControlNet 场景下,CFG 过高会放大 ControlNet 之间的冲突。我一般把 CFG 压在 5-6 之间,步数 26-30。步数再往上,提升的是细节稳定性而非质量,20 步和 28 步的差别在批量场景里远不如”同一组参数跑 100 张不出错”来得重要。
常见问题:把踩过的坑一次说完
为什么两个 ControlNet 明明权重一样,出的图却偏一个?
权重一样不代表影响力一样。不同 ControlNet 模型对同一权重值的响应强度不同,Depth 在 0.7 时已经很强,SoftEdge 在 0.7 时可能还偏弱。正确做法是按”效果”而不是”数值”去配,先把每个 ControlNet 单独跑一遍,记录它能出效果的最小权重,再按比例配。
IP-Adapter 开了之后人物脸变了,怎么保住脸?
这是权重博弈的典型表现。分两步解决:第一,把 IP-Adapter 的主体权重降到 0.55-0.6;第二,如果需要保脸,换成 ip-adapter-plus-face 专用模型,它对五官的注入更精确,权重 0.7-0.8 也不会压掉结构。注意 face 模型对全身构图影响很小,别指望它顺手改服装。
Tiled VAE 开了之后图变模糊了,正常吗?
不正常。变模糊通常是 tile 太小(<256)或者 overlap 不够。另一个常见原因是把 tile 开在了 VAE Encode 阶段而不是 Decode 阶段——有些节点把两者绑在一起,编码分块会丢掉细节。确认只对 Decode 分块,Encode 保持整图。
batch_size = 4 时只有第一张用了 ControlNet,后面三张忽略,为什么?
典型的 batch 不对齐。ControlNet 的三个输入(positive、negative、image)会按照各自的 batch 维度传播,只要有一个的 batch 是 1,输出就会收窄成 1。检查:Load Image 节点的 batch_size 是否为 4,预处理器是否输出 4 张,以及 ControlNetApplyAdvanced 是否被串了同一个 image 输入。
同一个工作流,昨天能跑今天 OOM,什么原因?
九成是隐式变量变了。检查三处:模型文件是否被更新(哈希变了),启动参数是否变(比如从 –medvram 变成了默认),以及自定义节点是否自动升级(尤其是 IPAdapter Plus 和 Advanced-ControlNet)。工业流要求这三处全部锁定,任何升级都要走一次对照测试再进生产。
多 ControlNet 一定要配 IP-Adapter 吗?
不一定,但商业图里 80% 的场景会用。ControlNet 管的是空间结构,IP-Adapter 管的是”像不像”。如果你的需求是”按这个线稿上色”,ControlNet 一个 Lineart 就够;如果是”按这个品牌风格做一张产品海报”,几乎必须上 IP-Adapter。多 ControlNet 与 IP-Adapter 组合的真正价值,是把”可控”和”相似”分开调,而不是靠一个参数同时满足两个需求。
两张卡的实测对照:把预算表落到自己的机器上
预算表是通用参考,落到具体项目一定要自己测一遍。下面是我在两个常见配置上做的对照实验,任务完全一样:SDXL 1024×1024、28 步、dpmpp_2m + karras、CFG 5.5,Depth 0.85 + SoftEdge 0.45 双 ControlNet,IP-Adapter 0.62。
12GB(RTX 3060 12G)的实测读法
batch_size = 1 时,单图 18 秒左右,显存峰值 9.1GB,其中采样阶段约 6.8GB,VAE 解码那一下冲到 9.1GB。把 VAE 解码换成 Tiled(512 / 64)之后,峰值掉到 7.4GB,单图总耗时只多了 0.4 秒。这就是 Tiled VAE 的价值:用几毛钱的解码时间,换 1.7GB 的显存余量。batch_size 提到 2 之后峰值 11.2GB,正好卡在 92% 的位置,再往上必炸。
24GB(RTX 4090)的实测读法
batch_size = 8 时峰值 19.6GB,单图均摊 12.8 秒;batch_size = 12 峰值 23.4GB,已经贴着上限,单图均摊反而回升到 13.4 秒——多了 4 张但总耗时涨得更快,说明这里已经进入显存交换区。最终选 8,留 4GB 余量给放大模型。GPU 利用率在 batch 拐点之前是线性上升的,过了拐点就开始互相等待。
一个反常识的观察
在 12GB 卡上,从 2 个 ControlNet 减到 1 个,单图耗时从 18 秒掉到 13.5 秒,但出图合格率从 91% 掉到 74%。砍 ControlNet 省出来的 4.5 秒,会被返工的 17% 吃掉。所以判断该不该加第三个 ControlNet,不只看它多花多少时间,要看它能把合格率抬多少。抬不到 3 个点,就不值得加。
选型决策:先定批量模式,再定卡
很多人先买卡再想批量,结果发现自己的场景根本用不上大 batch。如果出图是”不同产品、不同提示词”,Queue Batch 就够,显存需求比 batch_size 低一档,12GB 的卡可以撑住全流程。如果出图是”同构图筛 50 个 seed”,batch_size 才有意义,这时候卡的显存直接决定一轮筛多久。
写在最后:工业流是一种约束下的创作
把多 ControlNet、IP-Adapter、Tiled VAE 和批量渲染拼在一起,最后拼出来的不是”更强的生成能力”,而是一套在约束下稳定产出的机制。你放弃了一些自由发挥的空间(固定 seed、固定参数、最多三个 ControlNet),换来的是”这批图可以交差”。
我见过不少设计师把工作流搭到十几个节点,看起来像一座电路板,但问他”这张图为什么这么出”,他答不上来。工业级工作流的反面不是简陋,而是不可解释。每一个 ControlNet 为什么在这里、权重为什么是 0.45、tile 为什么是 512,都有理由,才叫可复现。
如果你只记住这篇文章的一句话,我希望是这句:构图用多 ControlNet 管住,显存用 Tiled VAE 管住,批量用结构管住。三者缺一,工作流都会在某个深夜的 OOM 里崩掉。









暂无评论内容