🧠 Anatomie d'un LLM

Guide visuel — Suivez "Le chat mange" à travers le modùle

🎯 Notre fil rouge tout au long du guide

Nous suivons la phrase "Le chat mange" à travers un LLaMA 70B pour comprendre chaque étape.

ModĂšleLLaMA 70B
Couches80
Dimension8192
TĂȘtes64 Q / 8 KV
Vocab128K
VRAM140 GB FP16

📚 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

📍 Notre exemple complet
"Le chat mange" → PREFILL (parallĂšle) → "sa" → DECODE (sĂ©quentiel) → "pĂątĂ©e." → <EOS>
ENTRÉE TOKENIZE EMBED 80 LAYERS Attention + FFN LOGITS đŸŽČ PREFILL (compute-bound) GPU saturĂ©, parallĂšle sur tous tokens DECODE (memory-bound) Bande passante limitante, 1 token/pass <EOS> = FIN
💡 L'essentiel

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).

📖 Glossaire
Prefill
— Traitement initial parallùle
Decode
— GĂ©nĂ©ration sĂ©quentielle
TTFT
— Time To First Token
Memory-bound
— LimitĂ© par bande passante

🔍 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

📍 Notre exemple
"Le chat mange" → ["Le", "chat", "mange"] → [2356, 7541, 64867]
"Le chat mange" BPE TOKENIZER vocab: 128K 2356 "Le" 7541 "chat" 64867 "mange" pos 0 pos 1 pos 2
💡 L'essentiel

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.

📖 Glossaire
BPE
— Byte Pair Encoding
Token
— UnitĂ© de base (mot/sous-mot)
Vocab
— Dictionnaire complet
<BOS>
— Begin Of Sequence

đŸ·ïž Tokens spĂ©ciaux

En plus des tokens de texte, il existe des tokens de contrĂŽle :

TokenRĂŽleExemple
<BOS>Begin Of SequenceDémarre la génération
<EOS>End Of SequenceArrĂȘte la gĂ©nĂ©ration
<PAD>PaddingRemplissage pour batch
<UNK>UnknownToken inconnu (rare)
[INST]...[/INST]InstructionDé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

📍 Notre exemple
[2356, 7541, 64867] → table[ID] → 3 vecteurs de 8192 dimensions
2356 7541 64867 EMBEDDING TABLE [128K × 8192] lookup: table[ID] +RoPE Matrice [3 × 8192] [0.12, -0.45, 0.78, ...] [0.84, 0.23, -0.56, ...] [-0.33, 0.91, 0.15, ...]
💡 L'essentiel

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.

📖 Glossaire
Embedding
— Vecteur dense d'un token
d_model
— Dimension (8192)
RoPE
— Rotary Position Encoding
Lookup
— Accùs par index

🎯 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

📍 Notre exemple
Matrice [3×8192] → 80 couches identiques (Attention+FFN) → Matrice [3×8192] transformĂ©e
Input [3×8192] COUCHE N (×80) LayerNorm ATTENTION LayerNorm FFN Residual 📊 RĂ©partition params Attn ~20% FFN ~80% ‱ Attention : tokens se parlent ‱ FFN : chaque token "rĂ©flĂ©chit"
💡 L'essentiel

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.

📖 Glossaire
Attention
— Communication inter-tokens
FFN
— Feed-Forward Network
LayerNorm
— Normalisation
Residual
— x + f(x)

đŸ‘ïž MĂ©canisme d'Attention

Comment les tokens se regardent

📍 Notre exemple
"mange" cherche son sujet → score Ă©levĂ© avec "chat" (85%) → rĂ©cupĂšre l'info de "chat"
"mange" Q "Qui fait?" K "Je suis..." V "Contenu" Q × Kᔀ / √d "Le" 0.1 "chat" 0.85 ✓ "mange" 0.05 softmax Poids 5% 90% 5% × V Output ≈ V(chat) ⚠ Causal Mask : "mange" ne voit pas le futur
💡 L'essentiel

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.

📖 Glossaire
Query
— "Ce que je cherche"
Key
— "Ce que je suis"
Value
— "Ce que je donne"
Causal
— Masque empĂȘchant le futur

🧠 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

TypeQ headsKV headsKV Cache
MHA6464100%
GQA64812.5%
MQA6411.5%

GQA = compromis optimal, utilisé par LLaMA 2+.

🧠 Feed-Forward Network (FFN)

Le "cerveau" qui stocke les connaissances

📍 Notre exemple
Vecteur "mange" [8192] → expansion [28672] → SwiGLU → contraction [8192] enrichi
Input [8192] × W1 Hidden [28672] ×3.5 + SwiGLU × W2 Output [8192] đŸ”„ 80% des params ! W1: 8192 × 28672 W2: 28672 × 8192 ≈ 705M params/couche
💡 L'essentiel

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...).

📖 Glossaire
FFN/MLP
— Feed-Forward Network
SwiGLU
— Activation avec gate
Expansion
— ×3.5 la dimension
Neurone
— UnitĂ© de la couche cachĂ©e

🧠 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

📍 Notre exemple
"Le chat mange" traverse 80 couches : syntaxe → grammaire → sĂ©mantique → raisonnement
x Couches 1-20 đŸ”€ Syntaxe locale "le" → nom probable +Δ₁ Couches 20-40 📖 Grammaire "chat" = sujet +Δ₂ Couches 40-60 💡 SĂ©mantique "mange" → nourriture +Δ₃ Couches 60-80 🧠 Raisonnement "quoi rĂ©pondre?" +Δ₄ Logits Sortie = x + Δ₁ + Δ₂ + Δ₃ + ... + Δ₈₀ (residual stream)
💡 L'essentiel

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.

📖 Glossaire
Profondeur
— Nombre de couches (80)
Residual
— "Riviùre" d'info continue
Delta (Δ)
— Contribution d'une couche

🌊 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

CouchesRĂŽleExemple
1-20Syntaxe locale"le" → article, nom attendu
20-40Structure grammaticale"chat" = sujet de "mange"
40-60SĂ©mantique"mange" → contexte nourriture
60-80Raisonnement haut niveauQu'est-ce qui suit logiquement ?

📊 Logits & Softmax

Du vecteur aux probabilités

📍 Notre exemple
Vecteur "mange" [8192] → LM Head → 128K logits → softmax → "sa": 62%
Dernier vect. [8192] LM Head 128K Logits "sa": 8.2 "la": 6.1 "une": 5.8 softmax ProbabilitĂ©s "sa": 62% "la": 18% "une": 12% đŸŽČ "sa" P(i) = e^(logit_i) / ÎŁ e^(logit_j)
💡 L'essentiel

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.

📖 Glossaire
Logit
— Score brut (peut ĂȘtre nĂ©gatif)
Softmax
— Scores → probabilitĂ©s
LM Head
— Projection [d → vocab]
Greedy
— Toujours prendre le max

đŸŽČ Sampling

Choisir le prochain token

📍 Notre exemple
Probas [62%, 18%, 12%...] → tempĂ©rature 0.7 → top-p 0.9 → tirage → "sa" ✓
Original sa 62% T=0.7 AprĂšs T sa 77% top-p AprĂšs top-p Ă©liminĂ© đŸŽČ Tirage : "sa" đŸŒĄïž T basse → amplifie Ă©carts 🎯 Top-p → garde 90% cumulĂ© đŸŽČ Tirage pondĂ©rĂ©
💡 L'essentiel

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.

📖 Glossaire
Température
— logits/T avant softmax
Top-p
— Garde tokens cumulant p%
Top-k
— Garde les k meilleurs
Greedy
— T=0, toujours le max

đŸŽ›ïž Combinaisons recommandĂ©es

UsageTempératureTop-p
Code / Maths0.0 - 0.30.9
Assistants0.70.9
Créatif1.0 - 1.30.95

đŸ’Ÿ KV Cache

Éviter les recalculs pendant la gĂ©nĂ©ration

📍 Notre exemple
Prefill : K₁₋₃, V₁₋₃ stockĂ©s → gĂ©nĂšre "sa" → ajoute K₄, V₄ → gĂ©nĂšre "pĂątĂ©e"...
KV Cache K₁, V₁ "Le" ✓ K₂, V₂ "chat" ✓ K₃, V₃ "mange" ✓ Nouveau : "sa" Calcule Q₄, K₄, V₄ Q₄ × K₁₋₃ Ajoute K₄, V₄ ⚠ Pourquoi pas Q ? ‱ Q = "Ce que JE cherche" ‱ K = "Ce que je SUIS" ‱ V = "Ce que je DONNE" → Nouveau interroge K,V anciens
💡 L'essentiel

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).

📖 Glossaire
KV Cache
— Stockage K,V passĂ©s
Prefill
— Cache initial du prompt
GQA
— RĂ©duit KV (8 au lieu de 64)
Paged
— Gestion par pages (vLLM)

📏 Calcul de la taille

KV par token = 2 × layers × kv_heads × head_dim × bytes
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

📍 Notre exemple
"mange" → Router choisit Expert 2 + Expert 5 → 0.7×E2 + 0.3×E5 → output
Token ROUTER softmax(x·Wg) Expert 2 w=0.7 Expert 5 w=0.3 ... ÎŁ Output Mixtral 8×7B Total: 47B Actif: 13B Top-2
💡 L'essentiel

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.

📖 Glossaire
MoE
— Mixture of Experts
Router
— SĂ©lectionne les experts
Top-K
— K experts par token (souvent 2)
Sparse
— Pas tout activĂ©

🔧 Function Calling, Tools, MCP & Agents

Le LLM qui interagit avec le monde

📍 Notre exemple
"MĂ©tĂ©o Ă  Paris ?" → LLM gĂ©nĂšre JSON → systĂšme appelle API → rĂ©sultat → LLM rĂ©pond
USER "MĂ©tĂ©o Paris demain?" 🧠 LLM → get_weather() OUTPUT JSON {"fn":"get_weather"...} ⚡ SYSTÈME Appelle API "18°C, nuageux" đŸ› ïž Tools get_weather() search_web() run_code() dĂ©finis en JSON
💡 L'essentiel

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.

📖 Glossaire
Tool
— Fonction externe appelable
Function Call
— JSON gĂ©nĂ©rĂ© par le LLM
MCP
— Model Context Protocol
Agent
— LLM autonome + boucle

đŸ› ïž 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

ConceptC'est quoi ?Qui l'exécute ?
ToolUne fonction disponibleLe systĂšme hĂŽte
Function CallJSON généré par LLMLLM génÚre, systÚme exécute
MCPProtocole standardServeur MCP
AgentLLM + tools + boucleAutonome (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)

📍 Notre exemple
"17×28 ?" → <think> 17×30-17×2 = 510-34 </think> → "476" ✓
LLM Standard "17×28?" "476" 1 forward → rĂ©ponse ❌ Souvent faux Reasoning (o1, R1) "17×28?" <think> 17×30-17×2=510-34=476 </think> "476" ✓ 💡 Pourquoi ? Plus de tokens = plus de compute Test-time scaling ⚠ Plus cher !
💡 L'essentiel

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.

📖 Glossaire
CoT
— Chain of Thought
Test-time
— Compute Ă  l'infĂ©rence
o1/o3
— OpenAI reasoning
R1
— DeepSeek reasoning (open)

🔱 Formats de PrĂ©cision

FP32, FP16, INT8, INT4...

📍 Notre exemple
70B params × 2 bytes (FP16) = 140 GB | × 0.5 byte (INT4) = 35 GB
FP32 4 bytes FP16 2 bytes BF16 2 bytes FP8 1 byte INT8 1 byte INT4 0.5 byte Mémoire pour 70B : FP32: 280 GB FP16: 140 GB INT8: 70 35
💡 L'essentiel

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Ă©.

📖 Glossaire
FP16
— 16 bits float (±65K)
BF16
— Brain Float (range FP32)
GPTQ/AWQ
— MĂ©thodes quantization
GGUF
— Format llama.cpp

đŸ’Ÿ MĂ©moire GPU

Combien de VRAM faut-il ?

📍 Notre exemple
LLaMA 70B FP16 : 140 GB poids + ~10 GB KV cache (8 req × 4K) = ~150 GB VRAM
2× H100 = 160 GB VRAM Poids : 140 GB (87.5%) KV 10G libre Poids = params × bytes/param KV = f(batch, seq_len)
💡 L'essentiel

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).

📖 Glossaire
VRAM
— MĂ©moire GPU
Tensor Parallel
— Split matrices
Pipeline Parallel
— Split couches
Offload
— DĂ©charge vers CPU

📊 VRAM selon prĂ©cision

PrécisionBytes/param70B13B
FP324280 GB52 GB
FP16/BF162140 GB26 GB
INT8170 GB13 GB
INT40.535 GB6.5 GB

🎓 Training & Fine-tuning

Du pre-training au déploiement optimisé

📍 Pipeline complet
Pre-train → SFT → RLHF/DPO → LoRA (optionnel) → Merge → AWQ/GPTQ → Deploy
1 PRE-TRAIN Next-token 2 SFT Instructions 3 RLHF Alignement 4 LoRA Adapters 5 QUANT AWQ/GPTQ 6 DEPLOY vLLM CoĂ»t : Pre-train = millions $ | SFT = milliers $ | LoRA = centaines $ | Quant = heures MĂ©moire : Full FT = 10-20× modĂšle | QLoRA = ~1× modĂšle (en 4-bit)
💡 L'essentiel

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.

📖 Glossaire
SFT
— Supervised Fine-Tuning
RLHF
— RL from Human Feedback
DPO
— Direct Preference Optim
LoRA
— Low-Rank Adaptation

🔧 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 :

ApprocheParams entraßnésRatio
Full fine-tune16.7M100%
LoRA r=64524K3%

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

AspectAWQGPTQ
Vitesse quantizationRapideLent
QualitéTrÚs bonneExcellente
Inference speedOptimisé batchStandard
CalibrationCritiqueMoins critique
Support vLLMNatif (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

📍 RĂ©sumĂ©
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'essentiel

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).

📖 Glossaire
vLLM
— Serveur optimisĂ© (Paged)
TensorRT-LLM
— NVIDIA optimizations
SRAM
— Cache ultra-rapide GPU
HBM
— High Bandwidth Memory

⚡ 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

TypeTailleBande passanteUsage
Registres~KB~20 TB/sCalculs immédiats
SRAM20 MB~19 TB/sFlash Attention
HBM80 GB~3 TB/sPoids, 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