Aller au contenu

Méthodologie de benchmark

asiai suit les standards de benchmarking établis (MLPerf, SPEC CPU 2017, NVIDIA GenAI-Perf) pour produire des résultats fiables, reproductibles et comparables.

Protocole

  1. Vérification pré-vol : Refuser de démarrer si la pression mémoire est critique ou si le système est fortement throttlé (<80%)
  2. Warmup : 1 génération non chronométrée par moteur pour amorcer les compilateurs JIT et les caches
  3. Exécutions mesurées : Par défaut 3 exécutions par prompt par moteur (configurable via --runs)
  4. Échantillonnage : temperature=0 (greedy) pour une sortie déterministe
  5. Déchargement du modèle : Après le benchmark de chaque moteur, le modèle est déchargé pour libérer la mémoire unifiée avant le prochain moteur. Cela empêche l'accumulation mémoire et le swapping lors de la comparaison de plusieurs moteurs sur de grands modèles
  6. Refroidissement adaptatif : Après le déchargement, asiai attend que la pression mémoire macOS revienne à « normal » (max 30s), puis ajoute un minimum de 5s de refroidissement thermique
  7. Contrôles de cohérence : Les résultats avec tok/s ≤ 0 sont rejetés. Un TTFT > 60s ou tok/s > 500 déclenche des avertissements (swapping probable ou erreurs de mesure)
  8. Reporting : Médiane tok/s comme métrique principale (standard SPEC), moyenne ± stddev en secondaire
  9. Throttling : Avertissement émis si thermal_speed_limit < 100% pendant une exécution. La dérive thermique (diminution monotone des tok/s entre les exécutions, baisse ≥ 5%) est détectée et signalée
  10. Métadonnées : Version du moteur, format du modèle, quantification, puce matérielle, version macOS stockés par résultat

Métriques

tok/s — Vitesse de génération

Tokens par seconde du temps de génération uniquement, hors traitement du prompt (TTFT).

Ollama (API native, /api/generate) :

tok_per_sec = eval_count / (eval_duration_ns / 1e9)
Source : timing GPU interne rapporté par Ollama. Pas de surcharge réseau. C'est la mesure la plus précise.

Moteurs compatibles OpenAI (LM Studio, llama.cpp, mlx-lm, vllm-mlx) :

generation_s = wall_clock_s - ttft_s
tok_per_sec  = completion_tokens / generation_s
Source : horloge murale côté client via streaming SSE. Inclut la surcharge HTTP par chunk (~1% plus lent que le timing côté serveur, validé par validation croisée).

Comptage de tokens : depuis usage.completion_tokens dans la réponse du serveur. Si le serveur ne rapporte pas ce champ, asiai revient à len(text) // 4 et enregistre un avertissement. Ce repli peut être décalé d'environ 25%.

Validation croisée (avril 2026, Qwen3.5-35B NVFP4, M4 Pro 64GB) :

Méthode tok/s Écart vs référence
Ollama native (GPU interne) 66.6 référence
OpenAI streaming (client) 66.1 -0.8%

Pour les grandes tailles de contexte (ex. 64k tokens), le TTFT peut dominer la durée totale. L'exclure du tok/s empêche les générateurs rapides de paraître lents.

TTFT — Time to First Token

Temps entre l'envoi de la requête et la réception du premier token de sortie, en millisecondes.

Depuis la v1.6.0, asiai mesure deux valeurs TTFT pour Ollama, et une seule pour tous les autres moteurs :

Ollama (double mesure) :

  • TTFT côté serveur (ttft_ms) : extrait de prompt_eval_duration dans la réponse Ollama. C'est le temps pur de traitement GPU du prompt, sans aucune surcharge réseau — la mesure la plus précise possible. Rapporté comme ttft_source: server.
  • TTFT côté client (ttft_client_ms) : mesuré à l'arrivée du premier chunk SSE de contenu. Inclut la configuration HTTP, la transmission de la requête et le traitement serveur. C'est la même méthode utilisée pour tous les autres moteurs.

Moteurs compatibles OpenAI (LM Studio, llama.cpp, mlx-lm, vllm-mlx) :

  • TTFT côté client (ttft_client_ms) : mesuré au premier chunk SSE de contenu. C'est la seule mesure disponible car ces moteurs n'exposent pas le timing interne de traitement du prompt. ttft_ms et ttft_client_ms contiennent la même valeur.

Métrique comparable : ttft_client_ms est la métrique comparable entre moteurs — elle utilise la même méthode de mesure quel que soit le moteur. Utilisez-la pour comparer le TTFT entre différents moteurs. Le ttft_ms côté serveur d'Ollama est plus précis pour le temps absolu de traitement du prompt, mais n'est pas directement comparable avec les autres moteurs.

Validation croisée (avril 2026, Qwen3.5-35B NVFP4, M4 Pro 64GB) :

Méthode TTFT Écart
Ollama côté serveur (ttft_ms) 27 ms référence
Ollama côté client (ttft_client_ms) 51 ms +24 ms

L'écart de 24ms représente la surcharge HTTP sur localhost. Cette surcharge est constante et prévisible, mais suffisamment significative pour compter lors de la comparaison entre moteurs.

Puissance — Watts GPU

Puissance GPU moyenne pendant l'exécution, mesurée via le framework Apple IOReport Energy Model (sans sudo requis). Une mesure par moteur — pas de moyenne sur la session entière.

tok/s/W — Efficacité énergétique

tok_per_sec_per_watt = tok_per_sec / power_watts

Variance — Stddev poolée

Écart-type intra-prompt poolé qui capture le bruit inter-exécutions sans mélanger la variance inter-prompts. Utilise la correction de Bessel (dénominateur N-1) pour une variance d'échantillon non biaisée.

Classification de stabilité :

  • CV < 5% → stable
  • CV < 10% → variable
  • CV >= 10% → unstable

Où CV = (std_dev / mean) * 100.

VRAM — Utilisation mémoire

Primaire : API native du moteur (Ollama /api/ps, LM Studio /v1/models). Repli : ri_phys_footprint via ctypes (identique au Moniteur d'activité). Marqué « (est.) » dans l'interface.

Mode Agentique — Benchmark de réutilisation du préfixe en cache

Les benchmarks single-shot classiques mesurent la vitesse de génération en isolation. Ils passent à côté du pattern de coût dominant des workloads agentiques multi-tour : un long prompt système partagé (outils, règles, persona — souvent 6K+ tokens) plus un message utilisateur court qui change à chaque tour. Un moteur qui ne réutilise pas le préfixe en cache re-traite ces 6K tokens à chaque appel, et le TTFT explose.

asiai bench --agentic-mode exécute un protocole en 8 phases conçu pour exposer ce comportement de manière explicite.

Protocole

Phase Système Utilisateur max_tokens Objectif
cold SYS_A USER_X 400 Premier run, cache vide
warm SYS_A USER_X 400 Même requête — full cache hit
prefix-test-1 SYS_A USER_Y 400 Sys identique, user différent — le vrai test
prefix-test-2 SYS_A USER_X 400 Retour à USER_X — cache hit attendu
prefix-test-3 SYS_A USER_Y 400 Répète le pattern cross-user
cold-prefix SYS_B USER_X 400 Le sys change — cache miss attendu
long-context SYS_A USER_L (~50K tok) 200 Saturer le decode sur contexte long
long-prefix SYS_A USER_L 200 Même contexte long — cache hit

Les prompts sont générés de façon déterministe avec un pattern sentinelle qui casse les caches substring naïfs ; les tailles sont calibrées pour les tokenizers de la famille Qwen (~5.3 char/token sur prose anglaise).

Verdict

prefix_cache_reuse est calculé sur les phases prefix-test :

  1. Signal primaire — si le moteur expose usage.prompt_tokens_details.cached_tokens dans sa réponse streaming (llama.cpp, mlx-lm), le ratio cached / prompt est moyenné sur les phases prefix-test :
  2. ≥ 0.5yes
  3. ≥ 0.1partial
  4. sinon → no
  5. Signal de repli — si le moteur n'expose pas cached_tokens (LM Studio, vllm-mlx, oMLX), le ratio TTFT est utilisé : TTFT prefix-test rapporté au TTFT cold.
  6. < cold/5yes
  7. < cold/2partial
  8. sinon → no

Quality gates

Trois gates tournent en parallèle du bench et apparaissent dans result["quality_gates"] :

  • early_stop — flagge les phases où completion_tokens chute sous 50 % du max_tokens demandé sur au moins deux runs. Détecte les bugs moteur où un token EOS spéculé est accepté à tort sous réutilisation du préfixe — la réponse reste valide en OpenAI-compat mais le moteur retourne silencieusement des réponses tronquées.
  • memory_pressure — un thread d'arrière-plan poll vm_stat et vm.swapusage toutes les 15 s avec une baseline prise au démarrage. Alerte quand le swap monte de plus de 500 MB ou les swapouts de plus de 1000 depuis la baseline. Les deux indiquent que l'OS pagine le modèle ou le KV cache sur disque, donc le tok/s mesuré ne représente plus le moteur lui-même.
  • duplicate_processes — un snapshot ps unique avant le bench rejette les runs où deux instances du même moteur tournent en parallèle, l'une risquant de se battre avec le bench pour le GPU.

Quand une gate déclenche, la CLI affiche un warning rouge sous la ligne de verdict et la sortie JSON garde tous les samples détaillés pour qu'un leaderboard ou un tracker de régression puisse refuser de publier le résultat.

Cold start reproductible (intégration aisctl opt-in)

asiai bench --agentic-mode --agentic-auto-restart appelle aisctl restart <engine> avant la première phase et poll /health jusqu'à disponibilité. Utile pour les moteurs sans API d'unload (llama.cpp, oMLX, TurboQuant) où un restart du daemon est le seul moyen fiable de vider le KV cache. Ajouter --agentic-auto-restart-required pour abandonner au lieu de continuer si aisctl n'est pas disponible.

Cette intégration nécessite asiai-inference-server installé ; sinon le bench affiche un warning et continue contre l'état courant du moteur.

Pourquoi c'est important

Un chiffre tok/s single-shot est peu informatif pour les workflows agentiques quand le moteur ne réutilise pas le préfixe système. Deux moteurs avec un throughput single-shot identique peuvent différer de 5 à 10× sur la latence d'un tick agentique selon que le préfixe en cache tient ou non.

agentic-mode expose cet écart explicitement pour que le leaderboard et les choix de moteur reflètent le workload dominant, pas un microbenchmark.

Sécurité de l'environnement

asiai effectue des vérifications pré-benchmark :

  1. Pression mémoire : refuse de démarrer si critique
  2. Throttling thermique : avertit si la limite de vitesse < 80%
  3. Processus dupliqués : avertit si plusieurs instances du même moteur sont en cours (ex. deux processus ollama serve sur le même port)
  4. Type de runner du moteur : pour Ollama, détecte si le runner --mlx-engine ou --ollama-engine est actif

Ces vérifications préviennent les erreurs de mesure causées par la contention de ressources ou le routage incorrect.

Conformité

Pratique Statut
Vérification pré-vol (pression mémoire + thermique) Implémenté
Détection de processus dupliqués Implémenté (v1.5.0)
Détection du type de runner Ollama (MLX vs llama.cpp) Implémenté (v1.5.0)
TTFT séparé du tok/s Implémenté
Étiquetage de la source TTFT (server vs client) Implémenté (v1.5.0)
Double mesure TTFT (server + client) Implémenté (v1.6.0)
Échantillonnage déterministe (temperature=0) Implémenté
Comptage de tokens via l'API serveur (pas les chunks SSE) Implémenté (avertissement en repli)
Monitoring de puissance par moteur (IOReport, sans sudo) Implémenté
1 génération de warmup par moteur Implémenté
3 exécutions par défaut (minimum SPEC) Implémenté
Médiane comme métrique principale (standard SPEC) Implémenté
Stddev intra-prompt poolée (Bessel N-1) Implémenté (corrigé v1.5.0)
Déchargement du modèle entre les moteurs Implémenté
Refroidissement adaptatif (sensible à la pression mémoire) Implémenté
Contrôles de cohérence (tok/s, bornes TTFT) Implémenté
Détection du throttling thermique + avertissement Implémenté
Détection de la dérive thermique (diminution monotone) Implémenté
Version du moteur + type de runner stockés par résultat Implémenté (v1.5.0)
VRAM universelle via ri_phys_footprint Implémenté
Détection de régression historique Implémenté
Script de validation croisée (3 méthodes comparées) Disponible (scripts/cross-validate-bench.py)

Considérations Apple Silicon

Mémoire unifiée

Apple Silicon partage la mémoire entre CPU et GPU. asiai exécute les moteurs séquentiellement et décharge les modèles entre les moteurs pour éviter la contention mémoire et le swapping. La VRAM est rapportée nativement par Ollama et LM Studio ; pour les autres moteurs, asiai estime l'utilisation mémoire via ri_phys_footprint (la métrique d'empreinte physique macOS, identique au Moniteur d'activité). Les valeurs estimées sont étiquetées « (est.) » dans l'interface.

Throttling thermique

  • MacBook Air (sans ventilateur) : throttling sévère sous charge soutenue
  • MacBook Pro (ventilateur) : throttling léger
  • Mac Mini/Studio/Pro : refroidissement actif, throttling minimal

asiai enregistre thermal_speed_limit par résultat et avertit si un throttling est détecté.

KV Cache

Les grandes tailles de contexte (32k+) peuvent causer de l'instabilité sur les moteurs qui pré-allouent le KV cache. Réglez la longueur de contexte du moteur pour correspondre à la taille réelle du test pour des résultats équitables.

Mesure de puissance

asiai mesure la consommation GPU, CPU, ANE et DRAM via le framework Apple IOReport Energy Model — sans sudo requis. La puissance est mesurée automatiquement dans chaque benchmark et chaque snapshot de monitoring.

IOReport lit les mêmes compteurs d'énergie matériels que sudo powermetrics, mais via une API en espace utilisateur (libIOReport.dylib via ctypes). Cela élimine le besoin de configuration sudo sans mot de passe.

Validation

Nous avons validé IOReport par rapport à sudo powermetrics sous charge d'inférence LLM sur M4 Pro 64 Go, avec 10 échantillons appariés par moteur à intervalles de 2 secondes :

Moteur Moy. IOReport Moy. powermetrics Écart moyen Écart max
LM Studio (MLX) 12.6 W 12.6 W 0.9% 2.1%
Ollama (llama.cpp) 15.6 W 15.4 W 1.3% 4.1%

Les deux moteurs confirment un écart moyen <1,5% avec 10/10 échantillons appariés. La puissance ANE était de 0.000W sur les 20 échantillons, confirmant qu'aucun moteur LLM n'utilise actuellement le Neural Engine.

Le flag --power active une validation croisée supplémentaire en exécutant simultanément IOReport et sudo powermetrics, stockant les deux mesures pour comparaison.

Efficacité énergétique

L'efficacité énergétique (tok/s par watt) est calculée comme tok_per_sec / gpu_watts pour chaque résultat de benchmark. Cette métrique permet de comparer le coût d'inférence entre moteurs et matériels.

Métadonnées

Chaque résultat de benchmark stocke : engine, engine_version, model, model_format, model_quantization, hw_chip, os_version, thermal_level, thermal_speed_limit, power_watts, power_source, metrics_version. Cela permet une comparaison de régression équitable et des benchmarks inter-machines.