Il y a deux jours j’ai shippé le champion Qwen 3.6 35B-A3B MTP à 249 t/s. Text only. J’ai écrit le post. Filé. Passé à autre chose.
Aujourd’hui le même hardware tourne Gemma 4 26B à 250.80 t/s avec vision et tool calling.
Même vitesse. Plus les images. Plus Hermes Agent.
Le déclencheur a atterri dans vLLM v0.21 le 15 mai — merge discret de la PR #41745 “official Gemma 4 MTP + Gemma4Proposer + centroids masking”. Aujourd’hui j’ai scalé le pod Qwen à zéro, lancé le bench, et l’écart entre mon champion text et mon app vision s’est volatilisé.
Le bench
cyankiwi/gemma-4-26B-A4B-it-AWQ-4bit (~17 Go, multimodal natif Gemma4ForConditionalGeneration) sur vllm/vllm-openai:v0.21.0-cu129. Config speculative :
--speculative-config '{
"model": "google/gemma-4-26B-A4B-it-assistant",
"method": "gemma4_mtp",
"num_speculative_tokens": 3
}'
google/gemma-4-26B-A4B-it-assistant c’est la tête de draft MTP officielle Google (840 Mo) pour la cible 26B. Elle remplace le drafter DFlash de z-lab que j’utilisais avant — celui avec le fameux cycle reproductible “5 runs rapides, 4 runs lents, recover, repeat”.
10 runs de complétion Space Invaders HTML, 2500 tokens chacun, single user :
| Run | t/s |
|---|---|
| 1 | 250.35 |
| 2 | 250.87 |
| 3 | 252.97 |
| 4 | 252.14 |
| 5 | 249.51 |
| 6 | 247.84 |
| 7 | 252.48 |
| 8 | 249.61 |
| 9 | 248.61 |
| 10 | 251.10 |
AVG 250.54 t/s. Range 5.13 sur les 10 runs. σ ≈ 1.7.
Taux d’acceptance depuis /metrics : 25920 acceptés / 29997 tokens draft = 86.4%. Quasi identique au chiffre de Qwen 3.6 35B-A3B (86.6%). Deux familles de modèles, deux drafters différents, qui convergent sur le même ~86%. Coïncidence ? Probable que les deux équipes ont visé le même taux cible pendant l’entraînement.
Smoke test vision, PNG rouge plein 64×64, “What color is this image? One word.” :
Red
Tool calling fonctionne toujours (--enable-auto-tool-choice --tool-call-parser gemma4). J’ai pas re-validé tout le flow Hermes Agent aujourd’hui, mais aucun arg n’a changé depuis la v1.0.2 qui passait ce smoke.
Ce qui change vs la version précédente
| Champ | v1.0.0 (avril) | v1.0.5 (aujourd’hui) |
|---|---|---|
| Image vLLM | tokenspeed-preview | v0.21.0-cu129 |
| Spec decoding | aucun | Gemma4 MTP, n=3 |
| Drafter | n/a | google/gemma-4-26B-A4B-it-assistant |
| Throughput | 135.97 t/s | 250.54 t/s |
| Context | 128K | 128K |
| Vision | ✓ | ✓ |
| Tool calling | ✓ | ✓ |
| Cycle bug | n/a | disparu |
+85% de throughput sur le même hardware, même fichier modèle, mêmes prompts. La seule chose qui a changé c’est l’image vLLM et la config speculative.
Le fantôme 5-fast/4-slow
Si tu suis depuis quelque temps tu te rappelles vllmgemma4dflashone — le sibling text-only de Gemma 4 que j’avais shippé avant à 224 t/s avec le drafter DFlash de z-lab. Pic plus haut (224 vs no-spec 135) mais pattern reproductible : 5 runs rapides (~220 t/s), 4 runs lents (~60 t/s), recover, repeat. J’avais sweep-testé n_spec de 6 à 20 pour trouver le régime qui évitait le bug. Échec.
Les 10 runs d’aujourd’hui sur le path MTP officiel : zéro run lent. Spread le plus serré que j’ai mesuré sur ce hardware. Ce que la state machine interne de vLLM faisait de travers avec DFlash, le path MTP officiel le contourne.
Je vais pas prétendre que je comprends pourquoi. Le drafter DFlash et le drafter MTP sont architecturalement différents (DFlash = petit modèle séparé entraîné à imiter le draft path de la cible, MTP = têtes auxiliaires boulonnées sur la cible elle-même). Ils passent par des morceaux différents du moteur spec decoding de vLLM. La PR #41745 a apporté Gemma4Proposer et centroids masking — et quelque part dans ce codepath le cycle a disparu.
Pousser le context, le modèle dit non
GPU libre, 24 Go. Qwen 35B-A3B scalé à zéro pour le bench. Pourquoi pas tester le max ?
Tenté 262144 (256K). vLLM a proprement reporté :
ValueError: To serve at least one request with the model's max seq len (262144),
3.38 GiB KV cache is needed, which is larger than the available KV cache memory (2.82 GiB).
Based on the available memory, the estimated maximum model length is 203584.
OK donc 203584 (200K) c’est le plafond dur. Tenté 196608 (192K) juste en dessous. Boot, tourne à 249.65 t/s stable. Même vitesse que 64K.
Mais 192K c’est au-delà du range natif d’entraînement de Gemma 4 (128K). vLLM scale automatiquement le RoPE quand tu dépasses le context déclaré, ce qui techniquement marche — le modèle produit des tokens — mais l’attention drift sur les positions au-dessus de la limite native, et le rappel needle-in-haystack sur ce tail de 64K bonus devient peu fiable.
Backed off à 131072 (128K natif). Bench :
| Run | t/s |
|---|---|
| 1 | 212.41 (cold) |
| 2 | 254.39 |
| 3 | 251.89 |
| 4 | 252.42 |
| 5 | 250.74 |
| 6 | 247.27 |
| 7 | 249.58 |
| 8 | 251.14 |
| 9 | 253.27 |
| 10 | 246.53 |
En excluant le premier run froid : 250.80 t/s AVG, range 7.86. Vitesse de decode identique au bench 64K. Confirme que sur une architecture sliding-window attention comme Gemma 4, le footprint KV scale avec le context complet mais le coût par-token decode ne scale pas.
Donc 128K. Natif. Qualité préservée. Pas de pénalité throughput.
Le leaderboard, encore
| Stack | t/s AVG | Multimodal ? | Tool calling ? |
|---|---|---|---|
| Gemma 4 26B-A4B Vision + MTP (vLLM v0.21) | 250.54 | ✓ | ✓ |
| Qwen 3.6 35B-A3B MTP (llama.cpp master) | 249.30 | ✗ | ✓ |
| Gemma 4 26B-A4B DFlash (vLLM, cycle bug) | 224.00 | ✗ | ✓ |
| Nemotron-Labs Elastic 30B-A3B NVFP4 (vLLM #40082) | 182.14 | ✗ | ✓ |
| Gemma 4 26B-A4B no-spec (vLLM, baseline v1.0.0) | 135.97 | ✓ | ✓ |
| BeeLlama Qwen 3.6 27B DFlash | 107.54 | ✗ | ✓ |
Le path Gemma égalise maintenant le champion Qwen 35B-A3B en vitesse brute, et il a la vision. Pour les use cases Hermes Agent qui mixent tool calls avec analyse de screenshot, cette app devient le endpoint évident.
Le Qwen reste meilleur pour les agents text pur qui ont besoin de 262K. L’architecture sliding window de Gemma 4 te laisse pas pousser aussi loin sans dégradation qualité. Pick par use case :
- Vision + tool calling + ≤128K → Gemma 4 (cette app)
- Text + tool calling + 262K → Qwen 3.6 35B-A3B MTP
Pourquoi ça marche
Trois choses se composent, à peu près la même histoire que Qwen :
Routing MoE-A4B. Gemma 4 26B-A4B a 26 milliards de params total, 3.8 milliards actifs par token. Le coût per-token c’est plus proche d’un dense 4B. Le 26B c’est juste une bibliothèque de paramètres dans laquelle le router pioche.
MTP à 86% d’acceptance. n_spec=3 = on draft 3 tokens par step decode. À 86.4% accept, tokens-per-step attendu ≈ 1 + 0.864×3 ≈ 3.6. À peu près 3.6× plus de tokens-per-step que no-spec.
Qualité du drafter officiel. La tête google/gemma-4-26B-A4B-it-assistant a été co-entraînée avec la cible pendant le pretraining Google. C’est pour ça qu’elle hit 86% du premier coup — ce n’est pas un modèle séparé qui essaie d’imiter la cible, c’est littéralement les next-token heads de l’entraînement de la cible elle-même. Même trick que Qwen 3.6 avec son variant -MTP. Quand drafter et cible sont co-entraînés, l’acceptance explose.
Le piège : il faut que l’équipe modèle ship une tête MTP avec le modèle. Google l’a fait. Alibaba l’a fait. La plupart des autres pas encore. Quand ils le feront, attends-toi au même pattern 200+ t/s.
Ce que j’ai shippé
vllmgemma426ba4bvisionone v1.0.4 → v1.0.5 sur ma source market Olares.
- Image :
vllm/vllm-openai:v0.21.0-cu129 - Cible :
cyankiwi/gemma-4-26B-A4B-it-AWQ-4bit - Drafter :
google/gemma-4-26B-A4B-it-assistant - Context : 131072 (128K natif)
--attention-backend triton_attn,--kv-cache-dtype fp8,--gpu-memory-utilization 0.94--enable-prefix-caching,--limit-mm-per-prompt '{"image":1}'--enable-auto-tool-choice --tool-call-parser gemma4préservé--speculative-config '{"model":"google/gemma-4-26B-A4B-it-assistant","method":"gemma4_mtp","num_speculative_tokens":3}'
Pull depuis https://orales-one-market.aamsellem.workers.dev si t’as Olares One et veux tester.
Coda
Je catalogue ces wins au fur et à mesure parce que je pense que le pattern compte : le hardware est fixe, le software continue à bouffer le problème de throughput. Même puce RTX 5090M mobile. Même fichier modèle sur disque. Baseline avril 135 t/s. Baseline mai 250 t/s. +85% gratuit grâce à des changements upstream que je n’ai pas écrits.
Le truc, c’est que les merges qui nous ont donné ça — vLLM #41745 (Gemma4 MTP), llama.cpp #22673 (support MTP), llama.cpp #23287 (CUDA backend sampling), le fait que Google et Alibaba aient tous les deux shippé des drafters MTP co-entraînés — aucun n’était spécifiquement pour le consumer Blackwell mobile. Ils sont juste tombés sur ce hardware aussi. La communauté de contributeurs upstream a fait le boulot. Je dois juste continuer à redéployer.
Prochain à surveiller : quand Gemma 5 va sortir avec des têtes MTP co-entraînées dès le jour 1, et quand vLLM va serrer encore plus la gestion KV sur attention hybride, ce nombre va remonter. Probablement 280+ avant l’été.