LLMOps

Conteneuriser l'inférence des LLM : Bonnes pratiques pour les builds d'images multi-architecture en LLMOps

L'essor des grands modèles de langage (LLM) a fondamentalement modifié les exigences en matière d'infrastructure. Contrairement aux microservices traditionnels, les charges de travail d'inférence des LLM sont gourmandes en ressources et très sensibles aux capacités matérielles. Lorsque les organisations passent du développement à la production, elles rencontrent un paysage matériel fragmenté : des clusters GPU pour l'entraînement et l'inférence à haut débit, des instances CPU uniquement pour les déploiements edge ou optimisés en coûts, et des architectures CPU variées (amd64 vs. arm64) chez les fournisseurs cloud. Garantir que vos conteneurs d'inférence LLM fonctionnent de manière fiable sur cet ensemble nécessite des stratégies de build sophistiquées.

Le défi du matériel hétérogène

Une image Docker standard construite sur un ordinateur portable de développement doté d'un GPU NVIDIA échouera sur un nœud de production CPU uniquement, et vice versa. De plus, mélanger des bibliothèques CUDA dans une image de base crée un gonflement et des conflits de dépendances si la machine cible ne dispose pas des pilotes NVIDIA. En LLMOps, nous devons séparer les préoccupations : la logique applicative, les poids du modèle et les dépendances d'exécution (CUDA/CPU) doivent être modulaires.

Stratégie 1 : Séparer les images de base par architecture

Au lieu d'une image « universelle » monolithique, maintenez des images de base distinctes pour différentes cibles matérielles.

  • Image GPU : Basée sur nvidia/cuda:11.8-runtime-ubuntu22.04. Inclut les toolkits CUDA et des bibliothèques spécifiques aux GPU comme flash-attn.
  • Image CPU : Basée sur python:3.10-slim. Inclut MKL ou OpenBLAS pour des calculs CPU optimisés, mais exclut toutes les dépendances CUDA.

Stratégie 2 : Gestion modulaire des dépendances

Utilisez le mécanisme de dépendance conditionnelle de Python pour installer les paquets uniquement lorsque le matériel pertinent est détecté. Cela prévient les problèmes ImportError sur les nœuds incompatibles.


# requirements-gpu.txt
torch==2.0.1+cu118
transformers==4.36.0
flash-attn==2.3.6  # GPU uniquement

# requirements-cpu.txt
torch==2.0.1
transformers==4.36.0

Dans votre Dockerfile, utilisez des arguments de build pour sélectionner le fichier de requirements correct :


ARG BUILD_VARIANT=cpu
RUN if [ "$BUILD_VARIANT" = "gpu" ]; then \
        pip install -r requirements-gpu.txt; \
    else \
        pip install -r requirements-cpu.txt; \
    fi

Stratégie 3 : Exploiter BuildX pour les builds multi-architecture

Pour les cibles CPU, vous devez prendre en charge à la fois amd64 et arm64 (courant dans AWS Graviton et Apple Silicon). Utilisez Docker Buildx pour créer des manifestes multi-architecture sans avoir besoin de matériel réel pour chaque architecture.


docker buildx create --use
docker buildx build --platform linux/amd64,linux/arm64 \
    --build-arg BUILD_VARIANT=cpu \
    -t my-llm-app:cpu-mixed .

Stratégie 4 : Séparation des poids du modèle

Ne brûlez jamais de grands poids de modèle (7B-13B paramètres) dans votre image Docker. Les images deviennent de plusieurs Go, ralentissant les pipelines CI/CD et les tirages du registre. Au lieu de cela, utilisez des montages de volumes ou tirez les poids depuis un registre (comme Hugging Face Hub) au démarrage du conteneur. Cela garantit que votre image reste légère et versionnable, tandis que les modèles peuvent être mis à jour indépendamment.

Meilleures pratiques d'implémentation CI/CD

1. Mettre en cache les couches agressivement : Gardez pip install pour les dépendances de base dans les couches initiales. N'copiez le code applicatif que dans les couches ultérieures.
2. Contrôles de santé : Implémentez CMD HEALTHCHECK qui vérifie la disponibilité du GPU (en utilisant nvidia-smi) ou le nombre de cœurs CPU avant de déclarer le service prêt.
3. Convention de balisage : Utilisez des balises comme my-llm:v1.0-gpu et my-llm:v1.0-cpu pour prévenir les erreurs de déploiement accidentelles.

Conclusion

Conteneuriser l'inférence des LLM ne consiste pas à créer une image unique qui convient à tous. Il s'agit de construire un portefeuille d'artefacts optimisés et spécifiques au matériel. En découplant les dépendances d'exécution, en exploitant les arguments de build et en séparant les poids du modèle du code applicatif, vous pouvez obtenir des déploiements LLM fiables, évolutifs et économiques sur n'importe quelle topologie matérielle. Commencez par des Dockerfiles modulaires tôt dans votre pipeline LLMOps pour éviter la dette technique lorsque votre infrastructure se développe.

Share: