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
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:
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. Emt=0, a rede só precisa limpar alguns pixels. Emtintermediários, o problema é difícil, mas a rede tem muitos gradientes fluindo pelos mesmos pesos a partir de cada nível de ruído.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.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
tbruto 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 · ε_uncondcomw ≈ 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
- 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? - Médio. Mude da predição de ε para predição de v. Re-derive o passo reverso. Compare a qualidade final da amostra.
- 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 emw = 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":
- 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×. - 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.
- Caching e compilação.
torch.compile(unet, mode="reduce-overhead"), backends de difusão do TensorRT-LLM, atençãoxformers/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
- Sohl-Dickstein et al. (2015). Deep Unsupervised Learning using Nonequilibrium Thermodynamics — o artigo sobre difusão, à frente do seu tempo.
- Ho, Jain, Abbeel (2020). Denoising Diffusion Probabilistic Models — DDPM.
- Song, Meng, Ermon (2021). Denoising Diffusion Implicit Models — DDIM, menos passos.
- Nichol & Dhariwal (2021). Improved DDPM — cronograma de cosseno, variância aprendida.
- Dhariwal & Nichol (2021). Diffusion Models Beat GANs on Image Synthesis — orientação com classificador (classifier guidance).
- Ho & Salimans (2022). Classifier-Free Diffusion Guidance — CFG.
- Karras et al. (2022). Elucidating the Design Space of Diffusion-Based Generative Models (EDM) — notação unificada, a receita mais limpa.