Phase 08 - Lesson 06

Modelos de Difusión — DDPM desde Cero

Ho, Jain, Abbeel (2020) le dieron al campo una receta que no pudo abandonar. Destruir los datos con ruido a lo largo de mil pequeños pasos. Entrenar una red neuronal para predecir el ruido. Revertir el proceso en la inferencia. Hoy en día, cada modelo principal de imagen, video, 3D y música se ejecuta en este bucle, posiblemente con trucos de flow matching o consistencia por encima.

Tipo: Construcción Lenguajes: Python Prerrequisitos: Fase 3 · 02 (Backprop), Fase 8 · 02 (VAE) Tiempo: ~75 minutos

El Problema

Quieres un muestreador para p_data(x). Las GAN juegan un juego minimax que a menudo diverge. Los VAE producen muestras borrosas a partir de un decodificador gaussiano. Lo que realmente quieres es un objetivo de entrenamiento que sea (a) una pérdida única y estable (sin punto de silla, sin minimax), (b) un límite inferior para log p(x) (para tener verosimilitudes) y (c) muestras que alcancen la calidad del estado del arte (SOTA).

Sohl-Dickstein et al. (2015) tenían una respuesta teórica: definir una cadena de Markov q(x_t | x_{t-1}) que añade ruido gaussiano gradualmente, y entrenar una cadena inversa p_θ(x_{t-1} | x_t) para eliminar el ruido. Ho, Jain, Abbeel (2020) demostraron que la pérdida se podía simplificar a una sola línea — predecir el ruido — y limpiaron las matemáticas. En 2020 esto era una curiosidad. En 2021 produjo muestras en el estado del arte. En 2022 se convirtió en Stable Diffusion. En 2026 es el sustrato.

El Concepto

DDPM: ruido directo, eliminación de ruido inversa

Proceso directo q. Añade ruido gaussiano en T pequeños pasos. La forma cerrada — la razón por la que las matemáticas son manejables — es que el paso acumulativo también es gaussiano:

q(x_t | x_0) = N( sqrt(α̅_t) · x_0,  (1 - α̅_t) · I )

donde α̅_t = ∏_{s=1..t} (1 - β_s) para un esquema de β_t. Elija β_t de 1e-4 a 0.02 linealmente a lo largo de T=1000 pasos y x_T es aproximadamente N(0, I).

Proceso inverso p_θ. Entrene una red neuronal ε_θ(x_t, t) que prediga el ruido que se agregó. Dado x_t, elimine el ruido mediante:

x_{t-1} = (1 / sqrt(α_t)) · ( x_t - (β_t / sqrt(1 - α̅_t)) · ε_θ(x_t, t) )  +  σ_t · z

donde σ_t es sqrt(β_t) o una varianza aprendida. La expresión es fea, pero es solo álgebra: resolver para x_{t-1} dada a posterior q(x_{t-1} | x_t, x_0) y sustituir x_0 con su estimación de ruido predicha.

Pérdida de entrenamiento.

L_simple = E_{x_0, t, ε} [ || ε - ε_θ( sqrt(α̅_t) · x_0 + sqrt(1 - α̅_t) · ε,  t ) ||² ]

Muestree x_0 de los datos, elija un t aleatorio, muestree ε ~ N(0, I), calcule el x_t ruidoso en un solo paso mediante la forma cerrada y realice la regresión sobre el ruido. Una sola pérdida, sin minimax, sin KL, sin trucos de parametrización.

Muestreo. Comience con x_T ~ N(0, I). Itere el paso inverso desde t = T hasta 1. Listo.

Por qué funciona

Tres intuiciones:

  1. La eliminación de ruido es fácil; la generación es difícil. En t=T, los datos son puro ruido: la red tiene que resolver un problema trivial. En t=0, la red solo tiene que limpiar unos pocos píxeles. En un t intermedio, el problema es difícil pero la red tiene muchos gradientes fluyendo a través de los mismos pesos desde cada nivel de ruido.

  2. Score matching disfrazado. Vincent (2011) demostró que predecir el ruido es equivalente a estimar ∇_x log q(x_t | x_0), la puntuación (score). La SDE inversa utiliza este score para ascender por el gradiente de densidad: una caminata aleatoria guiada hacia regiones de alta probabilidad.

  3. El ELBO se reduce a un MSE simple. El límite inferior variacional (ELBO) completo tiene un término KL por paso de tiempo. Con la parametrización de DDPM, esos términos KL se simplifican a MSE en la predicción del ruido con coeficientes específicos; Ho eliminó los coeficientes (llamándola pérdida "simple") y la calidad mejoró.

Constrúyalo

El archivo code/main.py implementa un DDPM 1-D. Los datos son una mezcla de dos modos (two-mode mixture). La "red" es una pequeña MLP que toma (x_t, t) y genera el ruido predicho. El entrenamiento es la pérdida de una línea. El muestreo itera la cadena inversa.

Paso 1: el esquema directo (forma cerrada)

betas = [1e-4 + (0.02 - 1e-4) * t / (T - 1) for t in range(T)]
alphas = [1 - b for b in betas]
alpha_bars = []
cum = 1.0
for a in alphas:
    cum *= a
    alpha_bars.append(cum)

Paso 2: muestrear x_t en un solo paso

def forward_sample(x0, t, alpha_bars, rng):
    a_bar = alpha_bars[t]
    eps = rng.gauss(0, 1)
    x_t = math.sqrt(a_bar) * x0 + math.sqrt(1 - a_bar) * eps
    return x_t, eps

Paso 3: un paso de entrenamiento

def train_step(x0, model, alpha_bars, rng):
    t = rng.randrange(T)
    x_t, eps = forward_sample(x0, t, alpha_bars, rng)
    eps_hat = model_forward(model, x_t, t)
    loss = (eps - eps_hat) ** 2
    return loss, gradient_step(model, ...)

Paso 4: muestreo inverso

def sample(model, alpha_bars, T, rng):
    x = rng.gauss(0, 1)
    for t in range(T - 1, -1, -1):
        eps_hat = model_forward(model, x, t)
        beta_t = 1 - alphas[t]
        x = (x - beta_t / math.sqrt(1 - alpha_bars[t]) * eps_hat) / math.sqrt(alphas[t])
        if t > 0:
            x += math.sqrt(beta_t) * rng.gauss(0, 1)
    return x

Para un problema 1-D con 40 pasos de tiempo y una MLP de 24 unidades, esto aprende la mezcla de dos modos en ~200 épocas.

Condicionamiento temporal

La red necesita saber qué paso de tiempo está eliminando el ruido. Dos opciones estándar:

  • Incrustación (embedding) sinusoidal. Como la codificación posicional de Transformer. embed(t) = [sin(t/ω_0), cos(t/ω_0), sin(t/ω_1), ...]. Páselo a través de una MLP, transmítalo (broadcast) a la red.
  • Condicionamiento FiLM / group-norm. Proyecte la incrustación a escala/sesgo por canal (FiLM) en cada bloque.

Nuestro código de juguete utiliza sinusoidal → concat. Las U-Nets de producción utilizan FiLM.

Dificultades

  • El esquema importa mucho. El β lineal es el predeterminado de DDPM, pero el esquema de coseno (Nichol y Dhariwal, 2021) ofrece mejor FID para el mismo cómputo. Cambie los esquemas si la calidad se estanca.
  • La incrustación de pasos de tiempo es frágil. Pasar t sin procesar como un número flotante funciona para el juguete 1-D pero falla para imágenes; use siempre una incrustación adecuada.
  • V-prediction vs ε-prediction. Para regímenes estrechos (t muy pequeño o muy grande), ε tiene una relación señal-ruido deficiente. La predicción V (v = α·ε - σ·x) es más estable; SDXL, SD3 y Flux la utilizan.
  • Guía libre de clasificador (Classifier-free guidance). En la inferencia, calcule tanto el ε condicional como el incondicional, luego ε_cfg = (1 + w) · ε_cond - w · ε_uncond con w ≈ 3-7. Cubierto en la Lección 08.
  • 1000 pasos es mucho. La producción utiliza DDIM (20-50 pasos), DPM-Solver (10-20 pasos) o destilación (1-4 pasos). Ver Lección 12.

Úselo

Rol Stack típica en 2026
Difusión en el espacio de píxeles de imagen (pequena, de juguete) DDPM + U-Net
Difusión latente de imagen VAE encoder + U-Net o DiT (Lección 07)
Difusión latente de video DiT Espaciotemporal (Sora, Veo, WAN)
Difusión latente de audio Encodec + transformer de difusión
Ciencia (moléculas, proteínas, física) Difusión equivariante (EDM, RFdiffusion, AlphaFold3)

La difusión es la columna vertebral generativa universal. El flow matching (Lección 13) es el competidor de 2024-2026 que generalmente gana en velocidad de inferencia para la misma calidad.

Envíe

Guarde outputs/skill-diffusion-trainer.md. La Skill toma un conjunto de datos + presupuesto de cómputo y genera: esquema (lineal/coseno/sigmoide), objetivo de predicción (ε/v/x), número de pasos, escala de guía (guidance scale), familia de muestreador y un protocolo de evaluación.

Ejercicios

  1. Fácil. Cambie T de 40 a 10 en code/main.py. ¿Cómo se degrada la calidad de la muestra (histograma visual de las salidas)? ¿En qué T se colapsa la estructura de dos modos?
  2. Medio. Cambie de predicción ε a predicción v. Vuelva a derivar el paso inverso. Compare la calidad final de la muestra.
  3. Difácil. Añada guía libre de clasificador (classifier-free guidance). Condicione en una etiqueta de clase c ∈ {0, 1}, descártela el 10% del tiempo durante el entrenamiento y, al momento del muestreo, use ε = (1+w)·ε_cond - w·ε_uncond. Mida la tasa de acierto del modo condicional en w = 0, 1, 3, 7.

Términos Clave

Término Lo que la gente dice Lo que realmente significa
Proceso directo "Añadiendo ruido" Cadena de Markov fija `q(x_t
Proceso inverso "Eliminación de ruido" Cadena aprendida `p_θ(x_{t-1}
Esquema de β "La escalera de ruido" Varianza por paso; lineal, coseno o sigmoide.
α̅ "Alfa barra" Producto acumulativo ∏(1 - β); proporciona x_t en forma cerrada a partir de x_0.
Pérdida simple "MSE en el ruido" `
Predicción de ε "Predecir ruido" La salida es el ruido añadido; DDPM estándar.
Predicción de V "Predecir velocidad" La salida es α·ε - σ·x; mejor condicionamiento a lo largo de t.
DDPM "El artículo" Ho et al. 2020; β lineal, 1000 pasos, U-Net.
DDIM "Muestreador determinista" Muestreador no markoviano, 20-50 pasos, mismo objetivo de entrenamiento.
Guía libre de clasificador "CFG" Mezcla predicciones de ruido condicionales e incondicionales para amplificar el condicionamiento.

Nota de producción: la inferencia de difusión es un problema de conteo de pasos

El artículo de DDPM ejecuta T=1000 pasos inversos. Nadie publica eso en producción. Cada pila (stack) de inferencia real elige una de tres estrategias, y cada una se asigna claramente a la perspectiva de producción de "de dónde proviene la latencia":

  1. Muestreador más rápido, mismo modelo. DDIM (20-50 pasos), DPM-Solver++ (10-20), UniPC (8-16). Reemplazo directo (drop-in) del bucle inverso; los pesos entrenados de ε_θ permanecen intactos. Reduce la latencia de 20 a 50 veces.
  2. Destilación. Entrena a un estudiante para que coincida con el profesor en menos pasos: Destilación Progresiva (2 → 1), Modelos de Consistencia (arbitrario → 1-4), LCM, SDXL-Turbo, SD3-Turbo. Reduce la latencia otras 5 a 10 veces, requiere reentrenamiento.
  3. Caché y compilación. torch.compile(unet, mode="reduce-overhead"), backends de difusión de TensorRT-LLM, atención xformers/SDPA, pesos bf16. Reduce la latencia por paso aproximadamente 2 veces. Se combina con (1) y (2).

Para un servidor de difusión en producción, la discusión sobre el presupuesto es la misma descrita en la literatura de producción para LLMs: la latencia es num_steps × step_cost + VAE_decode, el rendimiento (throughput) es batch_size × (num_steps × step_cost)^-1. El TTFT es pequeño (un paso); el equivalente a TPOT es el tiempo de respuesta completo porque la generación de imágenes ocurre "de una sola vez" desde la perspectiva del usuario.

Lecturas Adicionales

0 lifetime access. Curriculum based on AI Engineering from Scratch by Rohit Ghumare (MIT, used under attribution).