ARCHITECTURE
La couche de serving des LLM locaux : vLLM, SGLang, llama.cpp et Ollama
Une fois le modèle choisi, la première décision d'architecture est de savoir quelle couche de serving va l'exécuter. Cette note sépare quatre couches non par des listes de fonctionnalités mais par leur approche architecturale de l'ordonnancement, de la mémoire, de la réutilisation et de l'empaquetage.
Quand une organisation a choisi le modèle qu'elle compte exécuter, ce qu'elle tient est un fichier : des poids, un vocabulaire et le format de prompt que le modèle attend. Transformer ce fichier en service est un autre logiciel. Il met en file les requêtes entrantes, répartit la mémoire du GPU, décide combien d'utilisateurs peuvent tenir une conversation en même temps, et expose une surface d'API.
Cette note sépare quatre noms — vLLM, SGLang, llama.cpp et Ollama — non par un tableau de fonctionnalités, mais par leur approche architecturale selon quatre axes : ordonnancement, mémoire, réutilisation et empaquetage. Elle ne les classe pas en vitesse, et chaque chiffre ci-dessous est cité avec la date et la base de comparaison de sa propre source. Les calculs du côté du fichier de modèle — niveaux de quantification, arithmétique de la VRAM et valeurs par défaut de la fenêtre de contexte — relèvent du choix du modèle et ne sont pas repris ici.
Ce que fait la couche de serving, ce que fait le fichier de modèle
Deux responsabilités se séparent. Le fichier de modèle porte les poids, le vocabulaire et le format de prompt. La couche de serving prend en charge l'ordonnancement, la gestion de la mémoire, la concurrence et la surface d'API. Cette séparation a une conséquence concrète : le même fichier de modèle peut tourner sur les quatre couches, mais combien d'utilisateurs obtiennent une réponse en même temps sur le même matériel, c'est la couche de serving qui le décide, pas le fichier.
En pratique, la couche prend en général la forme d'un serveur HTTP : la documentation du serveur de llama.cpp décrit son propre outil comme un serveur HTTP léger, en C/C++ pur, construit sur httplib et nlohmann::json, offrant des API REST et une interface web. Les substrats se recouvrent aussi — le 15 mai 2025, Ollama a annoncé son propre moteur au-dessus de la bibliothèque de tenseurs GGML pour les modèles multimodaux, en indiquant dans le même billet s'être appuyé jusque-là sur le projet llama.cpp pour la prise en charge des modèles.
Approches du batching : statique, dynamique et continu
La première décision que prend une couche de serving est la façon de regrouper les requêtes qui arrivent en même temps.
- Un lot fixe : les requêtes sont réunies en un ensemble, exécutées comme un tout, et le lot suivant ne démarre pas tant que toutes les requêtes du précédent ne sont pas terminées. Cette étiquette est purement descriptive ; ce n'est pas un terme tiré d'une source.
- Le batching dynamique : le lot est formé par le serveur à l'exécution. Les requêtes attendent un instant et partent dès que la taille préférée est atteinte ou que le délai imparti est écoulé.
- Le batching continu : le contenu du lot est décidé de nouveau à chaque itération. Une requête terminée sort, et une requête en attente entre dès que l'itération en cours s'achève.
La forme documentée de l'approche intermédiaire se trouve dans NVIDIA Triton (voir Triton Inference Server — Dynamic Batcher). Le batching dynamique combine des requêtes individuelles en un seul lot à un moment que le serveur choisit. L'attente est bornée par max_queue_delay_microseconds, et la configuration d'exemple utilise 100 microsecondes.
La source primaire de la troisième approche est Orca (USENIX OSDI '22, pp. 521-538). L'article énonce d'abord le constat : jusque-là les systèmes ordonnançaient l'exécution à la granularité de la requête, en gardant un ensemble fixe de requêtes jusqu'à ce que toutes celles du lot soient terminées, si bien qu'une requête achevée tôt ne pouvait pas revenir au client et qu'une arrivée plus tardive attendait que le lot en cours soit entièrement vidé. La réponse d'Orca consiste à descendre l'ordonnancement à la granularité de l'itération : l'ordonnanceur sélectionne les requêtes à exécuter, appelle le moteur pour une seule itération, et collecte les résultats. C'est de là que vient ce qu'on appelle aujourd'hui le batching continu.
L'article définit aussi une seconde technique : le batching sélectif. L'itération suivante de deux requêtes ne peut pas toujours être fusionnée — si les deux sont en phase d'initiation avec des nombres différents de tokens d'entrée, ou si chacune est dans une phase distincte, les formes des tenseurs d'attention ne coïncident pas. Le batching n'est donc pas appliqué à toutes les opérations mais à certaines. Orca rapporte un débit 36,9 fois supérieur à celui de NVIDIA FasterTransformer sur GPT-3 175B au même niveau de latence : une mesure de 2022, face à la base de comparaison de l'époque.
Les termes ne sont pas tracés de la même façon dans tous les documents. La documentation du serveur de llama.cpp définit le drapeau --cont-batching comme « continuous batching (a.k.a dynamic batching) » et l'active par défaut. La séparation conceptuelle ci-dessus appartient à la terminologie de vLLM et de SGLang.
PagedAttention : découper le cache KV en pages
Une fois l'ordonnancement descendu à la granularité de l'itération, la gestion de la mémoire devient le facteur décisif. La raison est le comportement du cache KV : il est volumineux pour chaque requête et il grandit et rétrécit au fil de la génération. L'article sur PagedAttention le dit directement — ce comportement détermine la taille du lot (voir Efficient Memory Management for Large Language Model Serving with PagedAttention, arXiv:2309.06180, SOSP '23).
Ce que l'article mesure, c'est la part : seuls 20,4 % à 38,2 % d'un bloc de mémoire contigu préalloué contiennent des états de tokens réels, contre 96,3 % pour vLLM dans la même mesure. Le reste part en emplacements réservés pour des tokens futurs et en espace produit par une allocation dimensionnée sur la longueur de séquence maximale.
La réponse vient des systèmes d'exploitation. PagedAttention découpe le cache KV d'une requête en blocs, chacun contenant les vecteurs de clé et de valeur d'un nombre fixe de tokens, et n'exige pas que ces blocs soient contigus en mémoire physique. L'analogie de l'article porte la section : "one can think of blocks as pages, tokens as bytes, and requests as processes".
La comptabilité en découle. Chaque entrée de la table de blocs contient un numéro de bloc physique et le nombre d'emplacements remplis dans ce bloc ; le cache d'une requête est une suite de blocs logiques remplis de gauche à droite, et un nouveau bloc physique n'est alloué qu'une fois les précédents pleins. Chaque bloc physique porte un compteur de références et la copie sur écriture s'applique à la granularité du bloc : deux sorties qui partagent un prompt conservent une seule copie de l'état du prompt, et une écriture alloue un nouveau bloc.
Le gain du partage a été mesuré : en recherche en faisceau, le partage de blocs donne des économies de mémoire allant jusqu'à 55 %. L'article rapporte un débit 2 à 4 fois supérieur face à FasterTransformer et Orca au même niveau de latence — une mesure de 2023 face aux bases de comparaison de l'époque.
RadixAttention et cache de préfixe : réutiliser le contexte partagé
Le troisième changement de granularité porte sur la réutilisation. PagedAttention a rendu le partage bon marché à l'intérieur d'une requête ; SGLang vise le partage entre requêtes (voir SGLang: Efficient Execution of Structured Language Model Programs, arXiv:2312.07104).
Ce qui distingue RadixAttention est simple : il conserve le cache dans un arbre radix une fois la génération terminée, au lieu de le jeter. Un arbre radix est une forme économe en espace de l'arbre préfixe, dont les arêtes peuvent porter des séquences de longueur variable plutôt que des éléments isolés. L'arbre associe des séquences de tokens à leurs tenseurs de cache KV, et les prompts comme les générations sont mis en cache.
C'est une couche, pas une alternative. L'article indique clairement que RadixAttention est compatible avec le batching continu, l'attention paginée et le parallélisme de tenseurs, et que lorsqu'il n'y a pas de correspondance en cache, la surcharge en mémoire et en temps qu'il introduit est négligeable.
La récupération suit une politique LRU et part des feuilles : la feuille la moins récemment utilisée part en premier, et les ancêtres partagés restent réutilisables jusqu'à devenir eux-mêmes des feuilles. Aucun pool de cache de taille fixe n'est prélloué ; les tokens en cache et les requêtes en cours partagent le même pool mémoire.
L'ordonnancement tient compte du cache lui aussi : les requêtes sont triées par longueur de préfixe correspondant. Le théorème 3.1 établit que lorsque la taille du cache n'est pas inférieure à la longueur maximale d'une requête, l'ordre « plus long préfixe partagé d'abord » équivaut à un parcours en profondeur de l'arbre radix et donne le taux de correspondance optimal.
Là où cela paie est concret : les prompts à quelques exemples, l'historique de conversation multi-tours et le contexte partagé dans une chaîne RAG. Sur ces jeux de tests, le taux de correspondance va de 50 % à 99 %, et l'ordonnancement conscient du cache approche en moyenne 96 % du taux optimal. Lors d'un déploiement d'un mois sur Chatbot Arena, des taux de 52,4 % pour LLaVA-Next-34B et de 74,1 % pour Vicuna-33B ont été observés, avec un temps moyen jusqu'au premier token réduit d'un facteur 1,7 pour Vicuna-33B — une observation de 2024, propre à cette charge.
vLLM propose la même fonction aujourd'hui ; la différence est structurelle. Dans vLLM, l'identité d'un bloc repose sur un hachage : le hachage du bloc est formé du hachage du bloc parent et des tokens de ce bloc, seuls les blocs pleins sont mis en cache, et un sel entre dans le hachage pour séparer les caches en environnement multi-locataire. L'algorithme de hachage par défaut est sha256 depuis la v0.11, et le cache de préfixe est actif par défaut. Côté SGLang, la même fonction est bâtie sur un arbre radix avec éviction LRU des feuilles — même fonction, structure de données différente.
GGUF et le binaire unique : empaquetage et modèle d'exploitation
Le quatrième axe quitte l'ordonnancement et la mémoire : l'empaquetage. GGUF est un format de fichier pour stocker des modèles destinés à l'inférence avec GGML et les exécuteurs fondés sur GGML. Ses objectifs de conception sont listés dans l'ordre dans la spécification : déploiement en un seul fichier — les fichiers peuvent être distribués et chargés facilement et n'exigent aucun fichier externe ; extensibilité ; compatibilité mmap ; facilité d'usage ; et information complète — tout ce qu'il faut pour charger un modèle est contenu dans le fichier.
Ce dernier objectif n'est pas abstrait. Les clés de métadonnées générales sont définies comme general.architecture, general.quantization_version, general.file_type et general.alignment. Le tokenizer voyage intégralement dans le fichier : vocabulaire, règles de fusion, types de tokens et identifiants des tokens spéciaux. La clé la plus frappante est tokenizer.chat_template — un modèle Jinja décrivant le format d'entrée attendu par le modèle, logé dans le même fichier que les poids.
Dans un déploiement on-premise, il faut distinguer deux « fichiers uniques » différents. Le premier est le fichier GGUF unique du modèle. Le second est l'exécutable lui-même : la première ligne de fonctionnalités de llama.cpp est une implémentation en C/C++ pur sans dépendances, et l'unité de distribution est un binaire téléchargeable plutôt qu'une pile de conteneurs.
L'unité d'empaquetage d'Ollama est le Modelfile, décrit dans la documentation comme le plan pour créer et partager un modèle. Ses instructions sont FROM, PARAMETER, TEMPLATE, SYSTEM, ADAPTER, LICENSE et MESSAGE ; FROM accepte un nom de modèle existant, un répertoire Safetensors ou le chemin d'un fichier GGUF. La quantification s'attache à l'étape de création, sous la forme documentée ollama create --quantize q4_K_M, puis le modèle se partage avec ollama push et s'exécute de l'autre côté avec ollama run. Ce que signifie un niveau de quantification du côté de la qualité et de la mémoire relève du choix du modèle ; le sujet ici est l'empaquetage lui-même.
Là où l'écosystème a convergé : le dépôt TGI passé en archive
Le signal le plus récent expliquant pourquoi ces quatre noms sont cités ensemble vient d'un projet qui a fermé. Le dépôt Text Generation Inference de Hugging Face a été archivé le 21 mars 2026 ; la date figure dans le bandeau de la page du dépôt. Le README porte un avis de mode maintenance.
Ce qui compte est la deuxième phrase de ce même avis. Elle indique que TGI a initié le mouvement amenant les moteurs d'inférence optimisés à s'appuyer sur les architectures de modèles transformers, et oriente le lecteur nommément vers vLLM, SGLang, et les moteurs exécutés localement et inter-compatibles tels que llama.cpp ou MLX. Trois des quatre couches de cette note sont nommées dans le texte du projet qui ferme.
La chronologie est courte : la dernière version étiquetée était la v3.3.7 du 19 décembre 2025. La liste des fonctionnalités du dépôt montre aussi ce qui comptait comme standard à cette date — parallélisme de tenseurs sur plusieurs GPU, batching continu des requêtes entrantes, une Messages API compatible avec l'OpenAI Chat Completion API, et du code d'inférence utilisant Flash Attention et Paged Attention.
Quelle couche pour quel scénario dans une installation on-premise
Le couple de chiffres le plus concret qui sépare ces couches par posture d'exploitation se trouve dans la documentation d'Ollama. OLLAMA_NUM_PARALLEL est le nombre de requêtes simultanées par modèle et vaut 1 par défaut ; OLLAMA_MAX_LOADED_MODELS est le nombre de modèles chargeables en même temps et vaut 3 par GPU par défaut. Ces valeurs par défaut décrivent une posture : faible concurrence, beaucoup de modèles.
Les valeurs par défaut de vLLM et de SGLang décrivent l'inverse : un modèle, forte concurrence. vLLM ordonnance en premier arrivé premier servi ; comme les blocs d'une séquence sont accédés ensemble, l'éviction s'applique en tout ou rien. Le parallélisme de tenseurs à la manière de Megatron-LM est pris en charge, et comme chaque fragment de modèle traite le même ensemble de tokens d'entrée, un seul gestionnaire de cache KV et une seule correspondance de blocs logiques vers physiques sont conservés dans l'ordonnanceur central.
SGLang se décrit comme un framework de serving qui passe d'un seul GPU à de grands clusters distribués. Les valeurs par défaut de ses arguments de serveur confirment la posture : cache radix actif, lru comme politique d'éviction, taille de page 1, taille de parallélisme de tenseurs 1.
La posture de llama.cpp est l'installation minimale : une liste de backends allant de BLAS à CUDA, Metal et Vulkan, et une inférence hybride CPU+GPU qui accélère partiellement les modèles plus grands que la capacité totale de VRAM. Côté Ollama, les répertoires de modèles vivent à des emplacements documentés et se déplacent avec la variable d'environnement OLLAMA_MODELS ; là où la localisation des données est une condition écrite, ce chemin appartient aussi au document de déploiement.
La surface d'API standardise l'accès au modèle ; la couche qui standardise l'accès du modèle aux systèmes d'entreprise en est une autre, et elle est traitée dans la note « Ce qu'est un serveur MCP, et ce qu'il n'est pas ».
Le pendant architectural de la question de savoir qui peut voir quel contexte dans une installation partagée est développé dans la note « Contrôle d'accès dans le RAG : qui voit quoi ».
Le rythme des versions est rapide. Au 8 août 2026, la version étiquetée de vLLM est la v0.26.0 (27 juillet 2026) et celle de SGLang la v0.5.17 (8 août 2026). Les noms de drapeaux et les valeurs par défaut de cette note correspondent à cette même date ; comparez-les à la documentation de votre propre version avant de déployer.
Sources
- Orca: A Distributed Serving System for Transformer-Based Generative Models — USENIX OSDI '22, pp. 521-538 (iteration-level scheduling, selective batching, the 36.9x measurement)
- Efficient Memory Management for Large Language Model Serving with PagedAttention — arXiv:2309.06180, SOSP '23 (block table, copy-on-write, token-state share)
- SGLang: Efficient Execution of Structured Language Model Programs — arXiv:2312.07104 (RadixAttention, cache-aware scheduling, Theorem 3.1)
- NVIDIA Triton Inference Server Documentation — Dynamic Batcher (max_queue_delay_microseconds)
- vLLM — GitHub repository, README.md and vllm/config/cache.py (feature list, prefix caching default)
- vLLM Documentation — Automatic Prefix Caching, Structured Outputs, OpenAI-Compatible Server, Parallelism and Scaling
- SGLang — GitHub repository and Documentation, Server Arguments (radix cache default, lru, page size 1)
- Text Generation Inference — GitHub repository (archived on 21 March 2026; last release v3.3.7)
- GGUF Specification — ggml-org/ggml, docs/gguf.md (design goals, tokenizer.chat_template)
- llama.cpp — GitHub repository and tools/server/README.md (dependency-free implementation, backend table)
- Ollama Documentation — Modelfile Reference, Import a model, OpenAI compatibility and FAQ (concurrency defaults, model directories)
- Ollama's new engine for multimodal models — Ollama Blog, 15 May 2025
- GitHub Releases — vllm-project/vllm v0.26.0 and sgl-project/sglang v0.5.17