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.
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? | OuiYes | NonNo |
| 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 |
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_gpu | Où ça tourneWhere it runs | TTFT | prefill | decode | tokens/s |
|---|---|---|---|---|---|
0 | CPU 8 cœurs8 cores | 542 ms | 213 ms | 28 933 ms | 4,15 |
999 | 1× RTX 3060 | 385 ms | 16 ms | 1 877 ms | 63,93 |
| # | LevierLever | EffetEffect | CoûtCost |
|---|---|---|---|
| 1 | Dé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) |
| 2 | Quantification 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) |
| 3 | Ré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 |
| 4 | Taille de la fenêtreWindow sizenum_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 |
| 5 | Quantification 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 |
| 6 | Slots 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 |
| 7 | Taille 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 |
| 8 | Threads 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 |
| 9 | Flash 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 |
| 10 | Dé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 |
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.
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.
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.
| 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 |
python3 labs/lab5_benchmark.py --endpoint http://172.16.8.81:11434 \
--model llama3.1:8b --runs 5
python3 labs/lab5_benchmark.py --endpoint http://172.16.8.81:11434 \
--model llama3.1:8b --sweep num_ctx 2048 8192 32768 --runs 3
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.
python3 labs/lab5_benchmark.py --endpoint http://172.16.8.81:11434 \
--model llama3.1:8b --parallel 4 --runs 3
-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?-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.-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.