Skip to content
AILinkDeepTech
Go back
Reinforcement Learning Medium

DDIM (Denoising Diffusion Implicit Models): Non-Markovian Sampling and Probability-Flow ODEs

Abstract

Master DDIM (Denoising Diffusion Implicit Models): non-Markovian sampling, probability-flow ODE derivations, deterministic inference, and PyTorch pipelines.

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:

  1. Sub-Sequence Step Skipping: Sampling converges in discretization steps without retraining the base model.
  2. 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 / DimensionDDPM 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 ProcessMarkovian Gaussian ChainNon-Markovian Gaussian FamilyContinuous SDE/ODEContinuous Variance ExplodingLinear Vector Field Flow
Reverse MechanicsStochastic Ancestral SamplingDeterministic ODE / Scaled SDEMulti-Step Exponential Integrator2nd-Order Predictor-Corrector1st-Order Euler ODE Solver
Typical Steps ()
DeterminismNo ()Yes (when )Yes (Deterministic ODE Mode)Yes (Heun ODE Mode)Yes
Latent InversionNot InvertibleExact ODE Inversion ()Semi-InvertibleExact ODE InversionExact ODE Inversion
Training ObjectiveL2 Noise Prediction ()Reuses Pretrained Reuses Pretrained Preconditioned Denoising ScoreVelocity Vector Matching ()

Mathematical Foundations

flowchart TD TRAIN["Pretrained Base Diffusion Model\nNoise Predictor epsilon_theta(x_t, t)"] --> MARGINALS["Fixed Marginal Distribution Preserved\nq(x_t | x_0) = N(sqrt(alpha_bar_t) x_0, (1 - alpha_bar_t) I)"] MARGINALS --> NONMARKOV["Non-Markovian Forward Formulation\nq_sigma(x_t-1 | x_t, x_0) with stochastic parameter eta"] NONMARKOV --> REVERSE["Unified DDIM Update Equation\nx_t-1 = sqrt(alpha_bar_t-1) x_hat_0 + dir_xt + sigma_t epsilon"] REVERSE -->|eta = 0| DET["Deterministic Mode (Probability-Flow ODE)\nExact Invertibility, Smooth Latent Interpolation"] REVERSE -->|eta = 1| STOCH["Stochastic Ancestral Mode\nEquivalent to Standard DDPM Transition"] DET --> SKIP["Sub-Sequence Discretization tau = (tau_1, ..., tau_S)\nInference acceleration from 1000 to 20-50 steps"]

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 :

  1. (DDPM Ancestral Sampling): The formulation reverts exactly to the stochastic Markovian DDPM transition kernel.

  2. (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 / ModelSampler MethodNFE (Steps)FID-50k ()Inversion Error (L2 )Latency / Image ()
CIFAR-10 (Uncond)DDPM AncestralN/A (Stochastic)
DDPM AncestralN/A
DDIM ()
DDIM ()
DPM-Solver++ 2M
ImageNet DDPM AncestralN/A
DDIM ()
DDIM ()
Stable Diffusion 1.5DDPM AncestralN/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_steps to , or switch to a higher-order multi-step solver (e.g., DPMSolverMultistepScheduler with order 2).

2. Inversion Divergence During Image Editing

  • Symptom: Inverting a real image via invert_step and subsequently running step fails 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: NaN tensors 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

  1. Song, J., Meng, C., & Ermon, S. (2021). Denoising Diffusion Implicit Models. International Conference on Learning Representations (ICLR 2021 - Oral).
  2. Ho, J., Jain, A., & Abbeel, P. (2020). Denoising Diffusion Probabilistic Models. Advances in Neural Information Processing Systems (NeurIPS 2020).
  3. 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.
  4. 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.
  5. Karras, T., Aittala, M., Aila, T., & Laine, S. (2022). Elucidating the Design Space of Diffusion-Based Generative Models. NeurIPS 2022.


Cite this Explanation

@article{ailinkdeeptech2025ddim,
  title={DDIM (Denoising Diffusion Implicit Models): Non-Markovian Sampling and Probability-Flow ODEs},
  author={AILinkDeepTech},
  journal={AILinkDeepTech Algorithm Explanations},
  year={2025},
  url={https://ailinkdeeptech.com/research/ddim}
}

Related Explanations