Module 9 — Coût de calcul et optimisation mémoire
Les modules précédents supposaient tacitement une carte suffisamment grosse pour tout accueillir. La réalité est moins confortable : un pipeline complet — SDXL avec ControlNet et deux LoRA — dépasse facilement les 16 Go de mémoire graphique d'une carte grand public de milieu de gamme. Ce module est celui des compromis pratiques qui permettent quand même de générer.
D'où vient la consommation mémoire
Trois postes se cumulent, dans un ordre qui n'est pas intuitif.
Les poids du modèle eux-mêmes, chargés une fois pour toutes. SDXL base pèse 6,9 Go en float32, 3,4 Go en float16. Un ControlNet SDXL ajoute 2,5 Go en float16. Un LoRA de rang 32 : environ 200 Mo. C'est le poste le plus visible mais pas le plus gros à l'exécution.
Les activations intermédiaires du U-Net, qui varient avec la résolution. À 1024 sur 1024, elles occupent la majorité de la mémoire — parfois 6 à 8 Go en pointe. Doubler la résolution quadruple ces activations : à 2048, on tient rarement sur moins de 24 Go sans découpage.
Le cache d'attention dans les blocs transformeurs du U-Net. C'est la matrice d'attention dont la taille croît en carré de la longueur de séquence — ici, en carré du nombre de patches latents. À 1024 pour SDXL, c'est déjà un poste sensible.
Une règle qui guide bien les décisions : le pic de mémoire, ce n'est pas au chargement du modèle, c'est pendant l'étape de débruitage, sur un lot d'activations et de matrices d'attention temporaires.
Demi-précision : la première ligne de défense
Passer les poids et les activations de float32 (4 octets) à float16 (2 octets) divise par deux la consommation, avec une perte de qualité perceptuelle nulle sur SDXL. Aucune raison de s'en priver — c'est le défaut recommandé.
pipeline = StableDiffusionXLPipeline.from_pretrained(
"stabilityai/stable-diffusion-xl-base-1.0",
torch_dtype=torch.float16,
variant="fp16",
).to("cuda")
Sur les cartes récentes (RTX série 40, A100), bfloat16 est un choix équivalent : même taille, plage dynamique plus large que float16, moins de risques d'underflow. Sur du matériel plus ancien, float16 reste le défaut.
float8 existe dans les derniers pipelines de recherche mais dégrade visiblement la qualité sur la diffusion — pas encore un choix opérationnel en 2026.
Découpage de l'attention
L'attention croît en carré : c'est là que se cache la grosse économie. Trois mécanismes réduisent son empreinte sans changer le résultat.
Le découpage de l'attention (enable_attention_slicing) calcule l'attention par tranches successives au lieu de matérialiser toute la matrice d'un coup. Économie typique de 30 à 40 % sur le pic, coût en temps de 5 à 10 %. Presque toujours rentable sur les cartes en dessous de 12 Go.
xFormers propose un noyau d'attention optimisé qui économise de la mémoire et gagne du temps. Sur les cartes récentes, il est aujourd'hui superseded par l'attention native de PyTorch 2 (torch.nn.functional.scaled_dot_product_attention), qui utilise automatiquement Flash Attention quand c'est possible. À vérifier une fois, laisser tourner ensuite.
Le VAE en carreaux (enable_vae_tiling) découpe l'image en morceaux au moment de l'encodage et du décodage par le VAE. Utile pour les très hautes résolutions (2048 et plus), inutile en dessous.
pipeline.enable_attention_slicing() # meme pipeline, moins de memoire
pipeline.enable_vae_tiling() # utile a partir de 2048
Déchargement CPU : la solution du dernier recours
Quand la carte est vraiment trop petite (6 à 8 Go), on peut faire circuler les composants entre CPU et GPU au fur et à mesure de leur besoin.
Deux niveaux existent. Le déchargement séquentiel (enable_sequential_cpu_offload) déplace chaque module vers le GPU juste avant son utilisation, puis le renvoie sur CPU. Économie maximale — on peut faire tourner SDXL sur 4 Go — mais le prix est lourd : chaque étape attend le transfert PCIe, et le temps par image peut être multiplié par cinq.
Le déchargement par modèle (enable_model_cpu_offload) est plus modéré : il déplace un composant entier (encodeur de texte, U-Net, VAE) sur GPU pendant sa phase et le retire ensuite. Économie moindre, mais surcoût temps beaucoup plus supportable (environ 20 %). C'est le défaut recommandé pour les cartes de 6 à 8 Go.
# A choisir selon la memoire disponible, pas les deux.
pipeline.enable_model_cpu_offload() # 6-8 Go, surcout modere
# pipeline.enable_sequential_cpu_offload() # 4 Go, surcout eleve
Un tableau qui décide à votre place
| Carte | Mémoire | Configuration recommandée | Temps SDXL par image (30 étapes) |
|---|---|---|---|
| RTX 4090 | 24 Go | float16, tout branché | ~5 s |
| RTX 4070 Ti | 12 Go | float16 + découpage attention | ~10 s |
| RTX 3060 | 12 Go | float16 + découpage attention | ~18 s |
| RTX 2060 | 6 Go | float16 + déchargement modèle | ~45 s |
| GTX 1660 | 6 Go | float16 + déchargement modèle + SD 1.5 | ~40 s en SD 1.5 |
| CPU seul | — | SD 1.5, float32, 20 étapes maximum | 5 à 10 min par image |
Deux règles à extraire du tableau. En dessous de 8 Go, préférer SD 1.5 à SDXL : la différence de résolution native ne compense pas les acrobaties mémoire. Le temps par image dépend d'abord de la carte, pas des optimisations : passer du float32 au float16 gagne un facteur deux, mais rien ne rattrape un facteur 10 entre une RTX 2060 et une RTX 4090.
Le lot inversé : générer plusieurs images à la fois
Contre-intuitivement, générer 4 images ensemble (batch_size=4) est souvent plus rapide par image que 4 générations successives — les transferts et les passes d'encodage de texte sont mutualisés. À condition, bien sûr, que la mémoire disponible permette de tenir 4 pipelines en parallèle. C'est un compromis à mesurer selon votre carte : sur RTX 4090, un lot de 4 tient et gagne 30 % ; sur RTX 3060, un lot de 2 est déjà optimal.
Le nombre d'étapes revisité
Le module 3 posait 25 à 30 étapes comme référence pour DPM++ 2M Karras. Quand chaque seconde compte — production en série, génération dans une boucle d'application — descendre à 20 étapes économise 25 % de temps pour une perte de qualité perceptuelle faible. En dessous, les artefacts deviennent visibles ; au-dessus, on paie sans gagner.
Les modèles distillés (SDXL Turbo, SDXL Lightning, SD 1.5 LCM) descendent à 4 à 8 étapes avec une perte de qualité mesurable mais souvent acceptable. Pour un usage type maquette rapide, ils sont imbattables : une carte grand public génère plusieurs images par seconde.
torch.compile : la promesse et la réalitétorch.compile() sur le U-Net peut accélérer l'inférence de 20 à 40 % après une compilation initiale de 30 à 90 secondes. Utile si vous générez des milliers d'images sous la même forme d'entrée ; contre-productif pour quelques images ponctuelles. À l'inverse, tout changement de résolution ou de type de contrôle relance la compilation, ce qui explique pourquoi les scripts qui la mesurent mal concluent souvent à un ralentissement.
En résumé
- La mémoire pic vient des activations et de l'attention du U-Net, pas du chargement des poids ;
float16est un gain gratuit qui divise par deux. - Le découpage de l'attention et le VAE en carreaux économisent la mémoire quand elle manque, avec un surcoût de temps modéré.
- Le déchargement CPU rend
SDXLtenable sur 6 à 8 Go au prix d'un ralentissement notable ; en dessous, préférerSD 1.5. - Le temps par image dépend d'abord de la carte, ensuite du nombre d'étapes ; les modèles distillés (
Turbo,LCM) descendent à 4 à 8 étapes pour l'itération rapide.
Module suivant : droits, provenance et usages acceptables — la dimension juridique et éthique qui décide si tout ce cours peut servir à un client réel.