<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>GPU on kenji.blog</title><link>http://kenji.blog/fr/tags/gpu/</link><description>Recent content in GPU on kenji.blog</description><generator>Hugo -- gohugo.io</generator><language>fr</language><copyright>kenjinote</copyright><lastBuildDate>Fri, 11 Sep 2026 01:00:00 +0900</lastBuildDate><atom:link href="http://kenji.blog/fr/tags/gpu/index.xml" rel="self" type="application/rss+xml"/><item><title>Techniques pour résoudre le manque de mémoire GPU dans le développement de l'IA (déchargement CPU, etc.)</title><link>http://kenji.blog/fr/p/ai-gpu-vram-optimization-cpu-offloading/</link><pubDate>Fri, 11 Sep 2026 01:00:00 +0900</pubDate><guid>http://kenji.blog/fr/p/ai-gpu-vram-optimization-cpu-offloading/</guid><description>&lt;img src="http://kenji.blog/p/ai-gpu-vram-optimization-cpu-offloading/img/eyecatch.jpg" alt="Featured image of post Techniques pour résoudre le manque de mémoire GPU dans le développement de l'IA (déchargement CPU, etc.)" />&lt;h1 id="introduction--le-développement-de-lia-et-le-mur-de-la-vram">Introduction : Le développement de l&amp;rsquo;IA et le &amp;ldquo;Mur de la VRAM&amp;rdquo;
&lt;/h1>&lt;p>Ces dernières années, les technologies d&amp;rsquo;IA générative telles que les grands modèles de langage (LLM) et les modèles de diffusion (Diffusion Models) ont connu un développement rapide. Cependant, lors de l&amp;rsquo;apprentissage (fine-tuning) ou de l&amp;rsquo;exécution de l&amp;rsquo;inférence (Inference) de ces modèles d&amp;rsquo;IA de pointe dans un environnement local, de nombreux développeurs et chercheurs sont confrontés à un obstacle extrêmement physique : &lt;strong>le &amp;ldquo;manque de mémoire GPU (VRAM)&amp;rdquo;&lt;/strong>.&lt;/p>
&lt;p>Même avec un GPU haut de gamme grand public comme le NVIDIA GeForce RTX 4090, la VRAM maximale est de 24 Go, ce qui rend impossible le chargement d&amp;rsquo;un modèle gigantesque comme Llama 3 70B tel quel. Les GPU destinés aux centres de données tels que le H100 (80 Go) ou le B200 (192 Go) sont très coûteux et ne sont pas facilement accessibles aux individus ou aux petites équipes. Si l&amp;rsquo;on ne parvient pas à franchir ce &amp;ldquo;Mur de la VRAM (The Wall of VRAM)&amp;rdquo;, il est même impossible de toucher aux modèles de pointe.&lt;/p>
&lt;p>Cet article explique en profondeur les techniques avancées, tant du côté de l&amp;rsquo;inférence que de l&amp;rsquo;apprentissage, pour surmonter cette contrainte physique de limitation de VRAM grâce à l&amp;rsquo;ingéniosité de l&amp;rsquo;architecture matérielle et logicielle. Nous explorerons en détail le déchargement CPU (CPU Offloading), l&amp;rsquo;optimisation du cache KV, les points de contrôle de gradient (Gradient Checkpointing) et la toute dernière architecture de mémoire unifiée (Unified Memory), le tout illustré par des formules mathématiques et des diagrammes. La lecture de cet article vous permettra de comprendre en profondeur le comportement de la VRAM et d&amp;rsquo;acquérir des connaissances pratiques pour manipuler des modèles gigantesques avec des ressources limitées.&lt;/p>
&lt;hr>
&lt;h1 id="1-anatomie-de-la-consommation-de-vram-des-modèles-dia-inférence-et-apprentissage">1. Anatomie de la consommation de VRAM des modèles d&amp;rsquo;IA (Inférence et Apprentissage)
&lt;/h1>&lt;p>La première étape pour résoudre le manque de VRAM consiste à comprendre précisément &amp;ldquo;ce qui&amp;rdquo; consomme la mémoire et &amp;ldquo;dans quelle proportion&amp;rdquo;, d&amp;rsquo;un point de vue microscopique. Plutôt que de la traiter comme une boîte noire, si nous pouvons l&amp;rsquo;estimer avec précision à l&amp;rsquo;aide de formules mathématiques, nous pourrons choisir la méthode d&amp;rsquo;optimisation appropriée.&lt;/p>
&lt;h2 id="11-calcul-de-la-mémoire-des-paramètres-du-modèle-poids">1.1 Calcul de la mémoire des paramètres du modèle (Poids)
&lt;/h2>&lt;p>La quantité de mémoire de base consommée par les paramètres (Weights) qui composent un modèle d&amp;rsquo;IA est déterminée par le nombre total de paramètres du modèle et le type de données (Precision : précision) utilisé pour les représenter.&lt;/p>
&lt;p>Les types de données couramment utilisés en Deep Learning et le nombre d&amp;rsquo;octets par paramètre ($B$) sont les suivants :&lt;/p>
&lt;ul>
&lt;li>&lt;strong>FP32 (Nombre à virgule flottante simple précision) :&lt;/strong> 4 octets (précision standard lors de l&amp;rsquo;apprentissage)&lt;/li>
&lt;li>&lt;strong>FP16 / BF16 (Nombre à virgule flottante demi-précision) :&lt;/strong> 2 octets (inférence générale et apprentissage en précision mixte)&lt;/li>
&lt;li>&lt;strong>INT8 (Entier 8 bits) :&lt;/strong> 1 octet (modèles quantifiés)&lt;/li>
&lt;li>&lt;strong>INT4 (Quantification entière 4 bits) :&lt;/strong> 0,5 octet (quantification extrême comme GPTQ, AWQ, GGUF)&lt;/li>
&lt;/ul>
&lt;p>Si le nombre total de paramètres du modèle est $P$, la quantité de mémoire de base occupée par les poids eux-mêmes $M_{weights}$ est exprimée par la formule suivante :&lt;/p>
$$ M_{weights} = P \times B $$&lt;p>Par exemple, si l&amp;rsquo;on charge le modèle &amp;ldquo;Llama 3 8B&amp;rdquo; publié par Meta (environ 8 milliards de paramètres) en FP16 (demi-précision), le calcul est le suivant :&lt;/p>
$$ M_{weights} = 8,000,000,000 \times 2 \text{ octets} \approx 16,000,000,000 \text{ octets} \approx 16 \text{ Go} $$&lt;p>En d&amp;rsquo;autres termes, le simple chargement des poids du modèle dans le GPU consomme 16 Go de VRAM. Avec une RTX 3060 (12 Go), une erreur &amp;ldquo;Out of Memory&amp;rdquo; (OOM) se produirait à ce stade. Cependant, si le modèle est quantifié en INT4, cela devient $8 \times 0.5 = 4 \text{ Go}$, ce qui permet de le charger avec de la marge.&lt;/p>
&lt;h2 id="12-consommation-de-mémoire-lors-de-linférence--laugmentation-du-cache-kv">1.2 Consommation de mémoire lors de l&amp;rsquo;inférence : l&amp;rsquo;augmentation du cache KV
&lt;/h2>&lt;p>Lors de l&amp;rsquo;inférence des LLM (en particulier la génération de texte autorégressive), le &lt;strong>cache KV (Key-Value Cache)&lt;/strong> exerce une pression sur la VRAM aussi forte, voire plus forte, que les poids eux-mêmes.
Dans l&amp;rsquo;architecture Transformer, afin d&amp;rsquo;éviter de recalculer les informations des jetons (tokens) générés et traités précédemment, les tenseurs Key et Value de chaque couche d&amp;rsquo;attention continuent d&amp;rsquo;être mis en cache dans la VRAM. Cela améliore la vitesse de calcul (Compute), mais à mesure que la longueur du contexte (longueur du prompt d&amp;rsquo;entrée + longueur générée) augmente, la consommation de mémoire augmente de manière linéaire et explosive.&lt;/p>
&lt;p>La quantité de mémoire du cache KV consommée lors du traitement d&amp;rsquo;un jeton $M_{kv\_token}$ est calculée de manière stricte par la formule suivante en fonction de l&amp;rsquo;architecture du modèle :&lt;/p>
$$ M_{kv\_token} = 2 \times N_{layers} \times N_{heads\_kv} \times D_{head} \times B $$&lt;p>Ici, chaque variable a la signification suivante :&lt;/p>
&lt;ul>
&lt;li>$2$ : Car il existe deux tenseurs, Key et Value&lt;/li>
&lt;li>$N_{layers}$ : Nombre de couches (layers) du Transformer&lt;/li>
&lt;li>$N_{heads\_kv}$ : Nombre de têtes d&amp;rsquo;attention KV (Dans le cas du GQA : Grouped Query Attention, ce nombre est inférieur au nombre de têtes normales)&lt;/li>
&lt;li>$D_{head}$ : Nombre de dimensions de chaque tête (généralement, le nombre de dimensions de la couche cachée $D_{model} / N_{heads}$)&lt;/li>
&lt;li>$B$ : Nombre d&amp;rsquo;octets du type de données (2 pour FP16)&lt;/li>
&lt;/ul>
&lt;p>La taille totale du cache KV $M_{kv\_total}$ est obtenue en multipliant cela par la longueur de la séquence ($L_{seq}$) et la taille du lot ($BatchSize$).&lt;/p>
$$ M_{kv\_total} = M_{kv\_token} \times L_{seq} \times BatchSize $$&lt;p>&lt;strong>Exemple concret : Cas de Llama 2 7B&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>$N_{layers} = 32$&lt;/li>
&lt;li>$N_{heads\_kv} = 32$ (Dans le cas du MHA)&lt;/li>
&lt;li>$D_{head} = 128$&lt;/li>
&lt;li>FP16 ($B=2$)&lt;/li>
&lt;li>Taille du lot 1, longueur de la séquence 8192 (contexte 8K)&lt;/li>
&lt;/ul>
$$ M_{kv\_total} = 2 \times 32 \times 32 \times 128 \times 2 \times 8192 \times 1 = 4,294,967,296 \text{ octets} \approx 4 \text{ Go} $$&lt;p>Si l&amp;rsquo;on allongeait le contexte à 32K (32768 jetons), le cache KV à lui seul consommerait environ 16 Go. Si l&amp;rsquo;on augmente la taille du lot à 4, cela fait 64 Go. C&amp;rsquo;est l&amp;rsquo;un des défis majeurs de l&amp;rsquo;inférence : l&amp;rsquo;exigence d&amp;rsquo;une VRAM bien plus gigantesque que la taille du modèle lui-même.&lt;/p>
&lt;h2 id="13-consommation-de-mémoire-lors-de-lapprentissage--optimiseur-gradients-et-activations">1.3 Consommation de mémoire lors de l&amp;rsquo;apprentissage : Optimiseur, Gradients et Activations
&lt;/h2>&lt;p>Comparé à l&amp;rsquo;inférence, l&amp;rsquo;apprentissage d&amp;rsquo;un modèle (pré-entraînement ou fine-tuning) consomme beaucoup plus de VRAM. Cela est dû au fait qu&amp;rsquo;il est nécessaire de conserver des informations non seulement pour une simple propagation avant (forward pass), mais aussi pour la rétropropagation (backpropagation). La mémoire d&amp;rsquo;apprentissage est principalement composée des 4 éléments suivants :&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Poids du modèle (Model Weights) :&lt;/strong> Similaire à l&amp;rsquo;inférence, mais dans l&amp;rsquo;apprentissage en précision mixte, on peut conserver à la fois le FP16 et le FP32 (Master Weights).&lt;/li>
&lt;li>&lt;strong>Gradients (Gradients) :&lt;/strong> Les gradients pour chaque paramètre calculés lors de la rétropropagation. 2 octets par paramètre dans le cas du FP16.&lt;/li>
&lt;li>&lt;strong>États de l&amp;rsquo;optimiseur (Optimizer States) :&lt;/strong> Les optimiseurs avancés comme AdamW maintiennent le premier moment (Momentum) et le deuxième moment (Variance) pour chaque paramètre. Pour maintenir la stabilité de l&amp;rsquo;apprentissage, ceux-ci sont généralement conservés en FP32 (4 octets). Cela signifie que les deux moments consomment $4 + 4 = 8$ octets par paramètre.&lt;/li>
&lt;li>&lt;strong>Activations (Activations) :&lt;/strong> Afin de calculer les gradients lors de la rétropropagation, la sortie de chaque couche (état intermédiaire) lors de la propagation avant doit être conservée en mémoire. Cela dépend fortement de la taille du lot et de la longueur de la séquence, et devient très volumineux.&lt;/li>
&lt;/ol>
&lt;p>En résumé, dans un apprentissage en précision mixte (Mixed Precision Training) utilisant l&amp;rsquo;optimiseur Adam standard, &lt;strong>environ 16 à 20 octets&lt;/strong> (Poids principal 4 + Poids FP16 2 + Gradient 2 + Optimiseur 8 + α) de mémoire sont requis par paramètre.&lt;/p>
$$ M_{train\_param} \approx P \times 16 \text{ octets} $$&lt;p>Pour l&amp;rsquo;apprentissage d&amp;rsquo;un modèle de 7B (7 milliards de paramètres), la partie liée aux paramètres consomme à elle seule $7B \times 16 = 112 \text{ Go}$. Avec les activations ajoutées à cela, le calcul montre qu&amp;rsquo;il faudrait plus de 140 Go de VRAM. Pour exécuter cela avec 24 Go de VRAM, les techniques d&amp;rsquo;optimisation radicales expliquées dans les chapitres suivants sont indispensables.&lt;/p>
&lt;hr>
&lt;h1 id="2-techniques-déconomie-de-vram-lors-de-linférence">2. Techniques d&amp;rsquo;économie de VRAM lors de l&amp;rsquo;inférence
&lt;/h1>&lt;p>Pour faire fonctionner de gigantesques modèles lors de l&amp;rsquo;inférence, de nombreuses technologies logicielles franchissant les limites matérielles ont été développées.&lt;/p>
&lt;h2 id="21-déchargement-cpu-cpu-offloading-et-division-des-couches">2.1 Déchargement CPU (CPU Offloading) et division des couches
&lt;/h2>&lt;p>Lorsqu&amp;rsquo;un modèle trop volumineux ne tient pas dans un ou plusieurs GPU, la méthode consistant à placer une partie du modèle dans la mémoire système (RAM CPU) et à la transférer vers le GPU pour effectuer les calculs uniquement lorsque cela est nécessaire s&amp;rsquo;appelle le &lt;strong>déchargement CPU (CPU Offloading)&lt;/strong>. &lt;code>llama.cpp&lt;/code> et &lt;code>Accelerate&lt;/code> de Hugging Face prennent en charge cette fonctionnalité.&lt;/p>
&lt;pre class="mermaid">
graph TD
A[&amp;#34;RAM Système (DDR4 / DDR5)&amp;#34;] --&amp;gt; B[&amp;#34;VRAM du GPU (GDDR6X)&amp;#34;]
B[&amp;#34;VRAM du GPU (GDDR6X)&amp;#34;] --&amp;gt; C[&amp;#34;Cœurs Tensor (Calcul)&amp;#34;]
subgraph &amp;#34;Division des couches et Déchargement&amp;#34;
D[&amp;#34;Couches inférieures 1-15 (Épinglées sur GPU)&amp;#34;]
E[&amp;#34;Couches supérieures 16-32 (Déchargées sur CPU)&amp;#34;]
end
E[&amp;#34;Couches supérieures 16-32 (Déchargées sur CPU)&amp;#34;] -.-&amp;gt; B[&amp;#34;VRAM du GPU (GDDR6X)&amp;#34;]
&lt;/pre>
&lt;p>&lt;strong>Mécanismes et Défis :&lt;/strong>
Comme le modèle Transformer a une structure où les couches (layers) sont empilées en série, le calcul de la couche suivante ne commencera pas avant que le calcul d&amp;rsquo;une couche soit terminé. En tirant parti de cela, seules les couches qui rentrent dans le GPU (par exemple, couches 1 à 15) sont rendues résidentes (épinglées) dans la VRAM, tandis que les couches restantes (couches 16 à 32) sont placées dans la RAM CPU de grande capacité mais lente. Pendant l&amp;rsquo;inférence, une fois les calculs jusqu&amp;rsquo;à la 15ème couche terminés, les poids de la 16ème couche sont transférés (copiés) du CPU vers le GPU via le bus PCIe, et les calculs sont exécutés sur le GPU.&lt;/p>
&lt;p>Cependant, &lt;strong>la bande passante (Bandwidth) du PCIe constitue un goulet d&amp;rsquo;étranglement sévère&lt;/strong>. La bande passante maximale théorique du PCIe 4.0 x16 est de 32 Go/s (unidirectionnelle), mais comparée à la bande passante interne de la VRAM des derniers GPU (par exemple, la GDDR6X de la RTX 4090 est de 1008 Go/s, et la HBM3 de la H100 dépasse les 3 To/s), elle est deux ordres de grandeur plus lente. L&amp;rsquo;utilisation intensive du déchargement CPU réduit donc drastiquement la vitesse d&amp;rsquo;inférence (Tokens per Second).
Pour minimiser la perte de vitesse, la clé en pratique est de placer autant de couches que possible dans le GPU (maximisation des GPU Layers) et de minimiser les couches à décharger.&lt;/p>
&lt;h2 id="22-quantification-du-cache-kv-et-pagedattention">2.2 Quantification du cache KV et PagedAttention
&lt;/h2>&lt;p>Deux puissantes optimisations sont également appliquées au cache KV, qui est le principal coupable de la consommation de VRAM lors de l&amp;rsquo;inférence.&lt;/p>
&lt;p>&lt;strong>1. Quantification du cache KV (KV Cache Quantization) :&lt;/strong>
C&amp;rsquo;est une méthode permettant de quantifier non seulement les poids du modèle, mais aussi le cache KV lui-même, généré dynamiquement à l&amp;rsquo;exécution, en INT8, INT4 ou FP8 pour le stocker dans la VRAM. Cela permet de réduire la taille du cache KV de moitié, voire au quart. Les moteurs d&amp;rsquo;inférence récents (vLLM, llama.cpp) intègrent cette fonctionnalité, permettant d&amp;rsquo;économiser massivement la VRAM tout en minimisant la perte de précision.&lt;/p>
&lt;p>&lt;strong>2. PagedAttention :&lt;/strong>
L&amp;rsquo;application du concept de &amp;ldquo;pagination&amp;rdquo; de la mémoire virtuelle du système d&amp;rsquo;exploitation au cache KV est appelée &lt;strong>PagedAttention&lt;/strong>, introduite par le moteur d&amp;rsquo;inférence vLLM. Dans les moteurs d&amp;rsquo;inférence classiques, des zones de VRAM contiguës étaient préalablement réservées (Pre-allocation) en fonction de la longueur de séquence maximale configurée. En conséquence, si l&amp;rsquo;entrée réelle était courte, de la fragmentation ou un gaspillage de mémoire inutilisée se produisait, gaspillant parfois plus de 60 % de la VRAM.&lt;/p>
&lt;p>PagedAttention divise le cache KV en blocs (pages) de taille fixe, permettant de les stocker de manière distribuée dans un espace mémoire physique non contigu. Cela réduit le gaspillage de mémoire à presque zéro (limité seulement à la fragmentation interne) et permet d&amp;rsquo;augmenter considérablement la taille du lot avec la même capacité de VRAM.&lt;/p>
&lt;pre class="mermaid">
graph LR
A[&amp;#34;Cache KV Logique&amp;#34;] --&amp;gt; B[&amp;#34;Blocs VRAM Physiques&amp;#34;]
A1[&amp;#34;Jeton 1, 2, 3, 4&amp;#34;] --&amp;gt; B3[&amp;#34;Bloc 3 (Alloué)&amp;#34;]
A2[&amp;#34;Jeton 5, 6, 7, 8&amp;#34;] --&amp;gt; B1[&amp;#34;Bloc 1 (Alloué)&amp;#34;]
A3[&amp;#34;Jetons Futurs...&amp;#34;] -.-&amp;gt; B2[&amp;#34;Bloc 2 (Libre)&amp;#34;]
&lt;/pre>
&lt;h2 id="23-flashattention--briser-la-complexité-de-la-mémoire-dans-le-calcul-de-lattention">2.3 FlashAttention : Briser la complexité de la mémoire dans le calcul de l&amp;rsquo;attention
&lt;/h2>&lt;p>Le manque de VRAM n&amp;rsquo;est pas seulement causé par la quantité de mémoire pour stocker les données, mais aussi par le manque d&amp;rsquo;&amp;ldquo;espace de travail temporaire&amp;rdquo; pendant le calcul. Le mécanisme Self-Attention standard de Transformer nécessite de matérialiser (Materialize) sur la VRAM une énorme matrice d&amp;rsquo;attention de $N \times N$ pour une séquence de longueur $N$. Cela entraîne une complexité de mémoire de $O(N^2)$, ce qui est la principale cause des OOM avec de longs contextes.&lt;/p>
&lt;p>Ce problème a été résolu par &lt;strong>FlashAttention&lt;/strong> (ainsi que FlashAttention-2, 3).
FlashAttention est un algorithme qui tient compte de l&amp;rsquo;architecture matérielle du GPU (la hiérarchie entre la HBM, immense mais lente, et la SRAM, minuscule mais ultra-rapide). En utilisant une méthode appelée Tiling (pavage), les données sont chargées dans la SRAM par blocs pour y effectuer complètement le calcul de l&amp;rsquo;attention, ce qui permet d&amp;rsquo;éviter totalement le processus d&amp;rsquo;écriture de la matrice $N \times N$ dans la HBM (VRAM).&lt;/p>
&lt;p>Grâce à cela, la complexité de la mémoire des couches d&amp;rsquo;attention a considérablement chuté de $O(N^2)$ à $O(N)$ (proportionnelle à la longueur de la séquence), relâchant considérablement les restrictions sur la longueur du contexte.&lt;/p>
&lt;h2 id="24-lessor-de-la-mémoire-unifiée-unified-memory-et-apple-silicon">2.4 L&amp;rsquo;essor de la mémoire unifiée (Unified Memory) et Apple Silicon
&lt;/h2>&lt;p>&lt;strong>L&amp;rsquo;architecture de mémoire unifiée (Unified Memory Architecture : UMA)&lt;/strong>, adoptée par Apple Silicon (séries M1/M2/M3/M4 Max et Ultra) ou par certains APU récents (comme AMD Strix Point), s&amp;rsquo;attaque à ce problème à la base même de l&amp;rsquo;architecture PC.&lt;/p>
&lt;p>Dans ces architectures, le CPU et le GPU sur la carte mère partagent exactement la même mémoire physique (par exemple, jusqu&amp;rsquo;à 192 Go de LPDDR5). Par conséquent, le concept même de &amp;ldquo;transfert de données lent du CPU vers le GPU via PCIe&amp;rdquo; n&amp;rsquo;existe physiquement pas.&lt;/p>
&lt;pre class="mermaid">
graph TD
subgraph &amp;#34;Architecture de Mémoire Unifiée (ex. Apple Silicon)&amp;#34;
A[&amp;#34;Cœurs CPU&amp;#34;] &amp;lt;--&amp;gt; C[&amp;#34;Contrôleur de Mémoire Partagée&amp;#34;]
B[&amp;#34;Cœurs GPU / Neural Engine&amp;#34;] &amp;lt;--&amp;gt; C[&amp;#34;Contrôleur de Mémoire Partagée&amp;#34;]
C[&amp;#34;Contrôleur de Mémoire Partagée&amp;#34;] &amp;lt;--&amp;gt; D[&amp;#34;Pool de Mémoire Unifiée (ex. 192 Go)&amp;#34;]
end
&lt;/pre>
&lt;p>Le plus grand avantage de cette architecture est l&amp;rsquo;absence de frontière distincte comme la VRAM, ce qui permet d&amp;rsquo;utiliser la quasi-totalité de la mémoire système telle quelle pour charger des LLM gigantesques. Avec un Mac Studio disposant de 192 Go de mémoire unifiée, il est possible de charger des modèles colossaux de la classe des 70B ou plus (comme Grok-1) sur un seul appareil sans quantification, et de réaliser des inférences rapides. La bande passante d&amp;rsquo;accès à la mémoire atteint 800 Go/s sur le M2 Ultra, rivalisant avec les vitesses des GPU discrets grand public. C&amp;rsquo;est une approche extrêmement puissante qui résout le dilemme entre &amp;ldquo;capacité de mémoire&amp;rdquo; et &amp;ldquo;bande passante&amp;rdquo; au niveau matériel.&lt;/p>
&lt;hr>
&lt;h1 id="3-techniques-déconomie-de-vram-lors-de-lapprentissage-fine-tuning">3. Techniques d&amp;rsquo;économie de VRAM lors de l&amp;rsquo;apprentissage (Fine-Tuning)
&lt;/h1>&lt;p>De nombreuses percées ont également été réalisées lors de l&amp;rsquo;apprentissage (Training), qui nécessite encore plus de VRAM que l&amp;rsquo;inférence. Pour effectuer un fine-tuning avec des ressources limitées, il est indispensable de combiner les technologies suivantes.&lt;/p>
&lt;h2 id="31-points-de-contrôle-de-gradient-gradient-checkpointing">3.1 Points de contrôle de gradient (Gradient Checkpointing)
&lt;/h2>&lt;p>Dans la rétropropagation (backpropagation) du Deep Learning, afin de calculer les gradients, il est nécessaire de conserver en mémoire toutes les sorties intermédiaires (Activations) de toutes les couches lors de la propagation avant (forward pass). Si la longueur de la séquence ou la taille du lot augmente, cette mémoire d&amp;rsquo;activation commence à dominer la VRAM.&lt;/p>
&lt;p>Le &lt;strong>Gradient Checkpointing (Points de contrôle de gradient / Activation Recomputation)&lt;/strong> est une technique géniale qui exploite le compromis entre la capacité de mémoire et le temps de calcul (Compute).
Plutôt que de sauvegarder toutes les sorties intermédiaires en mémoire, seules les sorties de certaines couches spécifiques (points de contrôle) sont sauvegardées. Lors de la rétropropagation, si une valeur intermédiaire non sauvegardée est nécessaire, &lt;strong>la propagation avant est recalculée à partir du point de contrôle le plus proche sauvegardé pour restaurer la valeur&lt;/strong>.&lt;/p>
&lt;p>Bien que la quantité de calcul augmente d&amp;rsquo;environ 20 à 30 % et que le temps total d&amp;rsquo;apprentissage s&amp;rsquo;allonge, la consommation de VRAM due aux activations peut être drastiquement réduite de $O(N)$ ($N$ étant le nombre de couches) à $O(\sqrt{N})$. C&amp;rsquo;est un paramètre tellement essentiel que l&amp;rsquo;on pourrait dire que l&amp;rsquo;apprentissage des modèles à grande échelle actuels ne pourrait même pas commencer sans lui.&lt;/p>
&lt;h2 id="32-lora-et-qlora-low-rank-adaptation">3.2 LoRA et QLoRA (Low-Rank Adaptation)
&lt;/h2>&lt;p>L&amp;rsquo;acteur principal qui a résolu fondamentalement le manque de VRAM est &lt;strong>LoRA&lt;/strong>, le représentant phare du PEFT (Parameter-Efficient Fine-Tuning).&lt;/p>
&lt;p>La gigantesque matrice de poids d&amp;rsquo;origine $W_0 \in \mathbb{R}^{d \times k}$ du modèle est gelée (Frozen) et n&amp;rsquo;est pas entraînée. À la place, deux très petites matrices de rang faible $A \in \mathbb{R}^{r \times k}$ et $B \in \mathbb{R}^{d \times r}$ sont introduites en parallèle, et seuls ces $A$ et $B$ sont entraînés. (Ici, le rang $r$ est une petite valeur telle que $r \ll d, k$).&lt;/p>
$$ W_{adapted} = W_0 + \Delta W = W_0 + B A $$&lt;p>Cela permet de réduire le nombre de paramètres à entraîner à moins de 1 % (parfois moins de 0,1 %) de l&amp;rsquo;original, ce qui réduit considérablement les &amp;ldquo;gradients&amp;rdquo; et les &amp;ldquo;états de l&amp;rsquo;optimiseur&amp;rdquo; qui engloutissaient massivement la mémoire à moins de 1 %.&lt;/p>
&lt;p>De plus, &lt;strong>QLoRA (Quantized LoRA)&lt;/strong> a poussé ce concept à l&amp;rsquo;extrême.
Dans QLoRA, les poids du modèle de base $W_0$ sont quantifiés à l&amp;rsquo;extrême en 4 bits (format NF4 : NormalFloat4) et chargés dans la VRAM. Ensuite, les petites matrices de LoRA $A, B$ sont entraînées en BF16 (16 bits) afin de préserver la précision des calculs.
Tout en réduisant la taille VRAM du modèle de base au quart grâce à la quantification 4 bits, QLoRA utilise une technologie appelée &lt;strong>Paged Optimizers&lt;/strong> (Optimiseurs paginés), qui sauvegarde automatiquement les états de l&amp;rsquo;optimiseur vers la RAM CPU de manière temporaire lorsque la VRAM est sur le point de s&amp;rsquo;épuiser. Cela a rendu possible le fine-tuning de modèles hyper-gigantesques comme le Llama 3 70B, même sur un seul GPU disposant de 24 Go de VRAM (comme une RTX 4090).&lt;/p>
&lt;h2 id="33-deepspeed-zero-et-le-déchargement-offloading">3.3 DeepSpeed ZeRO et le Déchargement (Offloading)
&lt;/h2>&lt;p>Dans les environnements utilisant plusieurs GPU (Multi-GPU), une simple parallélisation des données (Data Parallelism) ne résout pas le problème de VRAM. Car chaque GPU conserve une copie complète du modèle, ce qui fait que la limite de la capacité VRAM individuelle ne peut être dépassée.&lt;/p>
&lt;p>&lt;strong>ZeRO (Zero Redundancy Optimizer)&lt;/strong> de la bibliothèque &lt;strong>DeepSpeed&lt;/strong> développée par Microsoft est une technologie qui divise (shard) minutieusement les paramètres du modèle, les gradients et les états de l&amp;rsquo;optimiseur entre plusieurs GPU. Ainsi, la &amp;ldquo;somme totale&amp;rdquo; de la VRAM de plusieurs GPU peut être traitée comme un seul gigantesque pool de mémoire.&lt;/p>
&lt;pre class="mermaid">
graph TD
subgraph &amp;#34;ZeRO Stage 3 (Partitionnement des Paramètres)&amp;#34;
A[&amp;#34;GPU 0&amp;#34;] --&amp;gt; D[&amp;#34;Partition 0 (Stocke 1/3 des Poids/Grads/Opts)&amp;#34;]
B[&amp;#34;GPU 1&amp;#34;] --&amp;gt; E[&amp;#34;Partition 1 (Stocke 1/3 des Poids/Grads/Opts)&amp;#34;]
C[&amp;#34;GPU 2&amp;#34;] --&amp;gt; F[&amp;#34;Partition 2 (Stocke 1/3 des Poids/Grads/Opts)&amp;#34;]
end
D[&amp;#34;Partition 0 (Stocke 1/3 des Poids/Grads/Opts)&amp;#34;] &amp;lt;--&amp;gt; E[&amp;#34;Partition 1 (Stocke 1/3 des Poids/Grads/Opts)&amp;#34;]
E[&amp;#34;Partition 1 (Stocke 1/3 des Poids/Grads/Opts)&amp;#34;] &amp;lt;--&amp;gt; F[&amp;#34;Partition 2 (Stocke 1/3 des Poids/Grads/Opts)&amp;#34;]
&lt;/pre>
&lt;ul>
&lt;li>&lt;strong>ZeRO Stage 1 :&lt;/strong> Répartit les états de l&amp;rsquo;optimiseur sur chaque GPU&lt;/li>
&lt;li>&lt;strong>ZeRO Stage 2 :&lt;/strong> Répartit également les gradients sur chaque GPU&lt;/li>
&lt;li>&lt;strong>ZeRO Stage 3 :&lt;/strong> Répartit même les paramètres du modèle (poids) sur chaque GPU&lt;/li>
&lt;/ul>
&lt;p>De plus, en utilisant la fonction &lt;strong>ZeRO-Offload&lt;/strong>, il est possible de décharger (offload) les calculs de mise à jour des états de l&amp;rsquo;optimiseur et des gradients répartis par ZeRO vers &lt;strong>la mémoire du CPU&lt;/strong> au lieu du GPU, pour les faire exécuter par le processeur hôte. Cela allège à l&amp;rsquo;extrême la charge sur la VRAM du GPU, permettant l&amp;rsquo;apprentissage de modèles géants même dans des environnements GPU limités. Comme les calculs sont effectués sur le CPU et les résultats renvoyés au GPU via PCIe, la vitesse d&amp;rsquo;apprentissage diminue, mais cela permet d&amp;rsquo;éviter le pire des scénarios : &amp;ldquo;un plantage de l&amp;rsquo;apprentissage dû à un manque de mémoire&amp;rdquo;.&lt;/p>
&lt;hr>
&lt;h1 id="4-exemples-dimplémentation--hugging-face-accelerate-et-deepspeed">4. Exemples d&amp;rsquo;Implémentation : Hugging Face Accelerate et DeepSpeed
&lt;/h1>&lt;p>Pour finir, voici des exemples simples de la façon dont on implémente concrètement le déchargement CPU et l&amp;rsquo;optimisation VRAM en code Python.&lt;/p>
&lt;h2 id="41-déchargement-automatique-avec-device_mapauto-de-hugging-face">4.1 Déchargement automatique avec device_map=&amp;ldquo;auto&amp;rdquo; de Hugging Face
&lt;/h2>&lt;p>L&amp;rsquo;utilisation des bibliothèques &lt;code>transformers&lt;/code> et &lt;code>accelerate&lt;/code> de Hugging Face permet de répartir automatiquement les couches entre le GPU et le CPU lors du chargement du modèle.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">transformers&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">AutoModelForCausalLM&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">AutoTokenizer&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">model_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;meta-llama/Llama-2-13b-hf&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Avec device_map=&amp;#34;auto&amp;#34;, ce qui ne rentre pas dans la VRAM est déchargé vers la RAM CPU&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># load_in_8bit=True quantifie les poids en 8 bits pour encore plus d&amp;#39;économies de mémoire&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">model&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">AutoModelForCausalLM&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">from_pretrained&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model_id&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">device_map&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;auto&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">load_in_8bit&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">offload_folder&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;offload_dir&amp;#34;&lt;/span> &lt;span class="c1"># Si insuffisant, il est même possible de décharger sur disque (SSD)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>En exécutant ce code, la bibliothèque sous-jacente &lt;code>accelerate&lt;/code> analyse la capacité libre de la VRAM du système et de la RAM du CPU, puis organise (Dispatch) le placement des couches de la manière la plus optimale.&lt;/p>
&lt;h2 id="42-configuration-du-déchargement-cpu-de-deepspeed-zero-2">4.2 Configuration du déchargement CPU de DeepSpeed (ZeRO-2)
&lt;/h2>&lt;p>Voici un exemple de fichier de configuration (JSON) pour activer le déchargement CPU avec DeepSpeed pendant l&amp;rsquo;apprentissage.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;fp16&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;enabled&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;zero_optimization&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;stage&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;offload_optimizer&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;device&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;cpu&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;pin_memory&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;allgather_partitions&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;allgather_bucket_size&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mf">2e8&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;overlap_comm&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;reduce_scatter&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;reduce_bucket_size&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mf">2e8&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;contiguous_gradients&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;train_batch_size&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">16&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;gradient_accumulation_steps&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">4&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Dans cette configuration, en assignant &lt;code>&amp;quot;cpu&amp;quot;&lt;/code> à &lt;code>offload_optimizer&lt;/code>, la conservation de l&amp;rsquo;état et les calculs de mise à jour de l&amp;rsquo;optimiseur (comme Adam), qui consomment massivement de la VRAM, sont exécutés par le CPU côté système. Cela permet à la VRAM du GPU de se consacrer exclusivement à la tâche la plus importante : les calculs forward/backward du modèle. En paramétrant &lt;code>pin_memory: true&lt;/code>, on empêche les défauts de page (page faults) et on accélère au maximum les transferts PCIe entre CPU et GPU.&lt;/p>
&lt;hr>
&lt;h1 id="conclusion">Conclusion
&lt;/h1>&lt;p>Dans le développement de l&amp;rsquo;IA, le manque de mémoire GPU (Out of Memory) est un problème éternel qui continuera de hanter les développeurs à mesure que l&amp;rsquo;échelle des modèles s&amp;rsquo;agrandit. Cependant, en combinant intelligemment une compréhension approfondie du matériel (architecture) et des techniques d&amp;rsquo;optimisation au niveau logiciel et algorithmique, telles que présentées dans cet article, il devient possible de réaliser des inférences et des apprentissages de modèles géants en environnement local, chose qui pourrait sembler impossible à première vue.&lt;/p>
&lt;p>&lt;strong>Résumé des mesures pour l&amp;rsquo;inférence :&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Quantification (INT4 / INT8 / FP8) :&lt;/strong> Compresser de manière drastique la taille du modèle lui-même et réduire l&amp;rsquo;occupation de la VRAM.&lt;/li>
&lt;li>&lt;strong>Déchargement CPU :&lt;/strong> Évacuer vers la mémoire système les couches qui ne rentrent pas dans la VRAM (au prix d&amp;rsquo;un compromis avec la baisse de vitesse due à la bande passante PCIe).&lt;/li>
&lt;li>&lt;strong>Optimisation du cache KV :&lt;/strong> Assurer la longueur du contexte (Context Length) grâce à la pagination (PagedAttention), la quantification du cache ou FlashAttention.&lt;/li>
&lt;li>&lt;strong>Utilisation de la mémoire unifiée :&lt;/strong> Exploiter les UMA, comme Apple Silicon, pour utiliser directement une mémoire de grande capacité pour l&amp;rsquo;inférence.&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>Résumé des mesures pour l&amp;rsquo;apprentissage :&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>&lt;strong>PEFT (LoRA / QLoRA) :&lt;/strong> Limiter les paramètres à entraîner et quantifier à l&amp;rsquo;extrême le modèle de base.&lt;/li>
&lt;li>&lt;strong>Points de contrôle de gradient (Gradient Checkpointing) :&lt;/strong> Éviter de conserver les sorties intermédiaires de la propagation avant et les recalculer lors de la rétropropagation, afin de limiter la consommation VRAM en échange de temps de calcul.&lt;/li>
&lt;li>&lt;strong>ZeRO &amp;amp; Déchargement CPU (DeepSpeed) :&lt;/strong> Répartir les états de l&amp;rsquo;optimiseur et les gradients sur plusieurs GPU, ou les décharger sur la mémoire CPU pour dépasser les limites de la VRAM.&lt;/li>
&lt;/ol>
&lt;p>En maîtrisant ces technologies avancées, maximisons les performances de développement de l&amp;rsquo;IA dans les limites de nos ressources matérielles. Dans ce domaine qui évolue à pas de géant, de nouveaux algorithmes d&amp;rsquo;économie de mémoire devraient continuer d&amp;rsquo;apparaître. La clé sera de vérifier régulièrement l&amp;rsquo;évolution des dernières bibliothèques et de les intégrer dans vos implémentations.&lt;/p></description></item></channel></rss>