📚 Table des matières
Cliquez sur une section pour y accéder directement
🔄 Pipeline
Vue d'ensemble du flux
✂️ Tokenizer
Texte → IDs
📐 Embedding
IDs → Vecteurs
🏗️ Transformer
Architecture d'une couche
👁️ Attention
Q, K, V expliqués
🧠 FFN
Le "cerveau" du modèle
📚 Layers
80 couches en profondeur
📊 Logits
Vecteur → Probabilités
🎲 Sampling
Température, top-p
💾 KV Cache
Éviter les recalculs
🧩 MoE
Mixture of Experts
🔧 Functions
Tool calling, MCP
🧠 Reasoning
o1, R1, CoT
🔢 Précision
FP16, INT8, INT4
💾 Mémoire
Calculs VRAM
🎓 Training
SFT, RLHF, DPO
⚡ Optim
Flash Attention, vLLM
🔄 Pipeline Complet
Le chemin du texte à la réponse générée
"Le chat mange" → PREFILL (parallèle) → "sa" → DECODE (séquentiel) → "pâtée." → <EOS>
Prefill = traiter tout le prompt en parallèle, GPU saturé (compute-bound). Decode = générer un token à la fois, on recharge 140 GB de poids par token (memory-bound). Le goulot d'étranglement n'est pas le calcul mais la bande passante mémoire (~3 TB/s sur H100).
🔍 Les 9 étapes du pipeline
"Le chat mange" (user)
↓
1. TOKENIZER : texte → IDs [2356, 7541, 64867]
↓
2. EMBEDDING : IDs → vecteurs [3 × 8192]
↓
3. POSITIONAL : + position (RoPE)
↓
4. PREFILL (parallèle, compute-bound) :
└→ 80 couches × [Attention → FFN]
└→ Stocke K,V dans le cache
↓
5. LOGITS : dernier vecteur → 128K scores
↓
6. SOFTMAX : scores → probabilités
↓
7. SAMPLING : top-p + température → "sa"
↓
8. DECODE (séquentiel, memory-bound) :
└→ "sa" traverse 80 couches
└→ KV cache réutilisé (pas recalculé)
└→ K,V de "sa" ajoutés au cache
└→ → "pâtée" → "." → <EOS>
↓
9. DETOKENIZER : IDs → "sa pâtée."
⚡ Pourquoi le decode est lent (memory-bound)
Pour générer 1 seul token, il faut :
• Charger 140 GB de poids (70B × 2 bytes) • Faire très peu de calcul (1 token × 8192 dims) Ratio calcul/transfert très faible ! H100 : 3 TB/s bande passante → 140 GB / 3 TB/s ≈ 47 ms minimum par token → ~21 tokens/sec théorique (1 batch) Avec batching (8 requêtes) : → Même 140 GB chargés, 8× plus de calcul → ~168 tokens/sec (8 × 21)
Le batching amortit le coût mémoire — c'est pour ça que vLLM bat llama.cpp en throughput.
✂️ Tokenization
Transformer le texte en nombres
"Le chat mange" → ["Le", "chat", "mange"] → [2356, 7541, 64867]
Le tokenizer découpe le texte en sous-mots (tokens) via l'algorithme BPE (Byte Pair Encoding). Chaque token reçoit un ID unique dans un vocabulaire de 128K entrées. Un mot peut devenir 1 ou plusieurs tokens : "aujourd'hui" → 3-4 tokens, "the" → 1 token.
🏷️ Tokens spéciaux
En plus des tokens de texte, il existe des tokens de contrôle :
| Token | Rôle | Exemple |
|---|---|---|
| <BOS> | Begin Of Sequence | Démarre la génération |
| <EOS> | End Of Sequence | Arrête la génération |
| <PAD> | Padding | Remplissage pour batch |
| <UNK> | Unknown | Token inconnu (rare) |
| [INST]...[/INST] | Instruction | Délimite user/assistant |
<EOS> est crucial : c'est lui qui dit au modèle de s'arrêter. Sans lui, le modèle continuerait indéfiniment.
🇫🇷 Pourquoi le français consomme ~1.5× plus de tokens ?
Les tokenizers sont entraînés sur des corpus majoritairement anglais.
"hello" → 1 token "bonjour" → 2 tokens (bon+jour) "today" → 1 token "aujourd'hui" → 3-4 tokens
Conséquence : contexte 8K tokens ≈ 6000 mots anglais mais ~4000 mots français.
📐 Embedding
Transformer les IDs en vecteurs sémantiques
[2356, 7541, 64867] → table[ID] → 3 vecteurs de 8192 dimensions
L'embedding est une simple lookup dans une table : table[ID] = vecteur. Chaque token devient un vecteur de 8192 dimensions. Les mots similaires ont des vecteurs proches dans cet espace. On ajoute le positional encoding (RoPE) pour que le modèle sache l'ordre des mots.
🎯 Pourquoi pas juste les IDs ?
Les IDs sont arbitraires : "chat" = 7541, "chien" = 9871 — aucune relation mathématique !
Avec embeddings : "chat" → [0.8, 0.3, -0.2, ...] ┐ "chien" → [0.7, 0.4, -0.1, ...] ┘ PROCHES ! "voiture"→ [-0.5, 0.9, 0.2, ...] LOIN
Le fameux : roi - homme + femme ≈ reine
🏗️ Architecture Transformer
La structure d'une couche
Matrice [3×8192] → 80 couches identiques (Attention+FFN) → Matrice [3×8192] transformée
Une couche = Attention → FFN, toujours dans cet ordre. L'Attention permet aux tokens de communiquer entre eux (20% des params). Le FFN transforme chaque token individuellement — c'est là que sont stockées les "connaissances" (80% des params). La connexion résiduelle (+) préserve l'info originale à travers les 80 couches.
👁️ Mécanisme d'Attention
Comment les tokens se regardent
"mange" cherche son sujet → score élevé avec "chat" (85%) → récupère l'info de "chat"
Q (Query) = ce que le token cherche. K (Key) = l'étiquette de chaque token. V (Value) = le contenu à récupérer. Le score Q × K détermine "qui regarde qui". Softmax transforme en probabilités. Résultat = somme pondérée des V. Le causal mask empêche de voir les tokens futurs.
🧠 Multi-Head Attention
Plusieurs "têtes" regardent en parallèle, chacune spécialisée :
Tête 1 : Relations sujet-verbe Tête 2 : Coréférences (sa → chat) Tête 3 : Proximité syntaxique LLaMA 70B : 64 têtes × 128 dimensions
⚡ GQA : Grouped-Query Attention
| Type | Q heads | KV heads | KV Cache |
|---|---|---|---|
| MHA | 64 | 64 | 100% |
| GQA | 64 | 8 | 12.5% |
| MQA | 64 | 1 | 1.5% |
GQA = compromis optimal, utilisé par LLaMA 2+.
🧠 Feed-Forward Network (FFN)
Le "cerveau" qui stocke les connaissances
Vecteur "mange" [8192] → expansion [28672] → SwiGLU → contraction [8192] enrichi
Le FFN expand la dimension ×3.5 (8192 → 28672), applique l'activation SwiGLU, puis contracte. Chaque token passe individuellement (pas de communication). C'est là que sont stockées les "connaissances" : chaque neurone encode un micro-concept (capitales, syntaxe Python, termes médicaux...).
🧠 Que font les neurones ?
Chaque neurone de la couche cachée est un détecteur de patterns :
Neurone 4821 : s'active sur "capitale de..." Neurone 12045 : s'active sur du code Python Neurone 8923 : s'active sur des termes médicaux Neurone 15678 : s'active sur des négations
Le modèle a appris ces patterns pendant le pre-training. C'est pourquoi le FFN représente 80% des paramètres : c'est la "mémoire" du modèle.
⚙️ SwiGLU vs ReLU
SwiGLU (Swish-Gated Linear Unit) est l'activation moderne :
ReLU(x) = max(0, x) → coupe les négatifs SwiGLU(x) = swish(xW₁) ⊙ (xW₃) → gate + smooth • Plus expressif que ReLU • Meilleure propagation du gradient • Utilisé par LLaMA, Mistral, Gemma
📊 Calcul des paramètres FFN
LLaMA 70B FFN (par couche) : W1 (gate) : 8192 × 28672 = 235M params W2 (down) : 28672 × 8192 = 235M params W3 (up) : 8192 × 28672 = 235M params Total FFN : 705M params/couche 80 couches × 705M = 56B params (80% du total)
📚 Traversée des 80 Couches
Spécialisation en profondeur
"Le chat mange" traverse 80 couches : syntaxe → grammaire → sémantique → raisonnement
Les couches se spécialisent : les basses traitent la syntaxe, les hautes le raisonnement. L'info circule via le residual stream : chaque couche ajoute son Δ sans perdre l'info précédente. Sortie finale = accumulation de contributions, pas remplacement.
🌊 Le Residual Stream = une rivière
Imagine le flux d'information comme une rivière qui traverse 80 villages (couches) :
┌─────────┐
Embedding ─────────▶│ Couche 1│──┬──────────────▶
(source) │ Attn+FFN│ │ +Δ₁ (affluent)
└─────────┘ │
▼
┌─────────┐
─────▶│ Couche 2│──┬──────────────▶
│ Attn+FFN│ │ +Δ₂ (affluent)
└─────────┘ │
▼
...80 couches...
│
▼
Logits
Chaque couche est comme un affluent qui enrichit la rivière sans remplacer ce qui coule déjà. La rivière principale (residual) transporte l'info originale + toutes les contributions.
🔍 Pourquoi c'est crucial ?
Sans connexions résiduelles, l'info de l'embedding serait perdue après quelques couches (vanishing gradient). Avec elles :
• L'info originale traverse directement • Chaque couche AJOUTE au lieu de REMPLACER • Gradient stable pour le training • Le modèle peut "skip" des couches inutiles
📊 Ce que fait chaque "zone" de couches
| Couches | Rôle | Exemple |
|---|---|---|
| 1-20 | Syntaxe locale | "le" → article, nom attendu |
| 20-40 | Structure grammaticale | "chat" = sujet de "mange" |
| 40-60 | Sémantique | "mange" → contexte nourriture |
| 60-80 | Raisonnement haut niveau | Qu'est-ce qui suit logiquement ? |
📊 Logits & Softmax
Du vecteur aux probabilités
Vecteur "mange" [8192] → LM Head → 128K logits → softmax → "sa": 62%
Le dernier vecteur est projeté vers 128K scores bruts (logits) — un par token du vocabulaire. Softmax transforme ces scores en probabilités (somme = 100%). Gros scores → grosses probas, petits → quasi-zéro. Puis vient le sampling pour choisir.
🎲 Sampling
Choisir le prochain token
Probas [62%, 18%, 12%...] → température 0.7 → top-p 0.9 → tirage → "sa" ✓
Température : ajuste les écarts. Basse (0.2) = déterministe, haute (1.5) = créatif/risqué. Top-p (nucleus) : garde les tokens jusqu'à p% cumulé, élimine les absurdités. Ensemble : température redistribue, top-p filtre → créatif mais pas fou.
🎛️ Combinaisons recommandées
| Usage | Température | Top-p |
|---|---|---|
| Code / Maths | 0.0 - 0.3 | 0.9 |
| Assistants | 0.7 | 0.9 |
| Créatif | 1.0 - 1.3 | 0.95 |
💾 KV Cache
Éviter les recalculs pendant la génération
Prefill : K₁₋₃, V₁₋₃ stockés → génère "sa" → ajoute K₄, V₄ → génère "pâtée"...
Pour générer le token N, l'attention regarde les tokens 1 à N-1. Sans cache : recalculer K,V pour TOUS (O(N²)). Avec cache : K,V stockés, on calcule seulement pour le nouveau (O(N)). On stocke K et V, pas Q (le nouveau génère sa propre Q).
📏 Calcul de la taille
LLaMA 70B, contexte 4K : Par token : 2 × 80 × 8 × 128 × 2 = 327 KB 4096 tokens : 327 KB × 4096 ≈ 1.3 GB 8 requêtes batch : 1.3 GB × 8 ≈ 10 GB
🧩 Mixture of Experts (MoE)
Plus de paramètres, même compute
"mange" → Router choisit Expert 2 + Expert 5 → 0.7×E2 + 0.3×E5 → output
Au lieu d'un seul gros FFN, on a 8 experts spécialisés. Un router sélectionne les 2 meilleurs pour chaque token. Avantage : plus de paramètres (47B) mais même compute (13B actifs). Inconvénient : tous les poids en VRAM quand même.
🔧 Function Calling, Tools, MCP & Agents
Le LLM qui interagit avec le monde
"Météo à Paris ?" → LLM génère JSON → système appelle API → résultat → LLM répond
Tool = une fonction que le LLM peut appeler. Function Calling = le LLM génère un JSON structuré pour déclencher un tool. MCP = protocole standardisé pour exposer des tools. Agent = LLM + tools + boucle d'exécution autonome.
🛠️ Tool (Outil)
Un Tool est simplement une fonction que le LLM peut décider d'utiliser :
Exemples de tools : • get_weather(city) → API météo • search_web(query) → Moteur de recherche • run_python(code) → Exécution de code • read_file(path) → Lecture de fichier • send_email(to, subject, body) → Envoi d'email
Le tool est décrit au LLM (nom, paramètres, description) mais exécuté par le système hôte.
📞 Function Calling
Function Calling = la capacité du LLM à générer un appel de fonction structuré :
User : "Quelle météo à Paris demain ?"
LLM génère (au lieu de texte) :
{
"function": "get_weather",
"arguments": {
"city": "Paris",
"date": "2025-01-02"
}
}
→ Le système exécute get_weather("Paris", "2025-01-02")
→ Résultat renvoyé au LLM
→ LLM formule la réponse finale
C'est un mode de sortie du LLM, pas une exécution. Le LLM ne fait que générer du JSON.
🔌 MCP — Model Context Protocol
MCP (Anthropic) est un protocole standardisé pour exposer des tools :
Avant MCP : • Chaque app définit ses tools différemment • Intégration custom à chaque fois • Pas de réutilisation Avec MCP : ┌─────────────┐ ┌─────────────┐ │ Claude.ai │────▶│ MCP Server │──▶ Google Drive │ ou autre │ │ (standard) │──▶ Slack │ client │ │ │──▶ GitHub └─────────────┘ └─────────────┘──▶ Base de données • Un serveur MCP = N tools exposés • Un client MCP = peut utiliser N serveurs • Protocol JSON-RPC standardisé
Analogie : MCP est aux tools ce que USB est aux périphériques — un standard de connexion.
🤖 Agent
Un Agent = LLM + Tools + Boucle autonome :
Agent vs Chatbot simple :
CHATBOT :
User → LLM → Réponse (1 tour)
AGENT :
User → LLM → "Je dois chercher X"
↓
Tool: search(X)
↓
LLM → "Maintenant je dois lire Y"
↓
Tool: read(Y)
↓
LLM → "J'ai assez d'info, voici la réponse"
↓
Réponse finale
L'agent décide seul quels tools utiliser et dans quel ordre. Il boucle jusqu'à avoir la réponse.
📊 Comparaison résumée
| Concept | C'est quoi ? | Qui l'exécute ? |
|---|---|---|
| Tool | Une fonction disponible | Le système hôte |
| Function Call | JSON généré par LLM | LLM génère, système exécute |
| MCP | Protocole standard | Serveur MCP |
| Agent | LLM + tools + boucle | Autonome (multi-tours) |
🔄 Exemple concret d'Agent
User : "Trouve les 3 meilleurs restos italiens près de chez moi
et réserve une table pour 2 demain soir"
Agent (Claude Code, Cursor, etc.) :
1. 🧠 "Je dois d'abord localiser l'utilisateur"
→ Tool: get_location() → "Paris 11ème"
2. 🧠 "Maintenant chercher les restos"
→ Tool: search_restaurants("italien", "Paris 11")
→ [Lista Ristorante, Ober Mamma, Pink Mamma...]
3. 🧠 "Je dois vérifier les notes"
→ Tool: get_reviews(["Lista", "Ober Mamma", "Pink Mamma"])
→ Scores et avis
4. 🧠 "Maintenant réserver"
→ Tool: book_table("Ober Mamma", "2025-01-02", "20:00", 2)
→ Confirmation #12345
5. 🧠 "J'ai tout, je peux répondre"
→ "J'ai réservé chez Ober Mamma demain 20h pour 2.
Confirmation #12345. Bon appétit !"
5 appels de tools, décidés autonomement par l'agent.
🧠 Reasoning
Les modèles qui "réfléchissent" (o1, R1)
"17×28 ?" → <think> 17×30-17×2 = 510-34 </think> → "476" ✓
Les modèles reasoning génèrent une chaîne de pensée (CoT) avant de répondre. Plus de tokens générés = plus de "compute à l'inférence" = meilleures réponses sur des problèmes complexes. Le CoT peut être visible (R1) ou caché (o1). C'est une nouvelle dimension de scaling.
🔢 Formats de Précision
FP32, FP16, INT8, INT4...
70B params × 2 bytes (FP16) = 140 GB | × 0.5 byte (INT4) = 35 GB
FP16/BF16 : standard pour l'inférence (2 bytes/param). INT8 : divise par 2 avec peu de perte. INT4/NF4 : divise par 4, perte qualité mesurable mais 70B tourne sur 2× RTX 4090 ! La quantization est un trade-off mémoire vs qualité.
💾 Mémoire GPU
Combien de VRAM faut-il ?
LLaMA 70B FP16 : 140 GB poids + ~10 GB KV cache (8 req × 4K) = ~150 GB VRAM
Poids = params × bytes (70B × 2 = 140 GB, pas 70 !). KV Cache grandit avec contexte × batch. Solutions : Quantization (INT4 → 35 GB), Tensor Parallelism (split sur N GPUs), Offloading (CPU/disk).
📊 VRAM selon précision
| Précision | Bytes/param | 70B | 13B |
|---|---|---|---|
| FP32 | 4 | 280 GB | 52 GB |
| FP16/BF16 | 2 | 140 GB | 26 GB |
| INT8 | 1 | 70 GB | 13 GB |
| INT4 | 0.5 | 35 GB | 6.5 GB |
🎓 Training & Fine-tuning
Du pre-training au déploiement optimisé
Pre-train → SFT → RLHF/DPO → LoRA (optionnel) → Merge → AWQ/GPTQ → Deploy
Pre-training : prédire le token suivant sur TB de texte. SFT : fine-tuning instruction→réponse. RLHF/DPO : aligner sur préférences humaines. LoRA : adapter sans toucher les poids. Quantization : compresser pour le déploiement.
🔧 LoRA — Low-Rank Adaptation
Problème : Fine-tuner 70B params = modifier 70 milliards de poids → impossible en mémoire.
Solution : Ajouter des petites matrices (adapters) sans toucher les poids originaux :
W' = W + ΔW = W + (A × B) W : poids originaux (frozen) A : matrice (d × r) ← r = rang (64-128) B : matrice (r × d)
Pour une couche 4096×4096 = 16.7M params :
| Approche | Params entraînés | Ratio |
|---|---|---|
| Full fine-tune | 16.7M | 100% |
| LoRA r=64 | 524K | 3% |
Pourquoi ça marche ? Les modifications pour adapter un LLM vivent dans un sous-espace de faible dimension.
⚡ QLoRA — Quantized LoRA
Charge le modèle base en 4-bit (frozen) + adapters LoRA en FP16 (trainable) :
Mémoire = Base(4-bit frozen) + LoRA(FP16 trainable) 70B en QLoRA ≈ 35-40 GB (vs 140+ GB full fine-tune)
→ Fine-tuner LLaMA 70B sur 1× A100 80GB devient possible !
📉 Quantization — Compresser pour l'inference
Réduire FP16 → INT4 divise la mémoire par 4, mais quantization naïve = perte de qualité.
Observation clé : tous les poids ne sont pas égaux. Certains ont un impact énorme sur les activations, d'autres très peu.
🎯 AWQ — Activation-aware Weight Quantization
Identifie les poids saillants via les activations sur un dataset de calibration :
Saillance(w) = |w| × ||activation||
Les poids importants (~1%) sont protégés de la quantization agressive.
Processus : 1. Charger modèle FP16 2. Passer dataset de calibration 3. Mesurer activations par couche 4. Identifier poids saillants 5. Quantifier en préservant les importants 6. Sauvegarder INT4
⚠️ Calibration importante : utiliser des samples représentatifs de ton use-case !
⚙️ GPTQ — Post-Training Quantization
Quantifie couche par couche avec compensation d'erreur (méthode OBQ/OBS) :
Pour chaque couche : 1. Quantifier un groupe de poids 2. Mesurer l'erreur introduite 3. Ajuster les poids restants pour compenser 4. Répéter
Plus lent à générer qu'AWQ, mais souvent légèrement meilleure qualité.
📊 Comparaison AWQ vs GPTQ
| Aspect | AWQ | GPTQ |
|---|---|---|
| Vitesse quantization | Rapide | Lent |
| Qualité | Très bonne | Excellente |
| Inference speed | Optimisé batch | Standard |
| Calibration | Critique | Moins critique |
| Support vLLM | Natif (GEMM) | Natif |
Recommandation : AWQ pour vLLM en production, GPTQ si qualité max requise.
🔄 Pipeline complet fine-tune + deploy
Modèle base (FP16, 140GB)
│
▼ QLoRA fine-tuning
┌─────────┐
│ LoRA │ ← Entraîne adapters sur ta tâche
│ adapters│ Base frozen en 4-bit
└────┬────┘
│
▼ Merge
┌─────────┐
│ Merge │ ← Fusionne adapters dans le modèle
└────┬────┘
│
▼ Quantization
┌─────────┐
│ AWQ │ ← Avec calibration sur ton domaine
└────┬────┘
│
▼
Modèle final (INT4, 35GB)
│
▼ Serving
┌─────────┐
│ vLLM │ ← Inference optimisée
└─────────┘
⚡ Optimisations
Techniques pour aller plus vite
Flash Attention (2-4×) | Speculative Decode (2-3×) | Paged Attention (+24× throughput) | Continuous Batching
⚡ Flash Attention
Calcul par blocs en SRAM, évite matrice N×N en HBM. 2-4× plus rapide, contextes plus longs.
🔮 Speculative Decode
Petit modèle "devine" K tokens, gros vérifie en batch. 2-3× speedup, qualité identique.
📄 Paged Attention
KV Cache par pages (comme mémoire virtuelle). +24× throughput via vLLM.
📦 Continuous Batching
Insertion dynamique dès qu'une requête finit. GPU toujours occupé.
💾 GQA
Partage K,V entre têtes Q. KV Cache ÷4 à ÷8, qualité ~identique.
📉 Quantization
GPTQ, AWQ, NF4. Mémoire ÷2 à ÷8, trade-off qualité.
L'inférence est memory-bound (on attend les données). Les optimisations ciblent : réduire les accès mémoire (Flash Attention), paralléliser la vérification (Speculative), mieux gérer la mémoire (Paged), maximiser l'utilisation GPU (Continuous Batching).
⚡ Le problème : la matrice N×N
L'attention standard calcule une matrice Q × Kᵀ de taille N × N (N = longueur séquence) :
Contexte 32K tokens : Matrice attention = 32K × 32K = 1 milliard de floats En FP16 = 2 GB juste pour cette matrice ! → Doit être stockée en HBM (lent) → Limite la longueur de contexte
💡 La solution : calcul par blocs
Flash Attention ne matérialise jamais la matrice complète :
Au lieu de : 1. Calculer toute la matrice Q×Kᵀ (N×N en HBM) 2. Appliquer softmax 3. Multiplier par V Flash Attention : 1. Charger un bloc de Q, K, V en SRAM (20MB, ultra-rapide) 2. Calculer attention sur ce bloc 3. Accumuler le résultat 4. Passer au bloc suivant → Jamais plus de quelques MB en mémoire → 2-4× plus rapide → Contextes 100K+ possibles
📊 Hiérarchie mémoire GPU
| Type | Taille | Bande passante | Usage |
|---|---|---|---|
| Registres | ~KB | ~20 TB/s | Calculs immédiats |
| SRAM | 20 MB | ~19 TB/s | Flash Attention |
| HBM | 80 GB | ~3 TB/s | Poids, KV Cache |
Flash Attention exploite le SRAM (6× plus rapide que HBM) en ne gardant que des blocs.
📄 Le problème : fragmentation du KV Cache
Sans Paged Attention, chaque requête réserve un bloc contigu de mémoire pour son KV Cache :
Requête A : [████████████████________] 16K alloués, 12K utilisés Requête B : [████████____] 12K alloués, 8K utilisés Requête C : [████████████████████____] 20K alloués, 18K utilisés → Gaspillage énorme (fragmentation interne) → Impossible de servir plus de requêtes
💡 La solution : mémoire virtuelle pour LLM
Paged Attention alloue le KV Cache par pages (comme un OS) :
Pages de 16 tokens chacune : Requête A : page 0 → page 3 → page 7 → page 12 Requête B : page 1 → page 5 → page 8 Requête C : page 2 → page 4 → page 6 → page 9 → page 11 → Pas de fragmentation → Allocation dynamique → Pages libérées dès que la requête finit → +24× throughput vs naïf !
🚀 vLLM = Paged Attention + Continuous Batching
Continuous Batching : • Requête A finit → sa place est immédiatement réutilisée • Nouvelle requête D s'insère sans attendre le batch llama.cpp : 1 requête à la fois (simple, CPU-friendly) vLLM : N requêtes en parallèle (optimisé serveur) Pour du self-hosting en production → vLLM Pour du dev local → llama.cpp / Ollama