Benchmarks LLM¶
Lancer les benchmarks LLM¶
Résultats (M4 Max, 128 Go)¶
| Modèle | Vitesse de génération | TTFT* | Mémoire |
|---|---|---|---|
| Qwen3-0.6B-8bit | 402,3 tok/s | 58,6 ms | 0,68 Go |
| Llama-3.2-1B-Instruct-4bit | 463,6 tok/s | 49,2 ms | 0,69 Go |
| Qwen2.5-1.5B-Instruct-4bit | 308,5 tok/s | 86,2 ms | 0,84 Go |
| Llama-3.2-3B-Instruct-4bit | 200,1 tok/s | 81,4 ms | 1,79 Go |
| Qwen3-30B-A3B-4bit | 123,9 tok/s | 126,9 ms | 16,05 Go |
| NVIDIA-Nemotron-3-Nano-30B-A3B-MLX-6Bit | 122,9 tok/s | 72,3 ms | 23,98 Go |
*TTFT = Time to First Token (latence jusqu'au premier token généré)
Résultats (M1 Max, 64 Go)¶
| Modèle | Requêtes | Tok. prompt | Tok. générés | Temps total (s) | TTFT moyen (ms) | TPOT moyen (ms) | Vitesse génération (tok/s) | Débit total (tok/s) |
|---|---|---|---|---|---|---|---|---|
| Qwen3-0.6B-8bit | 5 | 56 | 1280 | 5,66 | 119,0 | 3,97 | 251,9 | 236,1 |
Résultats du continuous batching¶
| Modèle | Requête unique | Batch (5 req) | Accélération |
|---|---|---|---|
| Llama-3.2-1B-Instruct-4bit | 299,1 tok/s | 613,0 tok/s | 2,05x |
| Llama-3.2-3B-Instruct-4bit | 137,6 tok/s | 208,1 tok/s | 1,51x |
| Qwen3-0.6B-8bit | 328,1 tok/s | 1111,8 tok/s | 3,39x |
| Qwen3-30B-A3B-4bit | 98,1 tok/s | 233,3 tok/s | 2,38x |
| Qwen2.5-1.5B-Instruct-4bit | 196,9 tok/s | 322,2 tok/s | 1,64x |
Le batching de 5 requêtes simultanées apporte une amélioration du throughput de 1,5 à 3x.
Continuous batching (M1 Max, 64 Go)¶
| Requêtes | Tokens totaux | Temps total (s) | Throughput (tok/s) | Requêtes/sec |
|---|---|---|---|---|
| 5 | 315 | 0,64 | 492,5 | 7,82 |
Performances en streaming¶
| Modèle | TTFT | Vitesse de génération |
|---|---|---|
| Llama-3.2-1B-Instruct-4bit | ~4,6 ms | 218,9 tok/s |
| Llama-3.2-3B-Instruct-4bit | ~10,7 ms | 93,6 tok/s |
| Qwen3-0.6B-8bit | ~3,0 ms | 328,5 tok/s |
| Qwen3-30B-A3B-4bit | ~10,2 ms | 98,4 tok/s |
| Qwen2.5-1.5B-Instruct-4bit | ~7,1 ms | 140,3 tok/s |
Détokeniseur en streaming (M1 Max, 64 Go)¶
vllm-mlx bench-detok :
| Tokens | Itérations | Temps naïf | Temps streaming | Accélération |
|---|---|---|---|---|
| 742 | 5 | 1,69 ms | 0,71 ms | 2,39x |
examples/benchmark_detokenizer.py :
| Séquence | Tokens | decode() | Streaming | Accélération |
|---|---|---|---|---|
| Courte | 8 | 0,029 ms | 0,028 ms | 1,04x |
| Moyenne | 103 | 0,206 ms | 0,129 ms | 1,59x |
| Longue | 511 | 1,040 ms | 0,502 ms | 2,07x |
| 1K | 1191 | 2,446 ms | 1,178 ms | 2,08x |
| 2K | 2381 | 4,949 ms | 2,356 ms | 2,10x |
| 4K | 4761 | 9,887 ms | 5,398 ms | 1,83x |
Accélération moyenne : 1,79x
Résultats du prefix cache¶
Prefix cache (M4 Max, 128 Go)¶
======================================================================
LLM PREFIX CACHE TEST
======================================================================
Model: mlx-community/Qwen3-0.6B-8bit
Expected behavior:
- Same prompt → cache HIT
- Different prompt → cache MISS
----------------------------------------------------------------------
Results:
Step | Description | Expected | Actual | Status
-------+---------------------+----------+--------+-------
1a | First request | MISS | MISS | ✓
1b | Same prompt | HIT | HIT | ✓
1c | Different prompt | MISS | MISS | ✓
1d | Return to prompt 1 | HIT | HIT | ✓
======================================================================
Prefix cache (M1 Max, 64 Go)¶
| Test | Attendu | Réel | Temps | Statut |
|---|---|---|---|---|
| Première requête | MISS | MISS | 203,5 ms | PASS |
| Même prompt | HIT | HIT | 131,6 ms | PASS |
| Prompt différent | MISS ou PREFIX_HIT | PREFIX_HIT (5 tok) | 135,3 ms | PASS |
Statistiques finales du cache :
| Hits cache | Misses cache | Taux de hit | Tokens économisés | Accélération avec cache |
|---|---|---|---|---|
| 2 | 1 | 66,7 % | 20 | 1,55x |
Résultats du paged cache¶
Test : 20 requêtes d'inférence réelles en 2 rounds avec un prompt système partagé d'environ 286 tokens
======================================================================
PAGED KV CACHE - REAL INFERENCE TEST
======================================================================
--------------------------------------------------
Test 1: WITHOUT Paged Cache (2 rounds of 10)
--------------------------------------------------
Time: 1.47s
Throughput: 681.2 tok/s
Cache hits: 0
Tokens saved: 0
--------------------------------------------------
Test 2: WITH Paged Cache (2 rounds of 10)
--------------------------------------------------
Time: 1.31s
Throughput: 765.8 tok/s
Paged Cache Stats:
Blocks allocated: 25
Shared blocks: 4
Cache hits: 10
Tokens saved: 2560
==================================================
SUMMARY
==================================================
Without paged cache: 681.2 tok/s
With paged cache: 765.8 tok/s
Speedup: 1.12x
Cache hits: 10 (all Round 2 requests)
Tokens saved: 2,560 (~256 tokens × 10 requests)
==================================================
KV cache paginé (M1 Max, 64 Go)¶
Benchmark d'inférence (20 requêtes) :
| Mode | Temps (s) | Throughput (tok/s) |
|---|---|---|
| Sans paged cache | 3,43 | 291,8 |
| Avec paged cache | 3,42 | 292,2 |
| Accélération | Blocs alloués | Blocs partagés | Hits cache | Tokens économisés |
|---|---|---|---|---|
| 1,00x | 45 | 4 | 10 | 2560 |
Inférence concurrente réelle (20 requêtes) :
| Mode | Temps (s) | Throughput (tok/s) |
|---|---|---|
| Sans paged cache | 4,32 | 231,7 |
| Avec paged cache | 4,35 | 229,7 |
| Accélération | Blocs alloués | Blocs partagés | Hits cache | Tokens économisés |
|---|---|---|---|---|
| 0,99x | 49 | 8 | 10 | 5120 |
Démonstration des économies mémoire :
| Scénario | Économies mémoire |
|---|---|
| Prompts système partagés | 70,8 % |
| Efficacité mémoire concurrente | 83,5 % |
| Branches avec partage de préfixe | 38,5 % |
Analyse du détokeniseur en streaming¶
Investigation phase 9.1 : BPEStreamingDetokenizer de mlx-lm vs tokenizer.decode() naïf
Contexte¶
L'approche naïve appelle decode([token]) pour chaque token. En théorie, les détokeniseurs en streaming offrent une complexité O(T) contre O(T²) pour le décodage naïf.
Résultats du benchmark isolé¶
En réutilisant la même instance de détokeniseur (avec reset() entre les utilisations) :
| Séquence | Tokens | decode() naïf | Streaming | Accélération |
|---|---|---|---|---|
| Courte | 8 | 0,020 ms | 0,019 ms | 1,05x |
| Moyenne | 103 | 0,155 ms | 0,097 ms | 1,59x |
| Longue | 511 | 0,752 ms | 0,371 ms | 2,03x |
| 1K tokens | 1191 | 1,743 ms | 0,833 ms | 2,09x |
| 2K tokens | 2381 | 3,493 ms | 1,737 ms | 2,01x |
Constat critique : surcoût de création d'instance¶
La création d'une nouvelle instance de BPEStreamingDetokenizer est extrêmement coûteuse :
Cela signifie que créer un nouveau détokeniseur par requête ajoute environ 52 ms de surcoût, annulant tout bénéfice.
Impact en conditions réelles¶
Intégré dans le scheduler (un détokeniseur par requête) :
| Métrique | decode() naïf | Streaming (nouvelle instance) |
|---|---|---|
| Throughput (20 req) | 681 tok/s | 275 tok/s |
| Impact | - | -60 % plus lent |
Conclusion¶
Le détokeniseur en streaming n'est pas viable actuellement pour un usage par requête, en raison du coût de création d'instance. L'approche naïve decode([token]) reste plus rapide en pratique.
Optimisation future : pré-créer un pool d'instances de détokeniseur au démarrage et les réutiliser entre les requêtes.
Référence des métriques¶
| Métrique | Description |
|---|---|
| TTFT | Time to First Token - latence jusqu'à ce que le modèle commence à répondre (ms) |
| TPOT | Time Per Output Token - temps entre chaque token généré (ms/token) |
| Generation TPS | Tokens de sortie par seconde (tok/s) |
| Processing TPS | Tokens d'entrée/prompt traités par seconde (tok/s) |
| End-to-End Latency | Temps total de la requête à la réponse complète |
| Total Throughput | Tokens totaux (entrée + sortie) par seconde |
Lancer les benchmarks¶
# Benchmark de base
vllm-mlx-bench --model mlx-community/Qwen3-0.6B-8bit
# Avec davantage de prompts
vllm-mlx-bench --model mlx-community/Qwen3-0.6B-8bit --prompts 10
# Sauvegarder les résultats
vllm-mlx-bench --model mlx-community/Qwen3-0.6B-8bit --output results.json
# Test de continuous batching
python tests/test_continuous_batching.py
# Test de prefix cache
python tests/test_prefix_cache.py
# Test de paged cache
python tests/test_paged_cache_real_inference.py
# Benchmark du détokeniseur en streaming
vllm-mlx bench-detok
vllm-mlx bench-detok mlx-community/Llama-3.2-1B-Instruct-4bit --iterations 5