SÉANCE 5 / 8SESSION 5 / 8 ⏱ ≈ 90 min

Réglage & benchmarking de performancePerformance tuning & benchmarking

Objectif du jour : transformer « c'est lent » en un chiffre, une cause et un réglage. À la fin de cette séance, vous savez mesurer proprement (TTFT et débit séparément, après échauffement, médiane sur plusieurs passages), vous connaissez les dix leviers qui comptent réellement — et vous savez lequel tirer selon que vous optimisez la latence d'un utilisateur ou le débit d'une file de traitement. Le gain du jour est un playbook de réglage et un tableau de benchs mesurés sur le lab. Today's goal: turn "it's slow" into a number, a cause and a setting. By the end of this session you can measure properly (TTFT and throughput separately, after warm-up, median over several runs), you know the ten levers that actually matter — and you know which to pull depending on whether you are optimising one user's latency or a job queue's throughput. Today's win is a tuning playbook and a table of benchmarks measured on the lab.

1

Où part le temps : deux phases, deux problèmesWhere the time goes: two phases, two problems

≈ 18 min

Une requête d'inférence a deux phases aux caractéristiques opposées. Les confondre est la cause n°1 des optimisations ratées. An inference request has two phases with opposite characteristics. Confusing them is the #1 cause of failed optimisations.

Préremplissage (prefill)Prefill Décodage (decode)Decode
Ce qu'il faitWhat it does Traite tout le prompt d'un coupProcesses the whole prompt at once Produit les tokens un par unProduces tokens one at a time
Parallélisable ?Parallelisable? OuiYesNonNo
Limité parLimited by Le calcul (FLOPS)Compute (FLOPS) La bande passante mémoireMemory bandwidth
MétriqueMetric TTFT (temps jusqu'au 1er token)(time to first token) tokens/s
Levier principalMain lever Taille de lot (batch), réutilisation de promptBatch size, prompt reuse Moins d'octets à déplacer : quantification, GPUFewer bytes to move: quantization, GPU
🔬 Les chiffres mesurés sur le lab le 2026-09-18 rendent tout ça concret. Le test le plus propre possible : même modèle, même moteur, mêmes réglages — une seule variable change (num_gpu, le nombre de couches déportées sur GPU). The figures measured on the lab on 2026-09-18 make this concrete. The cleanest possible test: same model, same engine, same settings — one variable changes (num_gpu, the number of layers offloaded to GPU).
num_gpuOù ça tourneWhere it runsTTFTprefilldecodetokens/s
0CPU 8 cœurs8 cores542 ms213 ms28 933 ms4,15
9991× RTX 3060385 ms16 ms1 877 ms63,93
Rapport ×15,4 sur le décodage, ×13 sur le préremplissage — pour le même modèle. Regardez la colonne « decode » : 28,9 secondes contre 1,9 seconde. Et remarquez que le TTFT ne varie que de 542 à 385 ms : le CPU lit le prompt presque aussi vite, mais il s'écroule pour produire les tokens. C'est la signature exacte du décodage memory-bound — et la preuve qu'optimiser TTFT et optimiser le débit sont deux métiers différents. Ratio ×15.4 on decoding, ×13 on prefill — for the same model. Look at the "decode" column: 28.9 seconds versus 1.9 seconds. And note that TTFT varies only from 542 to 385 ms: the CPU reads the prompt almost as fast, but collapses when producing tokens. That is the exact signature of memory-bound decoding — and the proof that optimising TTFT and optimising throughput are two different jobs.
📚 Source : llama-server README (distinction prefill/decode, paramètres de batch et de cache) et llama.cpp, performance tuning. Mesures : labs 1, 4 et 5 exécutés sur le lab le 2026-09-18. Source: llama-server README (prefill/decode distinction, batch and cache parameters) and llama.cpp, performance tuning. Measurements: Labs 1, 4 and 5 run on the lab on 2026-09-18.
2

Les leviers qui comptent, par ordre d'effetThe levers that matter, in order of effect

≈ 26 min
#LevierLeverEffetEffectCoûtCost
1Déporter sur GPUOffload to GPU
-ngl / num_gpu
Le plus gros effet de toute la séance : ×15 mesuré sur le lab, pour un modèle identique. Chaque couche déportée réduit les octets lus en RAM système.The single biggest effect here: ×15 measured on the lab, for an identical model. Each offloaded layer reduces bytes read from system RAM. Il faut un GPU, et assez de VRAM (lab 2)Needs a GPU and enough VRAM (Lab 2)
2Quantification des poidsWeight quantization ~×2 à ×4 de débit de Q8 vers Q4, proportionnel à la réduction d'octets.~×2–×4 throughput from Q8 to Q4, proportional to the byte reduction. Perte de qualité sur le code et le multilingue (séance 2)Quality loss on code and multilingual (session 2)
3Réutilisation du préfixePrefix reuse
prompt caching
Si le début du prompt est identique (prompt système, contexte RAG), le prefill est sauté. Effet massif sur le TTFT d'un RAG : de plusieurs secondes à quelques dizaines de millisecondes. Le serveur du lab l'active par défaut.If the prompt's beginning is identical (system prompt, RAG context), prefill is skipped. Huge effect on RAG TTFT: from seconds to tens of milliseconds. The lab's server enables it by default. Mémoire (cache), et un prompt stableMemory (cache), and a stable prompt
4Taille de la fenêtreWindow size
num_ctx / -c
Chaque token de contexte coûte du cache KV et du calcul d'attention. Réduire la fenêtre au besoin réel libère de la VRAM, ce qui permet de déporter plus de couches.Every context token costs KV cache and attention compute. Cutting the window to what you need frees VRAM, which lets you offload more layers. Vous perdez de la mémoire conversationnelleYou lose conversational memory
5Quantification du cache KVKV cache quantization Divise le cache par 2 (q8_0) ou 4 (q4_0) : de la place gagnée pour les poids, et un contexte long qui devient possible (lab 2).Halves (q8_0) or quarters (q4_0) the cache: room gained for weights, and long context becomes possible (Lab 2). Petite perte de qualité sur les longs contextesSmall quality loss on long contexts
6Slots parallèlesParallel slots
--parallel
Traite N requêtes en parallèle : le débit total augmente, la latence individuelle se dégrade. Le réglage clé pour un service à plusieurs utilisateurs.Handles N requests concurrently: total throughput rises, per-request latency worsens. The key setting for a multi-user service. Plus de cache KV à allouerMore KV cache to allocate
7Taille de lot de prefillPrefill batch size
-b / -ub
Accélère le traitement du prompt (donc le TTFT) sur les longs contextes.Speeds up prompt processing (hence TTFT) on long contexts. Mémoire de calcul (VRAM) pendant le prefillCompute memory (VRAM) during prefill
8Threads CPUCPU threads
-t
Sur CPU, réglez sur le nombre de cœurs physiques, pas de threads logiques. Au-delà, on se bat pour de la bande passante et le débit baisse.On CPU, set it to the number of physical cores, not logical threads. Beyond that you contend for bandwidth and throughput drops. Rien, mais mal réglé c'est contre-productifNothing, but mis-set it is counter-productive
9Flash attention Réduit l'empreinte mémoire de l'attention, permet des contextes plus longs et des lots plus gros.Reduces attention memory footprint, enabling longer contexts and bigger batches. Support et stabilité variables selon le backendBackend-dependent support and stability
10Décodage spéculatifSpeculative decoding Un petit modèle « brouillon » propose plusieurs tokens, le grand modèle les valide en un passage. Gain de ×1,5 à ×3 sur certains usages.A small "draft" model proposes several tokens, the large model validates them in one pass. ×1.5–×3 gain on some workloads. Complexité, mémoire supplémentaire, gain non garantiComplexity, extra memory, no guaranteed gain
Ce qui ne sert à rien (mais se fait beaucoup)What does not help (but is widely done) Optimiser le code Python qui appelle l'API : il passe 99,9 % de son temps à attendre le serveur. Ajouter de la RAM quand la VRAM est pleine. Overclocker le GPU pour gagner du débit de décodage : le décodage est limité par la bande passante, pas par la fréquence de calcul. Réduire max_tokens en croyant accélérer la génération : ça raccourcit la sortie, ça ne change pas la vitesse par token. Optimising the Python code that calls the API: it spends 99.9 % of its time waiting for the server. Adding RAM when VRAM is full. Overclocking the GPU to gain decode throughput: decoding is bandwidth-limited, not compute-clock-limited. Lowering max_tokens thinking it speeds up generation: it shortens the output, it does not change per-token speed.
3

Méthodologie : comment ne pas se mentirMethodology: how not to fool yourself

≈ 20 min
1. ÉCHAUFFEMENT   Jetez le premier passage. Le chargement du modèle est
                  inclus dedans : nous avons mesuré 4,5 s et 8,2 s de TTFT
                  à froid contre 334–434 ms à chaud sur le lab.
                  Une mesure non échauffée ne mesure pas votre modèle,
                  elle mesure votre disque.

2. UNE SEULE VARIABLE À LA FOIS.  Sinon vous ne saurez pas ce qui a agi.

3. PROMPT ET SORTIE FIXES.  Même prompt, même max_tokens, temperature 0,
                  seed fixe. Sinon vous mesurez la variance du tirage.

4. N PASSAGES, MÉDIANE.  Jamais la moyenne (un pic de charge la fausse),
                  jamais un seul passage. 3 minimum, 5 mieux.

5. SÉPARER TTFT ET DÉBIT.  Deux métriques, deux causes, deux leviers.

6. MESURER SOUS CHARGE pour parler de débit.  Un débit mono-requête
                  ne dit RIEN de la capacité d'un service.

7. NOTER LA CONFIGURATION AVEC LE RÉSULTAT.  Un chiffre sans sa config
                  (modèle, quant, contexte, ngl, threads, moteur) est inutile.
⚠️ Le biais le plus fréquent : l'échauffement. Sur le lab, le premier passage de llama3.1:8b a montré un TTFT de 8,2 s ; les suivants, 334–370 ms. Si vous ne jetez pas le premier, vous concluez que votre service a une latence de 8 secondes et vous partez optimiser un problème qui n'existe pas. À l'inverse, gardez ce chiffre : c'est votre temps de démarrage à froid, qui détermine combien de temps après un redémarrage votre service est réellement disponible. The most common bias: warm-up. On the lab, the first run of llama3.1:8b showed a TTFT of 8.2 s; subsequent ones, 334–370 ms. If you do not discard the first, you conclude your service has 8-second latency and go optimise a problem that does not exist. Conversely, keep that number: it is your cold-start time, which determines how long after a restart your service is actually available.

Ce qu'un rapport de performance doit contenirWhat a performance report must contain

Matériel    : 1x RTX 3060 12 Go / CPU 8 coeurs / 23 Go RAM
Moteur      : Ollama 0.32.13
Modèle      : llama3.1:8b, Q4_K_M, 4,92 Go
Réglages    : num_ctx=8192, num_gpu=999 (tout), threads=par défaut
Charge      : 1 requête, prompt de 40 tokens, sortie de 180 tokens
Résultats   : TTFT médian 369 ms (p95 434 ms)
              Débit médian 63,7 tokens/s (n=5, échauffement exclu)
              Démarrage à froid : 4,5 s
Conclusion  : convient à un usage interactif ; pour 10 utilisateurs
              simultanés, mesurer --parallel et envisager vLLM.
🎯 La règle du décideur : un chiffre sans sa configuration n'est pas une donnée, c'est une rumeur. « Ça tourne à 60 tokens/s » ne veut rien dire ; « llama3.1:8b Q4_K_M, contexte 8192, tout déporté sur une RTX 3060, 63,7 tokens/s médian sur 5 passages après échauffement » est une donnée qui permet de décider. The decision-maker's rule: a figure without its configuration is not data, it is a rumour. "It runs at 60 tokens/s" means nothing; "llama3.1:8b Q4_K_M, context 8192, fully offloaded to one RTX 3060, 63.7 tokens/s median over 5 warm runs" is data you can decide on.
4

Régler selon l'objectifTuning by objective

≈ 12 min
ObjectifObjective Ce qu'on optimiseWhat you optimise RéglagesSettings
Chat interactifInteractive chat TTFT (la perception de vitesse)TTFT (perceived speed) Tout déporter sur GPU · contexte modeste · réutilisation de préfixe · 1 slotOffload everything to GPU · modest context · prefix reuse · 1 slot
Traitement par lotsBatch processing Débit total (tokens/s agrégés)Total throughput (aggregate tokens/s) Plusieurs slots parallèles · gros lots de prefill · tolérer une latence plus hauteSeveral parallel slots · large prefill batches · accept higher latency
RAG TTFT malgré un long contexteTTFT despite long context Réutilisation de préfixe (prompt système stable en tête) · cache KV quantifié · contexte dimensionné au nombre de documents récupérésPrefix reuse (stable system prompt at the front) · quantized KV cache · context sized to the number of retrieved documents
AgentAgent Latence cumulée de plusieurs appelsCumulative latency over several calls TTFT bas · contexte suffisant pour les outils · décodage spéculatif · réduire le nombre d'appels, pas seulement leur vitesseLow TTFT · enough context for tools · speculative decoding · reduce the number of calls, not just their speed
🎯 Le levier le plus sous-estimé n'est pas un réglage : c'est la réduction du travail. Un agent qui fait 8 appels de 2 s est plus lent qu'un agent qui en fait 3 de 3 s. Avant de tuner, demandez-vous combien d'appels vous pouvez supprimer (décomposition mieux pensée, sortie contrainte qui évite une reprise, contexte mis en cache). Le meilleur réglage est celui qui n'a pas besoin d'être fait. The most under-rated lever is not a setting: it is reducing the work. An agent making 8 calls of 2 s is slower than one making 3 of 3 s. Before tuning, ask how many calls you can remove (better-thought-out decomposition, constrained output that avoids a retry, cached context). The best tuning is the one you do not need to do.
5

Lab 5 — Le banc d'essaiLab 5 — The benchmark bench

≈ 20 min

🔬 Objectif : un tableau de mesures avec échauffement géréGoal: a measurement table with warm-up handled

Étape 1 — RéférenceStep 1 — Baseline

python3 labs/lab5_benchmark.py --endpoint http://172.16.8.81:11434 \
        --model llama3.1:8b --runs 5

Étape 2 — Balayer la taille de contexteStep 2 — Sweep the context size

python3 labs/lab5_benchmark.py --endpoint http://172.16.8.81:11434 \
        --model llama3.1:8b --sweep num_ctx 2048 8192 32768 --runs 3

Étape 3 — Balayer le déport GPU (le levier n°1)Step 3 — Sweep GPU offload (lever #1)

python3 labs/lab5_benchmark.py --endpoint http://172.16.8.81:11434 \
        --model llama3.1:8b --sweep num_gpu 0 10 33 999 --runs 3

# num_gpu 0 = tout en CPU  ->  vous devez retrouver ~4 tokens/s
# num_gpu 999 = tout en GPU ->  vous devez retrouver ~60 tokens/s
# Le « genou » de la courbe est la VRAM disponible.

Étape 4 — Débit sous charge concurrenteStep 4 — Throughput under concurrency

python3 labs/lab5_benchmark.py --endpoint http://172.16.8.81:11434 \
        --model llama3.1:8b --parallel 4 --runs 3
🎯 Ce que vous devez produire : un tableau à trois colonnes (configuration → TTFT médian → débit médian) et la phrase de genou : « au-delà de N couches déportées, le débit cesse de progresser parce que la VRAM est saturée ». C'est votre playbook de réglage, et il est portable : les mêmes leviers existent dans llama.cpp, Ollama et vLLM sous des noms différents. What you must produce: a three-column table (configuration → median TTFT → median throughput) and the knee sentence: "beyond N offloaded layers, throughput stops improving because VRAM is saturated". That is your tuning playbook, and it is portable: the same levers exist in llama.cpp, Ollama and vLLM under different names.
6

Quiz — 5 questionsQuiz — 5 questions

≈ 8 min

1. Premier passage : TTFT 8,2 s. Suivants : 340 ms. Que retenez-vous ?1. First run: TTFT 8.2 s. Following: 340 ms. What do you conclude?

Deux chiffres, deux significations : le démarrage à froid (temps avant que le service soit disponible après un redémarrage) et la latence à chaud. On jette le premier passage pour mesurer la latence, on le garde pour documenter le démarrage.Two figures, two meanings: the cold start (time before the service is available after a restart) and the warm latency. Discard the first run to measure latency; keep it to document the start-up.

2. Quel levier a eu le plus gros effet mesuré sur le lab ?2. Which lever had the biggest measured effect on the lab?

4,07 tokens/s en CPU contre 64,02 en GPU sur un GPU unique, pour des modèles comparables. Aucun réglage logiciel ne produit un facteur 15 : c'est la bande passante mémoire qui décide, et le GPU en a beaucoup plus.4.07 tokens/s on CPU vs 64.02 on a single GPU, for comparable models. No software setting yields a factor of 15: memory bandwidth decides, and a GPU has far more of it.

3. Vous servez 10 utilisateurs simultanés. Quel réglage privilégiez-vous ?3. You are serving 10 concurrent users. Which setting do you favour?

Avec plusieurs utilisateurs, l'objectif est le débit agrégé, pas la latence d'un seul. Les slots parallèles (et, au-delà de quelques utilisateurs, vLLM avec son batching continu) sont les bons leviers. Notez le compromis assumé : chaque requête ralentit un peu.With several users the goal is aggregate throughput, not one user's latency. Parallel slots (and, beyond a few users, vLLM with continuous batching) are the right levers. Note the accepted trade-off: each request slows down a little.

4. Pourquoi régler -t sur le nombre de cœurs physiques et pas de threads logiques ?4. Why set -t to the number of physical cores rather than logical threads?

Le décodage étant memory-bound, ajouter des threads au-delà des cœurs physiques ajoute de la contention mémoire sans ajouter de bande passante. Le débit peut diminuer. Sur le lab (8 cœurs physiques), -t 8 est le bon point de départ.Decoding is memory-bound, so adding threads beyond the physical cores adds memory contention without adding bandwidth. Throughput can fall. On the lab (8 physical cores), -t 8 is the right starting point.

5. Un RAG a un TTFT de 3 secondes. Quel levier d'abord ?5. A RAG has a 3-second TTFT. Which lever first?

Le TTFT d'un RAG vient du prefill d'un long contexte. Si le début du prompt (instructions, format) ne change pas, le serveur peut réutiliser le cache et sauter cette phase : on passe de secondes à des dizaines de millisecondes. Le serveur du lab active cette réutilisation par défaut — encore faut-il que votre prompt soit stable en tête.A RAG's TTFT comes from the prefill of a long context. If the prompt's beginning (instructions, format) does not change, the server can reuse the cache and skip that phase: seconds become tens of milliseconds. The lab's server enables this reuse by default — but your prompt must be stable at the front.
Score : 0 / 5Score: 0 / 5

🏁 À retenirKey takeaways

🏆 Votre gain du jourToday's win
Un banc d'essai réutilisable (échauffement, médianes, balayages de paramètres, test de concurrence) et un tableau de mesures sur le lab avec la phrase de genou. Vous saurez désormais répondre à « c'est lent » par un chiffre, une cause et un réglage — au lieu d'un haussement d'épaules. A reusable benchmark bench (warm-up, medians, parameter sweeps, concurrency test) and a measurement table on the lab with its knee sentence. You will now answer "it's slow" with a number, a cause and a setting — instead of a shrug.
Sources de la séance : llama.cpp, llama-server README (paramètres -ngl, -c, -b/-ub, -t, --parallel, cache KV, flash attention, cache de prompt) · llama.cpp, performance-tuning · vLLM, Online Serving (batching continu, PagedAttention) · Ollama, API (bloc options : num_ctx, num_gpu, num_thread). Mesures : labs 1, 4 et 5 exécutés sur le lab, 2026-09-18. Session sources: llama.cpp, llama-server README (parameters -ngl, -c, -b/-ub, -t, --parallel, KV cache, flash attention, prompt cache) · llama.cpp, performance-tuning · vLLM, Online Serving (continuous batching, PagedAttention) · Ollama, API (options block: num_ctx, num_gpu, num_thread). Measurements: Labs 1, 4 and 5 run on the lab, 2026-09-18.