Stable Diffusion 3.5 e Flux: MMDiT e DiT por dentro
- ⬜🏗️ VAE + U-Net: a arquitetura por trás do Stable Diffusion(Diffusion Models & Geração Multimodal)
Recomendamos completar os pré-requisitos antes de seguir, mas nada te impede de continuar.
A virada: por que U-Net não escala mais
De 2020 a 2023 a U-Net foi a arquitetura padrão. Funcionou em SD 1.5 (859M), SD 2.x e até SDXL (2.6B). Mas a partir de ~3B de parâmetros, U-Net começou a apresentar diminishing returns: mais compute não melhorava FID na mesma taxa.
Em janeiro de 2023, Peebles & Xie (ICCV — "Scalable Diffusion Models with Transformers") publicaram o DiT (Diffusion Transformer). A tese era simples: substituir completamente U-Net por um transformer puro patchificando o latente, e a escala se comportaria como NLP.
Resultado central do paper: dobrar compute em DiT diminui FID quase linearmente em log-log até 675M params. O U-Net satura. A diferença prática vira óbvia em modelos > 3B — daí o uso em Sora, SD3, Flux, Stable Video.
adaLN-Zero: a injeção de condicionamento que DiT inventou
U-Net injeta timestep via FiLM em GroupNorm. DiT usa adaLN-Zero (Adaptive Layer Norm com pesos inicializados em zero):
class DiTBlock(nn.Module):
def __init__(self, dim, heads, mlp_ratio=4):
self.norm1 = nn.LayerNorm(dim, elementwise_affine=False)
self.attn = MultiHeadAttention(dim, heads)
self.norm2 = nn.LayerNorm(dim, elementwise_affine=False)
self.mlp = FeedForward(dim, mlp_ratio * dim)
# adaLN-Zero: 6 modulações (γ, β, α) × 2 (attn, mlp)
self.modulation = nn.Sequential(
nn.SiLU(),
nn.Linear(dim, 6 * dim, bias=True),
)
# CRÍTICO: pesos zerados na inicialização → bloco vira identidade no início
nn.init.zeros_(self.modulation[-1].weight)
nn.init.zeros_(self.modulation[-1].bias)
def forward(self, x, c): # c = t_emb + class/text_emb
shift1, scale1, gate1, shift2, scale2, gate2 = self.modulation(c).chunk(6, dim=-1)
# Attention com modulação
x = x + gate1.unsqueeze(1) * self.attn(modulate(self.norm1(x), shift1, scale1))
# MLP com modulação
x = x + gate2.unsqueeze(1) * self.mlp(modulate(self.norm2(x), shift2, scale2))
return x
def modulate(x, shift, scale):
return x * (1 + scale.unsqueeze(1)) + shift.unsqueeze(1)A escolha de inicializar com zero é deliberada — no início do treino, cada bloco vira identidade (não atrapalha), e gradativamente aprende a modular. Reduz instabilidade em modelos profundos. É hoje padrão em DiT, MMDiT, Sora.
MMDiT: a contribuição do SD3
Esser, Kulal, Lorenz et al. 2024 (paper técnico do Stable Diffusion 3) propuseram uma evolução: dois streams paralelos de tokens — um para imagem, outro para texto — que se misturam via joint attention em cada bloco.
| Aspecto | DiT (Peebles & Xie) | MMDiT (SD3) |
|---|---|---|
| Streams | Único (imagem) | Dois (imagem + texto) |
| Texto entra via | adaLN-Zero (vetor pooled) | Token sequence em joint attention |
| Capacity para texto | Limitada (vetor único) | Plena (tokens com pesos próprios) |
| Custo computacional | Menor | ~1.3-1.5× (tokens extras) |
| Qualidade prompt-image alignment | Boa | Melhor — especialmente prompts complexos |
Rectified Flow: o objetivo de treino
SD3 e Flux abandonaram o ε-prediction do DDPM em favor de Rectified Flow (Liu et al. 2022) / Flow Matching (Lipman et al. 2023). A intuição:
Em vez de pensar em "ruído sendo removido", pense em um fluxo que transporta uma amostra de ruído z₁ ~ N(0, I) até uma amostra de dado z₀ ~ p_data ao longo do tempo t ∈ [0, 1]. O caminho mais simples é a interpolação linear:
O vector field alvo ao longo dessa trajetória é simplesmente v(z_t, t) = z_1 − z_0 (derivada da interpolação). O modelo aprende:
Para amostrar, integra-se a ODE inversa partindo de z_1 ~ N(0, I):
def sample(model, n_steps=20):
z = torch.randn(1, 4, 64, 64) # z_1 ~ N(0, I)
timesteps = torch.linspace(1, 0, n_steps + 1)
for i in range(n_steps):
t = timesteps[i]; t_next = timesteps[i + 1]
v = model(z, t) # predição de velocidade
z = z + (t_next - t) * v # Euler step
return z # z ≈ z_0 (latente da imagem)Como a trajetória é quase reta, Euler simples com 20 passos basta. Para distillation extrema (Flux Schnell, SDXL Turbo) dá pra chegar em 1-4 passos com adversarial distillation por cima.
Os players principais hoje
| Modelo | Arquitetura | Params | Text encoder | Licença | Destaque |
|---|---|---|---|---|---|
| SD 1.5 | U-Net | 859M | CLIP-L | OpenRAIL | Padrão de fine-tune comunitário |
| SDXL | U-Net (maior) | 2.6B | CLIP-L + bigG | OpenRAIL | Última geração U-Net forte |
| SD 3 / 3.5 | MMDiT | 2B–8B | CLIP-L + CLIP-G + T5-XXL | Stability Community | Text rendering excelente |
| Flux.1 Pro | DiT (proprietário) | ~12B | CLIP-L + T5-XXL | API only | Topo de qualidade em 2024-25 |
| Flux.1 Dev | DiT | 12B | CLIP-L + T5-XXL | Non-commercial | Open weights, 20-50 steps |
| Flux.1 Schnell | DiT distilled | 12B | CLIP-L + T5-XXL | Apache 2.0 | 1-4 steps; livre comercial |
| Ideogram 2.0 | Proprietário | N/A | N/A | API | Best-in-class para texto em imagem |
| Imagen 3 (Google) | DiT-like + T5 | N/A | T5-XXL | API only | Topo em alinhamento textual |
Por que o ecossistema migrou para Flux
📋 Novembro 2024: você precisa escolher um modelo open weights para um produto comercial
Schnell tem qualidade quase igual a Dev, gera em 4 passos (latência baixa), e licença totalmente livre — único 'big' modelo open com licença permissiva em fim de 2024. SDXL ainda funciona mas qualidade visivelmente inferior. SD3 Medium tem licença Stability Community (gratuita só para receita ≤$1M).
Alt: SDXL
Alt: Qualidade inferior em 2024+; ecosystem ainda forte mas em declínio
Alt: Flux Dev
Alt: Licença non-commercial — só prototipagem
Alt: SD 3.5 Large
Alt: Boa licença mas qualidade ligeiramente abaixo de Flux Pro/Dev em geral
Por que operar no espaço latente em vez de nos pixels?
Timeline da transição U-Net → DiT
Patchify e position embedding
Detalhe importante: o latente 64×64×4 vira sequência de tokens via patchify. Patches 2×2 produzem 32×32 = 1024 tokens de dim 16 (=2·2·4). DiT-XL/2 usa patches 2×2 (n_patches=256 num latente 32×32). SD3 usa esquema similar mas com patches menores em alta resolução.
Position embeddings: DiT original usa sinusoidal 2D (mesma do ViT). MMDiT e Flux usam RoPE 2D (Rotary Position Embedding extendido para grade), seguindo Su et al. 2021. RoPE é mais bem-comportado para extrapolação para resoluções maiores que o treino.
RoPE multiplica Q e K por uma matriz de rotação dependente de posição — preserva produto interno relativo. Permite a Flux gerar em 1024×1024 mesmo se treinado majoritariamente em resoluções menores, sem retreinar position embeddings.
Perguntas que sobram
❓ Por que SD3 demorou tanto para sair depois do SDXL?
❓ Posso fazer fine-tune em Flux Dev como faço em SDXL?
❓ DiT funciona para vídeo igual para imagem?
❓ MMDiT é compatível com ControlNet?
Papers que você precisa ler
Próximo: como controlar de verdade essas máquinas. ControlNet (Zhang & Agrawala 2023, ICCV best paper) adiciona condicionamento espacial preciso — canny, depth, pose, scribble. Vamos ver o truque do zero-conv e por que ele se mantém o estado da arte.
Perguntas frequentes
❓ O que os modelos recentes de imagem melhoraram?
❓ Modelo aberto ou fechado para geração de imagem?
❓ Vale rodar geração de imagem em GPU própria?
Fixando
Qual é o papel do codificador de texto na geração condicionada?
Qual limitação persiste mesmo nos modelos mais recentes?
Terminou de ler?
Marcar como concluído registra o XP, mantém sua sequência e coloca 3 cartas deste módulo na fila de revisão espaçada.
Próximos passos sugeridos
Temas deste módulo
Discussão
Carregando comentários…