Phase 08 - Lesson 06

Modelos de Difusão — DDPM do Zero

Ho, Jain, Abbeel (2020) deram à área uma receita que ela não conseguiu abandonar. Destruir os dados com ruído ao longo de mil pequenos passos. Treinar uma rede neural para prever o ruído. Reverter o processo na inferência. Hoje, todo modelo principal de imagem, vídeo, 3D e música roda nesse loop, possivelmente com truques de flow matching ou consistência por cima.

Tipo: Construção Linguagens: Python Pré-requisitos: Fase 3 · 02 (Backprop), Fase 8 · 02 (VAE) Tempo: ~75 minutos

O Problema

Você quer um amostrador para p_data(x). GANs jogam um jogo minimax que frequentemente diverge. VAEs produzem amostras borradas a partir de um decodificador gaussiano. O que você realmente quer é um objetivo de treinamento que seja (a) uma perda única e estável (sem ponto de sela, sem minimax), (b) um limite inferior para log p(x) (para que você tenha verossimilhanças) e (c) amostras que atinjam a qualidade do estado da arte (SOTA).

Sohl-Dickstein et al. (2015) tinham uma resposta teórica: definir uma cadeia de Markov q(x_t | x_{t-1}) que adiciona ruído gaussiano gradualmente, e treinar uma cadeia reversa p_θ(x_{t-1} | x_t) para remover o ruído. Ho, Jain, Abbeel (2020) mostraram que a perda poderia ser simplificada para uma única linha — prever o ruído — e limparam a matemática. Em 2020, isso era uma curiosidade. Em 2021, produziu amostras no estado da arte. Em 2022, tornou-se o Stable Diffusion. Em 2026, é o substrato.

O Conceito

DDPM: ruído direto, remoção de ruído reversa

Processo direto q. Adicione ruído gaussiano em T pequenos passos. A forma fechada — a razão pela qual a matemática é tratável — é que o passo cumulativo também é gaussiano:

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

onde α̅_t = ∏_{s=1..t} (1 - β_s) para um cronograma de β_t. Escolha β_t de 1e-4 a 0.02 linearmente ao longo de T=1000 passos e x_T será aproximadamente N(0, I).

Processo reverso p_θ. Treine uma rede neural ε_θ(x_t, t) que prevê o ruído que foi adicionado. Dado x_t, remova o ruído por:

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

onde σ_t é sqrt(β_t) ou uma variância aprendida. A expressão é feia, mas é apenas álgebra — resolvendo para x_{t-1} dada a posterior q(x_{t-1} | x_t, x_0) e substituindo x_0 pela sua estimativa de ruído prevista.

Perda de treinamento.

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

Amostre x_0 dos dados, escolha um t aleatório, amostre ε ~ N(0, I), calcule o x_t ruidoso de uma só vez através da forma fechada e faça a regressão sobre o ruído. Uma única perda, sem minimax, sem KL, sem truques de reparametrização.

Amostragem. Comece com x_T ~ N(0, I). Itere o passo reverso de t = T até 1. Pronto.

Por que funciona

Três intuições:

  1. A remoção de ruído é fácil; a geração é difícil. Em t=T, os dados são puro ruído — a rede precisa resolver um problema trivial. Em t=0, a rede só precisa limpar alguns pixels. Em t intermediários, o problema é difícil, mas a rede tem muitos gradientes fluindo pelos mesmos pesos a partir de cada nível de ruído.

  2. Score matching disfarçado. Vincent (2011) provou que prever o ruído é equivalente a estimar ∇_x log q(x_t | x_0), o score. A SDE reversa usa esse score para subir o gradiente de densidade — uma caminhada aleatória guiada em direção a regiões de alta probabilidade.

  3. O ELBO se reduz a um MSE simples. O limite inferior variacional (ELBO) completo tem um termo KL por timestep. Com a parametrização do DDPM, esses termos KL se simplificam para MSE na predição de ruído com coeficientes específicos; Ho removeu os coeficientes (chamando-a de perda "simples") e a qualidade melhorou.

Construa

O arquivo code/main.py implementa um DDPM 1-D. Os dados são uma mistura de dois modos (two-mode mixture). A "rede" é uma pequena MLP que recebe (x_t, t) e gera o ruído previsto. O treinamento é a perda de uma linha. A amostragem itera a cadeia reversa.

Passo 1: o cronograma direto (forma fechada)

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)

Passo 2: amostrar x_t de uma só vez

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

Passo 3: um passo de treinamento

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, ...)

Passo 4: amostragem reversa

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 um problema 1-D com 40 timesteps e uma MLP de 24 unidades, isso aprende a mistura de dois modos em ~200 épocas.

Condicionamento temporal

A rede precisa saber qual timestep ela está de-ruidando (denoising). Duas opções padrão:

  • Embedding senoidal. Como a codificação posicional do Transformer. embed(t) = [sin(t/ω_0), cos(t/ω_0), sin(t/ω_1), ...]. Passe por uma MLP, faça o broadcast para a rede.
  • Condicionamento FiLM / group-norm. Projete o embedding para escala/viés por canal (FiLM) em cada bloco.

Nosso código de brinquedo usa senoidal → concat. U-Nets de produção usam FiLM.

Armadilhas

  • O cronograma importa muito. O β linear é o padrão do DDPM, mas o cronograma de cosseno (Nichol & Dhariwal, 2021) oferece melhor FID para o mesmo custo computacional. Mude os cronogramas se a qualidade estagnar.
  • O embedding de timestep é frágil. Passar t bruto como float funciona para o caso de brinquedo 1-D, mas falha para imagens; sempre use um embedding adequado.
  • V-prediction vs ε-prediction. Para regimes estreitos (t muito pequeno ou muito grande), ε tem uma relação sinal-ruído ruim. A predição de V (v = α·ε - σ·x) é mais estável; SDXL, SD3 e Flux a utilizam.
  • Orientação livre de classificador (Classifier-free guidance). Na inferência, calcule tanto o ε condicional quanto o incondicional, então ε_cfg = (1 + w) · ε_cond - w · ε_uncond com w ≈ 3-7. Abordado na Lição 08.
  • 1000 passos é muita coisa. A produção usa DDIM (20-50 passos), DPM-Solver (10-20 passos) ou destilação (1-4 passos). Veja a Lição 12.

Use

Papel Stack típica em 2026
Difusão no espaço de pixels de imagem (pequena, brinquedo) DDPM + U-Net
Difusão latente de imagem VAE encoder + U-Net ou DiT (Lição 07)
Difusão latente de vídeo DiT Espaçotemporal (Sora, Veo, WAN)
Difusão latente de áudio Encodec + transformer de difusão
Ciência (moléculas, proteínas, física) Difusão equivariante (EDM, RFdiffusion, AlphaFold3)

A difusão é a espinha dorsal generativa universal. O flow matching (Lição 13) é o concorrente de 2024-2026 que geralmente vence na velocidade de inferência para a mesma qualidade.

Implemente

Salve outputs/skill-diffusion-trainer.md. A Skill recebe um conjunto de dados + orçamento de computação e gera: cronograma (linear/cosseno/sigmoide), alvo de predição (ε/v/x), número de passos, escala de orientação (guidance scale), família de amostrador e um protocolo de avaliação.

Exercícios

  1. Fácil. Altere T de 40 para 10 em code/main.py. Como a qualidade da amostra (histograma visual das saídas) se degrada? Em qual T a estrutura de dois modos entra em colapso?
  2. Médio. Mude da predição de ε para predição de v. Re-derive o passo reverso. Compare a qualidade final da amostra.
  3. Difícil. Adicione orientação livre de classificador (classifier-free guidance). Condicione em um rótulo de classe c ∈ {0, 1}, descarte-o em 10% do tempo durante o treinamento e, no momento da amostragem, use ε = (1+w)·ε_cond - w·ε_uncond. Meça a taxa de acerto do modo condicional em w = 0, 1, 3, 7.

Termos-Chave

Termo O que as pessoas dizem O que realmente significa
Processo direto "Adicionando ruído" Cadeia de Markov fixa `q(x_t
Processo reverso "Remoção de ruído" Cadeia aprendida `p_θ(x_{t-1}
Cronograma de β "A escada de ruído" Variância por passo; linear, cosseno ou sigmoide.
α̅ "Alfa barra" Produto acumulativo ∏(1 - β); fornece x_t em forma fechada a partir de x_0.
Perda simples "MSE no ruído" `
Predição de ε "Prever ruído" A saída é o ruído adicionado; DDPM padrão.
Predição de V "Prever velocidade" A saída é α·ε - σ·x; melhor condicionamento ao longo de t.
DDPM "O artigo" Ho et al. 2020; β linear, 1000 passos, U-Net.
DDIM "Amostrador determinístico" Amostrador não-Markoviano, 20-50 passos, mesmo objetivo de treinamento.
Orientação livre de classificador "CFG" Mistura predições de ruído condicionais e incondicionais para amplificar o condicionamento.

Nota de produção: a inferência de difusão é um problema de contagem de passos

O artigo do DDPM executa T=1000 passos reversos. Ninguém coloca isso em produção. Cada stack real de inferência escolhe uma de três estratégias — e cada uma delas se mapeia claramente na perspectiva de produção de "de onde vem a latência":

  1. Amostrador mais rápido, mesmo modelo. DDIM (20-50 passos), DPM-Solver++ (10-20), UniPC (8-16). Substituição direta (drop-in) do loop reverso; os pesos treinado de ε_θ permanecem intocados. Reduz a latência em 20-50×.
  2. Destilação. Treina um aluno (student) para corresponder ao professor (teacher) em menos passos: Destilação Progressiva (2 → 1), Modelos de Consistência (arbitrário → 1-4), LCM, SDXL-Turbo, SD3-Turbo. Reduz a latência em mais 5-10×, exige retreinamento.
  3. Caching e compilação. torch.compile(unet, mode="reduce-overhead"), backends de difusão do TensorRT-LLM, atenção xformers/SDPA, pesos bf16. Reduz a latência por passo em ~2×. Acumula com (1) e (2).

Para um servidor de difusão em produção, a discussão sobre orçamento é a mesma descrita na literatura de produção para LLMs: a latência é num_steps × step_cost + VAE_decode, a vazão (throughput) é batch_size × (num_steps × step_cost)^-1. O TTFT é pequeno (um passo); o equivalente a TPOT é o tempo total de resposta porque a geração de imagens ocorre "de uma só vez" do ponto de vista do usuário.

Leituras Adicionais

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