Déployer un service de modèle avec vLLM
Quand on sert de nombreux utilisateurs à la fois, la question devient comment traiter le plus de requêtes possible avec la même carte graphique. Mesurer l'effet du traitement par lots avec le petit GPT, puis voir comment vLLM gère le cache KV et comment lancer un service compatible OpenAI.
- Environ 35 minutes
- Niveau : Approfondi
- Testé : 2026-09-15 expérience de lots exécutée sur CPU Apple M4 ; utilisation de vLLM d'après sa documentation officielle
Le code et les sorties des programmes sont reproduits tels qu’ils ont tourné : commentaires et sorties sont donc en chinois.
La leçon précédente faisait tourner un modèle sur votre ordinateur avec Ollama, très pratique pour une seule personne. Si vous voulez en faire un service, pour des centaines de personnes dans une entreprise ou pour les utilisateurs de votre produit, le problème change : des dizaines de requêtes arrivent en même temps, comment les traiter au plus vite avec une ou deux cartes graphiques ?
vLLM est l'un des frameworks open source de service de grands modèles les plus utilisés aujourd'hui. Cette leçon montre d'abord par l'expérience le problème central côté serveur, puis explique comment vLLM le résout et comment s'en servir.
vLLM vise surtout les serveurs Linux équipés de cartes graphiques NVIDIA ; ma machine d'expérience (un Mac sans carte NVIDIA) ne peut pas l'exécuter. Les expériences de cette leçon utilisent donc le petit GPT du module 09, et la partie sur l'utilisation de vLLM ne présente que les commandes de la documentation officielle, sans afficher de sorties que je n'ai pas obtenues moi-même.
Un poème à la fois, ou beaucoup à la fois ?
La leçon 6 du module 09 l'a dit : chaque étape de génération lit tous les paramètres du modèle pour ne calculer qu'un seul caractère. Sur un grand modèle, le temps de cette étape part surtout dans la lecture des paramètres depuis la mémoire graphique, et les unités de calcul passent l'essentiel du temps à attendre les données.
Et si l'on regroupait plusieurs requêtes, pour lire les paramètres une fois et calculer un caractère pour chacune en même temps ?
for batch in [1, 4, 16, 64]:
for _ in range(N_POEMS // batch):
model.generate(torch.full((batch, 1), NEWLINE), N_TOKENS) # batch 首诗同时写
python batch_throughput.py
一共要写 64 首,每首 60 个字,用 KV 缓存
一次写 1 首:总共 1.84 秒,每秒 2085 个字,每一首从开始到写完 0.03 秒
一次写 4 首:总共 0.93 秒,每秒 4110 个字,每一首从开始到写完 0.06 秒
一次写 16 首:总共 0.48 秒,每秒 8053 个字,每一首从开始到写完 0.12 秒
一次写 64 首:总共 0.44 秒,每秒 8706 个字,每一首从开始到写完 0.44 秒
Pour écrire les mêmes 64 poèmes : un par un, 1,84 seconde ; 16 à la fois, seulement 0,48 seconde, et le nombre de caractères par seconde (le débit) est multiplié par environ 4.
Mais regardez la dernière colonne. Avec 1 poème à la fois, chacun est écrit en 0,03 seconde ; avec 64 à la fois, chacun attend 0,44 seconde son résultat. Plus le lot est grand, plus l'efficacité globale est élevée, mais plus le temps d'attente de chaque requête (la latence) s'allonge.
En outre, de 16 à 64, le débit n'augmente presque plus (de 8053 à 8706) : la puissance de calcul du CPU est saturée. Une carte graphique a bien plus de puissance, et profite bien davantage du traitement par lots.
C'est le problème central que doit résoudre un service de modèle : regrouper le plus de requêtes possible dans une latence acceptable.
Deux difficultés des vrais services
Notre expérience est très idéale : 64 poèmes commencent en même temps, chacun de 60 caractères. Les vraies requêtes ne sont pas ainsi.
Les requêtes sont de longueurs différentes et arrivent à tout moment. Une requête doit écrire 20 caractères, une autre 2000. Si on les met dans un même lot et qu'on attend la fin de la plus longue pour traiter le lot suivant, les courtes sont terminées depuis longtemps mais occupent encore leur place, et les nouvelles requêtes doivent attendre. La solution de vLLM est le traitement par lots continu (continuous batching) : à chaque étape de génération, on vérifie ; les requêtes terminées quittent immédiatement le lot, les nouvelles y entrent aussitôt. Les requêtes du lot changent en permanence, et la carte graphique travaille toujours à pleine charge.
Le cache KV prend beaucoup de place, et sa taille est incertaine. La leçon 6 du module 09 et la leçon précédente l'ont calculé : le cache KV grandit avec le texte, et l'on ne sait pas d'avance combien chaque requête occupera au final. La méthode traditionnelle réserve à chaque requête un bloc contigu de mémoire graphique pour la longueur maximale, alors que la plupart des requêtes sont loin d'en avoir besoin : beaucoup de mémoire est gaspillée, et l'on peut traiter moins de requêtes à la fois.
PagedAttention
L'invention centrale de vLLM s'appelle PagedAttention, issue de l'article de 2023 « Efficient Memory Management for Large Language Model Serving with PagedAttention ». Elle s'inspire de la « pagination » avec laquelle les systèmes d'exploitation gèrent la mémoire :
传统做法:每个请求预留一整块,按最大长度
请求 A [■■■■□□□□□□□□□□□□] 用了 4 格,空着 12 格
请求 B [■■■■■■□□□□□□□□□□] 用了 6 格,空着 10 格
分页:KV 缓存切成固定大小的小块,用到哪儿分配到哪儿
块池 [A1][B1][A2][B2][B3][C1][空][空] ...
请求 A 的块表:A1 → A2
请求 B 的块表:B1 → B2 → B3
Le cache KV de chaque requête est découpé en petits blocs de taille fixe, qui n'ont pas besoin d'être contigus : quand un bloc est plein, on en alloue un autre, et chaque requête n'utilise que ce dont elle a réellement besoin. Il n'y a presque plus de gaspillage, et la même mémoire graphique sert bien plus de requêtes à la fois.
Quand plusieurs requêtes ont le même début (par exemple le même prompt system), ces blocs peuvent en plus être partagés au lieu d'être stockés séparément. « Les entrées servies depuis le cache coûtent moins cher », vu à la leçon 4 du module 06, repose côté serveur sur un mécanisme analogue.
Le résumé de l'article rapporte qu'à latence égale, le débit de vLLM était 2 à 4 fois celui des autres systèmes de l'époque, avec un gain d'autant plus net que les textes sont longs et le modèle grand.
Installation et démarrage
Les commandes ci-dessous sont citées de la documentation officielle de vLLM (septembre 2026) et demandent une machine Linux avec une carte graphique NVIDIA. vLLM prend aussi en charge les cartes AMD et Intel ainsi que certains CPU et autres matériels, mais l'installation diffère ; voyez la documentation officielle.
uv venv --python 3.12 --seed
source .venv/bin/activate
uv pip install vllm --torch-backend=auto
Lancer un service ne demande qu'une commande :
vllm serve Qwen/Qwen2.5-0.5B-Instruct
Par défaut, une interface compatible OpenAI est disponible sur http://localhost:8000. Comme avec Ollama à la leçon précédente, le code de la première partie fonctionne en changeant trois variables d'environnement :
export LLM_BASE_URL=http://localhost:8000/v1
export LLM_API_KEY=你设置的密钥
export LLM_MODEL=Qwen/Qwen2.5-0.5B-Instruct
Si le service doit être utilisé par d'autres, ajoutez impérativement une vérification de clé. Avec --api-key au démarrage, ou la variable d'environnement VLLM_API_KEY, le serveur vérifie la clé dans l'en-tête des requêtes. La leçon 6 du module 06 l'a dit : une interface sans aucune restriction mise en ligne, c'est payer pour les autres.
Plusieurs LoRA en même temps
Les leçons 2 et 3 ont montré un avantage de LoRA : un modèle de base et de nombreux petits adaptateurs. vLLM permet de charger plusieurs LoRA dans un même service, chaque requête indiquant lequel utiliser :
vllm serve Qwen/Qwen2.5-0.5B-Instruct \
--enable-lora \
--lora-modules repobot=./.cache/repobot-lora
En indiquant comme nom de modèle celui du LoRA, repobot, la requête utilise l'adaptateur fine-tuné à la leçon 3 ; avec le nom du modèle d'origine, elle utilise le modèle d'origine. Plusieurs clients ont chacun leur LoRA, mais n'occupent la mémoire graphique que d'un seul modèle de base.
Déployer soi-même, ou appeler une API ?
Arrivé ici, vous savez déployer un modèle vous-même. Faut-il le faire ? C'est une autre question.
Déployer soi-même, c'est : acheter ou louer des cartes graphiques, qui coûtent de l'argent même inactives ; gérer soi-même la montée en charge, la supervision, les pannes, les mises à jour ; et se limiter aux modèles que votre carte graphique peut accueillir.
Appeler une API, c'est payer à l'usage, sans coût d'inactivité, et pouvoir utiliser les meilleurs modèles. La leçon 4 du module 01 l'a calculé : les API comme DeepSeek sont déjà très bon marché.
En général, déployer soi-même mérite d'être envisagé dans ces cas : les données ne doivent pas quitter vos serveurs ; le volume d'appels est important et stable, si bien que le coût total du déploiement propre est moindre ; vous avez besoin d'un modèle que vous avez fine-tuné. Avant de décider, calculez réellement le coût des deux options selon la méthode de la leçon 4 du module 06.
Exercices
- Ajoutez les tailles de lot 8 et 32 dans
batch_throughput.py, et trouvez la taille de lot à partir de laquelle le débit n'augmente plus nettement sur votre ordinateur. - Modifiez l'expérience pour que les poèmes d'un même lot soient de longueurs différentes (par exemple la moitié de 20 caractères, l'autre de 120), le lot entier ne se terminant qu'avec le plus long. De combien le débit baisse-t-il par rapport à des poèmes tous de 60 caractères ? C'est exactement le problème que résout le traitement par lots continu.
- Si vous avez accès à une machine avec une carte graphique NVIDIA (un serveur cloud, par exemple), lancez vLLM avec les commandes de cette leçon et appelez-le avec RepoBot v4 du module 06.
Auto-test
1. Pourquoi le débit augmente-t-il quand on traite plusieurs requêtes ensemble ? Quel en est le prix ?
Chaque étape de génération lit une fois tous les paramètres du modèle, et sur un grand modèle, le temps part surtout dans cette lecture. En traitant plusieurs requêtes ensemble, une seule lecture des paramètres permet de calculer un caractère pour chacune, et les unités de calcul sont pleinement utilisées. Le prix est une latence plus longue pour chaque requête : elle est calculée avec les autres, et au-delà d'une certaine taille de lot, la puissance de calcul est saturée et le débit n'augmente plus.
2. Quel problème résout le traitement par lots continu ?
Les vraies requêtes sont de longueurs différentes et arrivent à tout moment. Si un lot doit attendre la fin de la plus longue avant de passer au suivant, les requêtes courtes terminées occupent encore leur place et les nouvelles doivent attendre. Le traitement par lots continu retire à chaque étape les requêtes terminées et ajoute les nouvelles, pour que la carte graphique travaille toujours à pleine charge.
3. Comment PagedAttention économise-t-elle la mémoire graphique ?
Elle découpe le cache KV de chaque requête en petits blocs de taille fixe, alloués à la demande, sans réserver un bloc contigu pour la longueur maximale : il n'y a presque plus de gaspillage. Les blocs de préfixe commun peuvent en plus être partagés entre requêtes. La mémoire économisée permet de traiter davantage de requêtes à la fois.
Questions et discussion
Bloqué sur cette leçon ? Posez votre question ici. Et si vous pouvez répondre à quelqu'un, n'hésitez pas.
Une question rapporte 3 points, une réponse 6. Les messages paraissent après vérification.
Chargement de la discussion…