| name | diffusion-models |
| description | Guide complet des modèles de diffusion — DDPM, score matching, DDIM, Stable Diffusion, FLUX, Latent Diffusion, flow matching, implémentations. En français. |
Modèles de Diffusion — Guide Complet
DDPM, score matching, modèles latents, génération d'images, vidéo et audio.
1. Les Modèles Génératifs en 2025
2. DDPM — Denoising Diffusion Probabilistic Models (Ho et al., 2020)
Forward Process (Diffusion)
def forward_diffusion(x_0, t, alpha_bar):
"""Ajoute du bruit jusqu'au timestep t.
x_0: (B, C, H, W) — image originale normalisée [-1, 1]
"""
noise = torch.randn_like(x_0)
noisy = torch.sqrt(alpha_bar[t]) * x_0 + torch.sqrt(1 - alpha_bar[t]) * noise
return noisy, noise
Reverse Process (Denoising)
Implémentation DDPM Complète
import torch
import torch.nn as nn
import math
def cosine_beta_schedule(timesteps, s=0.008):
"""Scheduler beta du bruit (cosine, plus stable que linear)."""
steps = timesteps + 1
x = torch.linspace(0, timesteps, steps)
alphas_cumprod = torch.cos(((x / timesteps) + s) / (1 + s) * math.pi * 0.5) ** 2
alphas_cumprod = alphas_cumprod / alphas_cumprod[0]
betas = 1 - (alphas_cumprod[1:] / alphas_cumprod[:-1])
return torch.clamp(betas, 0, 0.999)
class SinusoidalTimeEmbedding(nn.Module):
"""Positional encoding pour le timestep (comme les Transformers)."""
def __init__(self, dim):
super().__init__()
self.dim = dim
def forward(self, t):
half_dim = self.dim // 2
emb = math.log(10000) / (half_dim - 1)
emb = torch.exp(torch.arange(half_dim, device=t.device) * -emb)
emb = t[:, None] * emb[None, :]
return torch.cat([emb.sin(), emb.cos()], dim=-1)
class UNetBlock(nn.Module):
"""Block de base U-Net avec attention."""
def ():
().__init__()
.norm1 = nn.GroupNorm(, in_ch)
.conv1 = nn.Conv2d(in_ch, out_ch, , padding=)
.time_mlp = nn.Linear(time_dim, out_ch)
.norm2 = nn.GroupNorm(, out_ch)
.conv2 = nn.Conv2d(out_ch, out_ch, , padding=)
.attn = nn.MultiheadAttention(out_ch, , batch_first=) has_attn
.residual = nn.Conv2d(in_ch, out_ch, ) in_ch != out_ch nn.Identity()
():
h = .conv1(F.silu(.norm1(x)))
h = h + .time_mlp(F.silu(t))[:, :, , ]
h = .conv2(F.silu(.norm2(h)))
.attn :
B, C, H, W = h.shape
h_flat = h.view(B, C, H * W).transpose(, )
h_attn, _ = .attn(h_flat, h_flat, h_flat)
h = h + h_attn.transpose(, ).view(B, C, H, W)
h + .residual(x)
(nn.Module):
():
().__init__()
.time_mlp = nn.Sequential(
SinusoidalTimeEmbedding(time_dim),
nn.Linear(time_dim, time_dim * ),
nn.SiLU(),
nn.Linear(time_dim * , time_dim),
)
.inc = UNetBlock(img_channels, base_channels, time_dim)
.down1 = UNetBlock(base_channels, base_channels * , time_dim)
.down2 = UNetBlock(base_channels * , base_channels * , time_dim, has_attn=)
.down3 = UNetBlock(base_channels * , base_channels * , time_dim)
.bot = UNetBlock(base_channels * , base_channels * , time_dim, has_attn=)
.up3 = UNetBlock(base_channels * , base_channels * , time_dim)
.up2 = UNetBlock(base_channels * , base_channels, time_dim, has_attn=)
.up1 = UNetBlock(base_channels * , base_channels, time_dim)
.outc = nn.Conv2d(base_channels, img_channels, , padding=)
():
t = .time_mlp(t)
x1 = .inc(x, t)
x2 = .down1(F.avg_pool2d(x1, ), t)
x3 = .down2(F.avg_pool2d(x2, ), t)
x4 = .down3(F.avg_pool2d(x3, ), t)
x4 = .bot(x4, t)
x = F.interpolate(x4, scale_factor=)
x = .up3(torch.cat([x, x3], dim=), t)
x = F.interpolate(x, scale_factor=)
x = .up2(torch.cat([x, x2], dim=), t)
x = F.interpolate(x, scale_factor=)
x = .up1(torch.cat([x, x1], dim=), t)
.outc(x)
(nn.Module):
():
().__init__()
.model = model
.T = timesteps
betas = cosine_beta_schedule(timesteps)
.register_buffer(, betas)
.register_buffer(, - betas)
.register_buffer(, torch.cumprod(.alphas, dim=))
():
t = torch.randint(, .T, (x_0.size(),), device=x_0.device)
noise = torch.randn_like(x_0)
x_t = torch.sqrt(.alpha_bars[t, , , ]) * x_0 \
+ torch.sqrt( - .alpha_bars[t, , , ]) * noise
pred_noise = .model(x_t, t)
F.mse_loss(pred_noise, noise)
():
x = torch.randn(batch_size, channels, img_size, img_size, device=device)
t ((.T)):
t_tensor = torch.full((batch_size,), t, device=device)
pred_noise = .model(x, t_tensor)
alpha = .alphas[t]
alpha_bar = .alpha_bars[t]
coef1 = / torch.sqrt(alpha)
coef2 = ( - alpha) / torch.sqrt( - alpha_bar)
x_mean = coef1 * (x - coef2 * pred_noise)
t > :
noise = torch.randn_like(x)
sigma = torch.sqrt(( - alpha_bar / .alpha_bars[t-])
* ( - alpha) / ( - alpha_bar))
x = x_mean + sigma * noise
:
x = x_mean
torch.clamp(x, -, )
3. DDIM — Denoising Diffusion Implicit Models (Song et al., 2021)
@torch.no_grad()
def sample_ddim(model, batch_size, img_size, channels, device, ddim_steps=50):
"""Échantillonnage DDIM (50x plus rapide que DDPM)."""
T = 1000
step_ratio = T // ddim_steps
times = torch.linspace(0, T - 1, ddim_steps, dtype=torch.long)
x = torch.randn(batch_size, channels, img_size, img_size, device=device)
for i, t in enumerate(reversed(times)):
t_tensor = torch.full((batch_size,), t, device=device)
alpha_bar = model.alpha_bars[t]
alpha_bar_prev = model.alpha_bars[t - step_ratio] if i < ddim_steps - 1 else torch.tensor(1.0)
pred_noise = model.model(x, t_tensor)
x0_pred = (x - torch.sqrt(1 - alpha_bar) * pred_noise) / torch.sqrt(alpha_bar)
sigma_t = 0
noise = torch.randn_like(x) if sigma_t > 0 else 0
x = torch.sqrt(alpha_bar_prev) * x0_pred + \
torch.sqrt(1 - alpha_bar_prev - sigma_t**2) * pred_noise + \
sigma_t * noise
return torch.clamp(x, -1, 1)
4. Score Matching & SDE (Song et al., 2021)
Score-Based Generative Models
Variance Exploding / Preserving SDE
5. Latent Diffusion (Rombach et al., 2022)
Architecture Stable Diffusion
Texte → CLIP Text Encoder → Text Embeddings
│
▼
Bruit z_T ──→ U-Net (denoise) ──→ z_0 ──→ VAE Decoder ──→ Image
↑ ↑ (768×768)
│ │
└────── t ─────┘
Timestep embedding
+ Cross-attention texte
class LatentDiffusion(nn.Module):
"""Stable Diffusion : diffusion dans l'espace latent VAE.
Pourquoi dans le latent ?
- VAE encode 256×256×3 → 32×32×4 (compressé ×192)
- Diffusion sur 32×32 au lieu de 256×256
- ~4x plus rapide, moins de mémoire
- Qualité préservée (perceptual loss + GAN)
"""
def __init__(self, vae, unet, text_encoder):
super().__init__()
self.vae = vae
self.unet = unet
self.text_encoder = text_encoder
def encode(self, images):
"""Compresse l'image dans l'espace latent."""
with torch.no_grad():
return self.vae.encode(images).mode()
def decode(self, latents):
"""Décompresse le latent en image."""
with torch.no_grad():
return self.vae.decode(latents)
def train_step(self, images, captions):
"""Entraîne le U-Net dans l'espace latent."""
latents = self.encode(images)
text_embeddings = self.text_encoder(captions)
noise = torch.randn_like(latents)
t = torch.randint(0, .T, (latents.size(),))
noisy = sqrt_alpha_bar[t] * latents + sqrt( - alpha_bar[t]) * noise
pred = .unet(noisy, t, text_embeddings)
F.mse_loss(pred, noise)
Conditionnement par texte
class CrossAttnUNet(nn.Module):
"""U-Net avec cross-attention pour le conditionnement texte."""
def __init__(self, d_model=320, text_dim=768, n_heads=8):
self.cross_attn = nn.MultiheadAttention(d_model, n_heads,
kdim=text_dim, vdim=text_dim,
batch_first=True)
def forward(self, x, t, text_emb):
B, C, H, W = x.shape
x_flat = x.flatten(2).transpose(1, 2)
x_attn, _ = self.cross_attn(x_flat, text_emb, text_emb)
x = x + x_attn.transpose(1, 2).reshape(B, C, H, W)
return x
6. FLUX (Black Forest Labs, 2024)
7. Flow Matching (Lipman et al., 2023)
8. Guidance (Classifier-Free Guidance)
def cfg_sample(unet, latent, t, text_emb, uncond_emb, guidance_scale=7.5):
with torch.no_grad():
noise_uncond = unet(latent, t, uncond_emb)
noise_cond = unet(latent, t, text_emb)
return noise_uncond + guidance_scale * (noise_cond - noise_uncond)
9. Applications Multimodales (2024-2025)
| Domaine | Modèle | Architecture |
|---|
| Image | SD 3.5 | MMDiT (DiT + Text) |
| Image | FLUX | Flow Matching + T5 |
| Image | DALL-E 3 | Pixel diffusion |
| Vidéo | Sora | DiT spacetime |
| Vidéo | Stable Video Diff | Latent video |
| Audio | AudioLDM 2 | Latent audio |
| Music | MusicGen | Diffusion + EnCodec |
| 3D | Point-E | Diffusion points |
| 3D | DreamFusion | 2D→3D (SDS loss) |
10. Implémentation Complète (Petite échelle)
def train_ddpm():
model = SimpleUNet(img_channels=1, base_channels=64)
ddpm = DDPM(model, timesteps=200)
optimizer = torch.optim.Adam(ddpm.parameters(), lr=2e-4)
dataset = torchvision.datasets.MNIST(root='./data', transform=transforms.ToTensor())
for epoch in range(100):
for x, _ in dataset:
x = x * 2 - 1
loss = ddpm(x)
optimizer.zero_grad()
loss.backward()
optimizer.step()
samples = ddpm.sample(batch_size=16, img_size=28, channels=1)
Références