UniWorld-View 每次跑出来画面都不一样 / 是随机种子没固定还是条件没对齐?

文章导读
画面每次都不同,通常不是单一原因。可以先把它拆成两类:一类是进程里有随机源没被固定,比如种子没传进去、传进去但被后续初始化覆盖、采样阶段另起随机流;另一类是条件没真正对齐,你以为改了提示词或参考图,实际配置文件、默认值、缓存里的旧结果仍然生效。判断顺序建议反过来做:先固定种子连跑两次,确认能不能复现;能复现,说明随机性已被压住,剩下的差异就该归到条件输入上;不能复现,就继续往并行、精度、数据加载这
📋 目录
  1. 一 在脚本与配置里找 seed 相关参数的落点
  2. 二 固定种子连跑两次,比对输出是否完全一致
  3. 三 只改条件输入一项,观察输出哪部分跟着变
  4. 四 逐条排除其他随机源
A A

画面每次都不同,通常不是单一原因。可以先把它拆成两类:一类是进程里有随机源没被固定,比如种子没传进去、传进去但被后续初始化覆盖、采样阶段另起随机流;另一类是条件没真正对齐,你以为改了提示词或参考图,实际配置文件、默认值、缓存里的旧结果仍然生效。判断顺序建议反过来做:先固定种子连跑两次,确认能不能复现;能复现,说明随机性已被压住,剩下的差异就该归到条件输入上;不能复现,就继续往并行、精度、数据加载这些方向排查。整页围绕控制变量法展开,一次只改一项,把漂移归因清楚再谈调参。

UniWorld-View 出图不稳定时,不要先怀疑模型本身。建议先做固定种子双跑对比:两次输出逐像素一致,则问题在条件输入未对齐,按单变量表逐项改;不一致,则按采样、数据加载顺序、并行线程、精度模式四类随机源逐条关掉。所有结论以仓库实际字段、运行日志和文件哈希为准,找不到的入口记为未确认,不要臆造参数名。

在脚本与配置里找 seed 相关参数的落点

不同仓库对随机种子的命名差别很大,直接照抄别人的 `--seed` 很容易传了个寂寞。做法是先在仓库里搜字段,再顺着调用链确认这个值最终交给了谁。下面只是命令骨架,字段名以你本地仓库实际出现的为准。

# 1. 搜代码里的种子相关标识
grep -rn `--include`=*.py -E "(^|[^a-zA-Z_])(seed|random_seed|rng|torch_seed|numpy_seed|generator)([^a-zA-Z_]|$)" . | head -n 60

# 2. 搜配置与启动脚本里的同名键
grep -rn -iE "seed|deterministic|reproduc" `--include`=*.yaml `--include`=*.yml `--include`=*.json `--include`=*.toml `--include`=*.sh . | head -n 60

# 3. 确认这个值被谁消费:把 X 换成第 1 步命中的变量名
grep -rn "X" `--include`=*.py . | grep -iE "set_seed|manual_seed|seed_everything|RandomState|default_rng"

需要记录三件事:参数的默认值、它被读取的位置、它生效的时机。常见坑是种子在模型权重加载之后才设置,或者在采样循环里又重新构造了一次随机数生成器,前面的固定等于没做。如果仓库里确实搜不到任何种子入口,就把这一项明确记成「未确认」,在实验记录里注明,而不是自己补一个参数名上去假装能固定。

固定种子连跑两次,比对输出是否完全一致

这一步是所有后续对比的前提:固定之后能不能复现。输出目录一定分开命名,不要覆盖,否则你比的是同一份文件。

python infer.py `--config` cfg.yaml `--out` runs/seed_check_a
python infer.py `--config` cfg.yaml `--out` runs/seed_check_b

# 文件级哈希对比
sha256sum runs/seed_check_a/*.png runs/seed_check_b/*.png

哈希相同基本可以认为逐字节一致;哈希不同再往下看差异有多大,避免把元数据或压缩方式差异误判成画面差异。

# 逐像素差值骨架,按需替换库与路径
from PIL import Image
import numpy as np

a = np.asarray(Image.open("runs/seed_check_a/sample_0.png").convert("RGB"), dtype=np.int16)
b = np.asarray(Image.open("runs/seed_check_b/sample_0.png").convert("RGB"), dtype=np.int16)
d = np.abs(a - b)
print("max_abs_diff:", d.max())
print("mean_abs_diff:", d.mean())
print("pixel_equal_ratio:", (d.max(axis=2) == 0).mean())

判读方式:最大差值很小且集中在少数像素,通常是精度或算子非确定性造成,画面观感接近;平均差值明显、几乎整幅都在变,说明随机源还没关住,直接进入最后一节。这一步不要凭肉眼判断「看着差不多」,差值数字要落到记录里。

只改条件输入一项,观察输出哪部分跟着变

能复现之后,差异来源就从随机性切换到可控变量。每轮实验只动一个字段,其余全部锁死。可以先用下面这个模板把计划写下来再跑,避免中途顺手改了别的。

本次只改的字段其余固定项预期观察点实际观察
提示词中的一个名词seed、采样步数、分辨率、参考图、模型权重、配置文件中其他键全部不变主体变化,构图与色调保持接近待填
分辨率或宽高比同上,仅改尺寸构图重心位移,细节密度变化待填

单次实验的记录格式建议固定成一行,方便回看:日期 | 只改字段=旧值→新值 | 固定项清单 | 输出目录 | max_abs_diff 对比基准 | 观察结论。如果改了提示词画面几乎没变,要先怀疑配置没被读到,比如命令行参数被配置文件里的同名键覆盖、或者只改了模板没重新渲染,这时候结果说明的是链路问题,不是模型不敏感。

逐条排除其他随机源

固定种子后仍然不一致,按下面的清单一条条关,每一轮只关一项,关完回到上一节的双跑对比。具体接口名各仓库不同,这里只给方向。

  • 采样过程:初始噪声是否每次重新采样、采样器中是否有额外随机分支、以及某些增强式采样开关。通用思路是显式传入噪声张量或把采样器切到确定性路径,并在日志里打印噪声来源,确认不是每次都新建。
  • 数据加载顺序:训练或批量推理时打乱样本、多 worker 读取、随机裁剪与随机翻转都会引入随机。做法是把打乱关掉、worker 数设为单进程或固定、把预处理阶段的随机变换关闭后再对比。
  • 并行与线程设置:多卡、多进程切分、算子内部的并行归约都可能让浮点累加顺序不同。先退到单卡或单进程重跑,再配合固定线程数环境变量,观察是否变成一致;这一步主要是定位,不是长期方案。
  • 精度模式:半精度、低精度矩阵乘、卷积算法自动选择都会带来末位差异。把精度切回全精度、关闭算法自动搜索或固定算法选择后重跑,看差值是否收敛到很小。

排除过程中保留每轮的输出目录和差值记录,别覆盖。最终要形成一句可复述的判断:这套配置下画面差异由哪一类随机源贡献,哪些条件字段真正影响输出。判断依据是哈希、差值和日志,而不是感觉「这次好像稳定了」。