Reading

Diffusion


Fanyi Pu · GPT‑5.6 Sol

· Updated

生成模型需要做到一件有些特殊的事:面对同样的条件,它应该能给出许多不同、又都合理的结果。例如“一只坐在窗边的猫”可以对应不同的毛色、姿态和光照。训练数据提供了这些可能结果的例子,我们希望模型学到它们的分布。

Flow 和 diffusion models 从容易采样的 noise 出发,逐步改变样本。理解这个过程,需要把生成时怎样走、训练时怎样知道往哪里走联系起来。下面先从一个简单的分布变换理解生成,再构造能够训练这种变换的监督信号。

Flow and Diffusion Models

记真实数据为 z∼pdata,初始 noise 为 ϵ∼p0=N(0,I)。我们希望学到一个生成过程,把不同的 ϵ 变成不同的样本,使输出整体接近 pdata。

本文统一使用生成方向的时间:t=0 是 noise,t=1 是 data,xt 表示中间状态。先讨论不带 prompt 的生成;后面再加入条件 c。模型可以在 pixels 上工作,也可以在压缩后的 latent space 中工作,这暂时不影响下面的推理。

Normalizing flows and continuous flows

Transforming samples also transforms their density

先看一个可以手算的例子。若

ϵ∼N(0,1),x=f(ϵ)=2ϵ+3,

那么 x∼N(3,22)。每个 noise sample 都被拉伸两倍、再向右平移 3;对所有 samples 做同样的操作,就把整个分布变成了另一个分布。

复杂数据需要更灵活的 fθ。Normalizing flow (Grover, 2018) 用一串可逆变换构成这个生成器:

ϵ=h0→f1h1→f2⋯→fKhK=x.

生成时,抽一个 ϵ,依次计算这些变换即可。可逆性还让我们能处理另一个问题:给定数据集中的一个样本 z,模型给它多大的 density? 可以从 z 沿变换反向计算,找到唯一对应的 ϵ=fθ−1(z)。

这个 density 可以用来训练模型。Maximum likelihood 要让数据集中的样本得到较高的模型 density,等价于最小化

LNLL(θ)=−1N∑i=1Nlog⁡pθ(zi).

因此,生成只需要计算变换;用 likelihood 训练还需要知道变换怎样改变 density。

回到 x=2ϵ+3。输入区间 [−1,1] 被映射到输出区间 [1,5],其中的样本比例保持不变,但区间宽度翻倍。Density 是每单位长度上的概率质量,所以在对应位置要减半。

样本拉伸两倍,density 减半,对应区间的概率保持不变解析变换 x=2 epsilon+3 把标准 Gaussian 映射为 N(3,2^2)。两图使用相同的横轴单位与 density 纵轴单位,横轴显示 -3 到 9。均值 0,标准差 1,阴影区间 [-1,1],区间宽度 2,峰值 density 0.399;均值 3,标准差 2,阴影区间 [1,5],区间宽度 4,峰值 density 0.199。同一份概率从输入区间 [-1,1] 移到输出区间 [1,5],两个阴影面积相等。曲线由 Gaussian density 直接计算,未使用样本直方图或训练模型。
ϵ∼N(0,1)
0.20.4-202468
ϵ
p0(0)≈0.399
ϵ∈[−1,1],Δϵ=2
x∼N(3,22)
0.20.4-202468
x
pX(3)≈0.199
x∈[1,5],Δx=4
x=2ϵ+3
Pr(−1≤ϵ≤1)=Pr(1≤x≤5)
左右滑动查看完整图示
两图使用相同坐标尺度。平移改变位置,拉伸把同一份概率摊到更宽的区间:阴影宽度翻倍,对应位置的 density 减半,面积保持不变。

于是,任意给定的 x 对应 ϵ=(x−3)/2,它的 density 是

pX(x)=12p0(x−32).

例如计算 x=5 的 density,要先找到输入位置 ϵ=1,再把 p0(1) 除以局部拉伸倍数 2。只做平移时,局部长度没有变化,也就不需要这个除法。

在 d 维空间,长度换成局部体积,拉伸倍数由 Jacobian determinant 给出。对同维、光滑且 Jacobian 非奇异的可逆变换,change of variables (Ermon, 2023) 写成

log⁡pθ(x)=log⁡p0(ϵ)−log⁡|detJfθ(ϵ)|,ϵ=fθ−1(x).

这里 Jfθ=∂fθ/∂ϵ。第一项找到输入位置原有的 density,第二项补偿变换造成的局部体积变化。等价地,也可以写成 log⁡p0(fθ−1(x))+log⁡|detJfθ−1(x)|。

高维下,求逆和计算 determinant 可能很贵。Coupling layers (Ermon, 2023b) 通过限制每层的依赖关系,让这两步容易计算;再叠加多层,增加整体变换的表达能力。

RealNVP 怎样构造这样的可逆层?

RealNVP (Dinh et al., 2017) 把输入分成两部分,用第一部分决定第二部分的缩放和平移:

xa=ϵa,xb=ϵb⊙exp⁡(sθ(ϵa))+mθ(ϵa).

给定输出,先由 xa 找回 ϵa,就能重新算出缩放和平移量,再恢复另一部分:

ϵa=xa,ϵb=(xb−mθ(xa))⊙exp⁡(−sθ(xa)).

Jacobian 是 block triangular,forward log determinant 只需把 sθ(ϵa) 的各个分量相加。这里的 sθ,mθ 是普通神经网络;可逆性来自整个变换的构造。各层交替处理不同分组,让所有维度都参与变化。

Replacing layers with a velocity field

还可以用连续运动实现 noise 到 data 的变换。仍以 x=2ϵ+3 为目标,选一条从输入走到输出的轨迹:

xt=(1+t)ϵ+3t.

t=0 时它等于 ϵ,t=1 时恰好等于 2ϵ+3。对时间求导,得到每条轨迹的 velocity 是 ϵ+3。为了让生成过程只依赖当前状态,利用 ϵ=(xt−3t)/(1+t) 消去初值:

dxtdt=xt+31+t.

这样,“把输入直接变成输出”就被改写为“在每个位置、每个时刻给出一个移动速度”。例如初始值 ϵ=1 会沿 xt=1+4t 运动,最终到达 5;别的初值也遵循同一个 velocity rule,形成同一个分布变换。

对于真实数据,我们不知道合适的 velocity rule,就用网络 utθ(x) 表示它。由此得到 continuous normalizing flow(CNF) (Chen et al., 2018):

X0∼p0,dXtdt=utθ(Xt).

网络的输入是当前状态和时间,输出是与状态同维的 velocity。要得到最后的样本,必须沿这个 velocity field 积分。最简单的 Euler sampler 用小步长 h 近似:

xt+h=xt+hutθ(xt).

这一步与 residual update 很像:在当前状态上加一个小的变化量。采样器反复调用同一个网络;步长决定每次走多远,网络决定往哪个方向、以多大速度走。复杂的 velocity field 一般没有解析解,所以实际生成使用有限步数的数值近似。

如果 velocity 足够光滑,且解在整个时间区间内存在并唯一,就能沿同一个 ODE 从终点反向积分,恢复初值。可逆的是这个完整的 flow map;velocity network 本身不需要设计成可逆网络。

How do we learn the velocity?

到这里,我们定义了生成模型的形式,尚未决定怎么训练它。记当前网络诱导的分布为 ptθ,训练希望让 p1θ 接近 pdata。

一种办法仍然是 maximum likelihood:从真实样本出发反向求解 ODE,同时累计沿途的 density 变化,得到 log⁡p1θ(z),再对它求梯度。连续运动也遵守“局部体积膨胀,density 降低”的关系,只是现在需要把这件事沿时间累计。

怎样沿 ODE 计算 density?

Velocity field 的 divergence

∇⋅utθ(x)=∑j=1d∂ut,jθ(x)∂xj

描述当前位置附近的体积膨胀率。沿着一条轨迹,density 满足

ddtlog⁡ptθ(Xt)=−∇⋅utθ(Xt).

所以

log⁡p1θ(X1)=log⁡p0(X0)−∫01∇⋅utθ(Xt)dt.

在前面的例子里,ut(x)=(x+3)/(1+t),其 divergence 是 1/(1+t)。积分结果为 log⁡2,于是终点 log density 等于起点 log density 减去 log⁡2,正好对应那次两倍拉伸。

给定真实样本 z,把它作为 X1 反向求解,就可以找到 X0 并累计上式中的积分。实际计算的精度取决于 ODE solver;较大维度时,也可以用 trace estimator 估计 divergence。

Likelihood training 把完整的生成映射及其 density 计算放进训练过程。另一种思路是:如果能直接构造中间状态和正确的 velocity,就可以把网络训练成一个回归器。 这就是下一节的 flow matching。

两种训练方式得到的都是 velocity network,生成时都要解 ODE。区别在训练信号:前者提高数据的 likelihood,后者匹配给定的 velocity target。

Flow Matching

A regression target we can construct

真实图片怎么从 noise 变来,我们并不知道。但训练时有一张已知图片 z,也能自己采样一份 ϵ,于是可以人为连接这两个端点。

最简单的连接是线性插值:

xt=tz+(1−t)ϵ,t∈[0,1].

当 t 增大,data 的份额增加,noise 的份额减少。把这两个系数分别记为 αt=t 和 βt=1−t,图中的中间图像都可以直接算出来。

同一张图片与同一份 noise 的线性插值固定一个 20×20 的房屋图案和一份独立 Gaussian noise,按 x_t = t z + (1-t) epsilon 显示 t=0、0.25、0.5、0.75、1 的结果。Data coefficient 依次是 0、0.25、0.5、0.75、1,noise coefficient 相反。这是已知端点的训练插值,不是网络生成结果。显示时统一截断到 [-2,2] 再映射为明暗。
xt=tz+(1−t)ϵ
t=0
0
1
t=0.25
0.25
0.75
t=0.5
0.5
0.5
t=0.75
0.75
0.25
t=1
1
0
Gaussian noise已知的 data
αt
βt
线性 schedule 的两种系数
左右滑动查看完整图示
已知端点的训练插值:每一格都由同一对图片与 noise 计算。展示的是数值混合,不是模糊滤镜,也不是模型的生成轨迹。

这条已知轨迹的 velocity 也能直接算:dxtdt=z−ϵ。因此,训练时随机抽取一张图片、一份独立 noise 和一个时间点,让网络看 (xt,t),预测 z−ϵ:

LCFM(θ)=Et∼U(0,1)z∼pdata,ϵ∼p0[‖utθ(tz+(1−t)ϵ)−(z−ϵ)‖2].

已知的 z 用来制造训练输入和 target;网络只接收当前状态与时间。 因此生成时即使没有目标图片,也能从 noise 开始,反复预测 velocity、更新状态。

训练构造一个时间点,生成依次走过多个时间点训练:独立采样 data z、noise epsilon 和时间 t。z 与 epsilon 一路用于构造 x_t,一路计算固定 target z-epsilon;网络只接收 x_t 和 t,预测 velocity,MSE 的梯度更新网络。生成:初始 Gaussian noise 经过同一个 velocity network 和 Euler sampler 反复更新,当前输出成为下一次输入。训练不需要展开采样链。训练:随机取一个时间点独立采样
z,ϵ,t
构造 noisy input
xt=tz+(1−t)ϵ
Velocity network
utθ(xt)
已知 target
z−ϵ
MSE
‖utθ−(z−ϵ)‖2
梯度更新 velocity network生成:当前状态 → 下一状态 → 再预测初始 noise同一个网络
utθ(xt)
Euler sampler
xt+h=xt+hutθ
输出推进时间,将新状态送回网络
左右滑动查看完整图示
训练中的 target 可以直接算出来;生成中的下一个状态必须依赖网络。虚线表示用于监督的已知 target,所有采样步骤共享同一个网络。

一次训练只需计算一个时间点的回归 loss,无需先跑一遍生成过程。采样时则从 t=0 开始,每一步的输出成为下一步的输入;这时才需要 ODE solver。

Flow Matching (Lipman et al., 2023) 用这种可构造的 conditional velocity 监督网络。Rectified Flow (Liu et al., 2023) 也使用线性插值上的 velocity regression,并通过 reflow 重新构造 noise 与 data 的配对,使后续学到的生成轨迹更直。

为了同时描述其他 noise schedules,把插值写得更一般一些 (Holderrieth & Erives, 2026):

Xt=αtz+βtϵ,(α0,β0)=(0,1),(α1,β1)=(1,0).

这里 αt,βt 决定每个时刻的 data 和 noise 份额。线性插值就是 αt=t,βt=1−t;这也解释了图中两行系数的含义。一般形式的 velocity target 是

α˙tz+β˙tϵ,LCFM=E‖utθ(Xt)−(α˙tz+β˙tϵ)‖2.

Why conditional targets give the right flow

这里还有一个关键问题:训练时,我们随意把 noise 和图片配在一起。同一个附近的 xt 可能由不同配对产生,每个配对要求的 velocity 也可能不同,网络应该听谁的?

平方误差回归会学到给定输入后的 target 均值。因此,理想网络在当前位置输出

ut(x)=E[α˙tz+β˙tϵ∣Xt=x].

这叫 marginal velocity field。它综合了所有可能经过这里的配对,给出一个确定的局部速度。

要理解为什么这个平均速度有用,需要看整个分布。固定 z 时,Xt 的随机性来自 Gaussian noise,所以内部时刻有

pt(x∣z)=N(x;αtz,βt2I).

让 z 遍历数据分布,再把这些 conditional distributions 混合起来,就得到我们希望生成过程跟随的 probability path:

pt(x)=∫pt(x∣z)pdata(z)dz,p0=N(0,I),p1=pdata.

固定图片的 conditional distribution 在数据端点收缩成 point mass;所有图片的混合则恢复完整的数据分布。上面的平均 velocity 恰好能搬运这个混合分布。

配对的直线监督与沿平均 velocity 的生成轨迹一维 toy data distribution 是均值为 -1.5 与 1.5、标准差均为 0.18 的等权 Gaussian mixture。左图选取六对 noise 与 data 值,画出 x_t=t z+(1-t)epsilon 的直线,若配对顺序不同就会相交。右图从相同六个初值出发,用该 mixture 的解析 marginal velocity 积分;每一步都重新取条件平均,得到不相交的弯曲轨迹。右图使用 JavaScript double precision 的 400 步 RK4,不是训练网络的输出。两图横轴为生成时间,纵轴是同一个一维数据坐标。已知的独立配对只知道当前状态
xt=tz+(1−t)ϵ
x˙t=E[z−ϵ∣xt]
-20200.51
x
t
-20200.51
x
t
不同配对可以穿过同一个位置每走一步,重新计算当前位置的平均方向
pdata=12N(−1.5,0.182)+12N(1.5,0.182)
左右滑动查看完整图示
直线是训练 target 的来源,弯曲线是沿 marginal velocity 的采样。两图使用相同坐标范围;颜色与线型仅用于区分示例轨迹,不表示它们仍保留原来的配对关系。

图中每条训练直线都有自己配对的终点,平均 velocity 产生的轨迹则可能弯曲。生成过程需要实现的是整体分布的变化:每个时刻有哪些地方更拥挤、哪些地方更稀疏。它不要求某份 noise 最后到达训练时曾随机配给它的那张图片。

这个条件均值为什么能搬运整个分布?

固定 z,消去 ϵ=(x−αtz)/βt,可得 conditional velocity:

ut(x∣z)=(α˙t−β˙tβtαt)z+β˙tβtx.

它让 pt(x∣z) 满足 continuity equation:

∂tpt(x∣z)=−∇⋅(pt(x∣z)ut(x∣z)).

这表示一个区域内概率质量的变化,由穿过其边界的概率流决定。对 z 积分,再利用 Bayes' rule,得到

∂tpt(x)=−∇⋅(pt(x)ut(x)),ut(x)=∫ut(x∣z)pt(z∣x)dz.

所以 conditional paths 合成后的概率流,正好由 marginal velocity 搬运。另一方面,记 Ut=α˙tz+β˙tϵ,平方误差可以分解成

E‖utθ(Xt)−Ut‖2=E‖utθ(Xt)−ut(Xt)‖2+E‖Ut−ut(Xt)‖2.

第二项与 θ 无关,因此用 conditional target 训练,等价于优化网络与 marginal velocity 之间的误差。这里的微分、除法与密度关系用于 βt>0 的内部时刻,数据端点按极限理解。

Score Matching

刚才直接监督了样本的移动速度。还可以从 denoising 出发:看到一张 noisy image,模型估计其中混入了什么 noise,再利用这个估计指导生成。

连接 denoising 与分布运动的量叫 score (Wetzstein, 2026):

st(x)=∇xlog⁡pt(x).

它描述当前位置附近,log density 朝哪个方向增长、增长多快。例如在一维 Gaussian 中,score 指向均值;在多峰分布中,它根据附近各个峰的相对影响而变化。由于对 log density 求导,乘在 density 上的归一化常数会消失。

Score 指向 log density 增长的方向固定时间,取标准 Gaussian。上图是 density,中图是 log density,虚线切线展示局部斜率,下图把斜率画成沿 x 轴的 score 箭头。三行横轴相同。x=-2 时 score=2;x=-1 时 score=1;x=0 时 score=0;x=1 时 score=-1;x=2 时 score=-2。正 score 向右,负 score 向左,零 score 只画圆点。箭长与 score 的绝对值成正比。曲线、切线、箭头和数值都由同一个 Gaussian 解析式计算。Score 指向 log density 增长的方向
pt=N(0,1),st(x)=ddxlog⁡pt(x)=−x
Density
pt(x)
Log density
log⁡pt(x)
Score
st(x)
00.20.40-2-4
+2
-2-2-2
+1
-1-1-1
0
000
−1
111
−2
222
x
x
x
左右滑动查看完整图示
同一位置的三行图上下对齐。中图的虚线切线给出 log density 的局部斜率,底部箭头用方向和长度表示这个斜率的正负与大小。在这个 Gaussian 中,峰顶 density 最高,但 score 为零;离均值越远,指回均值的箭头越长。

直接监督 ∇xlog⁡pt(x) 仍然困难,因为我们只有数据 samples,没有 pt 的解析式。不过,对一张已知的 clean image 加噪,conditional distribution 是前面写出的 Gaussian,其 score 可以手算。固定 z 和 t,将图片看作 d 维向量,在 βt>0 时,这个 Gaussian 的 log density 为

log⁡pt(x∣z)=−d2log⁡(2πβt2)−‖x−αtz‖22βt2.

对 x 求梯度时,第一项是常数,导数为零;平方距离的梯度是 2(x−αtz),因此

∇xlog⁡pt(x∣z)=−12βt22(x−αtz)=−x−αtzβt2.

在实际构造的样本 xt=αtz+βtϵ 处,xt−αtz=βtϵ,所以这个条件 score 就是 −ϵ/βt。这里的 ϵ 是我们自己加入的 noise,因此可以用 −ϵ/βt 作为已知 target,做 denoising score matching:

LDSM=Et,z,ϵ[w(t)‖stθ(xt)+ϵβt‖2].

其中 w(t)>0 控制不同时间点的训练权重,βt 是所加入 noise 的标准差系数。

这与 flow matching 的训练逻辑相同:网络只看 (xt,t),平方误差使它学到 conditional targets 的均值;这个均值正是 marginal score。令 stθ=−ϵθ/βt,并取 w(t)=βt2,上式就变成直接预测 noise:

Lϵ=E‖ϵθ(xt,t)−ϵ‖2.

因此,“预测加入的 noise”给出了学习 score 的简单训练方式。生成时,我们再把这个预测转换成需要的 denoising direction 或 sampler update。

Noise, data, and velocity prediction

一张 noisy image 里,clean data 和 noise 通过同一个混合公式联系在一起。知道其中一个的估计,就能推出另一个;对同一条 path,也能换算出 velocity。以 xt=tz+(1−t)ϵ 为例:

z^=xt+(1−t)utθ(xt),ϵ^=xt−tutθ(xt),s^t=−ϵ^1−t.
网络输出一对训练样本提供的 targetMSE 的理想预测
Clean data z^zE[z∣xt]
Noise ϵ^ϵE[ϵ∣xt]
Velocity utθz−ϵE[z−ϵ∣xt]
Score stθ−ϵ/(1−t)∇log⁡pt(xt)

这些输出表达了相关的信息,但各自的 MSE 会给训练误差不同的权重。例如在线性路径上,由 z^=xt+(1−t)utθ 得到

‖z^−z‖2=(1−t)2‖utθ−(z−ϵ)‖2.

同样大小的 velocity error,在靠近 data 的时刻对应更小的 clean-data error。因此选择训练目标时,prediction target、时间采样分布和 loss weighting 需要一起考虑。

当噪声很大时,许多不同图片都能解释当前输入,z^=E[z∣xt] 可能只是一个模糊的平均结果。随着 sampler 更新状态,网络获得的信息改变,之后的预测也会改变。这解释了为什么一个擅长 denoising 的网络仍然需要多步生成。

一般 Gaussian path 下,怎样从 score 得到 velocity?

由 Gaussian conditional score 可以得到 denoiser identity:

E[z∣x]=x+βt2st(x)αt.

代入前面的 conditional velocity 并取条件均值:

ut(x)=α˙tαtx+(α˙tαtβt2−β˙tβt)st(x).

这些换算在分母非零的内部时刻使用,端点通过原始网络参数化或极限处理。对于 variance-preserving schedule,常见的 v-prediction target 是 v=αtϵ−βtz;它与 ODE velocity 的关系还取决于 αt,βt 对时间的导数。

ODE and SDE Sampling

前面已经学到了 velocity ut(x):给定当前状态和时间,它告诉我们样本应该怎样移动。现在的问题是,生成时,每一步一定要完全按照这个方向走吗?如果再加一点随机扰动,还能得到同样的数据分布吗?

Starting from a sampling step

先看已经熟悉的 Euler update。把从 t 到 t+h 的时间间隔记为 h,一次更新是

xt+h=xt+hut(xt).

假设 xt=2,网络在这里给出的 velocity 是 −1,步长 h=0.1,那么下一状态就是 1.9。固定输入后重复这一步,答案总是相同的。ODE 生成的多样性来自一开始抽取的 noise;后面怎样走,由初值和 velocity field 决定。

也可以让下一状态不再是唯一的 1.9,而是从它附近随机抽取。实现上,只需额外采样一个独立的标准 Gaussian noise:

xt+h=xt+hut(xt)+σstepξ,ξ∼N(0,I).

例如 σstep=0.2 时,一维的下一状态服从 N(1.9,0.22)。每一步都重新抽取 ξ,同一个初值就能走出不同的路线。后面的 RL post-training 会利用这种随机性,尝试不同的后续生成结果。

但是,改变一个 sample 的走法,也可能改变所有 samples 最后形成的分布。 原来的 ut 已经负责把 noise 搬运成 data;直接在它上面叠加 noise,并不能保证仍然到达原来的目标。先用一个简单例子看清楚这个问题。

Adding noise changes the distribution

暂时不做 noise 到图片的变换,考虑一个更容易检查的目标:已有一批 X∼N(0,1) 的样本,希望它们随机动一动后,整体仍然服从 N(0,1)。这里用 X 表示随机变量,x 表示一次具体取值;讨论 X 的分布,就是讨论大量这样的取值整体如何分布。

如果给每个样本都加上独立 noise 0.6ξ,得到

X′=X+0.6ξ,Var⁡(X′)=1+0.62=1.36.

虽然 noise 的均值为零,正负扰动平均起来会抵消,但方差会相加。所以均值仍然是 0,分布却变宽了。这就是“noise 把样本散开”的具体含义。

为了给新增的 0.36 方差留出空间,可以先把原有样本乘以 0.8:它们朝均值 0 收缩,方差变成 0.82=0.64。再加上刚才的 noise:

X′=0.8X+0.6ξ,Var⁡(X′)=0.64+0.36=1.

这时 X′ 仍然是 Gaussian,均值和方差也都没变,因此确实仍服从 N(0,1)。单个样本的位置变了,整个分布却保持不变。

只加 noise 会变宽,只收缩会变窄,配合起来可以保持分布三个独立对比都从标准 Gaussian X 开始,xi 是独立标准 Gaussian。只加 noise:原样本系数 1.0,新 noise 系数 0.6,更新后方差 1.36;只向均值收缩:原样本系数 0.8,新 noise 系数 0.0,更新后方差 0.64;收缩后再加 noise:原样本系数 0.8,新 noise 系数 0.6,更新后方差 1.00。三图使用相同的坐标范围和 density 纵轴尺度,灰色虚线是原来的标准 Gaussian,蓝色实线是更新后的理论 density。最后一图两条曲线重合。曲线由 Gaussian density 解析计算,不是训练模型或有限样本的统计结果。
X∼N(0,1),ξ∼N(0,1)
只加 noise
X′=X+0.6ξ
0.20.4-202
Var⁡(X′)=1.36
只向均值收缩
X′=0.8X
0.20.4-202
Var⁡(X′)=0.64
收缩后再加 noise
X′=0.8X+0.6ξ
0.20.4-202
Var⁡(X′)=1.00
更新前的 density更新后的 density
左右滑动查看完整图示
每列都从同一个标准 Gaussian 分布出发。虚线是更新前,实线是更新后;最后一列两者重合,表示样本可以随机移动,而整体分布保持不变。

这个例子说明了需要配合的两种作用:随机扰动把分布摊开,确定性的移动补偿这部分变化。 只加 noise 会越来越散;只向均值收缩,又会让样本越来越集中。

From this example to a score correction

上面的 0.8 和 0.6 是为了方便看清两份方差。要把更新做成许多小步,先规定一个长度为 h 的时间间隔新增 h 的 noise variance,即加上 hξ。相应地,在这个标准 Gaussian 例子中,保持方差的更新是

X′=1−hX+hξ,0<h<1.

因为 (1−h)+h=1,它与刚才的例子完全相同;h=0.36 时,两个系数恰好就是 0.8 和 0.6。当 h 很小时,1−h≈1−h/2,因此这一步近似为

x′=x−h2x+hξ.

现在再看前面定义过的 score。对于 p(x)=N(x;0,1),

s(x)=ddxlog⁡p(x)=−x.

所以刚才那项“往回拉”的移动 −hx/2,恰好就是 hs(x)/2。在这个例子中,score 为正的位置向右移,为负的位置向左移;离均值越远,补偿移动越大。系数 1/2 来自方差的平衡。

真实生成过程中的 pt 会随时间变化,也可能有许多峰,不能一律往某个全局均值拉。所需的局部方向由当前分布的 st(x)=∇xlog⁡pt(x) 给出。若每单位时间加入 gt2 的 noise variance,对应的补偿 velocity 是 gt2st(x)/2。

于是,保留原来负责生成的 ut,再配上补偿和新 noise,得到小步采样公式:

xt+h=xt+hut(xt)⏟original flow+hgt22st(xt)⏟score correction+gthξ⏟fresh noise,ξ∼N(0,I).

这里 gt≥0 控制新 noise 的强度,可以由 sampler 指定。gt=0 就回到原来的 Euler ODE update;gt>0 时,noise 与 score correction 必须成对加入,才能在连续时间下保持原来的分布演化 (Holderrieth & Erives, 2026)。上面的 Gaussian 计算解释了这种配合;一般分布下的证明放在下面的 details 中。

Reading the SDE notation

为什么新 noise 是 gthξ,而不是 gthξ?因为我们希望同一段时间积累的随机性,不随把它切成多少步而改变。暂时固定 gt=g:一个长度为 h 的区间贡献方差 g2h;拆成两个半步,每步贡献 g2h/2,独立相加后仍然是 g2h。若标准差与 h 成正比,两个半步的总方差就会比一个整步少一半。

把这些独立的 Gaussian 增量连续积累起来,就得到 Brownian motion,记为 Wt。它在长度为 h 的区间上的增量满足

Wt+h−Wt∼N(0,hI),

不相交区间上的增量独立。Wt 记录累积的随机扰动;采样代码每一步只需生成它的增量 hξ。

连续时间下,把总的确定性移动速度记为 drift

bt(x)=ut(x)+gt22st(x),

就可以把刚才的采样过程写成 stochastic differential equation(SDE):

dXt=bt(Xt)dt+gtdWt=[ut(Xt)+gt22st(Xt)]dt+gtdWt.

读这个式子时,btdt 对应小步公式里的 hbt,gtdWt 对应 gthξ。前面的离散近似就叫 Euler–Maruyama sampler。bt 是在原 velocity 上加了补偿后的速度;Wt 表示沿途新增的随机性。

在相同初始分布、准确的 ut 和 st、以及保证方程成立的正则性条件下,这个连续时间 SDE 与原 ODE 在每个时刻具有相同的分布 pt (Song et al., 2021)。这里的“相同”指:各自生成大量 samples,在同一个时刻比较它们的位置分布;单个 sample 不需要走相同的路线。

ODE 和 SDE:轨迹不同,但可以具有同一条边缘分布路径解析一维 Gaussian 例子:p_t=N(t,(1-0.6t)^2)。ODE 使用精确直线解 x_t=t+(1-0.6t)x_0。SDE 在相同 velocity 上加入 0.65^2/2 倍的真实 score correction 和 0.65 倍 Brownian motion,图中按其解析 Gaussian transition 抽取 100 个间隔的状态。两图从相同五个初值展示少量轨迹;绿色带表示共同理论分布均值加减一个标准差,不是这些少量样本的经验统计。下方共同的理论密度在 t=0、0.5、1 分别是 N(0,1)、N(0.5,0.7^2)、N(1,0.4^2)。ODE:确定性轨迹SDE:沿途持续随机探索
dx=ut(x)dt
dx=(ut+g22st)dt+gdW
-20200.51
x
t
-20200.51
x
t
共同的理论 density(色带:均值 ± 一个标准差)-202
p0=N(0,1.02)
-202
p0.5=N(0.5,0.72)
-202
p1=N(1,0.42)
左右滑动查看完整图示
同一条 probability path 可以配上不同的随机过程。色带与下方曲线来自共同的理论 density;图中的几条路径只是示例,不用于估计 marginal。

图中 pt=N(t,(1−0.6t)2) 的均值逐渐右移、分布逐渐变窄。左边只有原来的 flow,右边还加入了配套的 score correction 和 noise。两边的理论分布相同,右边的具体轨迹却会不断随机偏转。

连续时间的结论也不代表任意有限步长都精确。例如刚才的 Gaussian 更新用 1−h/2 近似 1−h 后,方差变成 (1−h/2)2+h=1+h2/4,会留下一个小的误差。实际模型还存在 velocity 和 score 的预测误差;使用 SDE 并不自动保证生成质量更好。

为什么一般分布下,score correction 也能抵消扩散?

关键是计算整批 samples 的 density 怎样变化。对于 ODE,原来的 velocity ut 搬运概率质量,满足 continuity equation:

∂tpt=−∇⋅(ptut).

左侧是当前位置的 density 随时间的变化;右侧表示周围的样本移动造成了多少净流入。

SDE 还会叠加随机扰动。当 gt 只依赖时间时,density 满足 Fokker–Planck equation:

∂tpt=−∇⋅(ptbt)⏟drift+gt22Δpt⏟diffusion,Δpt=∑j∂2pt∂xj2.

其中 Δ 是 Laplacian,描述 density 的局部弯曲。在一维的峰顶附近,pt″<0,diffusion 项使 density 降低;在向上弯曲的区域,它使 density 增加。这是随机扰动把分布摊开的数学形式。

我们希望在加入 noise 后,仍得到原 ODE 的 ∂tpt,因此需要额外的 drift 恰好抵消 diffusion 项。利用 score 的定义:

ptst=pt∇log⁡pt=∇pt,

选取 bt=ut+gt2st/2 后,额外 drift 对 density 的影响就是 −gt2Δpt/2。两项相加:

∂tpt=−∇⋅(ptut)−gt22Δpt+gt22Δpt=−∇⋅(ptut).

因此,补偿改变的是样本的移动,使新增随机性在 density 演化中的影响被抵消。这个推导要求密度与系数足够正则,并有适当的边界行为;gt 依赖位置时需要另外处理。

与 Langevin dynamics 的关系,以及有限步 sampler 的选择

如果目标分布固定为 p,不再沿时间改变它,就可以取 ut=0。保留 gt2∇log⁡p/2 的 drift 和 Brownian noise,得到 Langevin dynamics。目标 p 是它的 stationary density:从 p 开始,分布保持为 p。从其他初始分布出发是否收敛、多久收敛,还需要额外条件与分析。前面的标准 Gaussian 例子就是这种情形。

实际采样还要选择数值求解方法。Euler 每一步只使用当前位置的 velocity;Heun 用于 ODE 时,会先预测下一位置,再结合那里的 velocity 修正更新,通常需要更多 network evaluations。比较 sampler 时应同时看步数与 NFE(number of function evaluations)。对于 SDE,增大 gt 也会改变有限计算预算下的采样行为。

本文使用递增的生成时间。若改用递减的 noise level 计时,需要一起转换时间变量与 drift,不能直接照搬符号。

DDPM and DDIM

DDPM: learning the reverse of a noising chain

前面直接描述连续时间的分布运动。DDPM (Ho et al., 2020) 提供了一条容易构造训练问题的路线:先把真实图片逐步加噪,再学习每一步怎样把噪声去掉。

这一节用离散索引 k 记录加噪次数:Y0=z 是 data,我们希望经过足够多步后,YK 接近标准 Gaussian。它与前面的生成时间 t 方向相反。

先设计加噪的一步。为了逐渐抹去图片信息,可以把已有样本缩小一些,再加入独立的 Gaussian noise。暂时把两个系数留作未知量:

Yk=ckYk−1+dkξk,ξk∼N(0,I),

其中 ck,dk≥0,ξk 与已有样本独立。ck 决定原有内容保留多大幅度,dk 决定新 noise 的标准差。

还需要决定整个过程的数值尺度。既然选定的终点是 N(0,I),一个自然的要求是:如果输入已经服从这个分布,再加一步噪声,它仍然服从这个分布。 这样,在图片信息逐渐消失时,noise 的尺度有一个稳定的目标。

先在一个坐标上检查这个要求。把一个随机变量乘以 ck,它到均值的距离也乘以 ck,所以平方距离的平均——方差——乘以 ck2。两项独立时,方差相加。因此,若当前样本与新 noise 的方差都是 1,更新后的方差就是

Var⁡(ckYk−1+dkξk)=ck2+dk2.

例如,直接各取一半会得到 1/4+1/4=1/2 的方差:两个独立样本平均后,更容易靠近均值,分布就变窄了。要保持刚才选定的单位方差,所需的条件于是变成

ck2+dk2=1.

对于标准 Gaussian 输入,更新后的均值仍为零、结果仍是 Gaussian;各坐标合起来,covariance 为 (ck2+dk2)I。所以这个条件确实让 N(0,I) 保持不变。

现在选择每一步想加入多少 noise variance,把它记为 0<bk<1。新增方差为 dk2=bk,剩下的方差份额就是 ck2=1−bk。取非负系数,就得到了 DDPM 的一步更新:

Yk=1−bkYk−1+bkξk.

给定 Yk−1 后,第一项是确定的,第二项是 covariance 为 bkI 的 Gaussian。用 q 表示这个人为规定的 forward process,便可以写成

q(Yk∣Yk−1)=N(1−bkYk−1,bkI).

这就是 DDPM 的 variance-preserving 选择。真实图片的各坐标未必有单位方差;若某个坐标当前的方差为 v,下一步就是 (1−bk)v+bk,它会向 1 靠近。保持单位方差,是这个加噪规则的性质。

接下来把多步更新合在一起。为了简化记号,令 ak=1−bk。第一步之后,图片系数是 a1,混入的 noise variance 是 1−a1。第二步先把这两部分都乘以 a2,再加入方差为 1−a2 的新 noise:图片系数变为 a1a2,累积的 noise variance 则是

a2(1−a1)+(1−a2)=1−a1a2.

每多走一步,都会重复这个计算:图片系数继续相乘,累积的 noise variance 补上对应的剩余份额。因此记

a¯k=∏j=1kaj,

图片的系数就是 a¯k,累积的独立 Gaussian noise 可以合并成方差为 1−a¯k 的一项。于是,无需真的连续加噪 k 次,就能直接采样同一个 q(Yk∣z):

Yk=a¯kz+1−a¯kϵ,ϵ∼N(0,I).

这里 ϵ 是合并后的 noise,与 z 独立。选择 schedule 和总步数,使 a¯K 足够接近 0,图片的贡献就足够小,终点便接近标准 Gaussian。训练时随机抽一个 k,用上式构造 Yk,让网络预测其中的 ϵ。

把这条路径写回 αz+βϵ 的形式,才得到 α=a¯k、β=1−a¯k,以及 α2+β2=1。前面的线性 flow path 则从两个端点的直线插值出发,选择 α+β=1,允许中途的方差尺度发生变化。两者分别体现了不同的路径设计。

现在开始反向生成。给定 Yk,我们希望找回噪声更少的 Yk−1。但加噪时引入了随机性,同一个 Yk 可能由不同的 Yk−1 产生。因此需要学习的是 上一状态的条件分布,再从中采样。

先利用训练时已有的信息来建立这个分布:如果原图 z 也已知,怎样判断某个候选值 v 是否可能是 Yk−1?它需要同时符合两条线索:

  • 从原图 z 加噪到第 k−1 步,能得到怎样的 v?
  • 从这个 v 再加一步噪声,能否得到已经观察到的 Yk?

前面的 forward process 已经给出了这两个概率。先考虑 k≥2:

q(v∣z)=N(v;a¯k−1z,(1−a¯k−1)I),
q(Yk∣v)=N(Yk;akv,bkI).

用 Bayes' rule 把两条线索合起来,得到

q(v∣Yk,z)∝q(Yk∣v)q(v∣z).

这里固定了 Yk,z,只比较不同的候选 v。两个 Gaussian 的 density 相乘,取负 log 后,就变成两个误差项相加:

−log⁡q(v∣Yk,z)=‖v−a¯k−1z‖22(1−a¯k−1)+‖Yk−akv‖22bk+const.

第一项要求 v 符合原图加噪后的分布;第二项要求它能解释当前观察 Yk。每项除以对应的 variance,表示不确定性较小的线索有更强的约束。

这个负 log 是关于 v 的二次函数,因此 posterior 仍是 Gaussian。先求它的中心:对 v 求导并令其为零,posterior mean μ~k 就满足

μ~k−a¯k−1z1−a¯k−1+akμ~k−akYkbk=0.

整理同类项后,得到

μ~k=a¯k−11−a¯k−1z+akbkYk11−a¯k−1+akbk.

再看分布的宽度。Gaussian 的负 log 中,‖v‖2 的系数是 variance 倒数的一半;刚才两项提供的系数相加,所以 posterior variance 是

b~k=(11−a¯k−1+akbk)−1=1−a¯k−11−a¯kbk.

最后一步用到了 a¯k=aka¯k−1 与 ak+bk=1。用同样的关系整理 mean,就得到已知原图时的反向分布:

q(Yk−1∣Yk,z)=N(μ~k(Yk,z),b~kI),
μ~k(Yk,z)=a¯k−1bk1−a¯kz+ak(1−a¯k−1)1−a¯kYk.

这解释了反向 mean 的来源:它结合原图提供的信息与当前 noisy sample 提供的信息,决定上一状态应该落在哪里。b~k 则描述给定这两条信息后,上一状态还剩下多少不确定性。

生成时没有原图 z,但网络已经能预测累积到第 k 步的 noise。由前面推出的 Yk=a¯kz+1−a¯kϵ,先移走 noise 项,再除以 data coefficient,就能构造原图的估计:

z^θ=Yk−1−a¯kϵθ(Yk,k)a¯k.

然后把 posterior mean 中的 z 换成 z^θ。利用 a¯k=aka¯k−1,代入后的 mean 是

μθ(Yk,k)=bkak(1−a¯k)(Yk−1−a¯kϵθ(Yk,k))+ak(1−a¯k−1)1−a¯kYk.

合并两处 Yk 的系数时,分子中的 bk+ak(1−a¯k−1)=1−a¯k 恰好约去分母。于是得到采样代码里的 noise-prediction 写法:

μθ(Yk,k)=1ak(Yk−bk1−a¯kϵθ(Yk,k)).

这里 ϵθ 预测的是从原图累积到当前时刻的总 noise。它先用于估计原图,再通过 posterior mean 决定回退一步的中心;前面的系数由这两次换算产生。

为什么训练这个 noise predictor 就能学习反向 mean?平方误差的理想预测是 E[ϵ∣Yk],因此上面的 z^θ 对应 E[z∣Yk]。而 μ~k(Yk,z) 对 z 是线性的,把这个条件均值代进去,就得到 E[Yk−1∣Yk]。这把训练时能算出的 posterior,连接到了生成时只有 Yk 的情形。

但 mean 还不是一次完整的采样。我们需要上一状态的分布,而只取 mean 会丢掉这个分布的宽度。DDPM 用一个 Gaussian 来建模它:

pθ(Yk−1∣Yk)=N(μθ(Yk,k),σk2I).

因此,一次反向更新是

Yk−1=μθ(Yk,k)+σkξk,ξk∼N(0,I).

新增 noise 用来抽取一种可能的上一状态。下一状态比当前更干净,指的是整个反向分布沿着较低 noise level 演化;它仍然可以包含不确定性。例如 μθ=0.7、σk=0.1 时,下一状态从 N(0.7,0.01) 抽取,而不会每次都固定为 0.7。

接下来才需要选 σk2。已知 z 时,刚才算出的条件方差是 b~k;只知道 Yk 时,还存在“原图可能是什么”的不确定性,真实的 reverse conditional 一般是多个 Gaussian 的混合。DDPM 的单个 Gaussian 是对它的建模近似,因此 variance 还有设计空间。原论文 (Ho et al., 2020) 使用过两种固定选择:

Reverse variance对应的采样更新含义
σk2=b~kYk−1=μθ+b~kξk沿用已知原图时的 posterior variance
σk2=bkYk−1=μθ+bkξk直接使用 forward schedule 的 variance,通常比 b~k 大

所以 sqrt(beta) 对应第二种选择。反向 mean 的推导确定了网络预测如何转换成 mean,额外采样多少 noise 则由选定的 reverse variance 决定。也有让模型学习 variance 的做法。

把这些步骤放回生成循环:从 YK∼N(0,I) 开始,依次处理 k=K,K−1,…,1。每一步先预测 ϵθ(Yk,k),再计算上面推导出的 μθ,最后执行

Yk−1={μθ(Yk,k)+σkξk,k>1,ξk∼N(0,I),μθ(Y1,1),k=1.

在本文的索引里,最后一步是 Y1→Y0,常用采样流程直接输出重建 mean,停止加噪。代码里的 alpha、beta、alpha_bar 分别对应 ak、bk、a¯k;若同一组 steps 存在从 0 开始的数组里,代码索引就是 t=k−1,对应条件便写成 t > 0。此时数组索引 t=0 表示最后一次 denoising 调用,区别于数学记号中已经干净的 Y0。

Noise prediction loss 与 ELBO 有什么关系?

还可以把同一个生成过程看成 hierarchical latent-variable model (He, 2024):YK 是最深层的 noise,各层 Yk 逐步补回图片结构,最后生成 z。对 Y1,…,YK 使用已知的 forward noising chain 作为 variational distribution,负 ELBO 可以整理为

LVLB=Eq[DKL(q(YK∣z)‖p(YK))+∑k=2KDKL(q(Yk−1∣Yk,z)‖pθ(Yk−1∣Yk))−log⁡pθ(z∣Y1)].

中间的 KL 比较的是刚才推导出的 posterior q(Yk−1∣Yk,z) 与模型的 Gaussian reverse transition。固定模型 variance 后,这些 KL 中依赖参数的部分就是两个 mean 的加权平方差;使用上述 noise parameterization,就变成按 k 加权的 noise MSE。

这给出了 denoising 的另一种含义:修正每一步的 mean,就是改善这个 latent-variable model 的 likelihood bound。常用的 simplified loss E‖ϵθ(Yk,k)−ϵ‖2 进一步去掉了随时间变化的权重;它保留 noise regression 的形式,同时改变了各个时间点对训练的贡献。

DDIM: predicting endpoints, then changing the mixture

训练完成后,网络在各个 noise levels 都能估计 noise。我们可以利用这项能力,直接从较 noisy 的状态走到较 clean 的状态,而不必经过每一个训练索引。DDIM (J. Song et al., 2021) 构造了共享训练 marginals 的过程,使同一个 noise predictor 支持这样的采样。

沿用刚才从 noise prediction 得到的 clean-sample estimate z^θ,简记为 z^。接下来直接走到噪声更小的 j<k:

Yj=a¯jz^⏟predicted data+1−a¯j−ρk→j2ϵθ(Yk,k)⏟retained noise+ρk→jξ⏟fresh noise.

其中 ξ∼N(0,I) 独立,常用设置为

ρk→j=η1−a¯j1−a¯k1−a¯ka¯j,0≤η≤1.

η=0 时,给定初始 noise,采样过程确定;使用相邻时间点且 η=1 时,恢复前面 fixed posterior variance 的 DDPM 更新。减少采样时间点可以降低 NFE,但跳得更远仍会放大预测和数值误差。

这里值得记住的是三个项的分工:下一步 data 的系数由 schedule 决定,noise 的份额则在保留的预测噪声与新噪声之间分配。 后面的 CPS 会把这个结构用到 flow matching 的随机 rollout 上。

Conditioning and latent diffusion

Classifier-free guidance

到目前为止,模型只需要生成“像数据”的结果。加入 prompt c 后,我们希望生成的是符合这个条件的样本,例如一只猫,而不是数据集中任意一种动物。为此,网络也接收 c,学习相应的 conditional velocity 或 noise prediction。

从 score 看,Bayes' rule 给出

∇xlog⁡pt(x∣c)=∇xlog⁡pt(x)+∇xlog⁡pt(c∣x).

等式右侧第二项衡量:当前 noisy sample 朝哪个方向变化,会更符合条件 c。Classifier guidance 使用能读取 noisy inputs 的 classifier 来估计这个方向。

也可以利用同一个生成网络的两次预测来表达条件带来的变化。Classifier-free guidance(CFG)(Ho & Salimans, 2022) 在训练时随机丢弃条件,使网络既学 conditional prediction,也学 unconditional prediction。采样时组合两者:

uCFG(xt,c)=utθ(xt,∅)+w(utθ(xt,c)−utθ(xt,∅)).

差值 utθ(xt,c)−utθ(xt,∅) 提取条件带来的方向变化。w=0 使用 unconditional prediction,w=1 使用普通 conditional prediction,w>1 进一步加强这个变化。同样的组合也能用于 noise 或 score prediction。更强的 guidance 往往提高条件遵循,同时可能降低多样性或产生过饱和等 artifacts。

在每个固定 t,理想 CFG score 可以写成 pt(x∣c)wpt(x)1−w 的 log gradient。但这并不自动保证完整采样过程的终点就精确服从这个形式的重加权数据分布:引导后各个时刻的 density 还要满足同一套动力学。

Where the neural network operates

对于高分辨率图片,直接在 pixels 上反复调用大网络非常昂贵。Latent Diffusion Models (Rombach et al., 2022) 先用 autoencoder 把图片压缩到较小的 latent tensor,在那里训练和采样,最后通过 decoder 还原图片。此时,前面的 clean data z 就是图片的 latent representation。

图像、latent 与生成网络各在什么位置训练生成 backbone 时,真实图片经过固定 encoder 得到 clean latent z,再与 Gaussian noise 混合成 x_t;时间 t 和 text encoder 提供的条件共同输入可训练的 U-Net 或 DiT,以 velocity prediction 为例,输出 velocity 并与已知 target z-epsilon 计算 loss。推理时不需要真实图片和 encoder,从 latent noise 开始迭代调用 backbone 与 sampler,最后经过固定 decoder 得到图片。训练 backbone:从图片构造带噪 latent真实图片EncoderClean latent
z
加噪
xt
MSE target
z−ϵ
固定参数
ϵ,t
U-Net / DiT + training loss
utθ(xt,c)
Text encoder
c
这里更新 backbone 的参数生成:从 latent noise 出发Latent noise
x0
Backbone + sampler
xt0→xt1→⋯→x1
Decoder
D
生成图片
D(x1)
每一步都读取时间与条件固定参数
左右滑动查看完整图示
上方是训练时 clean latent 的来源,下方是生成流程。加噪与多步采样发生在 latent space,decoder 只负责把最终 latent 还原为图片。图示采用 velocity prediction,以及固定 autoencoder 与 text encoder 的常见设置。

通常先训练 autoencoder,再固定它来训练 diffusion backbone。训练一张图片时,encoder 产生 clean latent,然后加噪、计算 target、更新 backbone;生成时从 latent noise 开始,反复调用 backbone 和 sampler,最后只需调用 decoder。

Backbone 负责读入 noisy latent、时间和条件,输出同形状的预测。U-Net 用多尺度结构与 skip connections 交换信息;Diffusion Transformer(DiT)(Peebles & Xie, 2023) 把 latent 切成 patches,用 Transformer 处理后再恢复空间布局。这两种架构都可以配合相应的 noise 或 velocity target。

实际系统可以同时使用 latent space、DiT、flow matching 和 Euler sampler:latent space 决定数据表示,DiT 参数化预测函数,flow matching 提供训练 target,Euler 把网络预测变成一步步的生成过程。

RL for diffusion models

假设 prompt 要求“两个圆,在方块左侧”。一个已经预训练好的模型,可能生成了画面清晰、位置正确、但只有一个圆的图片。继续降低已有数据上的 noise prediction loss,并没有直接告诉模型:这次生成的数量错了。

RL post-training 让模型从自己生成的结果中获得反馈。可以用检测器检查物体数量,用偏好模型评价审美,也可以用视频 reward 评价运动。给定条件 c 和完整输出 y,把这个反馈记为 r(y,c),目标是提高生成结果的期望 reward:

J(θ)=Ec,y∼pθ(⋅∣c)[r(y,c)].

我们会建立这样一个训练循环:对同一个 prompt 生成一组结果,比较 reward,再调整产生这些结果的采样决策。Reward 只需要为输出打分,可以来自不可微的评判程序。

本节把前面的 velocity prediction 与 ODE/SDE sampling 接到这个训练循环中,继续使用 t=0 为 noise、t=1 为 data 的时间方向。Policy gradient 和 clipped policy update 的一般原理,分别见 RL 的 policy gradient 与 PPO;下面直接在生成过程上建立它们所需的量。

Denoising as an MDP

最终 reward 是对整张图片打分,模型参数却参与了许多次 denoising updates。要把两者连接起来,可以把每次“从当前 noisy sample 采样下一状态”看成一次 action。这就把生成过程写成了 Markov decision process(MDP),能够使用 policy gradient;DDPO (Black et al., 2024) 采用了这种建模方式。

取生成网格 0=t0<⋯<tK=1,简写 xk=xtk:

RL 中的量生成模型中的含义
State sk(c,tk,xk):条件、时间、当前 noisy sample
Action ak采样得到的下一个 sample xk+1
Policy πθ(ak∣sk)Sampler 定义的 transition density pθ(xk+1∣xk,c)
Environment transition把刚得到的 xk+1 放入下一个 state,并推进时间
Reward中间步骤为 0,最后对输出 y 计算 r(y,c)

对于 latent model,y=D(xK),其中 D 是固定的 decoder。这里的 action 是整个下一步 latent tensor;policy 则是当前状态下,这个 tensor 的采样分布。Velocity network 的预测会参与构造这个分布,例如决定 Gaussian transition 的 mean。

一旦采样出 action xk+1,环境只需把它放进下一状态,并推进时间。因此,随机性来自 policy 的采样,环境的状态更新可以是确定的。

From a deterministic flow to a stochastic policy

给定 xk,Euler ODE sampler 只有一个确定的下一状态:

xk+1=xk+hkuθ(xk,tk,c),hk=tk+1−tk.

这个更新在固定的 xk 上只给出一个结果,对应 point mass。为了通过每一步的 log density 计算 policy gradient,我们需要一个具有非零 variance 的 transition distribution。加入中途随机性,也让模型能从同一个状态尝试不同的后续方向。

一种做法是把确定性 ODE 改成 SDE,并离散成 Gaussian transition。随机扰动会把样本散开,所以同时加入 score correction,补偿它对目标分布的影响。

对线性 path xt=tz+(1−t)ϵ,由 velocity 得到 noise estimate

ϵ^θ=xt−tuθ(xt,t,c),sθ(xt,t,c)=−ϵ^θ1−t.

将 score correction 放入 drift,再用 Euler–Maruyama 更新,得到 transition mean:

μθ(sk)=xk+hk[uθ(xk,tk,c)+gtk22sθ(xk,tk,c)],
πθ(ak∣sk)=N(ak;μθ(sk),νk2I),νk2=gtk2hk.

这样,网络的一次预测确定 μθ,sampler 再从 N(μθ,νk2I) 中抽取下一状态。给定实际抽到的 action,我们也能计算它的 log density,policy-gradient 训练所需的量就齐了。连续时间下的分布关系与数值误差,见 ODE/SDE sampling。

整条 trajectory 的条件密度是

pθ(τ∣c)=p0(x0)∏k=0K−1pθ(xk+1∣xk,c).

初始 noise 不依赖参数。在非退化随机 transition 下,利用 ∇θpθ=pθ∇θlog⁡pθ,把期望 reward 的梯度写成可以用 trajectories 估计的形式:

∇θJ=E[r(y,c)∑k=0K−1∇θlog⁡pθ(xk+1∣xk,c)].

每个 transition 的 log density 都参与同一个最终 reward 的梯度。这正是终点反馈能够影响前面采样步骤的原因,这里不对终点 reward 做时间折扣,对应 RL 中的 γ=1。

计算这个梯度时,保存下来的 states、actions 和 reward 都作为固定数据,只对当前 policy 给这些 actions 的 log density 求导。因此,reward model 无需提供梯度,训练也无需反向传播穿过整条已采样的生成链。

如果 reward 可微,也可以直接对生成过程求导

如果 decoder、reward 和 sampling steps 都可微,可以把生成看成一个完整的计算图,对最终 reward 做 pathwise differentiation。这要求保存或重算多步计算图。

本节采用 score-function 路线,利用随机 actions 的 log density 学习,适用于只返回分数的 black-box reward。初始 noise 的随机性提供了输出多样性,逐步随机 transition 则为这种 policy 更新提供了条件密度。

Group rewards and policy updates

回到“两个圆”的例子。如果一组生成结果分别画了一个、两个、三个圆,reward 可以把数量正确的结果排在前面。我们希望增强生成高分结果的采样决策,并减弱相对低分结果的决策。

具体地,固定本轮 rollout policy θold,对同一个 prompt 生成 G 个结果。先在组内减去平均 reward,再用标准差归一化,得到每个结果的 advantage:

ri=r(yi,c),r¯=1G∑i=1Gri,A^i=ri−r¯1G∑j=1G(rj−r¯)2+εA.

这样得到的 A^i 表示“这个结果比同组结果好多少”,而不是它的绝对分数。上式使用 population standard deviation,εA>0 保证分母非零;同组 reward 全相等时,所有 advantage 都为零。

同一个 prompt 的三个生成结果,如何产生 group-relative advantage构造示例:prompt 为两个圆在方块左侧。三个独立 rollout 分别画出一个、两个、三个圆,简单规则只检查数量和左右关系,reward 为 0、1、0。组均值 0.333,population standard deviation 0.471;忽略稳定用 epsilon,advantage 为 -0.707、1.414、-0.707。每条 trajectory 的所有 transitions 共享其终点 advantage,但各自计算逐步 probability ratio。场景与 reward 为教学构造,不是真实模型输出。同一个 prompt:两个圆,在方块左侧保存每一步的 latent transition最终输出RewardAdvantage
x0(1)
x1(1)
x2(1)
x3(1)
0
−0.707
x0(2)
x1(2)
x2(2)
x3(2)
1
+1.414
x0(3)
x1(3)
x2(3)
x3(3)
0
−0.707
r¯=13,σr=23
A^i=ri−r¯σr
每行共享终点 advantage;每条边分别计算 ratio
左右滑动查看完整图示
图形与 reward 为教学构造。中间结果在同组中更好,因此它的采样步骤获得正 advantage;另外两个结果获得负 advantage。比较发生在同一个 prompt 内。

更新时,我们重新计算保存下来的每个 action 在当前 policy 下的 density,并与 rollout 时比较:

ρi,k(θ)=pθ(xk+1(i)∣xk(i),c)pθold(xk+1(i)∣xk(i),c).

若 A^i>0,提高 ρi,k 会提高 ρi,kA^i,因而鼓励模型增加这个 action 的 density;A^i<0 时方向相反。同一条 trajectory 的各步骤共享终点 advantage,ratio 则各自计算。

多次更新后,当前 policy 可能逐渐偏离收集数据时的 policy。PPO clipping 限制 surrogate 对过大更新的激励;还可以加 reference KL,约束它偏离预训练模型的程度。把两者写在一起,得到最大化目标:

J(θ)=Ec,{τi}∼πθold[1GK∑i=1G∑k=0K−1(min{ρi,kA^i,clip⁡(ρi,k,1−ϵ,1+ϵ)A^i}−βDi,k(θ))],
Di,k(θ)=DKL(πθ(⋅∣sk(i))‖πref(⋅∣sk(i))).

两个参照有不同作用:θold 记录这批 trajectories 是怎样生成的,用于 probability ratio;θref 是固定的模型,用于 KL regularization。Flow-GRPO 使用 reference KL,DanceGRPO 原论文的核心目标对应 β=0。Group-relative advantage 直接由 rewards 算出,因此这里不需要额外训练 value network。

更新时重新评估同一个 action,不重新生成 trajectory一维 Gaussian transition 示例。保存 state s_k 与旧 policy 采样的 action a_k=0.8。Old policy 的 mean 为 0,current policy 的 mean 为 0.4,二者 standard deviation 都是 0.6。在同一 action 处,density ratio 为 1.948。当 advantage=1、clip epsilon=0.2 时,clipped surrogate 为 1.2。曲线只是 latent transition 的一维示例,真实 policy density 定义在整个 latent tensor 上。计算梯度时 action、old log density 与 advantage 固定,当前 mean 来自可训练网络。Rollout 时保存更新时:读取同一个 state 与 action固定的 state
sk=(c,tk,xk)
已采样的 action
ak=0.8
固定的 old log density
ℓold=log⁡πold(ak∣sk)
πold: μ=0, ν=0.6
πθ: μ=0.4, ν=0.6
ak=0.8
ρ=exp⁡(ℓθ−ℓold)≈1.95
A^=1, ϵ=0.2:min(ρA^,clip⁡(ρ,0.8,1.2)A^)=1.2
这个正 advantage 样本已超过上侧 clip threshold固定数据;梯度只通过当前 policy 的 density 计算
左右滑动查看完整图示
参数更新后,同一个已保存 action 在新旧 policy 下具有不同 density。图示是一维 Gaussian;真实训练需明确整个 latent tensor 的 log density 是求和,还是采用了另一个缩放 surrogate。

图中的新旧 Gaussian 评价的是同一个已经保存的 action。更新参数会改变它的 density,从而改变 ratio;如果重新采样一个 action,再拿它与旧 action 比较,就不再是上式的 ratio。

实际的一轮训练先保存 trajectories、old log densities 和 rewards,再对这批固定数据做若干次 policy 更新,随后用新模型收集下一批数据。学习信号始终来自“哪些采样决策带来了更好的最终结果”。

终点 advantage 提供的是整条 trajectory 的评价,无法单独指出是哪一步修正了数量或文字。组内需要有可比较的结果,reward 也要能分辨这些差别,才有清晰的训练信号。

Flow-GRPO and DanceGRPO

在线 RL 的一项主要成本,是每轮都要重新生成一组图片。Flow-GRPO (J. Liu et al., 2025) 用 ODE-to-SDE 建立随机 policy,再用 Denoising Reduction 降低收集数据的成本:rollout 使用较少步骤,inference 保留较多步骤。其 SD3.5-M 实验使用 10 步 rollout、40 步 inference。这里改变的是 RL 数据收集过程;缩短到什么程度仍能有效训练,取决于模型和任务。

换成视频或 image-to-video,整套 RL 建模仍然适用:state 包含条件和当前视频 latent,action 是下一步的视频 latent,终点 reward 评价完整视频。DanceGRPO (Xue et al., 2025) 将 group-relative 更新用于 noise-prediction diffusion 与 rectified flow,覆盖 text-to-image、text-to-video 和 image-to-video。变化主要落在 sampler、条件 c 和 reward 上;action 可以是一整段视频 latent 的更新。

比较项Flow-GRPO 原论文DanceGRPO 原论文
主要范围Flow matching 的 text-to-image alignmentDiffusion 与 rectified flow,图像与视频任务
随机性ODE-to-SDE,构造 Gaussian transition为不同生成参数化构造随机 sampler
Flow 的 noise schedule本文时间下 gt=a(1−t)/tFlow 分支可用常数 gt=a
重点设计Denoising Reduction;reference KL 的作用多任务、多 reward;还考察从较大 sample pool 选取高低分样本

表中 a 控制探索强度。Flow-GRPO 的解析 schedule 在本文 t=0 处有奇异性,实际 sampler 需要单独处理这个端点。原论文及实现常用从 1 递减到 0 的 noise level 计时,转换到本文时间时,要同时转换 velocity 的方向。

训练采样器的随机性、步数和 guidance 会改变模型实际看到的 trajectories。评估时也需要说明这些设置,特别是是否改回 ODE sampler,才能判断 reward 的变化来自参数更新还是采样策略。

选择 reward 就是在选择优化方向。DanceGRPO 的 FLUX 实验中,仅优化 HPS 提高了偏好分数,却降低了 GenEval;加入 CLIP reward 后,权衡也随之改变。因此需要同时观察训练 reward、独立指标和实际输出。Reference KL 可以约束模型偏离 reference 的程度,reward 本身是否准确仍需另行检验。

Coefficients-Preserving Sampling

随机性有助于探索,但 sampler 也要控制加入多少 noise。如果 rollout 输出带着明显噪声伪影,偏好或审美 reward 可能主要在评价伪影,而无法准确区分生成内容的优劣。这会进一步影响组内 advantage。

Coefficients-Preserving Sampling(CPS) (Wang & Yu, 2025) 从每一步的系数分配解决这个问题:先确定下一 noise level 需要多少 noise,再把这份额度分给已有的 noise estimate 和新采样的 noise。它可以用作 Flow-GRPO、DanceGRPO 等方法的 rollout sampler,后续继续使用 reward 和 policy update。

下面固定条件 c,省略预测函数中的 c。沿线性路径,从生成时间 t 前进到 s=t+h。当前 velocity prediction 同时给出 clean data 和 noise 的估计:

z^=xt+(1−t)utθ(xt),ϵ^=xt−tutθ(xt).

Euler 的一步可以重写成

xs=xt+hutθ(xt)=sz^+(1−s)ϵ^.

这个式子揭示了下一步的两份组成:sz^ 和 (1−s)ϵ^。为了给探索留出空间,我们要把后者的一部分换成独立的新 noise ρξ,同时保持预定的总 noise coefficient:

xs=sz^+(1−s)2−ρ2ϵ^+ρξ,0≤ρ≤1−s.

这个构造在系数分析中把 ϵ^ 视为单位方差的 noise estimate,并独立采样 ξ。于是 data coefficient 仍然是 s,两份 noise 的方差之和仍然是 (1−s)2。这里需要匹配的是方差之和,所以 retained noise 的系数带一个平方根。

CPS 把新噪声的方差从原有噪声份额中分出来线性路径的一步 t=0.5 到 s=0.75,h=0.25,SDE diffusion coefficient g=0.4。目标 noise coefficient 为 0.25,目标方差为 0.0625。Euler ODE:data coefficient 0.75,retained noise 0.25,fresh noise 0.00,合成标准差 0.250;Euler + noise:data coefficient 0.75,retained noise 0.25,fresh noise 0.20,合成标准差 0.320;Euler–Maruyama:data coefficient 0.75,retained noise 0.21,fresh noise 0.20,合成标准差 0.290;CPS:data coefficient 0.75,retained noise 0.15,fresh noise 0.20,合成标准差 0.250。条形宽度表示两份 noise 的方差,蓝色实线框是 retained noise,棕色虚线框是 fresh noise;竖虚线标出目标总方差。这是将预测噪声视为单位方差并与 fresh noise 独立时的系数分析,不是经验测量。
t=0.5→s=0.75,h=0.25,gt=0.4
所有行的 data coefficient 都是 0.75;下面只比较 noiseSamplerNoise variance 的组成合成标准差Euler ODE
0.252+0.002=0.0625
0.250=0.25
Euler + noise
0.252+0.202=0.1025
0.320>0.25
Euler–Maruyama
0.212+0.202=0.0841
0.290>0.25
CPS
0.152+0.202=0.0625
0.250=0.25
目标总方差 0.0625retained noisefresh noise
左右滑动查看完整图示
横条相加的是方差,右侧显示合成标准差。CPS 把 retained noise 从 0.25 减至 0.15,再加入 0.20 的 fresh noise,恰好满足下一步 0.25 的目标。

例如目标 noise coefficient 是 0.25,想加入标准差为 0.20 的新噪声,就应把 retained noise coefficient 设为

0.252−0.202=0.15.

Flow-CPS 使用一个便于控制范围的参数化:

ρ=(1−s)sin⁡(ηπ2),xs=sz^+(1−s)cos⁡(ηπ2)ϵ^+(1−s)sin⁡(ηπ2)ξ,η∈[0,1].

η 控制这份 noise 中有多少来自新采样:η=0 恢复 Euler,η=1 全部使用 fresh noise。到 s=1 时两项 noise coefficient 都归零,输出当前的 z^;最终质量仍然取决于模型的预测。

What happens to the SDE equivalence?

连续时间的 score correction 与 noise 可以保持同一条 marginal path。实际的 Euler–Maruyama step 却把整段 drift 固定为当前点的值,步长有限时就会出现系数误差。把这个误差展开,才能看出 CPS 与它的差别。

对线性路径,有 stθ=−ϵ^/(1−t)。于是 SDE 的离散一步可以写成

xs=sz^+(1−s−gt2h2(1−t))ϵ^+gthξ.

在 CPS 使用的 noise coefficient 分析下,两项 noise 的合成方差为

(1−s−gt2h2(1−t))2+gt2h=(1−s)2+gt2h21−t+gt4h24(1−t)2.

右侧后两项给出了这个离散公式多出的 noise variance。图中 t=0.5,h=0.25,gt=0.4,合成 noise coefficient 是 0.29,大于目标 0.25。较大的步长、更强的探索噪声或接近端点的更新,都可能放大这种偏差,进而干扰 rollout 的 reward。

CPS 直接在有限步公式中保持这份系数关系。若令 ρ=gth 并对平方根做小步长展开,可以得到相应的 SDE 近似;固定 η 时则可以把它作为离散随机采样器使用。这里比较的是具体离散更新的系数,连续时间的 marginal equivalence 与其他 SDE solvers 的误差需要分别分析。

保持系数,是否就保证真实 marginal 完全正确?

新的 ξ 可以独立采样,但 ϵ^ 来自网络。MSE 网络的理想输出是 E[ϵ∣xt];这个条件均值一般不具有单位方差,也可能与 z^ 相关。因此,上面的 noise variance 计算依赖对 noise estimate 的理想化假设,CPS 的理论分析也需要这个假设。

CPS 精确保留的是 sampling formula 中规定的系数关系。这种约束控制了信号与噪声的预定份额,实际输出分布还取决于网络误差、有限步更新和 guidance。系数保持本身不足以证明任意网络都能精确采样目标 ps。

更一般地,对 Gaussian path,可以使用

xs=αsz^+βs2−ρ2ϵ^+ρξ,0≤ρ≤βs.

DDIM 与线性路径的 Flow-CPS 都呈现这个结构,但不同模型仍需要各自一致的预测参数化和 schedule。

Sampling details that change the RL objective

把上面的 CPS 更新放回 RL 的 transition policy。对 tk→tk+1,它的 mean 和 standard deviation 是

μθCPS=tk+1z^θ+(1−tk+1)cos⁡(ηπ2)ϵ^θ,νkCPS=(1−tk+1)sin⁡(ηπ2).

当 νk>0 时,这些参数定义了一个 Gaussian transition,可以用它计算前面的 probability ratio。Sampler 决定采到什么 action,log-density 公式决定怎样为这个 action 更新 policy;两者共同确定实际的 RL 目标。

Gaussian density、按维度取平均与去掉 variance denominator

对于 d 维 action、固定且与参数无关的 νk>0,严格 log density 为

ℓθ=−‖a−μθ(s)‖22νk2−dlog⁡νk−d2log⁡(2π).

同一个 state、action 和 noise schedule 下,ratio 是

log⁡ρ=−‖a−μθ(s)‖2−‖a−μold(s)‖22νk2.

两项 additive normalization constants 会相消,乘在平方误差前面的 1/(2νk2) 不会相消。同 covariance 的 reference KL 也有解析式:

DKL(πθ‖πref)=‖μθ−μref‖22νk2.

高维 latent 的 log density 会累加大量坐标的贡献。实现中为了控制更新的数值尺度,还会使用经过缩放的 surrogate:

  • Flow-GRPO 的 sampler (Flow-GRPO Authors, n.d.) 把逐坐标 log density 取平均后再计算 ratio。对固定 d,这相当于使用 ρ1/d,而不是整个 latent tensor 的 joint density ratio;同样的 clip threshold 也因此具有不同含义。
  • Flow-CPS 的论文与实现 (Flow-CPS Authors, n.d.) 进一步使用负平方误差,省略 variance denominator,并相应调整 reference regularization。这改变了不同时间点的权重,是一个额外的训练 surrogate;它不是通过“删去相消常数”得到的严格 Gaussian likelihood ratio。

当前 Flow-GRPO 官方仓库也已加入 CPS 分支。确定一次实验实际优化什么,需要一起看 sampler、log-density reduction、variance weighting 和 clip 设置。上面的公式给出了区分这些目标的依据。

还需要选择参与 policy 更新的 steps。当 η=0 或 CPS 到达最终 tk+1=1 时,νk=0,transition 是确定映射,Gaussian log-density 公式不再适用。一种受限制的训练方式是只更新随机 steps,并把其余生成步骤冻结为后续过程。如果多个步骤共享参数、会一起改变,跳过部分 score 项的更新一般就不再是完整生成目标的无偏梯度。

Further reading

References

Black, K., Janner, M., Du, Y., Kostrikov, I., & Levine, S. (2024). Training Diffusion Models with Reinforcement Learning. International Conference on Learning Representations. arxiv.org
Chen, R. T. Q., Rubanova, Y., Bettencourt, J., & Duvenaud, D. (2018). Neural Ordinary Differential Equations. Advances in Neural Information Processing Systems, 31. arxiv.org
Dinh, L., Sohl-Dickstein, J., & Bengio, S. (2017). Density Estimation Using Real NVP. International Conference on Learning Representations. arxiv.org
Ermon, S. (2023a). Normalizing Flow Models: Lecture 7. Stanford CS236, Fall 2023. deepgenerativemodels.github.io
Ermon, S. (2023b). Normalizing Flow Models: Lecture 8. Stanford CS236, Fall 2023. deepgenerativemodels.github.io
Flow-CPS Authors. (n.d.). Flow-CPS: SD3 Sampling Implementation. github.com
Flow-GRPO Authors. (n.d.). Flow-GRPO: SD3 SDE and CPS Sampling Implementation. github.com
Grover, A. (2018). Normalizing Flow Models. Stanford CS236 course notes. deepgenerativemodels.github.io
He, K. (2024). Diffusion Models: Lecture 5. MIT 6.S978 Deep Generative Models, Fall 2024. mit-6s978.github.io
Ho, J., Jain, A., & Abbeel, P. (2020). Denoising Diffusion Probabilistic Models. Advances in Neural Information Processing Systems, 33, 6840–6851. arxiv.org
Ho, J., & Salimans, T. (2022). Classifier-Free Diffusion Guidance. arxiv.org
Holderrieth, P., & Erives, E. (2026). An Introduction to Flow Matching and Diffusion Models. MIT 6.S184, IAP 2026 lecture notes. diffusion.csail.mit.edu
Lipman, Y., Chen, R. T. Q., Ben-Hamu, H., Nickel, M., & Le, M. (2023). Flow Matching for Generative Modeling. International Conference on Learning Representations. arxiv.org
Liu, J., Liu, G., Liang, J., Li, Y., Liu, J., Wang, X., Wan, P., Zhang, D., & Ouyang, W. (2025). Flow-GRPO: Training Flow Matching Models via Online RL. Advances in Neural Information Processing Systems. arxiv.org
Liu, X., Gong, C., & Liu, Q. (2023). Flow Straight and Fast: Learning to Generate and Transfer Data with Rectified Flow. International Conference on Learning Representations. arxiv.org
Peebles, W., & Xie, S. (2023). Scalable Diffusion Models with Transformers. Proceedings of the IEEE/CVF International Conference on Computer Vision, 4195–4205. arxiv.org
Rombach, R., Blattmann, A., Lorenz, D., Esser, P., & Ommer, B. (2022). High-Resolution Image Synthesis with Latent Diffusion Models. Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition, 10684–10695. arxiv.org
Song, J., Meng, C., & Ermon, S. (2021). Denoising Diffusion Implicit Models. International Conference on Learning Representations. arxiv.org
Song, Y., Sohl-Dickstein, J., Kingma, D. P., Kumar, A., Ermon, S., & Poole, B. (2021). Score-Based Generative Modeling through Stochastic Differential Equations. International Conference on Learning Representations. arxiv.org
Wang, F., & Yu, Z. (2025). Coefficients-Preserving Sampling for Reinforcement Learning with Flow Matching (Version 4). arxiv.org
Wetzstein, G. (2026). A Gentle Introduction to Diffusion Models: Lecture 12. Stanford EE367 / CS448I, Winter 2026. web.stanford.edu
Xue, Z., Wu, J., Gao, Y., Kong, F., Zhu, L., Chen, M., Liu, Z., Liu, W., Guo, Q., Huang, W., & Luo, P. (2025). DanceGRPO: Unleashing GRPO on Visual Generation. arxiv.org

Cite this post

@misc{pu2026mlmlrevisitdiffusion,
  author = {Pu, Fanyi and {GPT‑5.6 Sol}},
  title  = {Diffusion},
  year   = {2026},
  month  = {9},
  url    = {https://pufanyi.com/blog/ml/ml-revisit/diffusion}
}