扩散模型中固定 Seed 为什么仍可能无法完全复现?
简化版
Seed 只决定伪随机数生成器的初始状态,例如初始噪声和祖先采样中的额外噪声。它不固定模型版本、采样器、浮点精度、GPU 算子和执行顺序,所以“同 Seed”不是完整的复现条件。
若只要求视觉近似,记录模型、VAE、Prompt、Seed、尺寸、Scheduler、步数和 CFG 通常足够;若要求逐像素一致,还要固定硬件、依赖版本、确定性算法、随机数设备与 Batch 结构,并接受性能下降。
Seed 是实验配置的一部分,不是生成结果的全局身份证。
详细版
生成过程中的随机源可能包括:
initial_latent = randn(shape, generator=G)
scheduler_noise_t = randn(..., generator=G) # 祖先采样可能每步调用
dropout / stochastic layer # 若推理模式错误
random preprocessing / crop # 图生图或控制输入
即便随机张量相同,浮点计算也可能不同。并行归约的加法顺序不同会产生末位误差,误差经过 20~50 次非线性去噪后可能放大成可见像素差异。
| 复现等级 | 目标 | 需要固定的范围 |
|---|---|---|
| 语义复现 | 主体和构图接近 | 模型、Prompt、主要参数 |
| 视觉复现 | 肉眼几乎一致 | 再固定 Seed、Scheduler、VAE、尺寸 |
| 数值复现 | 像素/latent 误差极小 | 再固定设备、精度、算子与版本 |
| 位级复现 | 输出字节完全相同 | 相同环境、确定性实现和序列化 |
生产系统通常应先明确需要哪一级。跨 GPU 型号追求位级一致成本很高,也未必有业务价值。
完整版教学
1. Seed 实际控制什么
伪随机数生成器根据初始 Seed 产生确定的数列。在相同算法、相同设备和相同调用顺序下,Seed 能重建初始 latent。
g = torch.Generator(device="cuda").manual_seed(42)
latent = torch.randn(shape, generator=g, device="cuda")
但后续任何额外随机调用都会推进生成器状态。多一次 randn,之后整条随机序列都会错位。
2. 初始噪声为什么比 Seed 更直接
Seed 是生成初始噪声的方法,真正进入模型的是噪声 Tensor。不同框架或设备的随机算法可能让同 Seed 产生不同 Tensor。
若跨环境复现非常重要,可以保存初始 latent 的哈希或完整 Tensor。它占用更多存储,却能把“随机数算法差异”和“模型计算差异”分开排查。
3. Scheduler 可能引入额外随机数
DDIM η=0、普通 Euler 等路径通常可确定;DDPM、Euler Ancestral 等会在每步注入新噪声。它们必须使用同一 Generator,并保持调用次数与顺序一致。
某些实现只把 Generator 用于初始 latent,Scheduler 内部却调用全局随机状态,导致固定 Seed 后仍漂移。需要检查 API 是否把 Generator 传入每个随机步骤。
4. 为什么 Batch 大小会改变结果
从随机数生成器一次产生 [4,C,H,W],与分四次产生 [1,C,H,W],数列布局未必在所有设备上相同。动态 Batch 还会改变内核选择和并行归约顺序。
| 变化 | 是否可能影响随机张量 | 是否可能影响浮点计算 |
|---|---|---|
| Batch 大小 | 是 | 是 |
| 图片尺寸 | 是 | 是 |
| 样本排序 | 是 | 是 |
| 并发请求合批 | 是 | 是 |
需要强复现时,应给每个样本独立 Generator,避免请求顺序共享一个全局状态。
5. GPU 浮点计算为什么不总是确定
浮点加法不满足严格结合律:(a+b)+c 与 a+(b+c) 可能有末位差异。GPU 并行线程调度、Atomic 操作和高性能卷积算法会改变归约顺序。
TF32、FP16、BF16 与 FP32 也有不同舍入误差。扩散是迭代系统,微小差异可能在多步中被放大。
“模型推理没有随机层”只说明数学函数确定,不代表底层并行实现逐位确定。
6. 软件和权重版本必须可追踪
同名模型仓库可能更新权重或配置;Tokenizer、Scheduler 默认值和算子库也会变化。至少记录:
model / VAE / LoRA 文件哈希
pipeline 与 scheduler 配置 JSON
框架、CUDA、cuDNN、驱动版本
设备型号、dtype、量化配置
Prompt 原文及解析后的 token/权重
Seed、图片尺寸、Batch、步数、CFG
只记录模型名称和 Seed,通常不足以复现几个月前的结果。
7. Prompt 看似相同也可能不相同
全角空格、换行、Unicode 归一化和 Prompt Parser 版本都会改变 Token。Negative Prompt、加权语法、模板自动补词也属于真实输入。
可靠日志应保存序列化后的最终 Prompt、Token IDs 或其哈希,同时注意敏感内容脱敏和数据保留期限。
8. 如何配置确定性模式
深度学习框架通常提供确定性算法开关,并允许关闭某些基准自动选核。代价是部分算子不可用或性能下降。
torch.use_deterministic_algorithms(True)
torch.backends.cudnn.benchmark = False
model.eval()
这些设置不是跨设备位级一致的万能保证。应在目标硬件上运行回归,并把不确定算子视为显式失败,而不是静默接受漂移。
9. 如何设计复现测试
可以分层定位:
1. 比较初始 latent 哈希
2. 比较文本 embeddings
3. 比较每个时间步输入/输出摘要
4. 找到首次偏离的 step 与模块
5. 比较最终 latent 和 PNG 编码前像素
例如定义最大 latent 绝对误差 <1e-5、结构相似度 SSIM>0.999,并另设位级哈希测试。容差应对应业务复现等级。
10. 分布式服务如何避免随机状态串扰
不要让所有请求共享进程级 Generator。可以用请求 ID 与用户 Seed 派生样本级 Seed,并把它传给所有随机组件。
重试时必须复用原始 Seed 和完整配置;如果重试落到不同 GPU,只能保证平台定义的近似复现等级。若业务需要原图完全一致,直接缓存成品比重新计算更可靠。
11. 常见误区与追问
- 误区:固定 Seed 就一定逐像素相同。 它只固定特定随机源,浮点和版本仍可变化。
- 误区:CPU 与 GPU 使用相同 Seed 会产生相同噪声。 设备可能使用不同随机算法。
- 误区:确定性模式没有成本。 它可能禁用更快内核并降低吞吐。
- 误区:图片看起来一样就证明环境可复现。 视觉相同不等于数值或位级一致。
- 误区:只记录 Seed 就能审计历史结果。 还需权重哈希、完整 Pipeline 参数和环境版本。
- 追问:最稳的跨环境方案是什么? 保存初始 latent 和完整配置;若要求最终字节一致,则缓存最终产物。
- 追问:为何并发会影响结果? 动态合批和共享随机状态会改变形状、顺序与内核执行。
12. 加强记忆
- Seed 只定随机起点,不定整个环境。
- 四层复现:语义、视觉、数值、位级,先明确目标。
- 三类漂移:随机调用、浮点算子、模型与软件版本。
- 强证据:保存 latent 哈希、权重哈希和完整 Pipeline 配置。
- 生产选择:重算只能近似时,严格一致应缓存成品。