Performance d’inférence : chargement, premier token, décodage et débit
Les tokens par seconde ne décrivent qu'une partie de l'inférence. Un générateur rapide peut faire attendre pendant le traitement d'un long prompt, une file ou le chargement. Avant de comparer les moteurs, préciser s'il s'agit d'un utilisateur local ou de requêtes concurrentes, et si l'on privilégie le premier résultat ou la fin du lot.
Séparer les étapes d'une requête
Entre l'envoi et la fin, une requête traverse plusieurs attentes. Si le modèle n'est pas encore chargé, le moteur lit d'abord les fichiers sur disque, alloue la mémoire et transfère les poids vers le GPU ; certains moteurs compilent ou préparent aussi des noyaux. C'est le démarrage à froid. Le texte reçu est ensuite découpé en tokens (tokenisation), et la file côté serveur, le réseau et les tampons du client s'ajoutent à l'attente ressentie.
Le travail du modèle lui-même se fait en deux phases. Le prefill lit tout le prompt d'un coup et construit l'état dont la génération aura besoin. Le décodage, dans la génération autorégressive ordinaire, ajoute ensuite les tokens de sortie un par un. Dans la chronologie de la section suivante, le premier token arrive 0,8 seconde après l'envoi : ces 0,8 seconde servent surtout au prefill. Ensuite, un token toutes les 0,02 seconde donne le rythme du décodage.
Le benchmark de service vLLM expose ces différentes latences et leurs percentiles. Tout autre outil doit préciser des frontières comparables, notamment l'inclusion de la file côté client.
Le décodage spéculatif peut valider plusieurs tokens par tour de vérification ; le chronométrage doit inclure les propositions, la vérification et l’actualisation de l’état.
Calculer à partir d'une chronologie
Supposons un envoi à 0 seconde, un premier token à 0,8 seconde, et le 101e et dernier à 2,8 secondes. Sans délai final supplémentaire, le TTFT vaut 0,8 seconde. L'intervalle moyen suivant est (2.8 - 0.8) / (101 - 1) = 0.02 seconde, soit 50 tokens/s pendant cette phase. Diviser tous les tokens par la durée entière donne 101 / 2,8, environ 36,1 tokens/s. Les deux valeurs ont un sens différent.
Les blocs de streaming peuvent contenir plusieurs tokens : compter les blocs peut surestimer ou sous-estimer la vitesse. Utiliser le tokenizer du modèle pour compter les tokens de la sortie complète, ou un comptage serveur fiable, et conserver la définition choisie.
Chaque token généré relit tous les poids
Savoir où passe le temps du décodage explique pourquoi les lots, la quantification, le décodage spéculatif et le cache KV comptent. La réponse : pour chaque token produit, le modèle relit une fois tous ses poids depuis la mémoire du GPU, et cette lecture prend bien plus de temps que le calcul.
D'abord, pourquoi tous les poids. Pour produire le token suivant, le vecteur du token qui vient d'être généré traverse chaque couche, et chaque couche le multiplie par ses propres matrices de poids. Chaque poids sert donc une fois ; le token terminé, le suivant doit relire tous les poids.
Ensuite, qu'est-ce qui est le plus lent, lire ou calculer ? Prenons un modèle de 8 milliards de paramètres comme Llama 3 8B, stocké en BF16 à 2 octets par paramètre : 8×10⁹ × 2 = 16 Go de poids. Sur une NVIDIA L40S, dont les caractéristiques publiées annoncent 864 Go/s de bande passante mémoire et 362 000 milliards d'opérations BF16 par seconde sur les cœurs tensoriels :
- Lire les poids une fois : 16 / 864 ≈ 0,0185 s, soit 18,5 ms.
- Calculer un token : une multiplication et une addition par paramètre, 16×10⁹ opérations ; à 362×10¹² par seconde, environ 0,04 ms.
La lecture est plus de quatre cents fois plus lente. Quand une seule personne génère token après token, la vitesse dépend donc presque entièrement de la taille des poids et de la vitesse de lecture de la mémoire. Même avec un calcul instantané, ce modèle sur cette carte ne dépasserait pas 1 / 0,0185 ≈ 54 tokens par seconde. La chronologie ci-dessus, un token toutes les 0,02 seconde, soit 50 par seconde, se situe précisément à cette échelle.
Sur votre propre machine, c'est la même division. Apple annonce pour le M5 Pro une bande passante mémoire de 307 Go par seconde. Llama 3.1 8B quantifié en Q4_K_M pèse environ 4,9 Go (voir la note sur la quantification), d'où un plafond d'environ 307 / 4,9 ≈ 63 tokens/s.
Les modèles à mélange d'experts (MoE) font exception. Chaque token ne passe que par les quelques experts choisis par le routeur : les octets lus à chaque étape suivent donc les paramètres actifs, bien moins nombreux que l'ensemble des poids. Les experts non choisis doivent pourtant rester en mémoire, si bien que la capacité suit toujours le nombre total de paramètres ; voir le mélange d'experts.
Les techniques courantes pour accélérer le décodage ou économiser la mémoire agissent toutes sur cette étape :
Le prefill échappe à cette limite. Les milliers de tokens d'un prompt sont calculés ensemble : une lecture des poids les sert tous, et c'est alors le calcul qui limite. Un long prompt retarde donc surtout le premier token, une longue sortie allonge surtout le décodage.
Les lots changent le compromis
Les lots exploitent exactement cette étape : une lecture des poids peut produire un token pour chacun de plusieurs utilisateurs. Gardons le même modèle et la même carte, avec un contexte de 4K tokens par utilisateur. Avec la forme de Llama 3 8B, le cache KV occupe 128 Kio par token, donc 512 Mio pour 4K tokens, environ 0,54 Go (calcul détaillé dans la note sur le cache KV) :
À huit, le débit agrégé est multiplié par plus de six, mais l'intervalle entre tokens de chaque utilisateur passe de 19,1 à 23,5 ms. Les poids ne sont lus qu'une fois, alors que chaque utilisateur apporte son propre cache : plus le lot grossit, plus chaque étape lit de cache et se rapproche de la limite de calcul, et plus chaque utilisateur ralentit.
En plus d'étapes plus lentes, une requête peut devoir attendre la formation du lot. Le traitement continu permet aux séquences terminées de sortir et à de nouvelles d'entrer sans attendre la plus longue.
La concurrence augmente aussi le besoin de cache KV. PagedAttention gère ses allocations dynamiques par pages afin de réduire fragmentation et duplication et de permettre un ordonnancement plus efficace des requêtes. Cela traite un problème de gestion mémoire, sans supprimer les limites de poids, de capacité ou de calcul.
Les tâches à longue entrée et courte sortie mettent en évidence prefill et TTFT ; les tâches inverses exposent le décodage. Précision réduite, lots plus grands et contexte plus court modifient des coûts différents et ne sont pas interchangeables : la précision réduite lit moins d'octets de poids, un lot plus grand partage une lecture des poids entre plus d'utilisateurs, un contexte plus court lit moins de cache à chaque étape.
Distinguer les caches
Résidence du modèle, noyaux préchauffés et cache de préfixe sont trois situations différentes. Un modèle chargé n'implique pas un prompt en cache ; un texte système répété peut accélérer les requêtes suivantes. Comparer séparément démarrage à froid, modèle résident sans préfixe réutilisé, et taux de réutilisation réaliste.
La réutilisation suppose des entrées et une configuration du modèle identiques pour la partie mise en cache. Répéter exactement le même prompt mesure un cas idéal, pas des tâches changeantes. Changer modèle, adaptateur ou contexte ne doit pas laisser un ancien cache considéré comme valide.
Conserver échecs et longues attentes
Les estimations ci-dessus, tirées de la bande passante et du calcul annoncés, sont des bornes inférieures idéales ; le goulot réel se mesure sur le matériel visé. Fixer modèle, quantification, moteur, longueurs d'entrée et concurrence, consigner médiane et percentiles élevés, et garder dépassements de délai et erreurs dans les résultats ; le taux d'arrivée et la génération de charge d'un service multi-utilisateur relèvent de l'évaluation des modèles locaux.
Enfin, mesurer réponses correctes, réussite des outils et reprises nécessaires. Un débit élevé ne compense pas le temps consacré à un mauvais travail. Le choix du moteur traite la compatibilité ; l'évaluation des modèles et agents locaux traite les résultats des tâches.