Salut les amis !
Le 5 mai 2026, Google publie les drafters Multi-Token Prediction officiels pour les quatre tailles de Gemma 4 (E2B, E4B, 26B-A4B, 31B). Petite explosion de hype sur r/LocalLLaMA — 891 upvotes en 12 h. Mais à l’époque, personne ne pouvait les utiliser : l’arch Gemma4AssistantForCausalLM avec ses centroid LM heads (top_k=32, num_centroids=2048) n’était reconnue ni par llama.cpp ni par vLLM.
Le 6 mai à 14:39 UTC, lucianommartins merge dans vLLM main la PR #41745 : “[Spec Decode] Add Gemma4 MTP speculative decoding support”. 9 fichiers, +1 121 / -72 lignes. Nouveau modèle Gemma4MTP, nouveau Gemma4Proposer, et — détail croustillant — la centroids masking optimization qui réduit le calcul du lm_head de 262 K à 4 K candidats via la sélection learned.
Le 7 mai à 06:13 UTC, le nightly Docker vllm/vllm-openai:nightly-1acd67a795ebccdf9b9db7697ae9082058301657 est publié sur Docker Hub.
À 06:35 UTC, soit ~22 min plus tard, j’envoie le premier prompt sur mon Olares One. 178,6 t/s en moyenne sur trois runs, 77,3 % d’acceptance rate. C’est le premier bench public Gemma 4 MTP sur du Blackwell consumer mobile (RTX 5090M sm_120, 24 GB GDDR7).
Voilà comment j’en suis arrivé là.
Le drafter, en 2 mots
Gemma 4 ships avec un petit modèle “assistant” entraîné jointement avec le target. Pendant l’inférence, il prédit plusieurs tokens en avant à partir du dernier hidden state du target ; le verifier accepte la séquence en parallèle si elle matche. Pour E2B/E4B il y a un détail élégant : un head à centroïdes qui réduit le softmax sur 262 144 tokens à un mask de 2 048 centroïdes — plus rapide ET (selon Google) sans perte de qualité.
Architecture du drafter Gemma4AssistantForCausalLM (lue dans config.json hier) :
- 4 hidden layers, hidden_size 256, intermediate_size 2048 (minuscule)
- 4 attention heads, 1 KV head (KV-shared sur 4 layers)
- centroid_intermediate_top_k=32, num_centroids=2048
- vocab_size 262144 (matche Gemma 4)
- 78 M params pour E2B-it-assistant (158 MB en safetensors)
C’est dédié MTP, pas un small Gemma 4 réutilisé. D’où la nécessité d’une PR upstream dédiée.
Pourquoi ça vient juste maintenant
Avant le 6 mai 14:39, l’arch gemma4_assistant était inconnue de vLLM — boot direct → NotImplementedError. La PR #41745 ajoute :
- Nouveau modèle
vllm/model_executor/models/gemma4_mtp.py(~700 lignes) - Nouveau proposer
vllm/v1/spec_decode/gemma4.py - Centroids masking avec CUDA graph acceleration pour E2B/E4B
- Cross-model KV sharing avec multi-group attention (sliding + full, head_dim hétérogènes)
- TP>1 via all-gather du lm_head sharded
- Override
_create_draft_vllm_configpour préserver le backendTRITON_ATTNsur les couches draft (compat KV-shared)
Côté config, ça se résume à :
--speculative-config '{"method":"mtp","model":"google/gemma-4-E4B-it-assistant","num_speculative_tokens":3}'
Le code détecte automatiquement model_type == "gemma4_assistant" et le convertit en gemma4_mtp au load.
La course aux nightlies
Bizarrerie de timing : le nightly standard vllm/vllm-openai:nightly du 6 mai à 06:08 UTC est antérieur au merge. Quand j’ai voulu tester en fin d’après-midi, j’ai d’abord essayé tokenspeed-preview-ubuntu2404 (pushé à 14:47 UTC, soit 8 minutes après le merge) — boot crash : NotImplementedError: Unsupported speculative method: 'mtp'. Le build cutoff devait être pré-merge.
Le 7 mai à 06:13 UTC, vLLM publie un nouveau nightly tagué nightly-1acd67a795ebccdf9b9db7697ae9082058301657 — commit 1acd67a du 2026-05-07 04:57 UTC (post-#41745 de plusieurs heures). Cette fois, ça boote.
Le manifeste Kubernetes
Sur Olares One le pod est super simple — pas de Genesis, pas de patches, pas de fork :
containers:
- name: vllm-server
image: docker.io/vllm/vllm-openai:nightly-1acd67a795ebccdf9b9db7697ae9082058301657
command: ["sh", "-c"]
args:
- |
exec vllm serve google/gemma-4-E4B-it \
--served-model-name gemma-4-e4b-mtp \
--host 0.0.0.0 --port 8000 \
--max-model-len 32000 \
--gpu-memory-utilization 0.85 \
--max-num-seqs 1 --dtype auto \
--trust-remote-code --download-dir /models \
--enable-prefix-caching \
--speculative-config '{"method":"mtp","model":"google/gemma-4-E4B-it-assistant","num_speculative_tokens":3}'
env:
- name: CUDA_DEVICE_MEMORY_LIMIT_0
value: "24000m" # workaround HAMi 0m parsing bug
Le seul truc spécifique Olares One c’est l’override CUDA_DEVICE_MEMORY_LIMIT_0=24000m — sans ça, HAMi parse “0m” comme 0 octet et toute alloc CUDA crashe à EagleProposer.__init__ → torch.zeros. Hors Olares, oubliez cette ligne.
Le bench
Trois prompts Space Invaders standards (HTML+CSS+JS, max_tokens=800, temp=0.6, top_p=0.95) :
Run 1 (cold start): 800 tok in 6,17 s = 129,73 t/s
Run 2: 800 tok in 4,17 s = 191,73 t/s
Run 3: 800 tok in 3,73 s = 214,38 t/s
AVG = 178,6 t/s, range 129-214.
Côté MTP metrics (extraites des logs vLLM) :
Mean acceptance length : 3,32 (sur 3 draft tokens)
Per-position acceptance rate : 0,868 / 0,765 / 0,687
Avg Draft acceptance rate : 77,3 %
77 % d’acceptance, c’est très haut. Pour comparaison, Qwen3.6 + MTP llama.cpp tourne autour de 64 % sur le même hardware. La différence vient probablement de l’optimisation centroids — le drafter E4B est petit ET entraîné spécifiquement pour matcher le target, donc le draft est plus précis qu’un drafter générique.
Comparaison sur Olares One (RTX 5090M 24 Go sm_120)
| Stack | Modèle | t/s | Notes |
|---|---|---|---|
| llama.cpp baseline | Qwen3.6-27B Q4_K_M | 33-37 | upstream pur |
| llama.cpp + MTP (PR #22673) | Qwen3.6-27B-MTP-Q4_K_M @ 32K | 78,1 | recipe RDson |
| llama.cpp + MTP (PR #22673) | froggeric MTP @ 128K | 65,1 | trade context for perf |
vLLM no-Genesis + #39931 + --enforce-eager | Qwen3.6-27B int4 AutoRound | 72,55 | workaround CG bug |
| vLLM Turbo (Genesis) | Qwen3.6-27B int4 AutoRound | 88,0 | 28 patches |
| Lucebox DFlash v1.4.4 | Qwen3.6-27B Q4_K_M | 88,5 | engine custom |
| vLLM nightly + PR #41745 | Gemma 4 E4B + MTP | 178,6 | upstream pur, 1 PR |
Note : Gemma 4 E4B est un modèle ~5 B effective, beaucoup plus petit que Qwen3.6-27B. La comparaison brute t/s n’est pas équitable. Mais pour les use cases agentic/voice à faible latence sur Gemma 4, on est sur du 178 t/s en stack 100 % upstream.
Update 8 mai : le pendant llama.cpp livre 206 t/s sur E2B et 140 t/s sur 26B-A4B
Côté llama.cpp, AtomicChat a aussi sorti son support Gemma 4 MTP (post r/LocalLLaMA à 200+ upvotes le 7 mai). Ils maintiennent un fork — AtomicBot-ai/atomic-llama-cpp-turboquant — qui ajoute l’arch gemma4_assistant, le --mtp-head runtime flag, et au passage TurboQuant KV cache (-ctk turbo3 -ctv turbo3) — bonus inattendu.
J’ai built leur fork pour sm_120 (image aamsellem/llamacpp-atomic-mtp:0.1.0, 2,72 Go) et bench les deux variantes target qu’on a sur HF :
Gemma 4 E2B + MTP (llama.cpp atomic)
eval time = 4,84 ms per token = 206,56 t/s
draft acceptance rate = 60,93 %
3 198 tokens generated in 15,48 s
Gemma 4 26B-A4B + MTP (llama.cpp atomic)
eval time = 7,14 ms per token = 140,03 t/s
draft acceptance rate = 78,15 %
3 238 tokens generated in 23,12 s
Le 26B-A4B fait 140 t/s avec 78 % acceptance — sur le premier run, hors warm-up. Ça dépasse le bench M5Max de référence d’AtomicChat (138 t/s). Et c’est un MoE 26B → la qualité d’un dense ~26 B avec la latence d’un dense ~6 B.
Donc côté Olares One on a maintenant deux paths Gemma 4 MTP validés :
| Path | Modèle | t/s | Stack |
|---|---|---|---|
| vLLM nightly + PR #41745 | Gemma 4 E4B | 178,6 | upstream pur, 1 PR mergée |
| llama.cpp + atomic-llama-cpp-turboquant fork | Gemma 4 E2B | 206,6 | fork tier-3 + 2 GGUF (target unsloth + drafter AtomicChat) |
| llama.cpp + atomic-llama-cpp-turboquant fork | Gemma 4 26B-A4B | 140,0 | fork tier-3 + 2 GGUF |
Le pendant vLLM est plus propre côté maintenance (1 PR upstream qui va finir en stable), le pendant llama.cpp donne du t/s plus haut sur les petits modèles ET supporte le 26B-A4B MoE. Selon votre use case (latence vs qualité), vous prenez l’un ou l’autre.
Pourquoi ça change la donne
L’an dernier on aurait passé deux semaines à hacker un fork ou écrire des monkey-patches. Là, 24 heures après le merge upstream, le nightly Docker est dispo et le bench tourne. C’est le sens dans lequel l’écosystème va : les techniques qui passaient par des forks (DFlash via Lucebox, MTP via Genesis pour vLLM, MTP via am17an pour llama.cpp) finissent en mainline.
Pour Gemma 4 spécifiquement, l’équipe vLLM a fait du super taf — la PR ajoute une infrastructure qui sera réutilisable pour d’autres drafters arch dédiées (par exemple, un éventuel drafter dédié pour Mimo v2.5 ou les futurs Llama 4).
Pour reproduire
- Image :
docker.io/vllm/vllm-openai:nightly-1acd67a795ebccdf9b9db7697ae9082058301657(ou plus récent) - Target :
google/gemma-4-E4B-it - Drafter :
google/gemma-4-E4B-it-assistant - Recipe :
--speculative-config '{"method":"mtp","model":"google/gemma-4-E4B-it-assistant","num_speculative_tokens":3}' - Sur Olares One : ajouter
CUDA_DEVICE_MEMORY_LIMIT_0=24000m
Ma reco si vous testez sur d’autres hardwares Blackwell consumer (5070, 5080, 5090 desktop) : envoyez vos chiffres dans les commentaires, on construit la base de comparaison.
Crédits
- Google DeepMind pour Gemma 4 et les drafters MTP
- lucianommartins (vLLM contributor) pour la PR #41745 — architecture super propre
- vLLM core team pour le nightly publishing rapide
Voilà ! Si vous tournez Gemma 4 sur un Olares One et que vous reproduisez ces 178 t/s (ou les battez avec un sweep num_speculative_tokens), envoyez-moi vos chiffres. À très vite !
Disclosure — Tous les benchmarks de ce post tournent sur mon propre Olares One. Si le contenu vous a été utile et que vous envisagez d’en acheter un, commander via ce lien de parrainage vous donne 400 $ de réduction (3 599 $ au lieu de 3 999 $) et me rapporte 200 $. Je le mentionne par transparence — et oui, accessoirement, ça aide à faire vivre le blog (hébergement, domaine, et le temps que je passe à écrire ici). Lien valable jusqu’à fin juin 2026 environ.