Modèles et plateformes d’IA

Optimisation de la mémoire pour l’inférence et la fine-tuning des grands modèles de langage

mm
Ajouter Unite.AI à vos sources préférées sur Google

Les grands modèles de langage (LLM) comme GPT-4, Bloom et LLaMA ont atteint des capacités remarquables en augmentant leur taille à des milliards de paramètres. Cependant, déployer ces modèles massifs pour l’inférence ou la fine-tuning est difficile en raison de leurs immenses besoins en mémoire. Dans ce blog technique, nous allons explorer les techniques pour estimer et optimiser la consommation de mémoire pendant l’inférence et la fine-tuning des LLM sur différents appareils.

Comprendre les besoins en mémoire

La mémoire requise pour charger un LLM est principalement déterminée par le nombre de paramètres et la précision numérique utilisée pour stocker les paramètres. Une règle simple est:

  • Charger un modèle avec X milliards de paramètres nécessite environ 4X GB de VRAM en précision flottante 32 bits
  • Charger un modèle avec X milliards de paramètres nécessite environ 2X GB de VRAM en précision bfloat16/float16

Par exemple, charger le modèle GPT-3 avec 175 milliards de paramètres nécessiterait environ 350 GB de VRAM en précision bfloat16. Actuellement, les plus grandes cartes graphiques commerciales comme la NVIDIA A100 et H100 ne proposent que 80 GB de VRAM, nécessitant des techniques de parallélisme de tenseur et de modèle.

Pendant l’inférence, l’empreinte mémoire est dominée par les paramètres du modèle et les tenseurs d’activation temporaires produits. Une estimation haute de l’utilisation de mémoire pendant l’inférence est la somme de la mémoire requise pour charger les paramètres du modèle et la mémoire pour les activations.

Quantifier la mémoire d’inférence

Essayons de quantifier les besoins en mémoire pour l’inférence en utilisant le modèle OctoCode, qui a environ 15 milliards de paramètres au format bfloat16 (~ 31 GB). Nous allons utiliser la bibliothèque Transformers pour charger le modèle et générer du texte:

from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline
import torch

<p>model = AutoModelForCausalLM.from_pretrained(&quot;bigcode/octocoder&quot;,
torch_dtype=torch.bfloat16,
device_map=&quot;auto&quot;,
pad_token_id=0)
tokenizer = AutoTokenizer.from_pretrained(&quot;bigcode/octocoder&quot;)
pipe = pipeline(&quot;text-generation&quot;, model=model, tokenizer=tokenizer)</p>

<p>prompt = &quot;Question: Please write a Python function to convert bytes to gigabytes.\n\nAnswer:&quot;
result = pipe(prompt, max_new_tokens=60)[0][&quot;generated_text&quot;][len(prompt):]</p>

<p>def bytes_to_gigabytes(bytes):
return bytes / 1024 / 1024 / 1024</p>

<p>bytes_to_gigabytes(torch.cuda.max_memory_allocated())

Sortie:

29.0260648727417

L’utilisation de mémoire de pointe est d’environ 29 GB, ce qui correspond à notre estimation de 31 GB pour charger les paramètres du modèle en format bfloat16.

Optimisation de la mémoire d’inférence avec la quantification

Alors que la précision bfloat16 est la précision couramment utilisée pour l’entraînement des LLM, les chercheurs ont constaté que la quantification des poids du modèle à des types de données de précision inférieure comme les entiers 8 bits (int8) ou les entiers 4 bits peut réduire considérablement l’utilisation de la mémoire avec une perte d’exactitude minimale pour les tâches d’inférence comme la génération de texte.

Voyons les économies de mémoire avec la quantification 8 bits et 4 bits du modèle OctoCode:

&amp;lt;/div&amp;gt;
# Quantification 8 bits
model = AutoModelForCausalLM.from_pretrained(&quot;bigcode/octocoder&quot;, load_in_8bit=True,
pad_token_id=0)
pipe = pipeline(&quot;text-generation&quot;, model=model, tokenizer=tokenizer)
result = pipe(prompt, max_new_tokens=60)[0][&quot;generated_text&quot;][len(prompt):]
bytes_to_gigabytes(torch.cuda.max_memory_allocated())&lt;/pre&gt;
Sortie:
15.219234466552734
# Quantification 4 bits
model = AutoModelForCausalLM.from_pretrained(&quot;bigcode/octocoder&quot;, load_in_4bit=True,
low_cpu_mem_usage=True, pad_token_id=0)
pipe = pipeline(&quot;text-generation&quot;, model=model, tokenizer=tokenizer)
result = pipe(prompt, max_new_tokens=60)[0][&quot;generated_text&quot;][len(prompt):]
bytes_to_gigabytes(torch.cuda.max_memory_allocated())

Sortie:

9.543574333190918

Avec la quantification 8 bits, les besoins en mémoire passent de 31 GB à 15 GB, tandis que la quantification 4 bits les réduit encore à 9,5 GB ! Cela permet d’exécuter le modèle OctoCode de 15 milliards de paramètres sur des cartes graphiques grand public comme la RTX 3090 (24 GB de VRAM).

Cependant, notez que la quantification plus agressive comme 4 bits peut parfois entraîner une dégradation de la précision par rapport à la précision 8 bits ou bfloat16. Il y a un compromis entre les économies de mémoire et la précision que les utilisateurs doivent évaluer pour leur cas d’utilisation.

La quantification est une technique puissante qui peut permettre le déploiement de LLM sur des environnements à ressources limitées comme les instances cloud, les appareils de bord ou même les téléphones mobiles en réduisant considérablement l’empreinte mémoire.

Estimation de la mémoire pour la fine-tuning

Alors que la quantification est principalement utilisée pour l’inférence efficace, des techniques comme le parallélisme de tenseur et le parallélisme de modèle sont cruciales pour gérer les besoins en mémoire pendant l’entraînement ou la fine-tuning des grands modèles de langage.

La consommation de mémoire de pointe pendant la fine-tuning est généralement 3-4 fois supérieure à l’inférence en raison de besoins en mémoire supplémentaires pour:

  • Les gradients
  • Les états de l’optimiseur
  • Les activations de la passe avant stockées pour la rétropropagation

Une estimation conservatrice est que la fine-tuning d’un LLM avec X milliards de paramètres nécessite environ 4 * (2X) = 8X GB de VRAM en précision bfloat16.

Par exemple, la fine-tuning du modèle LLaMA de 7 milliards de paramètres nécessiterait environ 7 * 8 = 56 GB de VRAM par carte graphique en précision bfloat16. Cela dépasse la capacité de mémoire des cartes graphiques actuelles, nécessitant des techniques de fine-tuning distribuées.

Techniques de fine-tuning distribuées

Plusieurs méthodes de fine-tuning distribuées ont été proposées pour surmonter les contraintes de mémoire des cartes graphiques pour les grands modèles:

  1. Parallélisme de données: L’approche classique de parallélisme de données réplique le modèle entier sur plusieurs cartes graphiques tout en divisant et en distribuant les lots de données d’entraînement. Cela réduit le temps d’entraînement de manière linéaire avec le nombre de cartes graphiques, mais ne réduit pas les besoins en mémoire sur chaque carte graphique.
  2. ZeRO Stage 3: Une forme avancée de parallélisme de données qui partitionne les paramètres du modèle, les gradients et les états de l’optimiseur sur les cartes graphiques. Cela réduit la mémoire par rapport au parallélisme de données classique en ne gardant que les données partitionnées requises sur chaque carte graphique pendant les différentes phases de l’entraînement.
  3. Parallélisme de tenseur: Au lieu de répliquer le modèle, le parallélisme de tenseur divise les paramètres du modèle en lignes ou en colonnes et les distribue sur les cartes graphiques. Chaque carte graphique opère sur un ensemble partitionné de paramètres, de gradients et d’états de l’optimiseur, ce qui entraîne des économies de mémoire considérables.
  4. Parallélisme de pipeline: Cette technique partitionne les couches du modèle sur différents appareils/ouvriers, chaque appareil exécutant un sous-ensemble des couches. Les activations sont passées entre les ouvriers, ce qui réduit la mémoire de pointe mais augmente la charge de communication.

Estimer l’utilisation de la mémoire pour ces méthodes distribuées est non trivial car la distribution des paramètres, des gradients, des activations et des états de l’optimiseur varie selon les techniques. De plus, différents composants comme le corps du transformateur et la tête de modélisation du langage peuvent présenter des comportements d’allocation de mémoire différents.

La solution LLMem

Les chercheurs ont récemment proposé LLMem, une solution qui estime avec précision la consommation de mémoire de la carte graphique lors de l’application de méthodes de fine-tuning distribuées aux LLM sur plusieurs cartes graphiques.

Estimation de l'utilisation de la mémoire de la carte graphique pour la fine-tuning d'un LLM pré-entraîné

Estimation de l’utilisation de la mémoire de la carte graphique pour la fine-tuning d’un LLM pré-entraîné

LLMem prend en compte des facteurs tels que la recombinaison des paramètres avant le calcul (ZeRO Stage 3), la collecte de sortie dans la passe arrière (parallélisme de tenseur) et les différentes stratégies d’allocation de mémoire pour le corps du transformateur et la tête de modélisation du langage.

Les résultats expérimentaux montrent que LLMem peut estimer l’utilisation de mémoire de pointe de la carte graphique pour la fine-tuning d’un LLM sur une seule carte graphique avec des taux d’erreur allant jusqu’à 1,6 %, surpassant le taux d’erreur moyen de 42,6 % de DNNMem. Lors de l’application de méthodes de fine-tuning distribuées à des LLM avec plus d’un milliard de paramètres sur plusieurs cartes graphiques, LLMem atteint un taux d’erreur moyen impressionnant de 3,0 %.

En estimant avec précision les besoins en mémoire à l’avance, LLMem peut aider les utilisateurs à sélectionner la méthode de fine-tuning distribuée la plus efficace qui évite les problèmes de mémoire insuffisante tout en minimisant le temps d’entraînement.

Techniques émergentes

Alors que la quantification, le parallélisme de tenseur et le parallélisme de modèle sont des techniques établies, les chercheurs continuent à explorer de nouvelles méthodes pour repousser les limites de l’entraînement et du déploiement efficaces des LLM.

  1. LoRA et QLoRA: Ces techniques impliquent l’entraînement d’un petit module d’adaptateur résiduel pour mettre à jour le LLM pré-entraîné avec de nouvelles connaissances au lieu de procéder directement à la fine-tuning du grand nombre de paramètres. Cela peut entraîner des économies de mémoire considérables tout en conservant la plupart des performances du modèle.
  2. FlashAttention: Le mécanisme d’attention auto est un goulet d’étranglement en termes de mémoire et de calcul dans les modèles de transformateur. FlashAttention approxime l’attention standard avec une complexité linéaire, réduisant les besoins en mémoire de quadratique à linéaire dans la longueur de la séquence d’entrée.
  3. Mixture-of-Experts: Cette approche achemine conditionnellement chaque exemple de données d’entrée vers un modèle d’expert spécialisé au lieu de le traiter à travers l’ensemble du modèle. Cette parcimonie dynamique peut économiser de la mémoire en n’activant qu’un sous-ensemble d’experts pour chaque exemple.
  4. Chirurgie de modèle inversée: Les chercheurs ont exploré la compression de modèle chirurgicale en supprimant itérativement des composants moins importants comme les têtes d’attention pour échanger la mémoire/vitesse pour la précision.
  5. Offloading: Enfin, les techniques qui déchargent les paramètres, les états de l’optimiseur ou les activations sur la mémoire du processeur ou le disque peuvent compléter la mémoire limitée de la carte graphique pour les grands modèles.

Ces méthodes de pointe illustrent l’écosystème de recherche dynamique axé sur la démocratisation de l’entraînement et du déploiement efficaces des LLM sur différents environnements matériels.

Conclusion

Les besoins en mémoire des grands modèles de langage posent des défis importants pour leur adoption généralisée dans les applications du monde réel. En comprenant les techniques d’estimation de la mémoire et en exploitant la quantification, les stratégies d’entraînement distribuées et les innovations émergentes, nous pouvons optimiser les déploiements de LLM sur des appareils à ressources limitées.

Des outils comme LLMem ouvrent la voie à une estimation précise de la mémoire, permettant aux utilisateurs de sélectionner la configuration de fine-tuning la plus efficace. À mesure que le matériel évolue et que la recherche progresse, nous pouvons nous attendre à un entraînement et à une inférence de LLM plus efficaces, entraînant des progrès dans le traitement du langage naturel et l’intelligence artificielle.

Trouver un équilibre entre la capacité du modèle, la précision et l’utilisation des ressources sera crucial pour débloquer tout le potentiel des grands modèles de langage dans divers domaines et cas d’utilisation. En adoptant les techniques d’optimisation de la mémoire, nous nous rapprochons d’un avenir où l’intelligence artificielle de langage de pointe est accessible, évolutif et durable.

J'ai passé les cinq dernières années à plonger dans le monde fascinant de l'apprentissage automatique et du deep learning. Ma passion et mon expertise m'ont conduit à contribuer à plus de 50 projets de génie logiciel divers, avec un focus particulier sur l'IA/ML. Ma curiosité continue m'a également attiré vers le traitement automatique des langues, un domaine que je suis impatient d'explorer plus en profondeur.