← retour
Écoutez cet article
Voici un court extrait. La version audio complète, les news complètes de la veille, les chiffres et les cadeaux sont réservés aux inscrits à l'Espace Abonnés.

Le podcast ne reprend pas le texte mot pour mot : c'est une version parlée et simplifiée de l'article.

Pour écouter la version complète, connectez-vous ou créez votre compte gratuit.

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.

On descend d'un cran : #002 le même compilateur sur un GPU

Premier token vu du client sur une RTX 4080, Qwen3.5-0.8B, prompt de 21 tokens, une requête à la fois : Herbert 6,5 ms, llama.cpp CUDA 12,0, vLLM 18,9, llama.cpp Vulkan 23,2 ; sur le 4B à 4 106 tokens, vLLM répond 3,7 % plus tôt

L'article précédent se terminait sur une question. Herbert écrit son code machine au démarrage, pour le modèle et pour le processeur qu'il a devant lui. Est-ce que ce principe tient sur un GPU, face aux moteurs GPU de référence ?

Sur une RTX 4080, avec Qwen3.5-0.8B et une requête à la fois, Herbert donne son premier token en 6,5 ms sur un prompt court, là où les coûts fixes de chaque moteur dominent. vLLM en met 19, llama.cpp en CUDA 12. Sur le modèle de 4 milliards de paramètres, face à llama.cpp à ses meilleurs réglages, c'est l'égalité. Et sur le GPU Arc d'un portable Intel, en f16 contre f16, Herbert est nettement plus lent au prefill.

Les trois résultats sont vrais. Pour chacun, je donne la carte, la version et le réglage.

Une précision avant d'aller plus loin : aucune carte graphique AMD récente n'est dans ce banc. Côté AMD, je n'ai mesuré que les GPU intégrés de deux processeurs, un Ryzen 7 5700G et un Ryzen 9 9900X. J'ai commandé une Radeon RX 9060 XT de 16 Go, un GPU RDNA 4 : elle aura son propre article, qui sera aussi l'occasion de comparer avec les bibliothèques d'AMD.

En bref

L'essentiel en quatre lignes. Le reste de l'article explique comment je l'ai mesuré et dans quels cas Herbert est moins bon.

Après le premier article, trois lecteurs ont relevé la même chose, sur X et sur LinkedIn : ce qui les intéressait, c'étaient les cas défavorables à Herbert, écrits dans le texte avec leurs chiffres. L'un d'eux attendait de l'article sur Vulkan les références, les plateformes et les échecs. Je les donne ici. Quand je dis qu'un moteur est devant ou derrière, je décris une mesure. Je ne cherche pas à gagner une course : un cas où Herbert est plus lent me montre où chercher pour l'optimiser.

Émettre pour un GPU

Sur le processeur, Herbert écrit du code machine x86. Sur le GPU, il écrit du SPIR-V, le langage intermédiaire de Vulkan. Un module par noyau, au démarrage, pour la forme du modèle et pour la carte qu'il a devant lui. La taille des sous-groupes, la présence de matrices coopératives, le bf16 natif ou pas : il le lit sur la carte et il l'écrit dans le noyau. Pas de bibliothèque du constructeur, pas de shader compilé à l'avance, pas de compilateur externe.

Pourquoi Vulkan plutôt que le langage d'un constructeur ? Parce que Vulkan parle au moins aux trois grands, NVIDIA, Intel et AMD, avec le même binaire et le même émetteur. CUDA et ROCm ne parlent chacun qu'à un seul constructeur. OpenCL parle à tous, mais je trouve Vulkan mieux implémenté par les pilotes et plus à même de tirer parti de plusieurs types de matériel.

Comme dans l'article précédent, une option écrit sur le disque ce que le moteur a émis. Je l'ai lancée sur une machine qui a deux GPU, une RTX 4080 et le GPU intégré AMD de son Ryzen 9 9900X, avec deux tailles du même modèle.

target/release/herbert-vk-chat ~/models/Qwen3.5-0.8B --device NVIDIA --dump-spv spv/NVIDIA-0.8B
target/release/herbert-vk-chat ~/models/Qwen3.5-0.8B --device RADV --dump-spv spv/RADV-0.8B
target/release/herbert-vk-chat ~/models/Qwen3.5-4B --device NVIDIA --dump-spv spv/NVIDIA-4B

Prenons la projection qui calcule d'un coup les deux moitiés de la couche feed-forward, gate et up, au décodage. Sur le 0.8B, elle va de 1 024 valeurs à 7 168. Désassemblé avec spirv-dis, le module fait 194 lignes. Voici les seules qui changent d'un GPU à l'autre. Sur la RTX 4080 :

     %uint_4 = OpConstant %uint 4
    %uint_32 = OpConstant %uint 32
         %36 = OpIMul %uint %31 %uint_4
        %125 = OpIAdd %uint %124 %uint_32

Sur le GPU intégré du Ryzen 9 9900X :

     %uint_2 = OpConstant %uint 2
    %uint_64 = OpConstant %uint 64
         %36 = OpIMul %uint %31 %uint_2
        %125 = OpIAdd %uint %124 %uint_64

Le 32 et le 64, c'est la taille d'un sous-groupe sur chaque carte, donc le pas de la boucle ; le 4 et le 2 en découlent, 128 invocations divisées par 32 ou par 64. D'un modèle à l'autre, sur la même 4080, ce sont les dimensions qui changent : 7 168, 128 et 7 167 sur le 0.8B deviennent 18 432, 320 et 18 431 sur le 4B, le nombre de lignes de sortie, la longueur d'une ligne de poids en blocs de 16 octets (huit valeurs bf16) et le dernier indice pour ne pas déborder. Toutes sont des constantes du module : le noyau ne lit aucune borne au moment de tourner.

Le reste diffère davantage. Sur la 4080, Herbert émet 38 noyaux. Les multiplications de matrices du prefill passent par les matrices coopératives de la carte. Au décodage, deux paires d'opérations sont fusionnées en un seul noyau : la normalisation avec la projection qui la suit, l'activation avec la projection qui la suit. Sur le GPU intégré, dont le pilote Vulkan n'expose pas de matrices coopératives, il en émet 32, avec des multiplications de matrices écrites autrement et sans ces fusions. Le pas d'un token du 0.8B tient en 192 opérations sur la 4080, en 265 sur le GPU intégré. La différence, ce sont ces deux fusions : quand je les désactive sur la 4080, le pas remonte à 265 opérations.

Le contrat numérique

Le contrat est celui de l'article précédent : poids, activations et cache en bf16, accumulateurs en f32. Le premier article posait aussi une condition, des sorties identiques token par token. Sur un GPU, elle ne tient pas. Le GPU suit la référence processeur, puis à un moment il prend un autre mot : selon le prompt, au token 0, au token 8 ou au token 172. Sur la 4080, 3 sorties sur 20 restent identiques jusqu'au bout.

J'ai regardé chaque premier écart, en relevant les cinq meilleurs candidats de chaque côté. Les deux mots en balance sont à un ou deux quanta bf16 l'un de l'autre, la plus petite différence que le format sache écrire à cette hauteur. Sept fois sur dix-sept, le GPU les voit même à égalité parfaite. Le GPU ne se trompe pas : il n'additionne pas dans le même ordre, donc il n'arrondit pas pareil. Et c'est stable : après une mise à jour du pilote NVIDIA, les écarts sont retombés exactement sur les mêmes tokens. Même chose sur une RTX 3060 Ti, sous Windows. C'est ce contrat qui rend les chiffres comparables d'une machine à l'autre, pas des sorties identiques.

Le banc

Cinq GPU, les mêmes sources d'Herbert. Une RTX 4080 de 16 Go sous Linux. Une RTX 3060 Ti de 8 Go sous Windows 11. Le GPU Arc 140V d'un portable Core Ultra 7 258V. Les GPU intégrés d'un Ryzen 7 5700G et d'un Ryzen 9 9900X. En face, les versions du 12 octobre 2026 : vLLM 0.28.0 ; llama.cpp en CUDA et en Vulkan, compilés au même commit, 08ffba21e, vérifié sur les binaires : celui de mes premières mesures était un build du 17 juin, 8 à 16 % plus lent au prefill que le bon. vLLM ne tourne que sur la 4080. Le modèle est Qwen3.5, en 0.8B et en 4B ; le 4B ne tient pas dans la 3060 Ti, elle passe le 2B à la place.

Sur la 4080, les horloges sont verrouillées, à 2 805 MHz pour le calcul et 11 201 MHz pour la mémoire. Le 7 octobre, le 4B semblait avoir un point faible sur les prompts courts, 31 % plus lent que vLLM à 128 tokens. Même binaire, même prompt : un processus tournait soit en 21 ms, soit en 34. C'était l'horloge mémoire, qui ne montait pas toujours en régime alors que l'outil de NVIDIA affichait la bonne fréquence. Horloges verrouillées avec nvidia-smi, huit rapides sur huit : le point faible avait disparu. Une réserve demeure : sous CUDA, la 4080 baisse quand même l'horloge de sa mémoire à 10 801 MHz, 3,6 % de bande passante en moins, alors que sous Vulkan elle reste à 11 201. Je n'ai pas encore compris pourquoi et je compte l'étudier.

Le modèle est publié en bf16. Mais pour llama.cpp, le bf16 est un chemin secondaire : sur la 4080, en Vulkan, son prefill en bf16 est de 2,3 à 3,6 fois plus lent qu'en f16. Herbert en bf16 contre llama.cpp en f16, on ne compare pas la même chose : les deux moteurs ne comptent pas de la même façon. Alors j'ai écrit une version f16 d'Herbert. Chaque comparaison se fait à format égal : bf16 contre bf16, puis f16 contre f16, le format où llama.cpp est le plus rapide. Les deux formats tiennent sur 16 bits mais ne les répartissent pas pareil : le bf16 garde l'exposant d'un flottant 32 bits sur 8 bits et 7 bits de mantisse, le f16 donne 5 bits à l'exposant et 10 à la mantisse. Toutes les cartes n'ont pas un calcul bf16 de qualité, alors que le f16 se calcule nativement sur toutes celles de ce banc. La contrepartie : les sorties des deux versions ne se comparent pas token par token.

Une requête à la fois, comme quelqu'un devant sa machine. vLLM est fait pour servir beaucoup de requêtes en même temps : à une requête, on ne mesure pas ce pour quoi il est fait. C'est une condition aux limites de tout ce qui suit.

Deux instruments. Le banc de chaque moteur, llama-bench, le banc de latence de vLLM, le compteur d'Herbert, raconte le calcul, mais ne mesure pas la même chose d'un moteur à l'autre : llama-bench compte le prefill seul, vLLM la requête entière. Le second instrument est un seul client pour tous les moteurs mis en serveur, avec la même API, les mêmes prompts et sans cache de préfixe. Il mesure ce que voit l'utilisateur : le temps jusqu'au premier token, puis le débit de décodage. Herbert a reçu pour cela un serveur compatible, porté de celui de herbert-rs.

Le décodage : la mémoire décide

Au décodage, chaque token relit tous les poids. Dès la toute première version, le produit matrice-vecteur émis par Herbert lisait les poids entre 590 et 690 Go/s sur la 4080, pour 694 mesurés par une sonde en lecture pure. C'est presque le maximum. Comme sur le processeur, le temps qui restait à gagner était ailleurs, dans la récurrence du Gated DeltaNet, l'attention et l'aller-retour avec l'hôte.

Les deux instruments disent la même chose. Le format n'y change presque rien : en bf16 comme en f16, chaque moteur relit le même nombre d'octets. Face à llama.cpp, Herbert est plus rapide partout. Sur la 4080, de 4 à 7 % au banc, de 5 à 22 % au client commun. Sur la 3060 Ti, de 25 à 30 % sur le 0.8B, de 4 à 6 % sur le 2B. Sur l'Arc et les GPU intégrés, de 1 à 20 %.

Face à vLLM, ça dépend du modèle. Sur le 4B, ils sont à égalité : 1 à 3 % en faveur d'Herbert, moins que l'avantage que lui donne l'horloge mémoire sous Vulkan. Sur le 0.8B, il est plus rapide tant que le contexte reste sous 500 tokens environ, puis plus lent : 96 % de vLLM vers 1 000 tokens de contexte, 97 % à 2 000, 94 % à 4 000. Le décodage d'Herbert ralentit plus vite que celui de vLLM quand le contexte grandit.

Le prefill sur la 4080

Le prefill, c'est du calcul. C'est là qu'un moteur qui écrit son propre code peut montrer quelque chose.

En bf16 contre bf16, au banc, de 128 à 4 000 tokens, Herbert va de 1,26 à 1,78 fois llama.cpp CUDA sur le 0.8B, de 1,09 à 1,24 fois sur le 4B ; face à llama.cpp Vulkan, de 3,3 à 5 fois. Ces chiffres sont justes, mais ils prennent llama.cpp là où il est le moins bon. En f16 contre f16, l'écart se resserre. Il se resserre encore quand on règle llama.cpp. J'ai balayé, cas par cas, la taille des lots, la taille des micro-lots et la flash attention, chaque réglage dans son propre processus, cinq tours, Herbert dans les mêmes tours. llama.cpp CUDA y gagne jusqu'à 19 %. Je ne garantis pas que ce soient les meilleurs réglages possibles : ce sont les meilleurs que ce balayage a trouvés. Si vous en connaissez un meilleur, envoyez-le-moi : je le mesurerai sur la même carte, dans les mêmes tours.

f16 contre f16, prefill, tokens par secondeHerbertllama.cpp CUDA, meilleur réglagellama.cpp Vulkan, meilleur réglageHerbert / CUDAHerbert / Vulkan
0.8B, 759 tokens51 37433 32728 868×1,54×1,78
0.8B, 2 880 tokens55 48839 09529 747×1,42×1,87
4B, 759 tokens10 39210 3269 280×1,01×1,12
4B, 2 880 tokens10 45710 6729 043×0,98×1,16

Sur le 0.8B, Herbert reste une fois et demie plus rapide. Sur le 4B, c'est l'égalité face à llama.cpp CUDA : 1 % plus rapide sur 759 tokens, 2 % plus lent sur 2 880.

vLLM ne se mesure qu'en bf16, son format de référence pour ce modèle. Au banc, sur les prompts longs, Herbert en bf16 fait 108 à 113 % de vLLM sur le 0.8B ; sur le 4B, ils sont à égalité aux mesures près, de 99 à 106 % selon la longueur. Sur les prompts courts, le banc fait faire à Herbert presque trois fois vLLM, à 128 tokens sur le 0.8B. On peut soupçonner le chronomètre, puisque vLLM compte la requête entière. C'est là que le client commun tranche.

premier token vu du client, bf16, msHerbertvLLMllama.cpp CUDAllama.cpp Vulkan
0.8B, 21 tokens6,518,912,023,2
0.8B, 511 tokens13,120,334,065,8
0.8B, 4 106 tokens86,587,2159,7392,4
4B, 21 tokens22,426,239,392,3
4B, 511 tokens53,757,5121,4283,1
4B, 2 059 tokens216,4210,9343,6959,9
4B, 4 106 tokens418,5403,5602,51 807,9

Sur le 0.8B, le premier token d'Herbert arrive plus tôt jusqu'à 500 tokens, puis en même temps que celui de vLLM : 86,5 ms contre 87,2 à 4 106 tokens. Sur le 4B, il arrive de 7 à 15 % plus tôt jusqu'à 500 tokens. Au-delà, celui de vLLM arrive de 2,6 à 3,7 % plus tôt. Moi, je trouve ça bien : ça me montre où je peux encore gagner. Je regrette juste que les journées ne fassent que 24 heures.

En f16 contre f16, au même client, le résultat face à llama.cpp CUDA ne change pas : 6,3 ms contre 12,1 sur un prompt court du 0.8B, 22,8 contre 38,4 sur le 4B. Le client montre aussi l'écart entre le banc et ce que voit l'utilisateur : en bf16, llama.cpp CUDA servi, sur le 0.8B à 4 106 tokens, traite environ 25 700 tokens par seconde, contre 32 782 à son propre banc sur 4 000 tokens.

Les autres cartes

Sur la RTX 3060 Ti, la comparaison s'arrête au bf16 contre bf16. Le 12 octobre, sous charge, la carte a décroché quatre fois, avec trois moteurs et deux pilotes, à 65 °C ou moins. Je soupçonne son alimentation de 500 W : j'en ai acheté une nouvelle et je referai la mesure. Avant cela, au banc, Herbert faisait 1,7 à 2 fois llama.cpp CUDA au prefill du 0.8B, 1,4 à 1,5 fois sur le 2B. Windows ignore le verrouillage des horloges : la mémoire est restée à 6 801 MHz.

Les autres GPU ne sont pas des cartes de joueur. Herbert y est comparé à llama.cpp en Vulkan.

prefill sur 759 tokens, Herbert / llama.cpp Vulkanbf16 contre bf16f16 contre f16
Arc 140V, 0.8B×1,43×0,89
Arc 140V, 4B×1,18×0,57
Ryzen 7 5700G, 0.8B×1,26×0,96
Ryzen 7 5700G, 4B×0,93×0,85
Ryzen 9 9900X, 0.8B×1,10×1,04
Ryzen 9 9900X, 4B×0,76×0,61

En bf16 contre bf16, Herbert est plus rapide sur l'Arc et sur le 0.8B partout. Le 4B est plus lent sur les deux GPU intégrés AMD : 64 contre 69 tokens par seconde sur le 5700G, 20 contre 26 sur le 9900X. En f16 contre f16, Herbert est plus lent presque partout. Les GPU intégrés AMD calculent en f16 deux fois plus vite qu'en f32. Sans matrices coopératives exposées par Vulkan, la version bf16 d'Herbert y calcule en f32 ; la version f16 calcule en f16, ce qui rattrape une partie de l'écart sur le 0.8B, pas sur le 4B. Ce sont des GPU intégrés : la Radeon que j'ai commandée aura son propre article.

Sur l'Arc, il y a plus intéressant qu'un simple écart. Herbert y fait pratiquement la même vitesse dans les deux formats : 333 tokens par seconde en bf16 sur le 4B, 328 en f16. llama.cpp passe de 283 à 573. En bf16, Herbert est plus rapide parce que llama.cpp y est moins bon. En f16, il est plus lent parce que ses propres multiplications de matrices ne tirent pas tout de la carte : le chemin f16 de llama.cpp tire de la carte 1,75 fois ce qu'en tire Herbert. Au client commun, en f16, sur le 4B à 4 106 tokens, Herbert donne son premier token en 17,5 s, llama.cpp en 10,9 s. Je n'ai pas encore eu le temps de chercher pourquoi, mais je crois que ça va me plaire.

Au décodage, Herbert reste plus rapide partout sur ces cartes, dans les deux formats.

décodage, Herbert plus rapide que llama.cpp Vulkan debf16 contre bf16f16 contre f16
Arc 140V, 0.8B10 à 12 %4 à 5 %
Arc 140V, 4B5 à 6 %1 à 2 %
Ryzen 7 5700G, 0.8B12 %11 %
Ryzen 7 5700G, 4B4 %4 %
Ryzen 9 9900X, 0.8B17 %17 %
Ryzen 9 9900X, 4B19 %20 %

Je l'espérais, mais pas à ce point. Sur processeur, la façon dont Herbert range les poids en mémoire avait déjà payé au décodage : j'espérais qu'elle paierait aussi sur GPU. Je ne m'étais quand même pas risqué à en faire une prédiction en dur :-D C'est mon hypothèse du moment, je la mesurerai noyau par noyau.

Un cas précis

Deux formats, deux backends, des micro-lots, la flash attention, cinq cartes, trois tailles de modèle, plusieurs longueurs de prompt : dans un tableau aussi grand, on trouve toujours une case où Herbert est le plus rapide. Je ne suis pas dupe. Herbert n'est pas plus rapide que llama.cpp dans l'absolu. Il l'est dans un cas précis : un petit modèle, une requête à la fois, un prompt court ou moyen, sur une carte NVIDIA. Ailleurs, à format égal, il est à égalité ou plus lent : le 4B sur la 4080 face au meilleur réglage de llama.cpp, l'Arc en f16, le 4B sur les GPU intégrés AMD.

Ce cas précis a une explication probable. Herbert ne choisit pas des noyaux dans une bibliothèque : il compile le modèle. Au prefill, il traite le prompt par grands blocs, 2 048 lignes sur le 0.8B et 1 024 sur le 4B, pour ne lire les poids qu'une fois par bloc. Chaque bloc part à la carte d'un seul tenant. Au décodage, il enregistre une fois pour toutes le pas d'un token, avec ses fusions, puis le resoumet à chaque token. Sur un petit modèle, chaque noyau est court : ce qui coûte, c'est tout ce qu'il y a autour, les lancements, les synchronisations, les allers-retours. Sur le 4B, les multiplications de matrices dominent, les deux moteurs approchent ce que la carte sait faire et l'écart se referme. Ce n'est pas propre à Herbert : au-delà d'une centaine de tokens, vLLM aussi est plus rapide que llama.cpp sur le 0.8B.

Cette explication se mesure en partie sans toucher au code, puisque chacun de ces choix est un réglage du moteur. Le 14 octobre, j'ai désactivé ces réglages un par un, puis tous à la fois, sur la 4080, avec le même binaire, cinq tours alternés. Voici le temps de prefill, rapporté à celui des réglages par défaut :

temps de prefill quand on retire0.8B, 24 tokens0.8B, 513 tokens0.8B, 4 001 tokens4B, 24 tokens4B, 513 tokens4B, 4 001 tokens
les fusions du décodage×1,00×1,00×1,00×0,98×1,02×1,00
le bloc soumis en une fois×1,05×1,02×1,01×1,01×1,03×1,00
les blocs enchaînés sans retour à l'hôte×1,03×1,02×1,01×1,00×1,02×1,01
les grands blocs (256 lignes)×1,00×1,44×1,49×0,99×1,41×1,20
tout à la fois×1,15×1,63×1,64×1,02×1,44×1,25

Les grands blocs pèsent le plus : moins de passes, donc à la fois moins de lancements et moins de relectures des poids, deux effets que cette mesure ne sépare pas. Soumettre chaque bloc en une fois et enchaîner les blocs sans revenir à l'hôte comptent pour 1 à 5 %, surtout sur les prompts courts. Les fusions ne changent rien au prefill : elles valent 2 % au décodage. Tout désactivé, le prefill du 0.8B redescend entre 30 000 et 39 000 tokens par seconde selon la longueur, de l'ordre de llama.cpp CUDA. Sur un prompt de 24 tokens, ces réglages ne font que 15 % du temps : le reste tient aux noyaux eux-mêmes, que cette mesure ne désactive pas.

C'est le cœur de cette série. Émettre son propre code, c'est pouvoir découper et fusionner le travail pour une carte et un modèle précis. Pourquoi ça paie ici et pas là, je continuerai à le vérifier noyau par noyau dans les prochains articles.

Les prédictions, écrites avant

Le 12 octobre 2026 au matin, avant la campagne définitive, j'ai écrit mes prédictions dans un fichier daté, publié tel quel avec les données. Je les ai écrites après une première série de mesures, du 6 au 8 octobre, que je connaissais. Elles ne sont donc pas aveugles.

ce que j'avais préditce que la mesure dit
vLLM resterait devant au prefillfaux sur le 0.8B, à toutes les longueurs ; sur le 4B, égalité au banc, vLLM plus rapide de 2,6 à 3,7 % au client commun sur les prompts longs
on s'approcherait de llama.cpp CUDAjuste sur le 4B en f16 : 1 % plus rapide ou 2 % plus lent ; sur le 0.8B, Herbert va nettement plus vite
on dépasserait llama.cpp Vulkan, « c'est sûr »vrai sur la 4080 dans les deux formats, en bf16 sur l'Arc, pour le 0.8B partout en bf16 ; faux pour le prefill du 4B sur les GPU intégrés AMD, sur l'Arc en f16 et pour le 0.8B en f16 sur le 5700G
au décodage, à peu près la même hauteur, sauf vLLM devantjuste sur la 4080 et pour vLLM sur le 0.8B en contexte long ; ailleurs, l'écart en faveur d'Herbert face à llama.cpp est plus grand que prévu

Ce que je n'explique pas encore

J'ai essayé des multiplications de matrices découpées plus finement, par sous-groupe, en tuiles de 16 × 16 × 16. Elles sont plus lentes : de 16 à 84 % de temps en plus sur le 4B à 2 048 tokens. Les grandes tuiles aussi, de 1 à 33 %. Il manque à Vulkan deux instructions pour tenir ce schéma. Je les ai découvertes en écrivant une version CUDA d'Herbert : elles appartiennent à l'article sur CUDA.

Restent sans explication complète le prefill du 4B sur les GPU intégrés AMD, les multiplications de matrices de l'Arc et le décodage du 0.8B quand le contexte s'allonge.

Les limites

Pas de B200, pas de H100, pas de FP8. Pas de quantification non plus : cet article compare des poids en bf16 et en f16. La quantification aura ses propres articles, comme annoncé dans l'article précédent. Du GPU grand public, une carte à la fois, des modèles denses, une requête à la fois : c'est la niche que je revendique, pas celle que je subis. Le MoE et le multi-GPU viendront plus tard.

Ces chiffres valent du 12 au 14 octobre 2026, avec ces versions et ces pilotes : NVIDIA 595.99.02 sous Linux, 591.86 sous Windows, Mesa 25.2.8 pour les deux GPU intégrés AMD et 26.0.3 pour l'Arc. Tous ces moteurs évoluent. Certains de ces chiffres ne seront peut-être plus vrais quand vous lirez ceci.

Reproduire chez soi

Trois dépôts sont en ligne, sous licence MIT. Le moteur de cet article, en bf16, avec son serveur : github.com/xigh/herbert-vk-bf16, tag v0.1.0. Construire, puis lancer :

git clone --branch v0.1.0 https://github.com/xigh/herbert-vk-bf16.git
cd herbert-vk-bf16 && cargo build --release --locked --features serve
target/release/herbert-vk-chat ~/models/Qwen3.5-4B --no-think --ask 'Quelle est la capitale de la France ?'

La version f16, sans laquelle les tableaux f16 contre f16 ne se rejouent pas : github.com/xigh/herbert-vk-f16, tag v0.1.0. Elle se construit de la même façon. Sans sa fonctionnalité f16, elle redonne le moteur bf16, au bit près.

Les scripts des campagnes, mes prédictions, le journal de bord et les données brutes, carte par carte : github.com/xigh/herbert-gpu-bench, tag v1-2026-10-14, dossier campaign-v1/. Si un chiffre vous semble faux, vous avez de quoi le vérifier.

La suite

Mes prédictions reposaient sur une idée : CUDA donne accès à des instructions et à des fonctionnalités que Vulkan n'a pas. La question, maintenant, c'est ce que CUDA pourrait nous apprendre qu'on ne sait pas faire en Vulkan. Moi, je connais la réponse. Vous, vous allez devoir attendre l'article sur CUDA.

On descend d'un cran, ou plusieurs...

  1. On descend d'un cran : #000 un moteur d'inférence qui écrit son propre code
  2. On descend d'un cran : #001 un compilateur pour moteur de LLM
  3. On descend d'un cran : #002 le même compilateur sur un GPU