AI图鉴

AI图鉴首页 › AI 资讯 › 黑图、彩色噪声、NaN、偏色怎么排查

黑图、彩色噪声、NaN、偏色怎么排查

来源:掘金 · 发布时间:10-26 · 由 AI图鉴每小时自动聚合

黑图、彩色噪声、NaN、偏色怎么排查 这篇文章解决什么问题 工作流完成但输出全黑、彩色噪声、花屏、过曝或偏色,先查数值、协议与保存链路,也要核对输入是否本就黑暗。症状只给检查方向,不能单独证明模型或某

黑图、彩色噪声、NaN、偏色怎么排查

这篇文章解决什么问题

工作流完成但输出全黑、彩色噪声、花屏、过曝或偏色,先查数值、协议与保存链路,也要核对输入是否本就黑暗。症状只给检查方向,不能单独证明模型或某个算子出了错。先固定 prompt 与 seed,避免不断换输入掩盖同一问题。

先给结论:先判断 latent 是否有效,再检查 VAE协议、dtype、数值范围和颜色/媒体链。

症状分类

第一步:检查日志中的 NaN/Inf

下面是未运行的 PyTorch 诊断提示,适用于真实数值张量。该示意会创建布尔掩码与有限值副本,可能增加显存和同步开销。大张量应按目标实现分块采集,并说明检查覆盖范围;抽样结果不能写成全量统计。它保留原始 NaN/Inf 个数,仅在有限值子集上计算极值;本次没有执行 PyTorch 或 GPU,真实张量统计仍待目标环境采集:

import torch

def tensor_health(name, x):

finite = torch.isfinite(x)

finite_values = x[finite]

count = x.numel()

finite_count = int(finite.sum())

state = ("EMPTY" if count == 0 else "NO_FINITE_VALUES" if finite_count == 0

else "NON_FINITE_PRESENT" if finite_count < count else "FINITE_ONLY")

print(name, "shape=", tuple(x.shape), "dtype=", x.dtype,

"state=", state, "finite=", finite_count, "count=", count,

"finite_min=", float(finite_values.min()) if finite_count else None,

"finite_max=", float(finite_values.max()) if finite_count else None,

"nan=", int(torch.isnan(x).sum()), "inf=", int(torch.isinf(x).sum()))

检查:

- 初始 noise;

- 首帧 latent;

- 每若干采样步的 latent;

- 最终 latent;

- VAE输出。

首次观测到 NaN 的检查点比最终黑图更有定位价值;若只间隔采样,它还不能证明实际首次产生位置。

VAE/latent协议错配

VAE/latent 错配是一个检查方向,本文没有故障样本分布证明它的发生频率。检查:

模型家族

checkpoint版本

latent通道

空间/时间压缩

scale factor

mean/std

帧数对齐

VAE因果padding

现象:

- latent数值看似有限;

- Decode后完全噪声;

- shape通过节点但内容无意义;

- 换回官方VAE立即正常。

不要用图片模型VAE逐帧解视频latent,除非模型明确这样设计。

模型任务错配

以下是待核的组合类型示例,实际兼容性由对应模型版本的官方配置裁决:

- T2V checkpoint接I2V条件节点;

- Wan high/low expert顺序颠倒;

- LTX dev和distilled组件混用;

- Hunyuan原版VAE配1.5;

- Text Encoder版本不匹配;

- LoRA基座不同。

错误可能不报shape异常,因为自定义节点做了适配。最小化到官方模板验证。

dtype与数值溢出

FP16动态范围较小,某些VAE或归一化层可能溢出。排查顺序:

- 保持主DiT设置不变;

- 按目标 VAE 官方支持选择 BF16 以扩大动态范围,或使用 FP32 作数值对照;记录实际生效 dtype 与输出变化;

- 关闭不可靠FP8/INT4转换;

- 关闭fast math或实验kernel;

- 使用官方attention;

- 检查LoRA scale;

- 比较base权重。

PyTorch AMP会按算子选择精度,但第三方节点可能强制cast。1

量化错误

社区量化可能出现:

- 转换脚本不支持某层;

- scale异常;

- embedding/输出层被错误量化;

- high/low expert映射错;

- loader版本不匹配;

- LoRA在线注入失败;

- 量化格式名相同但实现不同。

验证:

官方BF16基线正常?

→ 同workflow只换量化文件

→ 只运行最小规格

→ 检查层缺失/unknown key

→ 比较数值范围

不要同时加入蒸馏LoRA和cache,否则无法定位。

Scheduler/模型参数化错配

若模型预测flow velocity,却被当作epsilon处理,或timestep/sigma映射错误,输出可能保持噪声。常见现象:

- 采样每步数值变化异常小或爆炸;

- steps增加仍是噪声;

- 官方sampler正常,通用sampler异常;

- 低步蒸馏使用错误时间点。

回到官方workflow的sampler、scheduler、shift、steps和CFG。

全黑并不总是NaN

可能是:

- RGB值范围是 [-1,1],保存器按[0,1]错误截断;

- alpha全0;

- YUV色彩范围错误;

- HDR metadata让播放器显示异常;

- 16-bit帧被8-bit编码器错误读取;

- 通道维顺序错误;

- 图像张量仍在BGR。

分别保存:

- VAE输出Tensor统计;

- 单帧PNG;

- 无压缩或常规H.264 MP4;

- 在不同播放器查看。

若核对的是同一批源帧,PNG 正常而最终 MP4 异常,优先缩小到帧读取、颜色转换、编码、封装和播放这一段。它不能证明具体编码器是根因,也不能排除抽样之外的源帧异常。

偏色排查

红蓝互换

检查 RGB/BGR。

整体灰白

检查归一化、limited/full range、HDR→SDR。

暗部发紫/发绿

检查VAE精度、量化、色彩矩阵和subsampling。

随时间漂色

检查temporal tile、自动调色、VAE时间一致和光源Prompt。

先用标准色块检查保存与颜色转换;若该 VAE 官方支持编码与解码,再做受控 round-trip,并分别保存输入与输出。两种对照不要混写成同一个已执行实验。

Tile接缝与分段错误

若画面空间上固定分区颜色不同,检查:

- spatial tile overlap;

- blending window;

- tile normalization;

- 边界padding。

若某个时间点后整体偏色,检查:

- temporal chunk初始上下文;

- overlap; -因果历史;

- 每段独立归一化;

- 分段写盘色彩设置。

黑图故障树

文件完整性

核对:

- SHA-256;

- 下载大小;

- safetensors header;

- Git LFS是否只下载了pointer;

- 磁盘坏块/下载中断;

- 权重分片是否齐全;

- 文本编码器和VAE路径;

- 模型卡版本。

Git LFS pointer被当权重时通常会在加载阶段报错,但部分自定义loader错误信息不清晰。

最小诊断顺序

- 官方workflow;

- 官方完整精度权重;

- 官方VAE和Text Encoder;

- 最小合法尺寸/帧;

- 无LoRA;

- 无cache;

- 官方sampler;

- 保存单帧PNG;

- 检查tensor数值;

- 再逐项恢复优化。

原始异常值不能被“修好”的统计覆盖

旧的统计方式先调用 nan_to_num,再求最小值和最大值,会把 NaN 替成零、把无穷替成有限极值,读者看到的范围便不再代表原始有限样本。若整组都是 NaN,替换后的范围可能变成零到零,很容易误读成“正常但画面黑”。本稿将两种目的分开:诊断先保留异常个数,有限值范围只从原始有限子集中提取;实际修复或展示需要替换时,另存转换后的数据并说明规则,不能覆盖诊断证据。

空张量、全非有限和混合异常也必须区分。空张量表示该检查点没有元素,范围不可计算;全非有限表示有元素,但没有可用于有限范围的数值;混合异常则既报告异常个数,又报告剩余有限值范围。None 代表没有范围,不是零。有限值全部为零只能证明这些数值没有 NaN/Inf,它可能对应黑画面,也可能是合法中间量,不能命名为“图像健康”。具体范围是否符合 latent、VAE 输出或图像保存协议,需要另查组件契约。

真实 Tensor 诊断还应记 shape、dtype、设备、检查点和运行编号。文本编码器输出、采样 latent 和最终 RGB 的范围不能套用同一个阈值,dtype 改变也不能自动算通过。上面的 PyTorch 函数适用于实数数值张量的诊断示意;它没有处理所有稀疏、复数或第三方容器,不能直接放进任意节点并声称通用。本次仅在下面的纯标量教学例验证有限值统计原则,没有验证真实 Tensor 的设备同步、性能成本或后端兼容性。

最小例:保留 NaN/Inf,再缩小检查边界

这段标准库程序不依赖 PyTorch,不解码 PNG 或 MP4,也不加载模型。health 接收作者构造的普通 int/float 列表;locate 接收完整字典,缺键会 KeyError。输入被限定为本文有限样例,不是用于任意外部 JSON 的产品接口。媒体是否正常是作者填写的观测标记,而不是程序对文件的检查结果。它的价值是把空、全异常、混合异常和有限数值分开,并让媒体段的结论停在“继续检查”。

在 examples 目录运行:

python3 check_numeric_boundary.py

完整程序如下:

"""Finite scalar fixtures; no tensors, image decoding, video or model execution."""

import math

def health(values):

# shortcut: author lists of ordinary int/float scalars, not arbitrary tensor input.

if any(type(v) not in (int, float) for v in values):

raise ValueError("real_scalars_required")

finite = [v for v in values if math.isfinite(v)]

nan_count = sum(math.isnan(v) for v in values)

inf_count = sum(math.isinf(v) for v in values)

state = ("EMPTY" if not values else "NO_FINITE_VALUES" if not finite

else "NON_FINITE_PRESENT" if nan_count or inf_count else "FINITE_ONLY")

return dict(state=state, count=len(values), finite_count=len(finite),

nan_count=nan_count, inf_count=inf_count,

finite_min=min(finite) if finite else None,

finite_max=max(finite) if finite else None)

def locate(record):

for key in ("png_ok", "mp4_ok"):

if record[key] is not None and type(record[key]) is not bool:

raise ValueError("media_observation_must_be_bool_or_none")

latent, decoded = health(record["latent"]), health(record["decoded"])

if latent["state"] in ("EMPTY", "NO_FINITE_VALUES"):

return "INSUFFICIENT_LATENT_FINITE_VALUES"

if latent["state"] == "NON_FINITE_PRESENT":

return "NON_FINITE_AT_RECORDED_LATENT"

if decoded["state"] in ("EMPTY", "NO_FINITE_VALUES"):

return "INSUFFICIENT_DECODED_FINITE_VALUES"

if decoded["state"] == "NON_FINITE_PRESENT":

return "LATENT_TO_DECODED_BOUNDARY_PENDING"

if record["png_ok"] is False:

return "RANGE_OR_SAVE_BOUNDARY_PENDING"

if record["png_ok"] is True and record["mp4_ok"] is False:

return "MEDIA_BOUNDARY_PENDING"

return "NUMERIC_OBSERVATIONS_ONLY"

def fixture(**changes):

row = dict(latent=[-1.0, 0.0, 1.0], decoded=[0.0, 0.5, 1.0],

png_ok=True, mp4_ok=True)

row.update(changes)

return row

def rejects(action, expected):

try:

action()

except ValueError as exc:

assert str(exc) == expected

else:

raise AssertionError("invalid fixture accepted")

def main():

cases = [([], "EMPTY", None, None),

([float("nan")], "NO_FINITE_VALUES", None, None),

([float("inf"), -float("inf")], "NO_FINITE_VALUES", None, None),

([-2.0, 3.0, float("nan"), float("inf")], "NON_FINITE_PRESENT", -2.0, 3.0),

([0.0, 0.0], "FINITE_ONLY", 0.0, 0.0)]

for values, state, low, high in cases:

result = health(values)

assert (result["state"], result["finite_min"], result["finite_max"]) == (state, low, high)

print(f"fixture_health={state} finite={result['finite_count']} nan={result['nan_count']} inf={result['inf_count']} range={low},{high}")

samples = [(fixture(latent=[]), "INSUFFICIENT_LATENT_FINITE_VALUES"),

(fixture(latent=[0.0, float("nan")]), "NON_FINITE_AT_RECORDED_LATENT"),

(fixture(decoded=[]), "INSUFFICIENT_DECODED_FINITE_VALUES"),

(fixture(decoded=[0.0, float("inf")]), "LATENT_TO_DECODED_BOUNDARY_PENDING"),

(fixture(png_ok=False), "RANGE_OR_SAVE_BOUNDARY_PENDING"),

(fixture(mp4_ok=False), "MEDIA_BOUNDARY_PENDING")]

for row, expected in samples:

assert locate(row) == expected

assert locate(fixture()) == "NUMERIC_OBSERVATIONS_ONLY"

rejects(lambda: health([True]), "real_scalars_required")

rejects(lambda: health(["nan"]), "real_scalars_required")

rejects(lambda: locate(fixture(png_ok=1)), "media_observation_must_be_bool_or_none")

rejects(lambda: locate(fixture(mp4_ok="black")), "media_observation_must_be_bool_or_none")

print("fixture_png_ok_mp4_bad=MEDIA_BOUNDARY_PENDING root_cause=UNKNOWN")

print("self_check=16_assertions_passed scope=author_scalar_fixture_only")

if __name__ == "__main__":

main()

本次 CPU 真实执行退出码为 0,标准错误为空。标准输出为:

fixture_health=EMPTY finite=0 nan=0 inf=0 range=None,None

fixture_health=NO_FINITE_VALUES finite=0 nan=1 inf=0 range=None,None

fixture_health=NO_FINITE_VALUES finite=0 nan=0 inf=2 range=None,None

fixture_health=NON_FINITE_PRESENT finite=2 nan=1 inf=1 range=-2.0,3.0

fixture_health=FINITE_ONLY finite=2 nan=0 inf=0 range=0.0,0.0

fixture_png_ok_mp4_bad=MEDIA_BOUNDARY_PENDING root_cause=UNKNOWN

self_check=16_assertions_passed scope=author_scalar_fixture_only

16 个断言包括五类数值样例、六个分层方向、一个有限观测边界与四种拒绝输入。混合样例保留一处 NaN 和一处 Inf,有限范围只来自 -2 和 3,未先替换异常值。全 NaN、全无穷和空列表都没有有限范围;全零列为 FINITE_ONLY,并未宣布画面可用。最后的媒体样例只输出待检查媒体边界和根因未知。没有任何真实生成图片、张量或视频参与这些断言。

按同一运行的中间物定位

先给失败运行一个编号,再将 latent 统计、VAE 输出统计、原生单帧、保存后的 PNG 和最终视频关联到相同编号与帧位置。若预览来自前一轮缓存,视频来自后一轮,它们的差异无法定位媒体段;若只看首帧 PNG,而后半视频全黑,也不能宣布全部源帧正常。至少覆盖首段、异常前后和末段,记录所查帧的时间或索引,不把抽样目检写成全帧通过。

假设记录到 latent 有有限值且不含 NaN/Inf,VAE 输出开始出现 Inf,下一项应检查解码边界的输入协议、精度、权重和相关算子。但这只是两个被记录检查点之间的观察差异;它不能证明一定是 VAE 权重损坏,也不能证明解码前完全没有短暂异常。若检查点是每五步一次,就说明这是一段区间;需要更细定位时,再缩小采集间隔或按目标实现添加检查点,而不是从稀疏日志推断准确的首次产生步骤。

若两个数值检查点都有限,下一项是核范围、通道和保存规则。有限不代表正确:把原本约定的负值当成零到一图像直接截断,可以改变输出;把 BGR 当 RGB 会改变颜色;alpha 为零可能让显示透明。这些变化没有必要制造 NaN。故障单应把原始统计和转换后统计分开,并记变换代码或节点版本;只有成片一张截图,通常不足以区分输入、解码和保存问题。

若同一组 PNG 在相同帧索引下看起来正常,而 MP4 异常,就比较送入编码器的实际文件序列、读取像素格式、颜色转换设置、输出封装和播放器。检查输出分辨率、帧数与帧率,避免把错误序列或不完整文件当作模型黑图。换播放器正常可以支持播放兼容性方向,但它也不证明源文件所有元数据都正确;再次编码正常同样会改变多个条件,仍需记录具体差异,不能省略“待查”两字。

把兼容性核验变成一个最小对照

先确认模型家族、任务类型与实际组件哈希,再核 VAE、文本编码器、scheduler 和节点版本。仓库名字相同、文件名有相同版本数字,不能证明它们构成官方支持的组合;同样,节点不报 shape 错误也不代表 latent 的缩放、均值或时序协议兼容。第三方节点可能补维度或做转换,这会让错误到解码才显现。核验需要官方模板与真实配置并排比,本文没有执行任何模型组合验证。

提高解码精度时只改该段,保留主模型与输入,核实际节点是否按设定运行。BF16 与 FP16 的动态范围和精度特征不同,某个实现允许配置不等于所有层都采用该精度;AMP 的算子选择也不能替第三方强制 cast 的节点保证数值稳定。若改了精度后现象改善,就写出具体配置与观察,再保留原失败日志;不要把一次改善写成“FP16 总是不稳定”或“FP32 已根治”。1

量化对照先回到受支持的完整精度组合,再只更换量化文件或对应加载路径。若量化版本同时需要不同 loader,就把这两项当一个组合差异报告,不能假装只改了权重精度。检查缺失层、unknown key、格式与基座条件;LoRA、缓存、采样器和实验 kernel 在这个对照阶段应固定。只要多项一起变,黑图消失仍无法给其中某项单独归因。本文的统计例也没有验证任何量化算法质量。

时间分块与空间分块不能随意套用。目标 VAE 的因果上下文、overlap 和官方模板决定可以怎么拆;HunyuanVideo-1.5 官方 ComfyUI 指南在本次核验仍对 tiled 解码的 temporal_size 提出明确要求,不能照搬“切得更小总更省”的经验。对接缝或后半变色,应保存分块边界前后源帧、节点配置和编码前后结果,先确定异常在哪一层出现,再决定是否调整 overlap 或时间上下文。2

诊断交接与完成条件

交接包应同时保存:失败配置与组件哈希、原始统计及检查点、被查看的帧索引、原生与最终媒体样本、一次只改变一项的对照表。对照表记退出码、输出是否完整、哪一层首先被观测到异常,以及尚未解释的候选。若暂时只有混合非有限统计,就保留异常个数与有限范围,不自动替换数据后上传一个“修好了”的范围;若只有播放器截图,就承认尚缺原生帧证据。

完成条件由实际需求确定:不是节点跑完就通过,也不是 NaN 个数变为零就通过。至少应确认目标规格完整、输出可读、数值与媒体链约束满足,并由实际内容检查判断是否达到业务质量。标准库自检只验证本例统计与分层逻辑,没有能力满足这些成片条件。没有 NaN、节点跑完或容量检查通过,都不能替代目标成片的完整性与视觉质量验收。

本文结论

黑图、噪声、NaN 和偏色首先是待解释的症状。保留原始非有限统计,再分 latent、解码帧、保存与媒体输出逐层核对;模型协议、数值精度、颜色与编码都是候选。固定输入做单变量对照,才能继续判断原因。

参考资料

Footnotes

- Automatic Mixed Precision package — torch.amp,PyTorch,持续更新,访问日期 2026-10-14。docs.pytorch.org/docs/2.14/a… autocast。 ↩ ↩2

- ComfyUI Usage Guide for HunyuanVideo-1.5,Tencent Hunyuan,2025-11,访问日期 2026-10-14。github.com/Tencent-Hun… ComfyUI 模板、采样配置和 Tiled VAE 注意事项。 ↩

本文由 AI图鉴自动抓取并翻译,版权归原作者所有;如需查看原文请点下方链接。

相关 AI 工具

相关免费羊毛

同源资讯

阅读原文(juejin.cn)

标题、摘要与正文均来自该信源的公开页面,版权归原作者所有;本站自动聚合与翻译,仅作出处跳转。

← 返回 AI 资讯列表AI工具图鉴首页

返回 AI图鉴首页 · 提交工具 · 商务合作 · 关于本站 · 591 款 AI 工具分类导航 · 每日 AI 热门项目与动态