Qwen / FLUX / WAN 混合部署黑图问题分析
Qwen / FLUX / WAN 混合部署黑图问题分析
生成日期:2026-06-16
远程仓库:https://github.com/Comfy-Org/ComfyUI
本地提交:2f4c4e98
结论摘要
当前证据指向:黑图不是单一 VAE 或单一模型文件问题,而是数值异常(NaN/Inf)在长时间混合工作负载中被放大后的最终表现。保存 PNG 前的 RuntimeWarning: invalid value encountered in cast 是结果侧症状,不是根因;真正需要定位的是采样期间哪个模型输出、attention 后激活、ref latent、VAE decode 输入第一次出现非有限值。
最强相关路径是 Qwen Image 的 diffusion model,尤其是 comfy/ldm/qwen_image/model.py 的 attention、RoPE、block 输出和 ref_latents 拼接。ComfyUI 当前 Qwen Image 配置已经限制推理 dtype 为 bfloat16 或 float32,说明主线代码意识到 fp16 风险;但 GitHub issue 中仍有 GGUF、fp8mixed、TensorBooster、Sage Attention、ROCm、旧版本 ComfyUI 组合绕过或放大了这些风险。
TensorBooster 源码未在本机 custom_nodes 中安装,GitHub/网页搜索也没有公开命中 TensorBoosterNode 源码。因此本报告不能断言 TensorBooster 是根因,只能把它列为高风险放大器:它在工作流中启用了 cache_dit、gpu_fp8 cache policy、token merge、可能的 attention/cache patch,正好覆盖 Qwen block 的 transformer_options["patches"] / patches_replace 接入面。
已核查源码位置
ComfyUI 主线
comfy/supported_models.py:1799-1815QwenImage.supported_inference_dtypes = [torch.bfloat16, torch.float32],默认不支持 fp16。
comfy/model_base.py:2100-2115- QwenImage 将
attention_mask、cross_attn、reference_latents传入 diffusion model。
- QwenImage 将
comfy/sd.py:1045-1099- VAE decode 主路径;decode 后没有对
pixel_samples做 finite check。
- VAE decode 主路径;decode 后没有对
nodes.py:310-318VAEDecode直接返回vae.decode(latent)。
nodes.py:334-351VAEDecodeTiled直接返回vae.decode_tiled(...)。
nodes.py:1651-1657- 保存图像时把
image.cpu().numpy()转uint8。NaN 到这里会触发RuntimeWarning: invalid value encountered in cast,并可能落成黑图。
- 保存图像时把
comfy/model_management.py:409-415- xformers
0.0.18被明确标注高分辨率黑图 bug。
- xformers
comfy/model_management.py:1640-1654- macOS 14.5+ 自动启用 attention upcast;注释明确提到 macOS black image bug。
Qwen Image 节点与模型
comfy_extras/nodes_qwen.py:28-50TextEncodeQwenImageEdit会把输入图缩放到约 1024x1024 后vae.encode成reference_latents。
comfy_extras/nodes_qwen.py:73-106TextEncodeQwenImageEditPlus会把视觉输入缩放到 384x384 给 Qwen VL,同时再缩放到约 1024x1024、8 对齐后 VAE encode 为多个reference_latents。
comfy/text_encoders/qwen_image.py:20-49- 图像 token 由 token id
151655替换为 image embed;图像数量和 token 数量不一致时没有显式日志。
- 图像 token 由 token id
comfy/text_encoders/qwen_image.py:61-85encode_token_weights裁掉模板 token 并裁attention_mask;这里如 template_end 异常,可能影响文本上下文长度。
comfy/ldm/qwen_image/model.py:158-203- Qwen attention Q/K/V 投影、RMSNorm、拼接 text/image stream、RoPE、
optimized_attention_masked。这是 NaN/Inf 首要插桩点。
- Qwen attention Q/K/V 投影、RMSNorm、拼接 text/image stream、RoPE、
comfy/ldm/qwen_image/model.py:403-427process_img生成 packed latent tokens 和 RoPE ids;分辨率、T/H/W、ref image offset 需要日志。
comfy/ldm/qwen_image/model.py:452-497attention_mask被转成torch.finfo(x.dtype).max级别的负偏置,ref_latents直接拼接到hidden_states和img_ids。
comfy/ldm/qwen_image/model.py:502-558- 60 层 transformer block 主循环,以及
patches/patches_replace/ ControlNet 注入点。TensorBooster 一类插件最可能在这里生效。
- 60 层 transformer block 主循环,以及
comfy/ldm/qwen_image/model.py:560-568norm_out、proj_out和 unpatchify 输出;这是 diffusion output 离开 Qwen 模型前的最后插桩点。
FLUX 对照
comfy/ldm/flux/model.py:258-260comfy/ldm/flux/layers.py:255-257comfy/ldm/flux/layers.py:353-354
FLUX 路径对 fp16 的 block 输出有 torch.nan_to_num(... posinf=65504, neginf=-65504) 防护。Qwen 路径没有同级别的 guard。这个差异是怀疑 Qwen 数值溢出的重要源码证据。
GitHub Issues 证据
与本问题最直接相关
-
- 标题即为混合 WAN / FLUX / Qwen 同 GPU 随机黑图。
- 复现条件:生产 pod/container,RTX 5090,多 workflow 连续处理,WAN/FLUX/Qwen 共享 GPU。
- 工作流中有
TensorBoosterNode,参数包括cache_dit=enable、cache_policy=gpu_fp8、token_merge=enable、merge_ratio=0.5、sage_attention=disable。 - 维护者回复重点:ComfyUI
0.13已旧,建议升级。
-
- Qwen GGUF 黑图,T4,
--force-fp16 --fp16-vae --dont-upcast-attention。 - 日志显示
qwen_2.5_vl_7b_fp8_scaled、Qwen GGUF、Q5/Q4 quant、WanVAEdecode,最后nodes.py invalid value encountered in cast。 - 第一次运行黑图,第二次可能正常,长步数又卡住,符合状态/缓存/数值不稳定交织的特征。
- Qwen GGUF 黑图,T4,
-
- RTX 2060,Qwen Image Edit 黑图,未使用 Sage Attention。
- 关键观察:
--force-fp16 --fp32-unet正常,--force-fp16和--fp32-vae不正常。作者判断更像 Qwen diffusion model fp16 问题,而非 VAE 或 text encoder。
-
- 作者继续指出 Qwen image fp16 会在 latent 中产生 NaN,黑图是 unet infer / VAE decode 阶段的后果。
- 提议方向:fp16 下 clamp 到 65536 附近,但尚未确定应放在哪个点。
-
- 明确说明原 Qwen Image reasoning code 在 fp16 下会产生完全黑图。
- PR 尝试只让部分 Q/K/V 和 joint cross attention 进入 fp16,保留更敏感部分原 dtype;说明 Qwen attention 精度路径是社区已定位的热点。
相关但非 Qwen 专属
-
- WAN2.1 使用
--use-sage-attention后黑图,禁用 custom nodes 仍可复现。 - 日志同样落到
invalid value encountered in cast,模型是 WAN21、fp8_e4m3fn weight、manual cast fp16。
- WAN2.1 使用
-
- SDXL + ControlNet +
--use-sage-attention --fast fp16_accumulation fp8_matrix_mult黑图。 - 去掉
--use-sage-attention正常;日志有Array must not contain infs or NaNs和保存阶段 NaN cast warning。
- SDXL + ControlNet +
-
- Mac M1/M3 上
--force-upcast-attention修复黑图。 - 说明 attention upcast 是已知有效缓解手段,不限 Qwen。
- Mac M1/M3 上
-
- Qwen Image Edit 2511 在 ROCm 7.2 + RX 7700XT/gfx1101 上黑图;fp8mixed 版本,LoRA 或多图输入时更快黑。
- 报告中提到黑图可在后续运行持续出现,需要干净重启/清状态,和本问题“长时间混合部署后出现,重启 worker 恢复”相似。
-
- AMD + PixelDiT/PiD 黑图,禁用 custom nodes 仍可复现。
--fp32-vae无效,最终也是nodes.py invalid value encountered in cast。说明“保存阶段警告”是跨模型的 NaN 症状。
根因分类
A. Qwen diffusion model fp16/低精度溢出
证据等级:高。
复现条件:
- 老卡或无 bf16 原生支持设备,例如 RTX 2060、T4。
- 启动参数或 GGUF/custom loader 迫使 Qwen UNet/diffusion model 走 fp16。
- Qwen Image Edit、Qwen GGUF、Lightning LoRA、fp8mixed / Q4 / Q5 组合。
- 高 ref image token 数、多图 edit、大分辨率或长 steps。
已知修复/缓解:
- 不要强制 Qwen UNet fp16;优先 bf16,其次 fp32。
- 移除
--force-fp16,或对 UNet 使用--fp32-unet。 - 对 Qwen attention/block 输出增加 finite guard 或局部 upcast。
- 关注/移植 PR #12522 的“敏感 Q/K 保持原 dtype,部分分支 fp16”方案。
怀疑源码:
comfy/ldm/qwen_image/model.py:158-203comfy/ldm/qwen_image/model.py:286-318comfy/ldm/qwen_image/model.py:526-568
B. Attention backend / Sage Attention / xformers / macOS attention 精度问题
证据等级:中到高。
复现条件:
- WAN 或 SDXL 使用
--use-sage-attention。 --fast fp16_accumulation fp8_matrix_mult与 Sage Attention 叠加。- macOS MPS 长 steps 或较高分辨率。
- xformers
0.0.18高分辨率。
已知修复/缓解:
- 禁用
--use-sage-attention做 A/B。 - macOS 使用
--force-upcast-attention。 - 避免 xformers
0.0.18。 - 使用 pytorch attention 或 split attention 对照。
怀疑源码:
comfy/ldm/modules/attention.pycomfy/model_management.py:409-415comfy/model_management.py:1640-1654comfy/ldm/qwen_image/model.py:192-194comfy/ldm/wan/model.pyattention blocks
C. TensorBooster cache / token merge / fp8 cache 对 Qwen block 状态污染
证据等级:中,因源码缺失。
复现条件:
- ComfyUI #14492 工作流中的
TensorBoosterNode:cache_dit=enablecache_policy=gpu_fp8attention_caching=disabletoken_merge=enablemerge_ratio=0.5
- 同一 worker 连续执行 WAN、FLUX、Qwen,多模型共享同 GPU。
- 黑图仅在生产-like 连续运行后出现,重启 worker 后恢复。
已知修复/缓解:
- 对 Qwen 暂时禁用 TensorBooster。
- 至少禁用
cache_dit和token_merge,保留其它选项逐项二分。 - 每次模型族切换后清理 TensorBooster 自有 cache。
- 按模型族隔离 worker:Qwen worker、WAN worker、FLUX worker 分开。
怀疑源码/接入面:
- TensorBooster 源码未知;应检查它是否注册
transformer_options["patches"]、patches_replace、wrapper 或 monkey patch。 - ComfyUI 接入口:
comfy/ldm/qwen_image/model.py:508-550comfy/ldm/qwen_image/model.py:182-187comfy/patcher_extension.pycomfy/model_patcher.py
D. VAE decode 只是暴露 NaN,不一定是根因
证据等级:高。
复现条件:
- 上游 latent 已经包含 NaN/Inf。
VAEDecode或VAEDecodeTiled将非有限 latent 解码为非有限 pixel。- 保存 PNG 前触发
invalid value encountered in cast。
已知修复/缓解:
- 不应只在保存前
nan_to_num,这会掩盖根因。 - 可以在 debug 模式下保存前拦截并报错,附带 min/max/finite ratio。
- 对生产临时止血可在 VAE decode 后
nan_to_num,但必须打日志和指标。
怀疑源码:
comfy/sd.py:1045-1099nodes.py:310-318nodes.py:334-351nodes.py:1651-1657
E. ComfyUI 旧版本和混合部署生命周期问题
证据等级:中。
复现条件:
- ComfyUI
0.13.0或旧部署长期不升级。 - 同一进程长期服务多模型族,低 VRAM/normal VRAM/自定义 unload/free memory 节点混用。
- 多 custom nodes patch model management、offload、attention、cache。
已知修复/缓解:
- 升级 ComfyUI 到当前版本,并同步更新前端、comfy-kitchen、相关自定义节点。
- 将 worker 生命周期做成 N 次请求后重启,或模型族切换后重启。
- 在混合部署里不要让所有模型族共享同一个长寿命 Python 进程。
怀疑源码:
comfy/model_management.pyload/unload/offloadcomfy/model_patcher.pyexecution.py- 自定义节点:Memory Cleanup、Unload Model、FreeMemory、ReservedVRAM、TensorBooster、GGUF、MultiGPU 等。
建议复现矩阵
每个实验只改一个变量,输出黑图时记录首次 NaN 出现位置。
| 组 | Qwen | TensorBooster | WAN/FLUX 前置负载 | dtype | 预期 |
|---|---|---|---|---|---|
| A1 | Qwen 单跑 | 关 | 无 | bf16 | 基线应稳定 |
| A2 | Qwen 单跑 | 关 | 无 | fp32 | 最稳但慢 |
| A3 | Qwen 单跑 | 关 | 无 | fp16/force-fp16 | 若黑,证明 Qwen fp16 问题 |
| B1 | Qwen 单跑 | 开 cache_dit | 无 | bf16 | 验证 TensorBooster cache |
| B2 | Qwen 单跑 | 开 token_merge | 无 | bf16 | 验证 token merge |
| C1 | WAN -> Qwen | 关 | 有 | bf16 | 验证模型族切换 |
| C2 | FLUX -> Qwen | 关 | 有 | bf16 | 验证模型族切换 |
| C3 | WAN -> FLUX -> Qwen | 开 | 有 | bf16 | 逼近生产 |
| D1 | Qwen | 开 | 有 | bf16 | 每次 Qwen 前清 cache |
| D2 | Qwen | 开 | 有 | bf16 | 每次 Qwen 前重启 worker |
需要增加的日志点
统一 tensor stats helper
建议加一个仅 debug flag 开启的 helper,避免默认性能损耗:
def log_tensor_stats(name, x, step=None, block=None):
if x is None or not torch.is_tensor(x):
return
with torch.no_grad():
finite = torch.isfinite(x)
bad = finite.numel() - finite.sum().item()
if bad:
logging.warning(
"%s nonfinite=%s/%s shape=%s dtype=%s device=%s min=%s max=%s step=%s block=%s",
name, bad, finite.numel(), tuple(x.shape), x.dtype, x.device,
torch.nan_to_num(x).amin().item(), torch.nan_to_num(x).amax().item(),
step, block,
)
Qwen 模型插桩
QwenImageTransformer2DModel._forward开始:x、context、attention_mask、ref_latents数量、shape、dtype、device、finite ratio。
process_img返回后:hidden_states、img_idsshape,T/H/W token 数,输入分辨率。
ref_latents拼接前后:- 每个 ref 的 shape、dtype、min/max、finite ratio。
reference_image_num_tokens。
Attention.forward:img_query/img_key/img_value/txt_query/txt_key/txt_value投影后。- RMSNorm 后。
- RoPE 后
joint_query/joint_key。 optimized_attention_masked输出后。
- block loop:
- 每 5 层或每层检查
hidden_states、encoder_hidden_states。 - 一旦出现 nonfinite,记录
block_index、backend、dtype、transformer patch 名称。
- 每 5 层或每层检查
- final output:
norm_out后、proj_out后、reshape 前后。
TensorBooster 插桩
由于源码缺失,建议在该节点源码中增加:
- 节点入口打印 model class、model id、dtype、device、ComfyUI version。
- 打印启用的 patch:
- attention backend
- svg acceleration
- fast rmsnorm
- optimize memory
- attention caching
- cache policy
- cache_dit / threshold
- token_merge / merge_ratio
- cache key 日志:
- 是否包含 model identity、dtype、device、shape、resolution、sequence length、block index、model family。
- 每次 cache hit/miss 时记录 key 摘要。
- token merge 日志:
- merge 前后 token 数,是否包含 text token、image token、ref image token。
- 是否跳过 Qwen ref_latents 或 attention_mask 场景。
- 模型族切换日志:
- 当前模型从 WAN/FLUX 切到 Qwen 时是否清 cache。
VAE / 保存阶段插桩
nodes.py:310VAEDecode.decode:- decode 前 latent stats。
- decode 后 image stats。
nodes.py:334VAEDecodeTiled.decode:- tile decode 前后 stats。
comfy/sd.py:1066-1076:- 每个 batch decode 输入/输出 stats。
nodes.py:1656保存前:- 如有 nonfinite,报错或至少 warning,带 node id、prompt id、文件前缀、min/max/finite ratio。
Model management / worker 生命周期
- 每个 prompt 开始和结束:
- 当前 loaded models、model class、device、dtype、patch 数。
torch.cuda.memory_allocated/reserved/max_memory_allocated。torch.cuda.mem_get_info。
- 模型 unload 后:
- 被卸载模型列表、仍驻留模型列表、cache 清理动作。
- worker 级别:
- 连续请求数、上一个模型族、当前模型族、是否发生模型族切换。
推荐修复优先级
-
快速止血:
- Qwen worker 禁用 TensorBooster 的
cache_dit和token_merge。 - 禁止 Qwen UNet fp16,使用 bf16 或 fp32。
- Qwen 与 WAN/FLUX 分 worker。
- 每 N 次请求或每次模型族切换重启 worker。
- Qwen worker 禁用 TensorBooster 的
-
确认根因:
- 加上述 tensor stats,找首次 nonfinite。
- 先跑无 TensorBooster 的 Qwen 单模型基线,再逐项打开 TensorBooster 功能。
- 对比 Qwen 前是否执行过 WAN/FLUX。
-
代码修复:
- 在 Qwen attention/block/final output 添加 debug finite check。
- 参考 FLUX 的
nan_to_num位置,为 Qwen fp16/低精度输出添加可控 guard。 - TensorBooster cache key 必须包含 model family、model object id、block index、dtype、shape、seq length、ref token 信息;模型族切换必须清 cache。
-
长期修复:
- 升级旧 ComfyUI
0.13.0部署。 - 建立混合 WAN/FLUX/Qwen 的集成压测:连续 100-1000 次请求,记录黑图率、NaN 首现点、显存碎片。
- 对每个 attention backend 建立数值一致性 smoke test。
- 升级旧 ComfyUI
附:证据链接
- ComfyUI mixed WAN/FLUX/Qwen issue: https://github.com/Comfy-Org/ComfyUI/issues/14492
- Qwen GGUF black image: https://github.com/Comfy-Org/ComfyUI/issues/10800
- RTX 2060 Qwen fp16 black output: https://github.com/Comfy-Org/ComfyUI/issues/10668
- Qwen fp16 NaN issue: https://github.com/Comfy-Org/ComfyUI/issues/10751
- Qwen fp16 partial PR: https://github.com/Comfy-Org/ComfyUI/pull/12522
- WAN + Sage Attention black image: https://github.com/Comfy-Org/ComfyUI/issues/9077
- SDXL + Sage Attention NaN/black image: https://github.com/Comfy-Org/ComfyUI/issues/13166
- macOS upcast attention black image: https://github.com/Comfy-Org/ComfyUI/issues/4799
- ROCm Qwen Image Edit black images: https://github.com/Comfy-Org/ComfyUI/issues/12839
- AMD PixelDiT black output / NaN cast: https://github.com/Comfy-Org/ComfyUI/issues/14379
更多推荐
所有评论(0)