要评估 DMAD 的少步蒸馏效果,先不要直接看学生模型的最终出图。蒸馏在学生侧学习的是教师模型在整条采样轨迹上的输出,而不是教师模型最后生成的那张图;如果教师轨迹本身没有被完整导出、教师与学生两侧的中间状态也没有对齐到同一时间步,那么最终出图差就只能说明“哪里不对”,无法说明是学生模型没有拟合好,还是采样设置(步数、scheduler、guidance、末步归一化)改变了结果。可行的顺序是:在教师模型上用固定 seed、固定 prompts、固定总步数导出多个时间步的中间结果,作为参照;再让学生模型在与教师相同的时间步上导出中间结果;然后逐点对比两侧的中间结果;最后把中间差异与最终出图表现放在同一张对照表中。未对齐轨迹就直接比较出图,通常只能得到无法复现的主观印象。
先固定教师侧的时间步网格、seed 和 prompts,把中间张量与中间图保存下来作为参照物;再让学生模型在可映射到同一网格的时间步上导出中间结果,逐点比较结构与细节差异,并把差异记录到“时间步—差异描述—最终出图表现”对照表中。只有中间差异较小时,出图差异才能归因到采样侧设置;如果中间步就明显偏离,应先调整训练。结论只在当前分辨率、scheduler 与步数配置下成立,换配置需要重测。
在教师模型上导出多个时间步的中间结果
这一步的目的,是在教师模型上建立可用于比较的基准,避免事后用不同 seed、不同步数的出图互相比较。导出前要把采样配置固定下来:seed、prompt 集合、总步数、scheduler 类型、guidance scale、分辨率、dtype。采样器不要用 early-stop 或跳步求解,否则得到的时间步网格不能作为参照。
导出中间结果有两种粒度:保存张量(latent 或去噪后的 x_t),用于精确对齐;保存中间图(用同一个 VAE 解码),用于人眼判断结构。显存和磁盘通常撑不住每一步都保存,建议只保留若干关键步,例如最前面的若干步、中间若干步和末步,具体步数按总步数比例取。
# 通用骨架,函数名与调用方式以仓库和框架的实际接口为准
pipe = load_teacher(...)
pipe.scheduler.set_timesteps(num_inference_steps=TEACHER_STEPS)
latents = init_latents(seed=SEED, shape=(1, C, H // SCALE, W // SCALE))
for i, t in enumerate(pipe.scheduler.timesteps):
latents = denoise_step(pipe, latents, t, prompt_embeds) # 替换成实际采样步
if i in KEEP_STEPS:
save_tensor(latents, f"teacher/step{i:03d}_t{int(t)}.pt")
save_image(vae_decode(latents / vae_scale), f"teacher/step{i:03d}.png")
log_meta(step=i, t=int(t), seed=SEED, scheduler=NAME, guidance=G, prompt_id=PID)
需要一并写进日志的字段至少有:prompt_id、seed、总步数、当前步序号与对应 t 值、scheduler 名称、guidance、分辨率、latent 通道数、VAE 缩放系数。这些字段在后面逐点对齐时会用到。
在相同时间步导出学生模型的中间结果
学生模型是少步的,它实际经过的时间点和教师模型多步采样时经过的时间点通常不在同一个网格上,所以“相同时间步”要按归一化时间(sigma 或 t/T)做映射,取两侧最接近的点作为比较点,而不是按步序号直接对位。步序号对位是最常见的错误来源:教师的第 0 步和学生的第 0 步往往对应完全不同的噪声水平。
映射完成后,先做一致性检查再做任何对比:
assert s_latent.shape == t_latent.shape # (B, C, H, W)
assert s_latent.dtype == t_latent.dtype
assert s_latent.shape[-2:] == (H // vae_scale, W // vae_scale)
assert same_vae(teacher, student) # 通道数与缩放系数一致
assert abs(s_sigma - t_sigma) < SIGMA_TOL # 映射误差在可接受范围内
还要确认两侧保存的是同一种量:有的实现存的是 x_t,有的存的是模型预测的速度 v 或噪声 ε。量不同而形状相同,直接计算距离会得到看似有意义、实际无法解释的数字。如果仓库默认存的是预测值,要么两边统一换算成 x_t,要么在对比时用同一换算规则。时间步取齐、形状一致、量类型一致、VAE 一致,这四项缺一项,后面的对比都不成立。
用相同提示词对比两侧的中间结果差异
对比时必须使用同一 prompt、同一 seed、同一 guidance 和同一解码设置。可以先看图像层面:主体位置、构图、色块分布是否一致;再看局部细节:纹理是否糊、边缘是否碎裂、小结构是否消失。张量层面可以算简单的距离指标(MSE、余弦相似度)用于排序,把差异大的时间步排到前面去看,但不要把某个数值当作“对齐成功”的绝对门槛;不同 prompt、不同分辨率下的量纲不一样。
建议从噪声最高的一段开始看。如果最前面几步学生模型就已经偏离教师模型,说明蒸馏没有学到起始阶段的行为,此时继续看末步意义不大。每一步需要记录的字段固定为:prompt_id、时间步(t 或 sigma)、指标值(若计算)、结构差异描述、细节差异描述、备注(是否与其它 prompt 表现一致)。描述要写得具体,例如“主体轮廓一致,背景纹理明显更平滑”,避免只写“差一点”。
把轨迹差异和最终出图质量对照起来
中间差异本身不是结论,需要确认这个差异是否会传导到最终出图。做法是一个 prompt 一张对照表,把每个比较时间步的差异描述和该 prompt 的最终出图表现放在同一行:
| 时间步 | 中间结果差异描述 | 最终出图表现 | 备注 |
|---|---|---|---|
| 高噪声段 | 主体位置偏离、色块分布不同 | 构图错位或主体缺失 | 多个 prompt 是否同样出现 |
| 中间段 | 结构基本一致,纹理更平滑 | 细节偏糊,整体可接受 | 与教师图并排看 |
| 末步 | 差异突然放大 | 边缘碎裂或出现伪影 | 检查解码与末步归一化 |
填写时,最终出图表现只写能在图上直接看到的东西:构图、细节、色彩、伪影,不要写“质量下降若干”这类无法核验的判断。多个 prompt 要分别成表,再横向看是否存在共性;只有一两个 prompt 有差异时,通常先怀疑条件编码或数据分布,而不是蒸馏本身。
根据对照结果决定调训练还是调采样
对照表填完后按现象分叉,而不是笼统地“再调调参数”:
- 高噪声段或前几步就明显偏离,且与 prompt 无关、普遍出现:差异来源在训练侧。可以优先检查蒸馏损失的权重、学习率、教师轨迹的覆盖范围(采样步数分布是否覆盖到学生实际经过的噪声区间)、学生容量是否足够。改动一次只动一个变量,用同一组 prompt 与 seed 重跑中间结果对比。
- 中间步差异较小,末步或解码后差异突然放大:差异来源更可能在采样侧或解码侧。先核对学生推理时的步数、scheduler、guidance 是否与评估学生时一致,再检查末步的裁剪与归一化、latents 到图像的缩放系数、VAE 是否与教师共用。这类问题改训练往往看不到效果。
- 仅个别 prompt 或长文本偏离:先看条件编码与文本截断,不适合直接归因到蒸馏效果。
每次调整后都回到同一组固定 seed 与 prompt,重跑一次“教师中间结果—学生中间结果—最终出图”三份数据,确认差异描述是否真的变化。当前结论只在现有分辨率、scheduler 和步数配置下成立;换分辨率或换调度器后,时间步映射关系会变,需要重新对齐一次再下判断。