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 为 bfloat16float32,说明主线代码意识到 fp16 风险;但 GitHub issue 中仍有 GGUF、fp8mixed、TensorBooster、Sage Attention、ROCm、旧版本 ComfyUI 组合绕过或放大了这些风险。

TensorBooster 源码未在本机 custom_nodes 中安装,GitHub/网页搜索也没有公开命中 TensorBoosterNode 源码。因此本报告不能断言 TensorBooster 是根因,只能把它列为高风险放大器:它在工作流中启用了 cache_ditgpu_fp8 cache policy、token merge、可能的 attention/cache patch,正好覆盖 Qwen block 的 transformer_options["patches"] / patches_replace 接入面。

已核查源码位置

ComfyUI 主线

  • comfy/supported_models.py:1799-1815
    • QwenImage.supported_inference_dtypes = [torch.bfloat16, torch.float32],默认不支持 fp16。
  • comfy/model_base.py:2100-2115
    • QwenImage 将 attention_maskcross_attnreference_latents 传入 diffusion model。
  • comfy/sd.py:1045-1099
    • VAE decode 主路径;decode 后没有对 pixel_samples 做 finite check。
  • nodes.py:310-318
    • VAEDecode 直接返回 vae.decode(latent)
  • nodes.py:334-351
    • VAEDecodeTiled 直接返回 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。
  • comfy/model_management.py:1640-1654
    • macOS 14.5+ 自动启用 attention upcast;注释明确提到 macOS black image bug。

Qwen Image 节点与模型

  • comfy_extras/nodes_qwen.py:28-50
    • TextEncodeQwenImageEdit 会把输入图缩放到约 1024x1024 后 vae.encodereference_latents
  • comfy_extras/nodes_qwen.py:73-106
    • TextEncodeQwenImageEditPlus 会把视觉输入缩放到 384x384 给 Qwen VL,同时再缩放到约 1024x1024、8 对齐后 VAE encode 为多个 reference_latents
  • comfy/text_encoders/qwen_image.py:20-49
    • 图像 token 由 token id 151655 替换为 image embed;图像数量和 token 数量不一致时没有显式日志。
  • comfy/text_encoders/qwen_image.py:61-85
    • encode_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 首要插桩点。
  • comfy/ldm/qwen_image/model.py:403-427
    • process_img 生成 packed latent tokens 和 RoPE ids;分辨率、T/H/W、ref image offset 需要日志。
  • comfy/ldm/qwen_image/model.py:452-497
    • attention_mask 被转成 torch.finfo(x.dtype).max 级别的负偏置,ref_latents 直接拼接到 hidden_statesimg_ids
  • comfy/ldm/qwen_image/model.py:502-558
    • 60 层 transformer block 主循环,以及 patches / patches_replace / ControlNet 注入点。TensorBooster 一类插件最可能在这里生效。
  • comfy/ldm/qwen_image/model.py:560-568
    • norm_outproj_out 和 unpatchify 输出;这是 diffusion output 离开 Qwen 模型前的最后插桩点。

FLUX 对照

  • comfy/ldm/flux/model.py:258-260
  • comfy/ldm/flux/layers.py:255-257
  • comfy/ldm/flux/layers.py:353-354

FLUX 路径对 fp16 的 block 输出有 torch.nan_to_num(... posinf=65504, neginf=-65504) 防护。Qwen 路径没有同级别的 guard。这个差异是怀疑 Qwen 数值溢出的重要源码证据。

GitHub Issues 证据

与本问题最直接相关

  • ComfyUI #14492

    • 标题即为混合 WAN / FLUX / Qwen 同 GPU 随机黑图。
    • 复现条件:生产 pod/container,RTX 5090,多 workflow 连续处理,WAN/FLUX/Qwen 共享 GPU。
    • 工作流中有 TensorBoosterNode,参数包括 cache_dit=enablecache_policy=gpu_fp8token_merge=enablemerge_ratio=0.5sage_attention=disable
    • 维护者回复重点:ComfyUI 0.13 已旧,建议升级。
  • ComfyUI #10800

    • Qwen GGUF 黑图,T4,--force-fp16 --fp16-vae --dont-upcast-attention
    • 日志显示 qwen_2.5_vl_7b_fp8_scaled、Qwen GGUF、Q5/Q4 quant、WanVAE decode,最后 nodes.py invalid value encountered in cast
    • 第一次运行黑图,第二次可能正常,长步数又卡住,符合状态/缓存/数值不稳定交织的特征。
  • ComfyUI #10668

    • RTX 2060,Qwen Image Edit 黑图,未使用 Sage Attention。
    • 关键观察:--force-fp16 --fp32-unet 正常,--force-fp16--fp32-vae 不正常。作者判断更像 Qwen diffusion model fp16 问题,而非 VAE 或 text encoder。
  • ComfyUI #10751

    • 作者继续指出 Qwen image fp16 会在 latent 中产生 NaN,黑图是 unet infer / VAE decode 阶段的后果。
    • 提议方向:fp16 下 clamp 到 65536 附近,但尚未确定应放在哪个点。
  • ComfyUI PR #12522

    • 明确说明原 Qwen Image reasoning code 在 fp16 下会产生完全黑图。
    • PR 尝试只让部分 Q/K/V 和 joint cross attention 进入 fp16,保留更敏感部分原 dtype;说明 Qwen attention 精度路径是社区已定位的热点。

相关但非 Qwen 专属

  • ComfyUI #9077

    • WAN2.1 使用 --use-sage-attention 后黑图,禁用 custom nodes 仍可复现。
    • 日志同样落到 invalid value encountered in cast,模型是 WAN21、fp8_e4m3fn weight、manual cast fp16。
  • ComfyUI #13166

    • SDXL + ControlNet + --use-sage-attention --fast fp16_accumulation fp8_matrix_mult 黑图。
    • 去掉 --use-sage-attention 正常;日志有 Array must not contain infs or NaNs 和保存阶段 NaN cast warning。
  • ComfyUI #4799

    • Mac M1/M3 上 --force-upcast-attention 修复黑图。
    • 说明 attention upcast 是已知有效缓解手段,不限 Qwen。
  • ComfyUI #12839

    • Qwen Image Edit 2511 在 ROCm 7.2 + RX 7700XT/gfx1101 上黑图;fp8mixed 版本,LoRA 或多图输入时更快黑。
    • 报告中提到黑图可在后续运行持续出现,需要干净重启/清状态,和本问题“长时间混合部署后出现,重启 worker 恢复”相似。
  • ComfyUI #14379

    • 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-203
  • comfy/ldm/qwen_image/model.py:286-318
  • comfy/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.py
  • comfy/model_management.py:409-415
  • comfy/model_management.py:1640-1654
  • comfy/ldm/qwen_image/model.py:192-194
  • comfy/ldm/wan/model.py attention blocks

C. TensorBooster cache / token merge / fp8 cache 对 Qwen block 状态污染

证据等级:中,因源码缺失。

复现条件:

  • ComfyUI #14492 工作流中的 TensorBoosterNode
    • cache_dit=enable
    • cache_policy=gpu_fp8
    • attention_caching=disable
    • token_merge=enable
    • merge_ratio=0.5
  • 同一 worker 连续执行 WAN、FLUX、Qwen,多模型共享同 GPU。
  • 黑图仅在生产-like 连续运行后出现,重启 worker 后恢复。

已知修复/缓解:

  • 对 Qwen 暂时禁用 TensorBooster。
  • 至少禁用 cache_dittoken_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-550
    • comfy/ldm/qwen_image/model.py:182-187
    • comfy/patcher_extension.py
    • comfy/model_patcher.py

D. VAE decode 只是暴露 NaN,不一定是根因

证据等级:高。

复现条件:

  • 上游 latent 已经包含 NaN/Inf。
  • VAEDecodeVAEDecodeTiled 将非有限 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-1099
  • nodes.py:310-318
  • nodes.py:334-351
  • nodes.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.py load/unload/offload
  • comfy/model_patcher.py
  • execution.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 开始:
    • xcontextattention_maskref_latents 数量、shape、dtype、device、finite ratio。
  • process_img 返回后:
    • hidden_statesimg_ids shape,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_statesencoder_hidden_states
    • 一旦出现 nonfinite,记录 block_index、backend、dtype、transformer patch 名称。
  • 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:310 VAEDecode.decode
    • decode 前 latent stats。
    • decode 后 image stats。
  • nodes.py:334 VAEDecodeTiled.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 级别:
    • 连续请求数、上一个模型族、当前模型族、是否发生模型族切换。

推荐修复优先级

  1. 快速止血:

    • Qwen worker 禁用 TensorBooster 的 cache_dittoken_merge
    • 禁止 Qwen UNet fp16,使用 bf16 或 fp32。
    • Qwen 与 WAN/FLUX 分 worker。
    • 每 N 次请求或每次模型族切换重启 worker。
  2. 确认根因:

    • 加上述 tensor stats,找首次 nonfinite。
    • 先跑无 TensorBooster 的 Qwen 单模型基线,再逐项打开 TensorBooster 功能。
    • 对比 Qwen 前是否执行过 WAN/FLUX。
  3. 代码修复:

    • 在 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。
  4. 长期修复:

    • 升级旧 ComfyUI 0.13.0 部署。
    • 建立混合 WAN/FLUX/Qwen 的集成压测:连续 100-1000 次请求,记录黑图率、NaN 首现点、显存碎片。
    • 对每个 attention backend 建立数值一致性 smoke test。

附:证据链接

更多推荐