← retour
Ne ratez plus rien.
Un email à chaque nouvel article, les annonces des formations à venir, rien d'autre. Les news complètes de la veille, les chiffres et les cadeaux sont réservés aux inscrits à l'Espace Abonnés.

Cet article existe aussi en anglais : lire la version anglaise.

Discutez de cet article avec votre IA
Le bouton copie l'article avec une courte introduction pour le modèle. Collez le tout dans Claude, ChatGPT, Le Chat ou une autre IA, puis posez-lui vos questions.

Les deux temps d'une réponse

Cette page reprend le texte de la vidéo, avec ses images, puis donne en fin d'article les conditions exactes de chaque mesure.

Ces dernières semaines, je vous ai montré comment Herbert, mon moteur d'inférence pour IA locales, fonctionne sur différents types de matériel : processeurs, cartes graphiques, puces Apple.

Mais de quoi parle-t-on exactement, quand on dit qu'un moteur d'IA tourne à 40 ou 55 tokens par seconde ? Il y a en fait deux vitesses, qui ne dépendent pas de la même chose. Et au final, ce qui compte vraiment, c'est le temps qu'il faut attendre pour obtenir une réponse.

En développant Herbert, je me suis donné les moyens de mesurer précisément chaque étape. Je m'en sers ici pour approfondir le sujet, sans une seule formule.

Un exemple simple

Je donne à un modèle d'IA, qui tourne avec Herbert sur un processeur grand public, le début d'une phrase : « My cat is purple. But my friends tell me there is no such thing as a ». Il la continue, et je le laisse écrire 100 tokens. Sur ma machine, entre l'envoi de la phrase et le dernier token écrit, il s'écoule 7,2 secondes.

La phrase « My cat is purple. But my friends tell me there is no such thing as a ... », la suite écrite par le modèle (« purple cat. Is there a purple cat? There is no such thing as a purple cat... ») et un chronomètre à 7,2 secondes, sur le processeur Ryzen 9 9900X.

Pendant ce temps, il se passe deux choses très différentes. D'abord, le modèle lit la phrase : c'est ce qu'on appelle le prefill. Ici, ça prend 8 centièmes de seconde. Ensuite, il écrit la suite, un token après l'autre : c'est le décodage (decode). Cette phase dure 7,1 secondes. Pourquoi l'un est-il presque instantané et l'autre si long ? Pour le comprendre, il faut regarder ce qui se passe dans le moteur.

Un modèle d'IA, qu'est-ce que c'est ?

Un modèle d'IA, c'est d'abord un fichier, parfois découpé en plusieurs. Dedans, il n'y a que des nombres, des milliards de nombres : ce qu'on appelle des paramètres. Ces paramètres sont rangés en couches, que le calcul traverse l'une après l'autre.

Les paramètres du modèle rangés en 28 couches, traversées l'une après l'autre, du texte en entrée jusqu'au token suivant.

Ils ont été réglés pendant l'entraînement, une fois pour toutes. Quand on pose une question, ils ne changent plus : on s'en sert seulement pour faire les calculs.

Et ce calcul ne fait qu'une chose : à partir du texte qu'on lui donne, il calcule le morceau de mot qui devrait venir ensuite. C'est ce qu'on appelle un token, ou un jeton en français. On ajoute ce token au texte, et on recommence. Une réponse, c'est juste cette boucle, un token après l'autre.

La boucle : le texte donne un token (« purple », 92,5 %), on l'ajoute, puis « cat » (90,5 %), puis le point (68,8 %), puis « Is » (12,8 %).

Herbert, vLLM, MLX ou llama.cpp : le programme qui fait ce calcul, on l'appelle un moteur d'inférence.

Deux opérations

Tout ce qui suit repose sur deux opérations très simples. La première prend deux listes de nombres de même longueur, les multiplie case par case et additionne les résultats. Ça donne un seul nombre : le produit scalaire.

Il permet de savoir simplement si deux listes vont dans le même sens. Ces listes, en mathématiques, on les appelle des vecteurs. Quand ils vont dans le même sens, le produit scalaire est positif ; quand ils vont dans des sens contraires, il est négatif. Et plus ils sont alignés, plus leurs nombres sont grands, plus il s'éloigne de zéro.

Trois paires de flèches en trois dimensions : même sens, produit scalaire 10 ; sens contraires, −10 ; à angle droit, 0.

La deuxième prend une série de scores et les transforme en pourcentages dont la somme fait 100 %. Le plus grand score prend la plus grosse part. C'est le softmax.

Quatre scores (1,2 ; 3,5 ; 0,4 ; 2,1) transformés en pourcentages : 7 %, 72 %, 3 %, 18 %, toujours positifs, total 100 %.

Un modèle de langage utilise bien d'autres opérations, mais ces deux-là suffisent pour comprendre l'essentiel de ce qui se passe.

Le modèle qu'on ouvre

Pour regarder à l'intérieur, je prends un petit modèle : Qwen2.5, en version 1,5 milliard de paramètres, dans sa version de base. J'ai fait ce choix parce que toutes ses couches fonctionnent de la même façon, contrairement à certains modèles plus récents, et je tenais à garder mes exemples simples.

Tout ce qu'on voit ici à l'intérieur de ce modèle, ce sont ses vrais nombres, sur de vraies phrases. Les temps et les choix du modèle sortent d'Herbert. Les valeurs internes, elles, sont extraites avec Transformers, la bibliothèque de référence.

Des tokens et des nombres

Reprenons notre phrase. Le texte est d'abord découpé en tokens : ici, dix-sept, un par mot, plus le point. Ce modèle en connaît plus de 150 000 différents.

La phrase découpée en 17 tokens (My, cat, is, purple, le point, But, my, friends, tell, me, there, is, no, such, thing, as, a) et leurs numéros dans le vocabulaire.

Chaque token est remplacé par une ligne d'un grand tableau appris. Chaque ligne, un vecteur, est composée de 1 536 nombres : c'est la table d'embedding. À partir de là, le modèle ne voit plus jamais de mots, seulement ces listes de nombres, une par token.

Le token « purple » remplacé par sa ligne du tableau : 0,017, −0,004, −0,008, −0,005, 0,026, 0,028... 1 536 nombres, une ligne d'un tableau de 151 936 lignes.

Chacune de ces listes traverse 28 couches. Aucune couche ne remplace la liste : chacune calcule des modifications et les ajoute pour la couche suivante. Couche après couche, la liste se transforme, selon ce que le modèle a appris.

Dans chaque couche, il y a deux étapes, toujours dans le même ordre. La première fait communiquer les tokens entre eux : c'est l'attention, le seul endroit du modèle où un token reçoit de l'information des autres. La seconde travaille sur chaque token tout seul, sans regarder les autres : le MLP.

Une couche : 1, les tokens communiquent, l'attention (12 têtes de requête, 2 têtes clé/valeur) ; 2, chaque token seul, le MLP (8 960 directions apprises).

L'attention

L'attention n'est pas faite une seule fois, mais plusieurs fois en parallèle, par ce qu'on appelle des têtes. Chaque tête regarde la phrase à sa façon et va chercher autre chose. Dans ce modèle, il y en a 12 par couche.

Depuis le dernier token « a », quatre des 12 têtes de la couche 19 : la tête 2 regarde « purple » à 91 %, la tête 7 « My » à 75 %, la tête 9 « as » à 73 %, la tête 11 « a » lui-même à 95 %.

Suivons-en une seule. Arrivé au dernier token, « a », le modèle fabrique trois listes à partir de lui, chacune par des produits scalaires avec des tableaux de paramètres appris : une requête, Q, une clé, K, une valeur, V. Dans ce modèle, 128 nombres chacune. Chaque token précédent a aussi sa clé et sa valeur.

Le moteur fait ensuite le produit scalaire de la requête de « a » avec la clé de chaque token jusqu'à lui. Ça donne un score par token. Pour schématiser, « a » demande à chacun des tokens qui le précèdent : à quel point es-tu utile pour moi ?

Avec un softmax, ces scores deviennent des pourcentages. Voici les vrais, dans la couche 19, pour une de ses têtes : 91 % sur « purple », 5 % sur « My » et presque rien sur le reste.

L'attention de « a », couche 19, tête 2 : 91 % sur « purple », 5 % sur « My », presque rien ailleurs.

J'ai pris un exemple totalement improbable : un chat violet. Ce n'est pas dans sa mémoire que le modèle peut trouver la couleur de mon chat, c'est dans ma phrase. Et justement, au moment d'écrire la suite, cette tête va chercher « purple », treize tokens plus tôt. Personne ne lui a dit de le faire : c'est ce que ses paramètres ont appris.

Ce qui revient à « a », c'est la somme des valeurs de ces tokens, chacune pesée par son pourcentage : 91 % de la valeur de « purple », 5 % de celle de « My », et ainsi de suite.

La somme pondérée : 91 % de la valeur de « purple », 5 % de « My », 1 % du point, 1 % de « But », 2 % pour les 13 autres, ajoutés à « a ».

Les 12 têtes font ce calcul en même temps, chacune avec sa propre requête. Mais elles se partagent les clés et les valeurs : deux jeux pour les douze. Si le chat est vert, je retrouve les mêmes têtes sur la couleur. S'il a cinq pattes, ce sont d'autres têtes qui réagissent. Et si j'éteins celle de la couche 19, le modèle écrit quand même « purple » : elle n'est donc pas indispensable, l'information passe aussi ailleurs dans le réseau.

On voit aussi où le modèle va chercher « purple » : jusqu'à la couche 10, l'attention moyenne sur ce mot reste sous 5 %. Puis elle monte, jusqu'à 29 % à la couche 17.

À gauche, l'attention de « a » sur « purple » pour les 28 couches × 12 têtes ; à droite, la moyenne des 12 têtes couche par couche, qui monte jusqu'à 29 % à la couche 17. En bas : 43 % en moyenne sur « My », le point de repos.

En moyenne, les têtes donnent 43 % de leur attention au tout premier token, « My ». Les chercheurs appellent ça un puits d'attention : dans de nombreux modèles, le premier token attire une grande part de l'attention, même quand il ne porte rien de particulièrement utile. Il sert un peu de point de repos.

Pour que l'ordre compte, la position de chaque token est inscrite dans sa requête et sa clé, en faisant tourner leurs nombres d'un angle qui dépend de la position.

Le KV cache

Une fois calculées, la clé et la valeur d'un token ne changent plus jamais. Alors le moteur les garde, dans un tableau qui gagne une ligne à chaque token : c'est le KV cache, K pour les clés, V pour les valeurs.

Quand le modèle écrit un nouveau token, il ne calcule que la requête, la clé et la valeur de ce token-là. Il ajoute sa ligne au tableau et compare sa requête à toutes les clés déjà gardées. Sans ce tableau, il faudrait tout recalculer depuis le début, à chaque token écrit.

Le vrai KV cache de la phrase, couche 19 : les clés et les valeurs (128 nombres chacune) des 17 tokens. 14 336 nombres par token, environ 29 ko, environ 117 Mo pour 4 096 tokens.

Sa taille se calcule. Dans ce modèle : 14 336 nombres par token, soit environ 29 kilo-octets. Pour 4 096 tokens, environ 117 mégaoctets, encore peu à côté des 3,1 gigaoctets de paramètres. Mais à 32 000 tokens, c'est près d'un gigaoctet de plus à relire, à chaque token écrit. Plus la conversation est longue, plus chaque nouveau token coûte cher. On vous a déjà conseillé de garder vos conversations avec une IA courtes ? En voilà une des raisons.

Le token seul : le MLP

La deuxième étape de chaque couche travaille sur chaque token seul. Ici, elle contient presque 9 000 paires de directions apprises. Pour chaque paire, le modèle fait deux produits scalaires avec la liste du token : le premier sert de vanne, il décide combien du second laisser passer. Ça donne une valeur par paire.

Pour « a », dans la couche 19, la plupart sont petites, en positif comme en négatif : la moitié ne dépasse pas 0,08. Près de 300 dépassent 0,5, 70 dépassent 1, et la plus forte vaut 3,3.

Les 600 plus fortes des 8 960 valeurs du MLP pour « a », couche 19, triées, positives en haut, négatives en bas. En valeur absolue : la moitié sous 0,08, près de 300 au-dessus de 0,5, 70 au-dessus de 1, maximum 3,3.

Chacune de ces valeurs règle une correction apprise : les fortes pèsent beaucoup, les petites presque rien. Le tout est ramené à 1 536 nombres et ajouté à la liste du token. C'est là que se trouve l'essentiel des paramètres de chaque couche : 41 millions sur 47.

Choisir le token suivant

Après la 28e et dernière couche, la liste du dernier token est comparée, par produit scalaire, à chacun des tokens connus : 151 936 scores. Je les montre en pourcentages, mais pour choisir, pas besoin. Ici, je prends toujours le plus grand score : c'est l'argmax. Dans la plupart des assistants, on ne choisit pas comme ça. Une partie du moteur, le sampler, tire le token au hasard en favorisant les plus probables. Là, les pourcentages servent vraiment.

Le premier token écrit après « ...no such thing as a » : les 10 premiers sur 151 936. « purple » à 92,5 %, « blue » à 0,6 %, « cat » à 0,6 %.

Pour notre chat violet : premier token, « purple », 92,5 %, loin devant « blue », moins de 1 %. Puis « cat », 90,5 %. Puis le point, 68,8 %. Un token à la fois.

Je ne sais pas pour vous, mais pour moi, c'est une dinguerie : personne ne lui a dit que mon chat était violet, sauf ma phrase, mon prompt. Et c'est là qu'il est allé le chercher.

Cette dernière étape pèse 233 millions de paramètres sur 1,5 milliard. Dans beaucoup de modèles, c'est un deuxième grand tableau, de la même forme que celui du début, mais appris pour un autre usage : non plus transformer un token en liste, mais donner un score à chaque token. Ce petit modèle, lui, se sert du même tableau dans les deux sens, pour économiser. Et pour écrire un seul token, il faut le parcourir en entier : 15 % de tout ce que le modèle doit relire.

La sortie : la part des paramètres relus pour écrire un token. Qwen2.5-1.5B, 233 M sur 1,5 Md, 15 % ; Qwen3.5-2B, 509 M sur 1,9 Md, 27 %.

Premier temps : lire

Le premier temps, le prefill, c'est lire le texte qu'on donne au modèle. Tous les tokens du texte traversent les couches ensemble. Dans chaque couche, chaque paramètre lu en mémoire sert à tous les tokens d'un coup : pour un document de 3 000 tokens, chaque paramètre sert 3 000 fois.

3 000 tokens traversent ensemble une couche dont les paramètres sont lus une fois pour tous.

Dans l'attention, chaque token est comparé à tous ceux d'avant. Ça fait un triangle de scores qui grossit très vite avec la longueur. Et le KV cache se remplit.

Ici, ce qui compte, c'est la quantité de calcul. Plus le processeur calcule vite, plus la lecture va vite.

Lire 3 139 tokens de The Gold-Bug d'un coup : environ 9 100 milliards d'opérations en 2,1 s, soit environ 4 300 milliards par seconde.

Deuxième temps : écrire

Le deuxième, le décodage, c'est écrire la suite. Là, un seul token à la fois, parce que le suivant dépend de celui-ci. Chaque paramètre lu en mémoire ne sert plus qu'une seule fois.

Pour chaque token écrit, il faut donc faire venir de la mémoire tous les paramètres du modèle, 3,1 gigaoctets, plus le KV cache. Et on recommence pour le token suivant.

Pour un seul token écrit, 1,5 milliard de paramètres relus, 3,1 Go en bf16.

Le processeur de mon PC écrit 14 tokens par seconde. 14 fois 3,1 gigaoctets, ça fait environ 44 gigaoctets lus par seconde. Mesurée seule, sa mémoire en donne au mieux 46 à 48. On est tout près du maximum : le calcul, lui, attend.

Processeur Ryzen 9 9900X : 14 tokens/s × 3,1 Go, environ 44 Go/s utilisés sur 46 à 48 mesurés seuls.

1 % ou 22 % ?

Ce qui compte, c'est le total : lire plus écrire. J'ai fait l'essai sur le processeur de mon PC, un Ryzen 9 9900X, avec deux textes.

Notre phrase courte, le chat violet : 17 tokens lus en 8 centièmes de seconde, 100 tokens écrits en 7,1 secondes, 7,2 secondes en tout. Lire ne pèse que 1 % du temps.

Un long texte, un passage d'une nouvelle d'Edgar Allan Poe, The Gold-Bug : 3 139 tokens lus en 2,1 secondes, 100 tokens écrits en 7,5 secondes, 9,6 secondes en tout. Lire représente 22 % du temps.

Chat violet : 17 tokens lus, 100 écrits, lire 0,08 s, écrire 7,1 s, 7,2 s. The Gold-Bug : 3 139 tokens lus, 100 écrits, lire 2,1 s, écrire 7,5 s, 9,6 s.

La part de chaque temps : chat violet, lire 1 %, écrire 99 % ; The Gold-Bug, lire 22 %, écrire 78 %.

Même avec un long texte, c'est l'écriture qui fait l'essentiel de l'attente, parce que chaque token écrit relit tout le modèle.

La preuve que les deux temps ne dépendent pas de la même chose : sans les instructions AVX-512, qui calculent sur 512 bits d'un coup, la lecture de la nouvelle est près de 6 fois plus lente. L'écriture ne bouge pas.

The Gold-Bug avec et sans AVX-512 : lire 2,1 s contre 12,2 s ; écrire 100 tokens 7,5 s contre 7,3 s. Lire ×5,9 sans AVX-512, écrire inchangé.

Même chose quand j'optimise mon moteur : la lecture gagne 12 %, l'écriture ne bouge toujours pas.

The Gold-Bug avant et après la première optimisation : lire 2,1 s puis 1,8 s (−12 %) ; écrire 7,5 s dans les deux cas.

Sur ce processeur, avec une seule conversation, lire, c'est du calcul. Écrire, c'est de la mémoire et elle est déjà presque à son maximum.

Et quand le modèle réfléchit ?

Beaucoup de modèles récents réfléchissent avant de répondre. Concrètement, ils écrivent d'abord un brouillon, que vous ne voyez pas toujours, puis la vraie réponse. Ce brouillon, le think, ce sont des tokens écrits un par un, exactement comme la réponse, à la vitesse de l'écriture.

J'ai posé une question à un autre petit modèle, Qwen3.5, en version 2 milliards de paramètres, qui, lui, sait réfléchir : « Pourquoi le ciel est-il bleu en pleine journée, mais rouge orangé au coucher du soleil ? ». Sans réflexion, il répond en 7,5 secondes, cinq fois sur cinq. Mais sa réponse est fausse : il dit que le bleu est absorbé par l'air. C'est un tout petit modèle, pas de miracle ici.

Réflexion activée, sur plusieurs essais, il écrit d'abord entre 3 600 et 5 100 tokens de brouillon. La réponse complète arrive au bout de 5 à 8 minutes. Et cette fois, toutes les réponses sont justes. Ici, une bonne réponse a donc coûté 40 à 60 fois plus d'attente, parce que chaque token de réflexion se paie au prix de l'écriture.

Qwen3.5-2B, « Pourquoi le ciel est-il bleu... », avec et sans réflexion : sans, 7,5 s et une réponse fausse ; avec, 3 600 à 5 100 tokens de brouillon et 5 à 8 minutes, une réponse juste à chaque essai, 40 à 60 fois plus d'attente.

Le modèle qu'on a ouvert plus haut, lui, ne réfléchit pas : c'est un modèle de base, il complète le texte, c'est tout. Les assistants que vous utilisez ont été entraînés une deuxième fois, pour répondre à une question et s'arrêter à la fin de leur réponse. C'est ce qu'on appelle le post-entraînement, et le modèle obtenu, la version Instruct. Mais dessous, c'est la même boucle : un token après l'autre, au même prix.

Ce qu'il faut retenir

Une réponse, c'est donc deux temps. Sur ma machine, lire, c'est du calcul : tous les tokens passent ensemble, et un processeur qui calcule plus vite lit plus vite. Écrire, c'est de la mémoire : un token à la fois, et pour chaque token, tout le modèle à relire.

Lire : du calcul, tous les tokens ensemble. Écrire : de la mémoire, un token à la fois, tout le modèle relu à chaque fois.

Alors, la prochaine fois qu'on vous annonce un moteur à tant de tokens par seconde, posez-vous deux questions : combien de temps lui faut-il pour lire ce que je lui donne ? Et combien de temps pour écrire sa réponse ? Au bout du compte, ce n'est pas le nombre de tokens par seconde qui compte, c'est le temps qu'on passe à attendre.

Dans une prochaine vidéo, je parlerai des modèles qui, pour écrire, ne relisent qu'une partie de leurs paramètres : les modèles à experts.

Les conditions de mesure

Tout ce qui précède vaut pour ces conditions-là : un processeur, une seule conversation à la fois, deux petits modèles, une question pour la réflexion.

La machine. AMD Ryzen 9 9900X (12 cœurs, AVX-512 et AVX512-BF16), 12 fils. Débit mémoire mesuré seul, par une lecture en flux émise par Herbert : 46,0 Go/s sur 1 fil, 48,1 Go/s au mieux sur 2 fils, 45,0 Go/s sur 12 fils.

Le moteur. Herbert, version JIT pour processeur, en bf16 (aucune quantification). Les temps du chat violet et de The Gold-Bug sont mesurés avant optimisation ; la colonne « après » de l'optimisation est la première étape du même moteur. Sans AVX-512, le moteur passe par ses noyaux AVX2 et perd aussi les instructions bf16 dédiées. Chaque temps est une médiane de 5 passages, contexte vidé entre deux passages, 100 tokens écrits.

Le modèle ouvert. Qwen2.5-1.5B, version de base (pas Instruct), poids d'origine en bf16 : 28 couches, 1 536 nombres par token, 12 têtes de requête et 2 têtes de clé et valeur de 128 nombres, MLP de 8 960, vocabulaire de 151 936 tokens, même tableau pour l'entrée et la sortie. Le choix des tokens est l'argmax (décodage glouton).

Les valeurs internes. Attention, KV cache, MLP et embedding sont extraits avec Transformers 5.14.1 (PyTorch 2.13), en bf16 sur le même processeur, attention en mode eager. Le modèle y prédit « purple » à 90,3 %, contre 92,5 % dans Herbert : les deux calculs n'arrondissent pas exactement aux mêmes endroits. Les numéros de couche et de tête sont comptés à partir de 1 dans le texte (la couche 19 est l'indice 18).

Les têtes. Les variantes « green » et « five legs » reprennent la même phrase avec un seul mot changé. Pour l'ablation, la sortie d'une tête est mise à zéro avant la projection de sortie de l'attention, dans toutes les positions. Tête 2 de la couche 19 éteinte : « purple » passe de 90,3 % à 92,5 %. Les 10 têtes qui regardent le plus la couleur éteintes ensemble : 83,0 %. Dix têtes tirées au hasard : 93,2 %. Ces observations montrent une redondance, elles ne disent pas à elles seules quelle tête « porte » l'information.

Les textes. Le passage de The Gold-Bug est l'explication du cryptogramme par Legrand, coupée après « about four miles to the northward of the island. » (3 139 tokens).

La réflexion. Qwen3.5-2B en bf16 dans Herbert (première étape d'optimisation), contexte de 16 384 tokens. Sans réflexion : décodage glouton, une chauffe puis 5 répétitions, 85 tokens écrits, fin à 7,49 s les cinq fois. Avec réflexion : l'échantillonnage recommandé par la carte du modèle (température 1,0, top_p 0,95, top_k 20, min_p 0, presence_penalty 1,5), graines 1 à 5, plafond de 8 192 tokens. Réponse complète en 320,6 à 464,6 s, à environ 11,2 tokens/s. Une limite à dire : les graines 2 et 3 ont donné exactement le même texte, les graines 4 et 5 aussi. Il n'y a donc que trois tirages différents sur les cinq ; le traitement de la graine dans le serveur est à corriger.

Les relevés. Les scripts de mesure, tels qu'ils ont tourné, avec les sorties brutes : emplacement du dépôt à venir.