Aller au contenu

Traduction automatique

Cet article a été traduit automatiquement depuis la version originale en anglais.

Mise à l’échelle des grands modèles de langage : stratégies multi-GPU et multi-nœuds qui tiennent en pratique

Les LLM actuels ne tiennent pas sur un seul GPU. Un modèle de 70B paramètres demande environ 140GB rien que pour les poids en FP16, soit presque deux fois ce qu’un A100 peut contenir. Entraîner ou servir ces modèles implique de répartir le travail sur plusieurs GPU, et un mauvais découpage gaspille l’essentiel de votre budget de calcul.

Voici un guide pratique des stratégies de parallélisme qui fonctionnent réellement en production, tiré de l’Ultra-Scale Playbook de Hugging Face.

Prérequis

Vous tirerez le meilleur parti de cet article si vous êtes déjà à l’aise avec :

  • la rétropropagation, les gradients et des optimiseurs comme AdamW ;
  • les mécanismes des transformers : attention et réseaux feed-forward ;
  • les bases de PyTorch : nn.Module, DataLoader, la boucle d’entraînement standard.

Pourquoi la mise à l’échelle compte

Quatre facteurs vous obligent à sortir du GPU unique. Un modèle de 70B demande environ 140GB en FP16, soit presque le double des 80GB d’un A100. Même sur huit A100, un entraînement from scratch d’un 13B prend des semaines. Les contextes longs (32K tokens et plus) dépassent la mémoire d’un seul GPU avant même d’avoir fait un travail utile. Et en charge de production, l’inférence distribuée est ce qui empêche la tail latency de s’emballer.

1. Techniques de parallélisme, expliquées simplement

1.1 Parallélisme de données (DP)

Le découpage le plus simple. Chaque GPU conserve le modèle complet et traite une tranche différente du batch. Après la rétropropagation, les GPU font un all-reduce de leurs gradients (moyennage), puis chacun met à jour sa propre copie. Des modèles identiques, des données différentes, des poids synchronisés.

À utiliser quand le modèle tient confortablement sur un GPU et que vous voulez simplement traiter plus de batches par seconde. Le coût de mise en place est quasi nul ; le DDP de PyTorch s’ajoute en une ligne. Le piège, c’est la mémoire : chaque GPU conserve toujours le modèle complet, les états de l’optimiseur et les gradients. Vous gagnez donc en débit, pas en capacité.

Parallélisme de données

Outils : PyTorch DDP, Horovod.

1.2 Fully sharded data parallelism (FSDP)

Le DP, mais avec une gestion mémoire. Les paramètres, les gradients et les états de l’optimiseur sont shardés entre les GPU. Pendant le passage forward, chaque GPU récupère auprès des autres les paramètres dont il a besoin, calcule, puis libère les shards empruntés pour récupérer de la mémoire. Le passage backward répète le gather, puis réduit les gradients pour que chaque GPU ne mette à jour que son propre shard.

C’est la couche à utiliser une fois que le modèle a dépassé la capacité d’un seul GPU (en général au-delà d’environ 10B paramètres) et que vous voulez continuer à entraîner sur une seule machine sans réécrire votre code. En pratique, FSDP permet d’entraîner des modèles 4 à 8× plus grands que ce qui tient sur un seul GPU.

FSDP

[!NOTE] Les stages ZeRO, en bref FSDP est souvent décrit avec le vocabulaire de ZeRO (Zero Redundancy Optimizer) :

  • Stage 1 : shard des seuls états de l’optimiseur (~4× de gain mémoire).
  • Stage 2 : shard des gradients + états de l’optimiseur (~8× de gain mémoire).
  • Stage 3 : shard des paramètres + gradients + états de l’optimiseur (scaling linéaire avec N GPU).

Le FSDP de PyTorch adopte par défaut un comportement de Stage 3.

Activer FSDP dans PyTorch

from torch.distributed.fsdp import FullyShardedDataParallel as FSDP

# 1. Wrap your model
model = MyLLM()
model = FSDP(model)

# 2. Train as usual
output = model(input)
loss = output.sum()
loss.backward()
optimizer.step()

Outils : PyTorch FSDP, DeepSpeed ZeRO-3.

1.3 Parallélisme tensoriel (TP)

On répartit les couches elles-mêmes entre les GPU. Prenez une matrice de poids, découpez-la par colonnes (ou par lignes), et donnez un morceau à chaque GPU. Chaque device calcule sa partie de la sortie ; un all-reduce ou une concaténation recolle ensuite les résultats avant la couche suivante. Cela se produit à chaque couche.

Le TP devient utile quand les couches individuelles sont trop grosses même après FSDP — grosses matrices d’attention ou FFN très larges. Il suppose aussi un interconnect intra-nœud rapide : NVLink ou NVSwitch, pas PCIe. Entre plusieurs nœuds, l’all-reduce à chaque couche devient le goulot d’étranglement et le gain disparaît. Un degré de TP de 2 à 8 dans une seule machine est en général le bon compromis.

Parallélisme tensoriel

Outils : Megatron-LM, TensorRT-LLM, ColossalAI.

1.4 Parallélisme de pipeline (PP)

On découpe le modèle verticalement, par groupes de couches. Les couches 1 à 10 vivent sur le GPU 1, les couches 11 à 20 sur le GPU 2, etc. On envoie ensuite des micro-batches dans le pipeline pour que chaque stage reste occupé : le GPU 1 termine le batch 1 et le transmet au GPU 2, puis démarre immédiatement le batch 2. Avec suffisamment de micro-batches en vol, chaque device travaille en permanence sur quelque chose.

À utiliser quand le modèle est si profond que même FSDP ne suffit pas à le faire tenir, ou quand vous devez répartir sur plusieurs nœuds et que la bande passante inter-nœuds est le facteur limitant. La partie pénible, ce sont les « bulles » du pipeline — des stages inactifs au début et à la fin de chaque batch — qu’on réduit au minimum en lançant beaucoup de petits micro-batches plutôt que quelques gros.

Parallélisme de pipeline

Outils : DeepSpeed PP, Megatron-LM, GPipe.

1.5 Parallélisme de contexte (CP)

Pour les séquences très longues. On répartit un contexte de 64K tokens sur, par exemple, quatre GPU (16K tokens chacun). Chaque GPU exécute la self-attention sur son chunk local, puis les GPU échangent les clés et valeurs pour calculer les parties d’attention cross-chunk. Le résultat fusionné est celui que vous obtiendriez en exécutant le contexte complet sur un seul device, sans le coût mémoire.

C’est le levier à utiliser quand la longueur du contexte, et non la taille du modèle, est le goulot d’étranglement : analyse de documents longs, raisonnement à l’échelle d’un livre, génération de code sur un gros dépôt. C’est le CP qui rend possible l’entraînement à 100K+ tokens sur un matériel qui plafonnerait sinon à 8K.

Parallélisme de contexte

Outils : Picotron, Nanotron.

1.6 Parallélisme d’experts (Mixture of Experts)

La spécialisation. On remplace les couches FFN denses par N sous-réseaux experts (8, 64, parfois plus). Un petit réseau de gating choisit les top-k experts (généralement top-2) pour chaque token. Seuls ces experts sont exécutés pour ce token ; les autres restent inactifs. Des experts différents peuvent résider sur des GPU différents, ce qui permet au modèle d’avoir énormément de paramètres au total tout en gardant un coût de calcul par token faible.

Mixtral-8x7B a 56B paramètres au total mais seulement ~13B actifs par token ; Grok et DeepSeek-V2 utilisent le même principe. Le prix à payer, c’est la complexité côté entraînement : l’équilibrage de charge entre experts devient un problème d’ingénierie à part entière, et l’instabilité du routage a déjà fait diverger plus d’un entraînement MoE.

Mixture of Experts

Outils : Picotron, Nanotron.

Comparaison rapide : quel parallélisme utiliser ?

Technique Ce qui est découpé Idéal pour Gain mémoire Coût de communication
Parallélisme de données (DP) Les batches de données Modèles qui tiennent sur 1 GPU Aucun (copie du modèle) Faible (gradients uniquement)
FSDP Modèle + optimiseur + gradients Modèles trop gros pour 1 GPU Élevé (4–8×) Moyen
Parallélisme tensoriel (TP) Couches individuelles Grosses couches, GPU rapides Moyen Élevé (à chaque couche)
Parallélisme de pipeline (PP) Groupes de couches (stages) Modèles très profonds Moyen Faible (entre stages)
Parallélisme de contexte (CP) Longueur de séquence Contextes longs (64K+ tokens) Élevé (pour les activations) Moyen
Parallélisme d’experts (MoE) Experts dans les couches MoE Modèles clairsemés massifs Aucun (plus de params, moins de FLOPs) Moyen

Une valeur par défaut raisonnable : commencez par FSDP. Ajoutez du TP si les couches individuelles sont encore trop grosses. Ajoutez du PP quand vous devez répartir sur plusieurs nœuds. Ajoutez du CP quand la longueur du contexte devient le goulot d’étranglement.

2. Stratégies d’entraînement pratiques

Des configurations matérielles différentes appellent des combinaisons différentes. Voici ce que je ferais réellement dans trois cas fréquents.

2.1 Une seule machine, 2 à 8 GPU

Commencez par du FSDP pur — PyTorch FSDP ou DeepSpeed ZeRO-2/ZeRO-3 — piloté par Hugging Face accelerate ou torchrun. Si certaines couches d’attention ou FFN restent trop grosses même après le sharding, ajoutez ensuite un TP=2.

Quelques remarques spécifiques au matériel. Les GPU grand public (RTX 4090 et assimilés) sur PCIe devraient rester à TP=1 ou TP=2 au maximum ; l’interconnect ne suit pas au-delà. Les GPU serveur (A100, H100) avec NVLink gèrent sans problème du TP=2 à TP=4. Et sur huit GPU dans une seule machine, du FSDP pur suffit souvent jusqu’à 70B sans avoir besoin de TP.

2.2 Petit cluster, 2 à 16 nœuds (≤128 GPU)

Il vous faut du parallélisme 2D ou 3D : TP plus FSDP, éventuellement plus PP. La structure qui fonctionne en pratique :

  1. TP à l’intérieur de chaque nœud (TP=4 ou TP=8 avec NVLink).
  2. FSDP entre les nœuds pour le parallélisme de données.
  3. Ajoutez du PP si le modèle est si profond que même FSDP ne suffit pas, en le découpant verticalement entre les nœuds.

Pourquoi cette organisation fonctionne : NVLink est assez rapide pour absorber les échanges à chaque couche du TP, tandis que l’InfiniBand entre les nœuds n’a qu’à synchroniser les shards FSDP, ce qui coûte bien moins en communication. Effet net : vous minimisez le trafic inter-nœuds, qui est presque toujours le goulot d’étranglement à cette échelle.

Quand vous ajoutez du PP, réglez le nombre de micro-batches à au moins 4× le degré du pipeline. En dessous, les bulles mangent votre throughput.

2.3 Grand cluster (centaines à milliers de GPU)

C’est là que le parallélisme 4D (DP × TP × PP × CP) commence à avoir du sens. Associez chaque dimension à votre topologie matérielle et utilisez Megatron-LM ou Nanotron — ils prennent en charge la 4D nativement, et tout implémenter soi-même est un projet à part entière.

En pratique, vous n’en avez besoin que pour le préentraînement from scratch de modèles 70B+ avec des fenêtres de contexte 32K+ . La plupart des fine-tunings, même sur de gros modèles, n’en ont pas besoin.

Un exemple concret. Entraîner un modèle 70B avec un contexte 32K sur 512 GPU :

  • TP=8 à l’intérieur de chaque nœud de 8 GPU.
  • PP=4 sur quatre nœuds.
  • CP=4 pour le contexte long.
  • DP=4 pour le débit.
  • 8 × 4 × 4 × 4 = 512 GPU.

L’efficacité de scaling sur une configuration comme celle-ci se situe autour de 70–80 % avec un bon InfiniBand, et vous pouvez pousser jusqu’à ~85 % avec un réglage fin. En dessous, vous laissez vraiment de l’argent sur la table.

3. Outils à connaître

Voici un mémo rapide pour choisir le bon :

Outil Quand l’utiliser Courbe d’apprentissage Idéal pour
Hugging Face Accelerate Tout entraînement distribué avec peu de changements de code ★☆☆☆☆ Débutants, prototypes rapides
PyTorch FSDP Modèles moyens à grands (1–30B) sur un seul nœud ★★☆☆☆ Le cas le plus courant
DeepSpeed ZeRO Entraînement multi-nœuds avec une bonne documentation ★★★☆☆ Entraînement en production
Megatron-LM Très grands modèles (70B+), parallélisme 3D/4D ★★★★☆ Production à grande échelle
Nanotron Apprentissage et recherche sur le parallélisme moderne ★★★☆☆ Éducation, expérimentation
vLLM Inférence rapide avec PagedAttention et KV caching ★★☆☆☆ Serving en production
TensorRT-LLM Vitesse d’inférence maximale sur GPU NVIDIA ★★★★☆ Optimisation de l’inférence en production

Configuration FSDP minimale avec accelerate

compute_environment: LOCAL_MACHINE
distributed_type: FSDP
fsdp_config:
    fsdp_auto_wrap_policy: TRANSFORMER_BASED_WRAP
    fsdp_backward_prefetch: BACKWARD_PRE
    fsdp_state_dict_type: SHARDED_STATE_DICT
machine_rank: 0
main_process_ip: null
main_process_port: null
main_training_function: main
mixed_precision: bf16
num_machines: 1
num_processes: 8
use_cpu: false

Si vous débutez, je vous conseille de prendre Hugging Face Accelerate pour démarrer rapidement, puis de descendre vers PyTorch FSDP ou DeepSpeed une fois que vous avez besoin d’un contrôle plus fin.

4. Un cadre de décision

Arbre de décision de mise à l’échelle

L’image ci-dessus reflète ce que je ferais en pratique : FSDP d’abord, TP quand les couches sont trop grosses, PP pour la profondeur sur plusieurs nœuds, CP pour les contextes longs. N’ajoutez de la complexité qu’une fois l’approche la plus simple réellement à bout de souffle.

5. Le mémo Ultra-Scale

L’équipe de Hugging Face a préparé un résumé visuel d’une page qui couvre l’essentiel de ce qui précède :

Aide-mémoire Ultra-Scale LLM

Conclusion

FSDP couvre la plupart des cas que vous rencontrerez. Le TP s’insère dans un nœud quand les couches individuelles ne tiennent toujours pas. Le PP répartit le modèle sur plusieurs nœuds quand la profondeur est la contrainte. Le CP intervient quand c’est la longueur du contexte qui vous fait manquer de mémoire. Le principe commun à toutes ces approches est d’aligner la stratégie de parallélisme sur la topologie matérielle dont vous disposez réellement, et d’ajouter une nouvelle dimension seulement quand la plus simple a atteint ses limites.

Pour aller plus loin :