The Sampling Bottleneck in Standard Markovian Diffusion
Standard Denoising Diffusion Probabilistic Models (DDPM) (Ho et al., NeurIPS 2020) define the forward diffusion chain as a strictly Markovian Gaussian process:
While the closed-form marginal allows direct training at arbitrary timesteps :
the reverse generation process is tightly bound to sequential step-by-step Markovian ancestral transitions:
Generating a single high-resolution image requires evaluating the neural noise predictor across all sequential timesteps. This constraint renders inference computationally prohibitive ( per sample on modern GPUs).
Denoising Diffusion Implicit Models (DDIM) (Song et al., ICLR 2021) eliminates this limitation by generalizing the forward trajectory to a non-Markovian family of distributions that strictly preserves the identical marginal distributions . This unlocks:
- Sub-Sequence Step Skipping: Sampling converges in discretization steps without retraining the base model.
- Deterministic Invertibility (): The reverse process becomes a deterministic discretization of the continuous-time Probability-Flow Ordinary Differential Equation (ODE), establishing an exact bidirectional mapping between latent noise and synthesized data .
Architectural & Sampler Comparison
| Metric / Dimension | DDPM Ancestral (Ho et al.) | DDIM (Song et al.) | DPM-Solver++ (Lu et al.) | EDM Heun (Karras et al.) | Flow-Match Euler (Lipman et al.) |
|---|---|---|---|---|---|
| Forward Process | Markovian Gaussian Chain | Non-Markovian Gaussian Family | Continuous SDE/ODE | Continuous Variance Exploding | Linear Vector Field Flow |
| Reverse Mechanics | Stochastic Ancestral Sampling | Deterministic ODE / Scaled SDE | Multi-Step Exponential Integrator | 2nd-Order Predictor-Corrector | 1st-Order Euler ODE Solver |
| Typical Steps () | |||||
| Determinism | No () | Yes (when ) | Yes (Deterministic ODE Mode) | Yes (Heun ODE Mode) | Yes |
| Latent Inversion | Not Invertible | Exact ODE Inversion () | Semi-Invertible | Exact ODE Inversion | Exact ODE Inversion |
| Training Objective | L2 Noise Prediction () | Reuses Pretrained | Reuses Pretrained | Preconditioned Denoising Score | Velocity Vector Matching () |
Mathematical Foundations
Figure 1: Mathematical unification of DDIM showing marginal preservation, non-Markovian parameterization, and deterministic ODE discretization.
1. Non-Markovian Forward Distribution Formulation
DDIM constructs a family of non-Markovian inference distributions that maintain identical marginals :
The conditional forward step is parameterized as a Gaussian:
where the variance is controlled by a hyperparameter :
2. General Reverse Sampling Derivation
During reverse sampling, the clean data point is unknown and approximated via Tweedieβs formula using the trained noise predictor :
Substituting into the forward conditional yields the Universal DDIM Update Rule:
Special Regimes of the Stochasticity Parameter :
-
(DDPM Ancestral Sampling): The formulation reverts exactly to the stochastic Markovian DDPM transition kernel.
-
(Deterministic DDIM): The reverse trajectory becomes completely deterministic, removing all injected random noise at each step.
3. The Probability-Flow ODE Derivation
To establish the continuous-time connection, consider an infinitesimal timestep transition . Rewriting the deterministic DDIM update () in terms of continuous time variables :
Rearranging into discrete difference quotient form:
Taking the continuous limit yields the continuous ODE:
Using the score function identity , this simplifies to the Probability-Flow ODE:
where is the drift coefficient and is the diffusion coefficient. This proves that deterministic DDIM is an Euler-Maruyama discretization of the exact Probability-Flow ODE.
4. Spherical Latent Interpolation ()
Because deterministic DDIM () forms a continuous bijective mapping , interpolating between two generated images and is executed via Spherical Linear Interpolation () on the initial standard Gaussian sphere:
where . Sampling generates semantically smooth interpolations without semantic collapse or structural artifacts.
Production PyTorch Implementation
Below is a complete, vectorized PyTorch implementation of the DDIM Scheduler, supporting both forward deterministic ODE inversion and reverse accelerated sampling with sub-sequence stride scheduling.
Step 1: Vectorized DDIM Sampler & Inversion Engine
from __future__ import annotations
import math
import torch
import torch.nn as nn
class DDIMScheduler:
def __init__(
self,
num_train_timesteps: int = 1000,
beta_start: float = 0.0001,
beta_end: float = 0.02,
beta_schedule: str = "linear",
) -> None:
self.num_train_timesteps = num_train_timesteps
if beta_schedule == "linear":
self.betas = torch.linspace(beta_start, beta_end, num_train_timesteps, dtype=torch.float32)
elif beta_schedule == "cosine":
steps = num_train_timesteps + 1
s = 0.008
x = torch.linspace(0, num_train_timesteps, steps, dtype=torch.float32)
alphas_cumprod = torch.cos(((x / num_train_timesteps) + s) / (1 + s) * math.pi * 0.5) ** 2
alphas_cumprod = alphas_cumprod / alphas_cumprod[0]
self.betas = torch.clip(1.0 - alphas_cumprod[1:] / alphas_cumprod[:-1], 0.0001, 0.9999)
else:
raise NotImplementedError(f"Schedule {beta_schedule} is not supported.")
self.alphas = 1.0 - self.betas
self.alphas_cumprod = torch.cumprod(self.alphas, dim=0)
def set_timesteps(self, num_inference_steps: int, device: torch.device) -> torch.Tensor:
"""Constructs an evenly spaced sub-sequence of timesteps."""
self.num_inference_steps = num_inference_steps
step_ratio = self.num_train_timesteps // self.num_inference_steps
timesteps = (torch.arange(0, num_inference_steps) * step_ratio).round().flip(0).long().to(device)
self.timesteps = timesteps
return self.timesteps
def step(
self,
model_output: torch.Tensor,
timestep: int,
prev_timestep: int,
sample: torch.Tensor,
eta: float = 0.0,
) -> torch.Tensor:
"""Executes a single DDIM step from timestep t to prev_timestep."""
alpha_prod_t = self.alphas_cumprod[timestep]
alpha_prod_t_prev = self.alphas_cumprod[prev_timestep] if prev_timestep >= 0 else torch.tensor(1.0)
# 1. Predict clean x_0 from current noise estimate
pred_original_sample = (sample - torch.sqrt(1.0 - alpha_prod_t) * model_output) / torch.sqrt(alpha_prod_t)
# 2. Compute variance sigma_t
variance = ((1.0 - alpha_prod_t_prev) / (1.0 - alpha_prod_t)) * (1.0 - alpha_prod_t / alpha_prod_t_prev)
sigma_t = eta * torch.sqrt(variance.clamp(min=0.0))
# 3. Compute direction pointing to x_t
pred_sample_direction = torch.sqrt((1.0 - alpha_prod_t_prev - sigma_t**2).clamp(min=0.0)) * model_output
# 4. Compute x_{t-1}
prev_sample = torch.sqrt(alpha_prod_t_prev) * pred_original_sample + pred_sample_direction
if eta > 0.0:
noise = torch.randn_like(sample)
prev_sample = prev_sample + sigma_t * noise
return prev_sample
def invert_step(
self,
model_output: torch.Tensor,
timestep: int,
next_timestep: int,
sample: torch.Tensor,
) -> torch.Tensor:
"""Deterministic Probability-Flow ODE Inversion: steps forward from t to next_timestep."""
alpha_prod_t = self.alphas_cumprod[timestep] if timestep >= 0 else torch.tensor(1.0)
alpha_prod_t_next = self.alphas_cumprod[next_timestep]
# 1. Estimate x_0
pred_original_sample = (sample - torch.sqrt(1.0 - alpha_prod_t) * model_output) / torch.sqrt(alpha_prod_t)
# 2. Step forward along deterministic trajectory
pred_sample_direction = torch.sqrt(1.0 - alpha_prod_t_next) * model_output
next_sample = torch.sqrt(alpha_prod_t_next) * pred_original_sample + pred_sample_direction
return next_sample
Step 2: Full Inference Loop with Classifier-Free Guidance (CFG)
from __future__ import annotations
import torch
import torch.nn as nn
from ddim_scheduler import DDIMScheduler
@torch.no_grad()
def ddim_generate(
unet: nn.Module,
scheduler: DDIMScheduler,
shape: tuple[int, int, int, int],
text_embeddings: torch.Tensor,
uncond_embeddings: torch.Tensor,
guidance_scale: float = 7.5,
num_inference_steps: int = 50,
eta: float = 0.0,
device: str = "cuda",
) -> torch.Tensor:
dev = torch.device(device)
timesteps = scheduler.set_timesteps(num_inference_steps, dev)
# 1. Initialize random Gaussian latent
latents = torch.randn(shape, device=dev, dtype=torch.float32)
# 2. Reverse Diffusion Loop
for i, t in enumerate(timesteps):
prev_t = timesteps[i + 1] if i + 1 < len(timesteps) else -1
# Dual-batch forward pass for Classifier-Free Guidance
latent_model_input = torch.cat([latents] * 2)
encoder_hidden_states = torch.cat([uncond_embeddings, text_embeddings])
# Predict noise
t_input = torch.tensor([t, t], device=dev)
noise_pred = unet(latent_model_input, t_input, encoder_hidden_states=encoder_hidden_states)
# CFG Extrapolation
noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)
noise_pred_guided = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond)
# DDIM Step
latents = scheduler.step(
model_output=noise_pred_guided,
timestep=t.item(),
prev_timestep=prev_t.item() if isinstance(prev_t, torch.Tensor) else prev_t,
sample=latents,
eta=eta,
)
return latents
Empirical Benchmark Evaluation
Quantitative evaluation measuring FrΓ©chet Inception Distance (FID) vs. Number of Function Evaluations (NFE) across unconditional and text-to-image datasets:
| Dataset / Model | Sampler Method | NFE (Steps) | FID-50k () | Inversion Error (L2 ) | Latency / Image () |
|---|---|---|---|---|---|
| CIFAR-10 (Uncond) | DDPM Ancestral | N/A (Stochastic) | |||
| DDPM Ancestral | N/A | ||||
| DDIM () | |||||
| DDIM () | |||||
| DPM-Solver++ 2M | |||||
| ImageNet | DDPM Ancestral | N/A | |||
| DDIM () | |||||
| DDIM () | |||||
| Stable Diffusion 1.5 | DDPM Ancestral | N/A | |||
| DDIM () | |||||
| DDIM () |
Troubleshooting Common Sampling Faults
1. High-Frequency Noise Leakage at Low Step Counts ()
- Symptom: Generated images retain noticeable grain, washed-out textures, or high-frequency checkerboard artifacts.
- Root Cause: First-order Euler discretization introduces cumulative truncation errors when step size is large.
- Remedy: Increase
num_inference_stepsto , or switch to a higher-order multi-step solver (e.g.,DPMSolverMultistepSchedulerwith order 2).
2. Inversion Divergence During Image Editing
- Symptom: Inverting a real image via
invert_stepand subsequently runningstepfails to reconstruct the original content. - Root Cause: Accumulation of local linearization errors in Euler ODE steps under high classifier-free guidance scales.
- Remedy: Perform inversion with
guidance_scale = 1.0(unconditioned), increase inversion steps to , and utilize Null-Text Optimization (Mokady et al., 2023).
3. Numerical Instability in Variance Computation
- Symptom:
NaNtensors appearing during the final sampling steps (). - Root Cause: Dividing by or taking the square root of negative values caused by floating-point precision clamping.
- Remedy: Clamp variance terms strictly using
torch.clamp(variance, min=0.0)and ensure explicitly evaluates to .
References
- Song, J., Meng, C., & Ermon, S. (2021). Denoising Diffusion Implicit Models. International Conference on Learning Representations (ICLR 2021 - Oral).
- Ho, J., Jain, A., & Abbeel, P. (2020). Denoising Diffusion Probabilistic Models. Advances in Neural Information Processing Systems (NeurIPS 2020).
- Song, Y., Sohl-Dickstein, J., Kingma, D. P., Kumar, A., Ermon, S., & Poole, B. (2021). Score-Based Generative Modeling through Stochastic Differential Equations. ICLR 2021.
- Lu, C., Zhou, Y., Bao, F., Chen, J., Li, C., & Zhu, J. (2022). DPM-Solver: A Fast ODE Solver for Diffusion Probabilistic Model Sampling. NeurIPS 2022.
- Karras, T., Aittala, M., Aila, T., & Laine, S. (2022). Elucidating the Design Space of Diffusion-Based Generative Models. NeurIPS 2022.