Service LLM avancé
Pour déployer un grand modèle de langage (LLM) dans une infrastructure, il est essentiel de comprendre comment requêter le modèle, les quelques couches techniques immédiates qui l’entourent et les solutions disponibles pour un déploiement efficace.
Ce dont vous avez besoin pour mettre à disposition un LLM
Lorsqu’il s’agit de mettre en service des applications basées sur des LLM, il y a 2 composants principaux : le moteur et le serveur. Le moteur gère tout ce qui concerne les modèles et le regroupement des demandes, tandis que le serveur gère l’acheminement des demandes des utilisateurs.
Moteurs
Les moteurs sont les composants exécutant les modèles et tout ce que nous avons couvert jusqu’à présent sur le processus de génération avec différents types d’optimisations. À leur cœur, ce sont des bibliothèques Python. Ils gèrent le regroupement des demandes qui proviennent des utilisateurs vers notre chatbot et génèrent la réponse à ces demandes.
Serveurs
Les serveurs sont responsables de l’orchestration des requêtes HTTP/gRPC entrantes des utilisateurs. Dans les applications du monde réel, nous aurons de nombreux utilisateurs qui posent des questions à notre chatbot à différents moments de la journée. Les serveurs mettent ces demandes en file d’attente et les transfèrent vers le moteur pour la génération de la réponse. Les serveurs apportent également les métriques telles que le débit et la latence, qui sont importantes à suivre pour le service de modèle.
Résumé
- Moteurs
- Optimisation de la mémoire
- Optimisation spécifique au modèle
- Prise en charge du regroupement
- Serveurs
- API HTTP/gRPC
- Mise en file d’attente des demandes
- Mise en service de plusieurs modèles
- Prise en charge de plusieurs moteurs
Exemples d’outils de mise à disposition de LLM
Quels outils sont les mieux adaptés à nos besoins ? Comment choisir ? Voici un survol rapide de grands noms du milieu pour références.
Une recommandation de framework rapide à prendre en main et dont l’utilité a déjà été prouvée dans une de nos administrations se trouve à la fin et est développée dans le prochain paragraphe.
Moteurs
TensorRT-LLM est une bibliothèque open-source qui optimise les performances d’inférence des grands modèles de langage (LLM) en utilisant les GPU NVIDIA Tensor Core. Elle utilise le parallélisme tensoriel, propose une API Python simple et comprend des versions optimisées de LLM populaires. Elle prend en charge le batching en vol et vise à simplifier la construction et l’expérimentation de nouveaux LLM. Cependant, les utilisateurs doivent spécifier la longueur d’entrée/sortie maximale et la taille de lot avant de construire le moteur, et la gestion de la mémoire du cache KV n’est pas open source.
vLLM est une bibliothèque à hautes performances pour l’inférence et le service LLM, axée sur le débit de service et l’efficacité mémoire grâce à son mécanisme PagedAttention. Il prend en charge le batching continu, le parallélisme GPU et la sortie en streaming, ainsi que la compatibilité OpenAI. Cependant, la mémoire peut devenir un goulot d’étranglement avec des taux de demande élevés et de grandes tailles de lot.
Serveurs
RayLLM avec RayServe est construit sur un framework de calcul distribué qui simplifie le développement et le déploiement de modèles d’IA à grande échelle. Il prend en charge les points de terminaison multi-modèles, les fonctionnalités serveur et les optimisations via les intégrations avec vLLM et TGI.
Triton avec TensorRT-LLM fournit un logiciel d’inférence de serveur pour le déploiement et l’exécution efficaces de LLM avec des techniques telles que le batching en vol et le cache KV paginé.
Moteurs et serveurs
Génération de texte Inférence (TGI) est un serveur Rust, Python et gRPC utilisé chez HuggingFace pour HuggingChat, l’API d’inférence et le point de terminaison d’inférence. Il prend en charge le batching continu, le parallélisme tensoriel, la quantification, les mécanismes d’attention, le recuit simulé des logits et des LLM spécifiques. Cependant, la licence d’utilisation a été modifiée et n’est pas gratuite pour une utilisation commerciale.
Enfin, Fastchat est une solution auto-hébergée pour héberger des modèles d’IA génératifs et qui propose la gestion des modèles, des API OpenAI-compatibles et une web interface simple.
Nous allons développer FastChat dans la partie suivante car c’est un outil qui a été testé et qui semble fournir beaucoup des éléments nécessaires pour une utilisation de première intention.
Le choix d’une solution technique : le cas d’une administration
Premier cas : Les traitements par batch
Pour certains cas d’usage, l’enjeu est de traiter de nombreuses données avec le même mode opératoire en un coup de manière ponctuelle. C’est ce qu’on appellera le traitement par batch. Cela consiste à charger un modèle, le requêter sur un tableau de prompt et obtenir la sortie pour pouvoir l’exporter. On peut le faire avec vLLM par exemple avec un morceau de code de ce type :
from vllm import LLM, SamplingParams
import re
import pandas as pd
import re
import json
from tqdm import tqdm
from transformers import AutoTokenizer
= json.load(open("Data.json"))
list_data
= [ v for x,v in list_data.items()]
list_prompts = [ x for x,v in list_data.items() ]
list_ids
= SamplingParams(temperature=0.1, top_p=0.1, max_tokens=4096)
sampling_params = LLM(model="/data/models/hub/models--upstage--Llama-2-70b-instruct-v2/snapshots/36b2a974642846b40fbbafaabad936cd6f8a7632", tensor_parallel_size=2)
llm print("STARTING INFERENCE")
= llm.generate(list_prompts, sampling_params)
outputs
= { idx:output.outputs[0].text for idx, output in zip(list_ids, outputs) }
resume
open("Sortie.json", "w")) json.dump(resume,
Mais cette méthodologie a des limites, car cela nécessite de bloquer des gpus, ce qui entraîne des problématiques de gestion et de partage.
Deuxième cas : Beaucoup d’utilisateurs et/ou d’applications différents
Que ce soit une équipe de plusieurs data-scientists, ou un ensemble d’application, si les besoins sont importants, les GPUs ont tout intérêt à être partagés. Il ne sera donc pas possible que chaque script python charge son modèle en mémoire et bloque des GPUs. Il est également plus rassurant de séparer l’infrastructure GPU des utilisateurs pour que chacun travaille dans son environnement, afin d’éviter les casses accidentelles.
La solution qui consiste à mettre à disposition des APIs vient répondre à ces problématiques. Les modèles sont cachés derrière les API, les datascientist et les applications peuvent venir les requêter et n’ont pas besoin de s’occuper de l’infrastructure. Ainsi, plutôt que chaque datascientist déploie un même modèle avec réservation de GPU, l’architecture en API permet la mise en commun du déploiement au même besoin.
Dans ce guide, FastChat est présenté comme exemple pour la simplicité mais d’autres solutions existent, avec chacunes leurs avantages et inconvénients.
FastChat
FastChat propose des API OpenAI-compatibles pour ses modèles pris en charge, de sorte que vous puissiez utiliser FastChat comme une alternative locale aux API OpenAI. Cela permet d’utiliser la bibliothèque openai-python et les commandes cURL, ce qui facilite le travail des datascientists.
La documentation complète est disponible sur le repo du module
Nous allons tout de même parcourir les grandes étapes pour pouvoir lancer son installation et ensuite l’utiliser.
RESTful API Server
Tout repose sur la complémentarité de trois services : le controller, les modèles et l’API. Il faut commencer par lancer le controller.
python3 -m fastchat.serve.controller
Ensuite, les model_workers. (Un modèle vicuna est pris pour l’exemple.)
python3 -m fastchat.serve.model_worker --model-path lmsys/vicuna-7b-v1.5
Et enfin, l’API.
python3 -m fastchat.serve.openai_api_server --host localhost --port 8000
Utilisation avec l’OpenAI Official SDK
Le but de openai_api_server.py
est d’implémenter un serveur d’API entièrement compatible avec OpenAI, de sorte que les modèles puissent être utilisés directement avec la bibliothèque openai-python.
Tout d’abord, installez le package Python OpenAI >= 1.0 :
pip install --upgrade openai
Ensuite, interagissez avec le modèle Vicuna :
import openai
= "EMPTY"
openai.api_key = "http://localhost:8000/v1/"
openai.base_url
= "vicuna-7b-v1.5"
model = "Il était une fois"
prompt
# créer une complétion
= openai.completions.create(model=model, prompt=prompt, max_tokens=64)
completion # imprimer la complétion
print(prompt + completion.choices[0].text)
# créer une complétion de chat
= openai.chat.completions.create(
completion =model,
model=[{"role": "user", "content": "Bonjour ! Quel est votre nom ?"}]
messages
)# imprimer la complétion
print(completion.choices[0].message.content)
Utilisation avec curl
curl est un autre bon outil pour observer la sortie de l’API.
List Models:
curl http://localhost:8000/v1/models
Chat Completions:
curl http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "vicuna-7b-v1.5",
"messages": [{"role": "user", "content": "Hello! What is your name?"}]
}'
Text Completions:
curl http://localhost:8000/v1/completions \
-H "Content-Type: application/json" \
-d '{
"model": "vicuna-7b-v1.5",
"prompt": "Once upon a time",
"max_tokens": 41,
"temperature": 0.5
}'
Embeddings:
curl http://localhost:8000/v1/embeddings \
-H "Content-Type: application/json" \
-d '{
"model": "vicuna-7b-v1.5",
"input": "Hello world!"
}'
Combiner avec vLLM
Vous pouvez utiliser vLLM comme une mise en œuvre optimisée d’un travailleur dans FastChat. Il offre une mise en batch continue avancée et un débit beaucoup plus élevé (~10x). Consultez la liste des modèles pris en charge ici : https://docs.vllm.ai/en/latest/models/supported_models.html
Il suffit de remplacer le model_worker par le vllm_worker
python3 -m fastchat.serve.vllm_worker --model-path lmsys/vicuna-7b-v1.5
Combiner avec Docker
Pour permettre le lancement et l’arrêt de modèles, et pour éviter qu’une erreur dans un des modèles ne déregle l’ensemble du système, une bonne pratique est souvent de conteneuriser les différentes parties. Cela necessite la préparation de quelques fichiers et quelques tests, mais ensuite, cela assure la reproductibilité de votre infrastructure. Une fois que les images sont préparées, on peut les arrêter, les relancer et les reproduire autant de fois que nécessaire.
Une façon d’implémenter vos services avec FastChat est de faire :
- Un conteneur pour le controller
- Un conteneur pour l’API OpenAI like
- Un conteneur par modèle
Les conteneurs pourront tous avoir la même image de base où l’on a installé les packages necessaires, comme vllm et notamment FastChat, que l’on a téléchargé et copié dans notre arbre local :
Dockerfile :
FROM nvidia/cuda:12.2.0-devel-ubuntu20.04
ENV DEBIAN_FRONTEND noninteractive
RUN apt-get update -y && apt-get install -y python3.9 python3.9-distutils curl
RUN curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
RUN python3.9 get-pip.py
# Copiez le répertoire FastChat dans le conteneur Docker
COPY ./FastChat_k /FastChat
# COPY ./models/Mixtral-8x7B-Instruct-v0.1 /data/models/vllm/Mixtral-8x7B-Instruct-v0.1
# Allez dans le répertoire FastChat et installez à partir de ce répertoire
WORKDIR /FastChat
RUN pip3 install -e ".[model_worker]" pydantic==1.10.13
RUN pip3 install plotly==5.18.0
RUN pip3 install accelerate==0.25.0
RUN pip3 install vllm==0.4.1
RUN pip3 install minio==7.2.2
RUN pip3 install pynvml==11.5.0
# nvidia/cuda:12.2.0-runtime-ubuntu20.04 docker pull nvidia/cuda:12.2.0-devel-ubuntu20.04
Ensuite, il faut lancer les conteneurs docker avec la bonne commande pour que chaque docker remplisse bien sa fonction. Cela se gère avec des docker_compose.yml
Le fichier de déploiement des deux conteneurs obligatoires ressemblera à cela :
version: "3.9"
services:
fastchat-controller:
build:
context: .
dockerfile: Dockerfile
image: llm-api-light:1.0.0
network_mode: "host"
environment:
no_proxy: localhost,127.0.0.1,0.0.0.0
ports:
- "21001:21001"
volumes:
- ./FastChat:/FastChat
entrypoint: ["python3.9", "-m", "fastchat.serve.controller", "--host", "0.0.0.0", "--port", "21001"]
fastchat-openai:
build:
context: .
dockerfile: Dockerfile
image: llm-api-light:1.0.0
network_mode: "host"
environment:
no_proxy: localhost,127.0.0.1,0.0.0.0
ports:
- "8000:8000"
volumes:
- ./FastChat:/FastChat
entrypoint: ["python3.9", "-m", "fastchat.serve.openai_api_server", "--host", "0.0.0.0", "--port", "8000", "--api-keys", "key1,key2,key3", "--controller-address", "http://0.0.0.0:21001"]
Et le fichier de déploiement d’un modèle pourrait ressembler à ceci :
version: "3.9"
services:
fastchat-model-mixtral-latest:
network_mode: "host"
build:
context: .
dockerfile: Dockerfile
volumes:
- /data/models:/data/models
- ./FastChat:/FastChat
environment:
no_proxy: localhost,127.0.0.1,0.0.0.0
TRANSFORMERS_OFFLINE: 1
image: fastchat:cudadevel-latest
deploy:
resources:
reservations:
devices:
- driver: nvidia
device_ids: ["1", "5"]
capabilities: [gpu]
entrypoint: ["python3.9", "-m", "fastchat.serve.vllm_worker", "--model-path", "/data/models/vllm/Mixtral-8x7B-Instruct-v0.1",
"--worker-address", "http://0.0.0.0:26003", "--host", "0.0.0.0", "--port", "26003", "--controller", "http://0.0.0.0:21001",
"--trust-remote-code", "--model-names", "mixtral-instruct", "--num-gpus", "2"] # "--quantization", "awq" "--num-gpus", "2"
Pour finir, il suffit de lancer les commandes associées à chaque docker_compose pour lancer tous les services. Par exemple,
# Define your Docker Compose files
compose_openai_service="docker-compose_openai.yml"
compose_mixtral="docker-compose_mistral.yml"
# Execute Docker Compose commands
echo "Executing Docker Compose for $compose_openai_service"
docker compose -f $compose_openai_service up -d
echo "Executing Docker Compose for $compose_mixtral"
docker compose -f $compose_mixtral up -d
A ce stade, vous avez déjà une installation utilisable par plusieurs personnes (à condition que l’url soit accessible). Voici des exemples de code de cellules notebooks.
import openai
import requests
import json
# to get proper authentication, make sure to use a valid key that's listed in
# the --api-keys flag. if no flag value is provided, the `api_key` will be ignored.
= "key1" # 1rentrez l'api key
openai.api_key = "host" # mettre l'url du serveur
openai.api_base #eventuellement régler des problèmes de proxy
= openai.Model.list()
models for d in models["data"]:
print(d["id"])
# Instruct mode
= """Bonjour toi. Donne moi un pays qui commence par F.
prompt """
= openai.Completion.create(
completion ="mixtral?",
model=prompt,
prompt=25,
max_tokens=0.5,
temperature=1
top_p
)# print the completion
print(completion.choices[0].text)