Consistency Model:让模型在任意 $t$都能直接预测 $x_0$。 数学上:$f_\theta(x_t, t) \approx x_0$; 训练让 $f_\theta(x_{t}, t) = f_\theta(x_{t+1}, t+1)$。 部署只需 1 步(或 2–4 步迭代)→ 50× 加速。 LCM:把它套到 latent diffusion(SD 系列),用 LoRA 蒸出 8-step / 4-step 版。
从 1000 步到 1 步
DDPM 原版要 $T = 1000$ 步去噪。SDXL Turbo 一步出图, HunyuanVideo 一段视频从 100 步压到 8 步。 四条路同时压:更高阶 ODE 求解器把步数砍到 10; consistency / 蒸馏砍到 1-4; cache 让单步成本减半; 量化把每步从 fp16 压到 INT4。
- §9.1DDIM / DPM-Solver / UniPC
- §9.2Consistency / LCM
- §9.3DMD / DMD2
- §9.4DeepCache / Δ-DiT / TGate
- §9.5ToMe-SD
- §9.6SVDQuant / Q-Diffusion
§9.1ODE 求解器 · DDIM / DPM-Solver / UniPC
Diffusion 是按 $T$ 步逐步去噪:
$$ x_{t-1} = \mu_\theta(x_t, t) + \sigma_t \, \epsilon $$原 DDPM 要 $T = 1000$;要部署,步数即一切。 Diffusion ODE 形式(DDIM 揭示):
$$ \frac{dx}{dt} = -\frac{1}{2} g^2(t) \nabla \log p_t(x) \approx -\frac{1}{2}g^2(t) \cdot \epsilon_\theta(x, t)/\sigma_t $$| 方法 | 关键想法 | 典型步数 |
|---|---|---|
| DDIM (2020) | 确定性 ODE,可降到 50 步 | 50 |
| DPM-Solver (2022) | 把 ODE 用半解析高阶展开 | 10–20 |
| DPM-Solver++ (2022) | condition / cfg 数值稳定 | 10–20 |
| UniPC (2023) | predictor-corrector 高阶 | 5–10 |
| Heun / Karras (EDM) | 更好的 noise schedule | 20–35 |
DDIM 单步代码
# DDIM: 给定 x_t, 预测 x_0, 然后用 ODE 跳到 x_{t-1}
def ddim_step(x_t, t, t_prev, model, alpha_bar):
# 模型预测噪声
eps = model(x_t, t)
# 从 x_t 与 eps 反推 x_0
x_0_pred = (x_t - (1 - alpha_bar[t]).sqrt() * eps) / alpha_bar[t].sqrt()
# 用 alpha_bar[t_prev] 重构 x_{t-1} (确定性, sigma=0)
x_prev = alpha_bar[t_prev].sqrt() * x_0_pred + (1 - alpha_bar[t_prev]).sqrt() * eps
return x_prev
# 50 步: 用 50 个 t 等间距, 比 DDPM 快 20x
§9.2Consistency Models / LCM · 一步到位
# Consistency Model loss: 让相邻 step 的预测一致
def cm_loss(student, teacher_ema, x, t, t_next):
# x_{t} ~ q(x_t | x_0)
x_t = forward_diffuse(x, t)
x_t_next = forward_diffuse(x, t_next) # 同一 x_0 在两个 noise level
pred_t = student(x_t, t) # 预测 x_0
pred_t_next = teacher_ema(x_t_next, t_next)
return (pred_t - pred_t_next.detach()).pow(2).mean()
# 训完: 一步推理 f(x_T, T) → 直接 x_0
§9.3DMD / DMD2 · 用 GAN 的方式蒸 diffusion
1 步生成器 $G$ + 两个 score 网络(pretrained "real" score + "fake" score) 互相博弈: 让 $G$ 输出的分布与 teacher 一致。 数学上等价于最小化反向 KL。 DMD2 加 GAN loss 让单步质量逼近 1000 步 teacher。 SDXL 单步出图 $\approx$ 多步 teacher 质量。
DMD 的训练循环
# DMD: 用两个 score model 估算梯度
def dmd_step(G, real_score, fake_score, z):
# 1. 生成一张图
x_gen = G(z)
# 2. 加 noise 到一个 timestep t
t = sample_t()
x_noisy = forward_diffuse(x_gen, t)
# 3. 两个 score 网络分别预测噪声
s_real = real_score(x_noisy, t).detach() # pretrained, frozen
s_fake = fake_score(x_noisy, t).detach()
# 4. G 的梯度 ∝ (s_fake - s_real)
grad = (s_fake - s_real)
loss = (x_gen * grad).sum()
loss.backward()
# 5. 另一边: fake_score 也要学 G 输出的分布 (online)
fake_score_loss = (fake_score(x_noisy, t) - true_noise_for(x_gen)).pow(2).mean()
同家族派生:SDXL Turbo (ADD)SD3 TurboPCMHyper-SDSwiftBrushSiD。
§9.4DeepCache / Δ-DiT / TGate · 不重算"深层"
U-Net 中、深层特征在相邻 timestep 之间高度相关。 DeepCache:每 N 个 step 才重算一次 deep block, 其余 step 复用上次的中间结果, 浅层每步重算(控制变化方向)。 SD-1.5 上 ~2× 加速,几乎无损。
# DeepCache: 浅层 (downsample/upsample 入出) 每步算, 深层每 N 步算一次
class DeepCachedUNet:
def __init__(self, unet, cache_interval=2):
self.unet = unet
self.cache_interval = cache_interval
self.cached_deep = None
def forward(self, x, t, step_idx):
h_down = self.unet.down_blocks(x, t)
if step_idx % self.cache_interval == 0 or self.cached_deep is None:
self.cached_deep = self.unet.mid_block(h_down, t)
# 复用上次 cached 的 deep feature
h_up = self.unet.up_blocks(self.cached_deep, h_down, t)
return self.unet.head(h_up)
DiT(PixArt / Sora-class)没有 U-Net 的 hourglass 结构。 Δ-DiT 把 "deep block" 类比改成"高频/低频" block: 早 timestep 缓存全局结构、晚 timestep 缓存高频细节。 TGate / TeaCache 进一步 sample-aware 决定哪一 step 跳过哪些 block。 Flux.1、HunyuanVideo、CogVideoX 都用 caching 推到 2–5× 加速。
§9.5ToMe-SD · 视觉 token 合并
SD / DiT 的 latent token 之间常常很相似(背景、空旷天空)。 每个 attention block 入口处把相似 token 合并, attention 之后解合并。 自然剪到一半 token,~2× 加速,~1% 质量。 后续 ToMe-SDXL、CacheToMe 沿此线。
# ToMe: 把 token 分成 src 与 dst, src 中每个并到最相近的 dst, attention 后再解合并
def tome_merge(x, r):
# x: [B, S, D], r = 要合并掉的 token 数
src, dst = x[:, ::2], x[:, 1::2] # 简单的 stride 划分
sim = cosine_similarity(src, dst) # [B, S/2, S/2]
edges = sim.argmax(dim=-1) # 每个 src 最相近的 dst
scores = sim.max(dim=-1).values
keep = scores.topk(src.shape[1] - r, largest=False).indices # 不合并的 src
# 合并: 对 dst 中"被合并"的位置取平均
return apply_merge(src, dst, edges, keep)
§9.6SVDQuant / Q-Diffusion / ViDiT-Q · 把 DiT 量到 4-bit
DiT 量化的核心痛 = 激活有大 outlier。 SVDQuant:把权重 $W = LR + Q$ 分解为低秩部分 $LR$(吸 outlier)+ 量化 INT4 残差 $Q$。 低秩部分 fp16 跑、$Q$ 走 INT4 tensor core。 Flux.1-dev 量到 W4A4 仅掉 ~0.1 FID。
# SVDQuant: W ≈ L R + Q
def svdquant_decompose(W, rank=32, n_bits=4):
# 1. SVD 分解
U, s, V = torch.linalg.svd(W, full_matrices=False)
L = U[:, :rank] * s[:rank].sqrt()
R = (V[:rank, :].T * s[:rank].sqrt()).T
LR = L @ R # 低秩近似
# 2. 残差量化到 INT4
residual = W - LR
Q = quantize_int4(residual)
return L, R, Q
# Forward: y = x @ L @ R + x @ dequant(Q)
# 第一项 fp16, rank 小, 很快
# 第二项 INT4 tensor core, 主体运算
Q-Diffusion:noise-schedule-aware PTQ, 在不同 timestep 用不同 calibration set 选 scale。 ViDiT-Q:把同样思路推到 video DiT,处理跨帧分布漂移。
- 有 teacher 模型 + 训练资源 → LCM / DMD2,1-4 步出图。
- 已部署 SD,只想免费提速 → DeepCache + ToMe-SD,2-3× 不掉分。
- 边缘部署 → SVDQuant W4A4。
- 长视频 → Δ-DiT / TeaCache + LCM。
- 组合上限:SVDQuant + DMD2 + DeepCache, Flux 在 4090 上 1 步出图 0.4 s(vs baseline 8 s)。