Bonnes pratiques pour optimiser l'inférence de grands modèles de langage avec des GPU sur Google Kubernetes Engine (GKE)


Google Kubernetes Engine (GKE) offre un contrôle précis pour l'inférence de grands modèles de langage (LLM) avec des performances et des coûts optimaux. Ce guide décrit les bonnes pratiques pour optimiser l'inférence et la diffusion de grands modèles de langage (LLM) ouverts avec des GPU sur GKE, à l'aide des frameworks de diffusion vLLM et d'inférence de génération de texte (TGI).

Pour obtenir un résumé de toutes les bonnes pratiques, consultez la checklist.

Objectifs

Ce guide est destiné aux clients d'IA générative, aux utilisateurs GKE nouveaux ou existants, aux ingénieurs en ML et aux ingénieurs LLMOps (DevOps) qui souhaitent optimiser leurs charges de travail LLM à l'aide de GPU avec Kubernetes.

À la fin de ce guide, vous serez capable de :

  • Choisir des techniques d'optimisation de LLM post-entraînement, y compris la quantification, le parallélisme de tenseur et l'optimisation de la mémoire.
  • Peser le pour et le contre lors de l'examen de ces techniques d'optimisation de haut niveau.
  • Déployer des modèles LLM ouverts sur GKE avec des paramètres d'optimisation activés, à l'aide de frameworks de diffusion tels que vLLM ou TGI.

Présentation des techniques d'optimisation de diffusion LLM

Contrairement aux charges de travail non basées sur l'IA, les charges de travail LLM présentent généralement une latence plus élevée et un débit plus faible en raison de leur dépendance aux opérations de multiplication matricielle. Pour améliorer les performances d'inférence des LLM, vous pouvez utiliser des accélérateurs matériels spécialisés (par exemple, des GPU et des TPU) et des frameworks de diffusion optimisés.

Vous pouvez appliquer une ou plusieurs des bonnes pratiques suivantes pour réduire la latence des charges de travail LLM tout en améliorant le débit et l'efficacité :

Les exemples de ce guide utilisent le LLM Gemma 7B avec les frameworks de diffusion vLLM ou TGI pour appliquer ces bonnes pratiques. Cependant, les concepts et les fonctionnalités décrits sont applicables à la plupart des LLM ouverts.

Avant de commencer

Avant d'essayer les exemples de ce guide, effectuez les tâches préalables suivantes :

  1. Suivez les instructions de ces guides pour accéder au modèle Gemma, préparer votre environnement, et créer et configurer des ressources Google Cloud :

    Veillez à enregistrer le jeton d'accès Hugging Face dans votre secret Kubernetes.

  2. Clonez le dépôt d'exemples https://github.com/GoogleCloudPlatform/kubernetes-engine-samples/ dans votre environnement de développement local.

  3. Remplacez le répertoire de travail par /kubernetes-engine-samples/ai-ml/llm-serving-gemma/.

Bonne pratique : quantification

La quantification est une technique analogue à la compression d'image avec pertes qui réduit la taille du modèle en représentant les pondérations dans des formats de précision inférieurs (8 bits ou 4 bits), ce qui réduit les besoins en mémoire. Cependant, comme la compression d'image, la quantification implique une concession : une taille de modèle réduite peut réduire la précision.

Il existe différentes méthodes de quantification, chacune présentant ses propres avantages et inconvénients. Certains, comme AWQ et GPTQ, nécessitent une préquantification et sont disponibles sur des plates-formes telles que Hugging Face ou Kaggle. Par exemple, si vous appliquez les méthodes GPTQ sur le modèle Llama-2 13B et AWQ sur le modèle Gemma 7B, vous pouvez diffuser les modèles sur un seul GPU L4 au lieu de deux GPU L4 sans quantification.

Vous pouvez également effectuer une quantification à l'aide d'outils tels que AutoAWQ et AutoGPTQ. Ces méthodes peuvent améliorer la latence et le débit. En revanche, les techniques utilisant EETQ et la bibliothèque bitsandbytes pour la quantification ne nécessitent pas de modèles préquantifiés. Elles peuvent donc être un choix approprié lorsque les versions préquantifiées ne sont pas disponibles.

La meilleure technique de quantification à utiliser dépend de vos objectifs spécifiques et de la compatibilité de la technique avec le framework de diffusion que vous souhaitez utiliser. Pour en savoir plus, consultez le guide de quantification de Hugging Face.

Sélectionnez l'un de ces onglets pour voir un exemple d'application de la quantification à l'aide des frameworks TGI ou vLLM :

TGI

GKE est compatible avec les options de quantification suivantes avec TGI :

  • awq
  • gptq
  • eetq
  • bitsandbytes
  • bitsandbytes-nf4
  • bitsandbytes-fp4

Les méthodes de quantification AWQ et GPTQ nécessitent des modèles pré-quantifiés, tandis que les quantifications EETQ et bitsandbytes peuvent être appliquées à n'importe quel modèle. Pour en savoir plus sur ces options, consultez cet article Hugging Face.

Pour utiliser la quantification, définissez le paramètre -–quantize lorsque vous démarrez le serveur de modèle.

L'extrait suivant montre comment optimiser Gemma 7B avec la quantification bitsandbytes à l'aide de TGI sur GKE.

args:
- --model-id=$(MODEL_ID)
- --num-shard=2
- --quantize=bitsandbytes

Pour appliquer cette configuration, utilisez la commande suivante :

kubectl apply -f tgi/tgi-7b-bitsandbytes.yaml

vLLM

GKE accepte les options de quantification suivantes avec vLLM :

Pour utiliser la quantification de modèle avec vLLM, les modèles doivent être pré-quantifiés. Lorsque vous démarrez l'environnement d'exécution, définissez le paramètre –quantization.

L'extrait suivant montre comment optimiser le modèle Gemma 7B avec la quantification awq à l'aide de vLLM sur GKE :

args:
- --model=$(MODEL_ID)
- --tensor-parallel-size=1
- --quantization=awq
env:
- name: MODEL_ID
  value: google/gemma-7b-AWQ
resources:
  requests:
    nvidia.com/gpu: 1
  limits:
    nvidia.com/gpu: 1

Pour appliquer cette configuration, utilisez la commande suivante :

kubectl apply -f vllm/vllm-7b-awq.yaml

Améliorer la latence à l'aide la quantification du cache KV

Vous pouvez utiliser la quantification du cache KV FP8 E5M2 pour réduire considérablement l'espace mémoire utilisé du cache KV et améliorer la latence, en particulier pour les lots de grande taille. Toutefois, cela réduit la justesse des inférences.

Pour activer la quantification du cache KV FP8 E5M2, définissez le paramètre --kv-cache-dtype fp8_e5m2 :

args:
- --model=$(MODEL_ID)
- --tensor-parallel-size=1
- --kv-cache-dtype=fp8_e5m2
- --max-model-len=1200
resources:
  requests:
    cpu: "2"
    memory: "25Gi"
    ephemeral-storage: "25Gi"
    nvidia.com/gpu: 1
  limits:
    cpu: "2"
    memory: "25Gi"
    ephemeral-storage: "25Gi"
    nvidia.com/gpu: 1

Pour appliquer cette configuration, utilisez la commande suivante :

kubectl apply -f vllm/vllm-7b-kvcache.yaml

Bonne pratique : parallélisme de tenseur

Le parallélisme de tenseur est une technique qui répartit la charge de calcul sur plusieurs GPU, ce qui est essentiel lorsque vous exécutez des modèles volumineux qui dépassent la capacité de mémoire d'un seul GPU. Cette approche peut s'avérer plus rentable, car elle vous permet d'utiliser plusieurs GPU abordables au lieu d'un seul GPU coûteux. Cela peut également améliorer le débit d'inférence des modèles. Le parallélisme de tenseur exploite le fait que les opérations de tenseur peuvent être effectuées indépendamment sur de plus petits blocs de données.

Pour en savoir plus sur cette technique, consultez le guide sur le parallélisme de tenseur de Hugging Face.

Sélectionnez l'un de ces onglets pour voir un exemple d'application du parallélisme de tenseur à l'aide des frameworks TGI ou vLLM :

TGI

Avec TGI, l'environnement d'exécution de diffusion utilise par défaut tous les GPU disponibles pour le pod. Vous pouvez définir le nombre de GPU à utiliser en spécifiant le paramètre --num-shard avec le nombre de GPU comme valeur.

Consultez la documentation de Hugging Face pour obtenir la liste des modèles compatibles avec le parallélisme de tenseur.

L'extrait suivant montre comment optimiser le modèle Gemma 7B avec réglage des instructions à l'aide du parallélisme de tenseur et de deux GPU L4 :

args:
- --model-id=$(MODEL_ID)
- --num-shard=2

Pour appliquer cette configuration, utilisez la commande suivante :

kubectl apply -f tgi/tgi-7b-it-tensorparallelism.yaml

Dans les clusters GKE Autopilot, l'exécution de cette commande crée un pod avec des exigences minimales de ressources de 21 vCPU et 78 Gio de mémoire.

vLLM

vLLM est compatible avec l'inférence à parallélisme de tenseur distribuée. vLLM active cette fonctionnalité par défaut si plusieurs GPU sont disponibles.

L'extrait suivant montre comment vous pouvez optimiser le modèle Gemma 7B avec réglage des instructions à l'aide du parallélisme de tenseur et de deux GPU L4 :

args:
- --model=$(MODEL_ID)
- --tensor-parallel-size=2

Pour appliquer cette configuration, utilisez la commande suivante :

kubectl apply -f vllm/vllm-7b-it-tensorparallelism.yaml

Dans les clusters GKE Autopilot, l'exécution de cette commande crée un pod avec des exigences minimales de ressources de 21 vCPU et 78 Gio de mémoire.

Bonne pratique : optimisation de la mémoire du modèle

L'optimisation de l'utilisation de la mémoire des LLM est essentielle pour une inférence efficace. Cette section présente des stratégies d'optimisation de la couche "attention", telles que l'attention paginée (PagedAttention) et l'attention flash (FlashAttention). Ces stratégies améliorent l'efficacité de la mémoire, ce qui permet des séquences d'entrée plus longues et réduit le temps d'inactivité des GPU. Cette section explique également comment ajuster les tailles des entrées et des sorties du modèle en fonction des contraintes de mémoire et de l'optimisation de frameworks d'inférence spécifiques.

Optimisation de la couche Attention

Les couches d'auto-attention permettent aux modèles de comprendre le contexte des tâches de traitement du langage, car la signification des mots peut varier en fonction du contexte. Toutefois, ces couches stockent les poids des jetons d'entrée, les clés (K) et les valeurs (V) dans la vRAM du GPU. Ainsi, à mesure que la séquence d'entrée s'allonge, la taille et le temps de calcul augmentent de manière quadratique.

L'utilisation de la mise en cache KV est particulièrement utile lorsque vous travaillez avec de longues séquences d'entrée, où les coûts liés à l'auto-attention peuvent devenir importants. Cette approche d'optimisation réduit le traitement des calculs à la complexité linéaire.

Voici des techniques spécifiques permettant d'optimiser les mécanismes d'attention dans les LLM :

  • Attention paginée : l'attention paginée améliore la gestion de la mémoire pour les grands modèles et les longues séquences d'entrée à l'aide de techniques de pagination, semblables à la mémoire virtuelle de l'OS. Cela réduit efficacement la fragmentation et la duplication dans le cache KV, ce qui permet d'obtenir des séquences d'entrée plus longues sans manquer de mémoire GPU.
  • Attention flash : l'attention flash réduit les goulots d'étranglement de la mémoire GPU en minimisant les transferts de données entre la RAM du GPU et le cache L1 lors de la génération des jetons. Cela élimine les temps d'inactivité des cœurs de calcul, ce qui améliore considérablement les performances d'inférence et d'entraînement pour les GPU.

Régler la taille des entrées et des sorties du modèle

Les besoins en termes de mémoire dépendent de la taille des entrées et des sorties. Une sortie plus longue et plus de contexte nécessitent plus de ressources, tandis qu'une sortie plus courte et un contexte moindre peuvent réduire les coûts en utilisant un GPU plus petit et moins cher.

Sélectionnez l'un de ces onglets pour voir un exemple de réglage des exigences de mémoire d'entrée et de sortie du modèle dans les frameworks TGI ou vLLM :

TGI

L'environnement d'exécution de diffusion TGI vérifie les besoins en mémoire au démarrage et ne démarre pas si l'espace mémoire maximal possible du modèle dépasse la mémoire GPU disponible. Cette vérification élimine les plantages liés à une mémoire insuffisante (OOM) sur les charges de travail exigeantes en mémoire.

GKE est compatible avec les paramètres TGI suivants pour optimiser les besoins en mémoire du modèle :

L'extrait suivant montre comment diffuser un modèle Gemma 7B avec réglage des instructions et un seul GPU L4, avec les réglages de paramètres suivants : --max-total-tokens=3072, --max-batch-prefill-tokens=512, --max-input-length=512 :

args:
- --model-id=$(MODEL_ID)
- --num-shard=1
- --max-total-tokens=3072 
- --max-batch-prefill-tokens=512
- --max-input-length=512
env:
- name: MODEL_ID
  value: google/gemma-7b

Pour appliquer cette configuration, utilisez la commande suivante :

kubectl apply -f tgi/tgi-7b-token.yaml

vLLM

Dans vLLM, configurez la longueur de contexte du modèle, ce qui a un impact direct sur la taille du cache KV et les besoins en RAM du GPU. Les longueurs de contexte plus courtes permettent d'utiliser des GPU plus abordables. La valeur par défaut est le nombre maximal de jetons acceptés par le modèle. Limitez la longueur maximale du contexte avec --max-model-len MAX_MODEL_LEN si nécessaire.

Par exemple, le modèle Gemma 7B avec réglage des instructions, avec sa longueur de contexte par défaut de 8 192, dépasse la capacité de mémoire d'un seul GPU NVIDIA L4. Pour effectuer le déploiement sur un GPU L4, limitez la longueur combinée des requêtes et des sorties en définissant --max-model-len sur une valeur inférieure à 640. Cet ajustement permet d'exécuter le modèle sur un seul GPU L4 malgré sa longueur de contexte par défaut élevée.

Pour effectuer le déploiement avec la limite de jetons modifiée, utilisez l'extrait de code suivant :

args:
- --model=$(MODEL_ID)
- --tensor-parallel-size=1
- --max-model-len=600

Pour appliquer cette configuration, utilisez la commande suivante :

kubectl apply -f vllm/vllm-7b-token.yaml

Checklist

Objectif d'optimisation Entrainement
Latence
  • Donnez la priorité aux GPU puissants : Envisagez de passer à des GPU offrant des capacités de calcul et un débit d'E/S de mémoire plus élevés.
  • Explorez la quantification : Des techniques telles que la quantification AWQ peuvent améliorer la latence, mais soyez attentif aux compromis potentiels sur la précision.
Débit
  • Utilisez le scaling horizontal : Augmentez le nombre d'instances répliquées de diffusion (pods) pour répartir la charge de travail.
  • Utilisez le parallélisme de tenseur : Pour les modèles volumineux qui dépassent la capacité d'un seul GPU, utilisez le parallélisme de tenseur pour répartir les calculs sur plusieurs GPU. Pour les modèles plus petits, envisagez d'utiliser plusieurs instances répliquées avec un parallélisme de tenseur de "1" pour éviter des frais généraux.
  • Groupez les requêtes et quantifiez : Combinez les requêtes et explorez les techniques de quantification qui maintiennent une précision acceptable.
Rentabilité
  • Choisissez des modèles plus petits : Sélectionnez des modèles dans une famille répondant à vos contraintes de ressources et à votre budget.
  • Appliquez la quantification : Utilisez la quantification pour réduire les besoins en mémoire, en particulier lorsque vous travaillez avec des modèles plus volumineux.
  • Limitez la longueur de contexte : Limitez la longueur de contexte pour réduire davantage l'utilisation de la mémoire et permettre l'exécution sur des GPU plus petits et plus économiques.

Étape suivante