Bonnes pratiques : inférence de l'IA sur Cloud Run avec des GPU

Cette page présente les bonnes pratiques à suivre pour optimiser les performances lorsque vous utilisez un service Cloud Run avec un GPU pour l'inférence de l'IA, en particulier pour les grands modèles de langage (LLM).

Vous devez créer et déployer un service Cloud Run capable de répondre en temps réel aux événements de scaling. Voici ce que vous devez faire:

  • Utilisez des modèles qui se chargent rapidement et qui nécessitent une transformation minimale en des structures compatibles avec les GPU, et optimisez leur chargement.
  • Utilisez des configurations qui permettent une exécution simultanée, efficace et maximale afin de réduire le nombre de GPU nécessaires pour diffuser un nombre cible de requêtes par seconde tout en réduisant les coûts.

Méthodes recommandées pour charger de grands modèles de ML sur Cloud Run

Nous vous recommandons de stocker les modèles de ML dans les images de conteneurs ou d'optimiser leur chargement depuis Cloud Storage.

Compromis pour le stockage et le chargement des modèles de ML

Voici un comparatif des options :

Emplacement du modèle Heure du déploiement Expérience en développement Délai de démarrage du conteneur Coût du stockage
Image du conteneur Lent. L'importation d'une image contenant un modèle volumineux dans Cloud Run prend davantage de temps. Les modifications apportées à l'image de conteneur nécessitent un redéploiement, qui peut être lent pour les images de grande taille. Dépend de la taille du modèle. Pour les modèles très volumineux, utilisez Cloud Storage pour des performances plus prévisibles mais plus lentes. Il peut y avoir plusieurs copies dans Artifact Registry.
Cloud Storage, chargé à l'aide de l'installation de volume Cloud Storage FUSE Rapide. Modèle téléchargé au démarrage du conteneur. Facile à configurer, ne nécessite pas de modifications de l'image Docker. Rapide lors des optimisations du réseau. N'exécute pas le téléchargement en parallèle. Une copie dans Cloud Storage.
Cloud Storage, téléchargé simultanément à l'aide de la commande Google Cloud CLI gcloud storage cp ou de l'API Cloud Storage, comme indiqué dans l'exemple de code de téléchargement simultané de transfer manager. Rapide. Modèle téléchargé au démarrage du conteneur. Un peu plus difficile à configurer, car vous devrez installer la Google Cloud CLI sur l'image ou mettre à jour le code pour utiliser l'API Cloud Storage. Rapide avec les optimisations réseau. La Google Cloud CLI télécharge le fichier de modèle en parallèle, ce qui est plus rapide que l'installation de FUSE. Une copie dans Cloud Storage.
Internet Rapide. Modèle téléchargé au démarrage du conteneur. Généralement plus simple (de nombreux frameworks téléchargent des modèles à partir de dépôts centraux). Souvent de mauvaise qualité et imprévisible :
  • Les frameworks peuvent appliquer des transformations de modèle lors de l'initialisation. (Vous devez le faire au moment de la compilation).
  • L'hôte du modèle et les bibliothèques permettant de le télécharger peuvent ne pas être efficaces.
  • Le téléchargement depuis Internet présente un risque de fiabilité. Votre service peut ne pas démarrer si la cible de téléchargement est indisponible et le modèle sous-jacent téléchargé peut changer, ce qui diminue la qualité. Nous vous recommandons d'héberger votre propre bucket Cloud Storage.
Dépend du fournisseur d'hébergement du modèle.

Stocker des modèles dans des images de conteneurs

Le stockage de modèles de ML dans des images de conteneurs déployées dans Cloud Run bénéficie des optimisations intégrées du streaming d'images de conteneurs de Cloud Run, qui maximisent le temps de chargement des fichiers sans optimisation réseau supplémentaire.

La création de conteneurs incluant des modèles de ML peut prendre un certain temps. Si vous utilisez Cloud Build, vous pouvez le configurer pour qu'il utilise des machines plus volumineuses pour accélérer les compilations. Pour ce faire, créez une image à l'aide d'un fichier de configuration de compilation incluant les étapes suivantes :

steps:
- name: 'gcr.io/cloud-builders/docker'
  args: ['build', '-t', 'IMAGE', '.']
- name: 'gcr.io/cloud-builders/docker'
  args: ['push', 'IMAGE']
images:
- IMAGE
options:
 machineType: 'E2_HIGHCPU_32'
 diskSizeGb: '500'
 

Vous pouvez créer une copie de modèle par image si la couche contenant le modèle est distincte entre les images (hachage différent). Des coûts Artifact Registry supplémentaires peuvent être facturés, car il peut y avoir une copie du modèle par image si votre couche de modèle est unique pour chaque image.

Stocker des modèles dans Cloud Storage

Pour optimiser le chargement de modèles ML à partir de Cloud Storage, que ce soit à l'aide de montages de volumes Cloud Storage ou directement à l'aide de l'API Cloud Storage ou de la ligne de commande, vous devez utiliser un VPC direct avec la valeur du paramètre de sortie définie sur all-traffic, ainsi que Private Service Connect.

Considérations relatives à la compilation, au déploiement, à l'exécution et à la conception du système

Les sections suivantes décrivent les considérations à prendre en compte pour la compilation, le déploiement, l'exécution et la conception du système.

Au moment de la compilation

La liste suivante présente les éléments à prendre en compte lorsque vous planifiez votre build :

  • Choisissez une bonne image de base. Commencez par une image provenant des conteneurs de deep learning ou du registre de conteneurs NVIDIA pour le framework de ML que vous utilisez. Les derniers packages liés aux performances sont installés sur ces images. Nous vous déconseillons de créer une image personnalisée.
  • Choisissez des modèles quantifiés sur 4 bits afin d'optimiser la simultanéité, sauf si vous pouvez prouver qu'ils affectent la qualité des résultats. La quantification produit des modèles plus petits et plus rapides, ce qui réduit la quantité de mémoire GPU nécessaire pour exécuter le modèle et peut augmenter le chargement en parallèle au moment de l'exécution. Idéalement, les modèles devraient être entraînés à la profondeur de bits cible plutôt que d'être quantifiés en conséquence.
  • Choisissez un format de modèle offrant des temps de chargement rapides afin de minimiser le temps de démarrage du conteneur, tel que le format GGUF. Ces formats reflètent plus précisément le type de quantification cible et nécessitent moins de transformations lors de leur chargement sur le GPU. Pour des raisons de sécurité, n'utilisez pas de points de contrôle au format pickle.
  • Créez et préchauffez les caches LLM au moment de la compilation. Démarrez le LLM sur la machine de compilation lors de la création de l'image Docker. Activez la mise en cache des requêtes et alimentez des requêtes courantes ou des exemples de requêtes afin de faciliter le préchauffage du cache pour une utilisation réelle. Enregistrez les résultats qu'il génère pour qu'ils soient chargés au moment de l'exécution.
  • Enregistrez votre propre modèle d'inférence que vous générez lors de la compilation. Cela permet de gagner un temps considérable par rapport au chargement de modèles stockés de façon moins efficace et l'application de transformations telles que la quantification au démarrage du conteneur.

Lors du déploiement

  1. Assurez-vous de définir la simultanéité des services avec précision dans Cloud Run.
  2. Ajustez les vérifications de démarrage en fonction de votre configuration.

Les vérifications de démarrage déterminent si le conteneur a démarré et est prêt à accepter le trafic. Tenez compte des points clés suivants lorsque vous configurez des vérifications de démarrage :

  • Temps de démarrage adéquat : laissez suffisamment de temps à votre conteneur, y compris aux modèles, pour s'initialiser et se charger complètement.
  • Vérification d'aptitude du modèle : configurez votre vérification d'aptitude de façon à ne passer ce test que lorsque votre application est prête à diffuser les requêtes. La plupart des moteurs de diffusion y parviennent automatiquement lorsque le modèle est chargé dans la mémoire GPU, ce qui évite les requêtes prématurées.

Notez qu'Ollama peut ouvrir un port TCP avant qu'un modèle ne soit chargé. Pour résoudre le problème :

  • Préchargez les modèles : consultez la documentation Ollama pour savoir comment précharger votre modèle au démarrage.

Lors de l'exécution

  • Gérez activement la longueur de contexte compatible. Plus la fenêtre contextuelle est petite, plus le nombre de requêtes pouvant être exécutées en parallèle est élevé. Les détails de la procédure à suivre dépendent du framework.
  • Utilisez les caches LLM que vous avez générés au moment de la compilation. Fournissez les mêmes options que celles utilisées au moment de la compilation lorsque vous avez généré la requête et le cache de préfixe.
  • Chargez le modèle à partir du modèle enregistré que vous venez d'écrire. Pour comparer les méthodes de chargement du modèle, consultez la section Compromis pour le stockage et le chargement des modèles.
  • Envisagez d'utiliser un cache clé-valeur quantifié si votre framework le permet. Cela peut réduire les exigences de mémoire par requête et permet de configurer davantage de parallélisme. Toutefois, cela peut aussi avoir un impact sur la qualité.
  • Ajustez la quantité de mémoire GPU à réserver pour les pondérations, les activations et les caches clé/valeur du modèle. Définissez une valeur aussi haute que possible sans obtenir d'erreur de mémoire insuffisante.
  • Configurez correctement la simultanéité dans votre code de service. Assurez-vous que votre code de service est configuré pour fonctionner avec les paramètres de simultanéité de votre service Cloud Run.
  • Vérifiez si votre framework dispose d'options permettant d'améliorer les performances de démarrage des conteneurs (par exemple, en utilisant le chargement en parallèle de modèle).

Au moment de la conception du système :

  • Ajoutez des caches sémantiques, le cas échéant. Dans certains cas, la mise en cache des requêtes et des réponses entières peut être un excellent moyen de limiter le coût des requêtes courantes.
  • Contrôlez la variance dans vos préambules. Les caches de requêtes ne sont utiles que lorsqu'ils contiennent les requêtes de manière séquentielle. Les caches sont effectivement mis en cache avec un préfixe. Les insertions ou les modifications dans la séquence signifient qu'elles ne sont pas mises en cache ou qu'elles ne sont que partiellement présentes.

Autoscaling et GPU

Cloud Run adapte automatiquement le nombre d'instances de chaque révision en fonction de facteurs tels que l'utilisation du processeur et la simultanéité des requêtes. Toutefois, Cloud Run n'adapte pas automatiquement le nombre d'instances en fonction de l'utilisation du GPU.

Pour une révision avec un GPU, si la révision n'utilise pas de manière significative le processeur, Cloud Run effectue un scaling horizontal pour la simultanéité des requêtes. Pour obtenir un scaling optimal pour la simultanéité des requêtes, vous devez définir un nombre maximal de requêtes simultanées par instance optimal, comme décrit dans la section suivante.

Nombre maximal de requêtes simultanées par instance

Le paramètre nombre maximal de requêtes simultanées par instance contrôle le nombre maximal de requêtes que Cloud Run envoie à une seule instance à la fois. Vous devez ajuster la simultanéité pour qu'elle corresponde à la simultanéité maximale que le code de chaque instance peut gérer tout en ayant de bonnes performances.

Simultanéité maximale et charges de travail d'IA

Lorsque vous exécutez une charge de travail d'inférence d'IA sur un GPU dans chaque instance, la simultanéité maximale que le code peut gérer avec de bonnes performances dépend du framework spécifique et des détails d'implémentation. Les éléments suivants ont un impact sur la façon dont vous définissez le paramètre optimal du nombre maximal de requêtes simultanées :

  • Nombre d'instances de modèle chargées sur le GPU
  • Nombre de requêtes parallèles par modèle
  • Utilisation de la méthode de traitement par lot
  • Paramètres de configuration de lot spécifiques
  • Quantité de travail non GPU

Si le nombre maximal de requêtes simultanées défini est trop élevé, les requêtes risquent d'attendre dans l'instance pour accéder au GPU, ce qui entraîne une augmentation de la latence. Si le nombre maximal de requêtes simultanées défini est trop faible, le GPU peut être sous-utilisé, ce qui entraîne un scaling horizontal de plus d'instances que nécessaire de la part de Cloud Run.

Voici une bonne règle de base pour configurer le nombre maximal de requêtes simultanées pour les charges de travail d'IA :

(Number of model instances * parallel queries per model) + (number of model instances * ideal batch size)

Par exemple, supposons qu'une instance charge 3 instances de modèle sur le GPU et que chaque instance de modèle peut gérer 4 requêtes en parallèle. La taille de lot idéale est également 4, car il s'agit du nombre de requêtes parallèles que chaque instance de modèle peut gérer. En suivant cette règle, vous devez définir le nombre maximal de requêtes simultanées 24 : (3 * 4) + (3 * 4).

Notez que cette formule n'est qu'une règle de base. Le paramètre de requêtes simultanées maximales idéal dépend des détails spécifiques de votre implémentation. Pour obtenir des performances optimales, nous vous recommandons d'effectuer un test de charge de votre service avec différents paramètres de requêtes simultanées maximales afin d'évaluer l'option la plus performante.

Compromis entre débit, latence et coûts

Consultez la section Compromis entre débit, latence et coûts pour connaître l'impact du nombre maximal de requêtes simultanées sur le débit, la latence et les coûts. Notez que tous les services Cloud Run qui utilisent des GPU disposent d'un processeur alloué en permanence.