Salut les amis ! Suite de mon aventure sur le DFlash de Gemma 4 sur Olares One : la dernière fois j’avais sorti 214 t/s steady en bench Space Invaders avec n_spec=13 sur le RTX 5090 Laptop 24 Go. Puis Tech-Practice a publié un sweep complet sur le desktop 5090 32 Go qui montre 228 → 600 t/s à n_spec=15. Naturellement je me suis dit : est-ce que n=15 gagne aussi en mobile ?
Eh ben non. C’est n_spec=8 qui gagne sur le 5090M, avec un peak à ~235 t/s et un stable autour de 224 t/s — environ +5% sur mon default précédent à n=13. Et j’ai trouvé un truc encore plus chelou en validant : un cycle de dégradation 100% reproductible toutes les 9-10 requêtes, que j’ai pas réussi à fixer côté config.
TL;DR
- Optimum n_spec=8 sur le 5090M : AVG 223.9 t/s, MAX 235.2 t/s (5 runs propres, Space Invaders 2000 tokens)
- n=15 (la reco Tech-Practice) régresse à 201 t/s en mobile — la reco desktop transfère pas
- 5-10× plus rapide que la baseline communautaire : le post dasroot.net “Gemma 4 Speed Hacks” du 9 mai rapporte 22-40 t/s pour Gemma 4 26B-31B DFlash sur RTX 5090 desktop avec vLLM 0.7.1. Nous sommes à 224 t/s en mobile avec la bonne image + le bon n_spec — pas un gain marginal, c’est un ordre de magnitude.
- Cycle de dégradation : toutes les 9-10 requêtes, 4 runs passent de ~220 t/s à ~60 t/s (DFlash apparemment désactivé), puis ça récupère
- Le cycle n’est pas causé par prefix-caching, max-num-seqs, cudagraph, ou chunked prefill — ressemble à un fallback adaptatif “low-yield spec” (voir hypothèse plus bas)
- AVG long session ≈ 161 t/s (avec phases dégradées) vs 224 t/s en peak
Shippé en vllmgemma4dflashone v1.0.4 dans orales-one-market.
Le matos
- GPU : RTX 5090 Laptop, 24 Go GDDR7, 896 Go/s, sm_120 Blackwell consumer mobile
- CPU : Intel Core Ultra 9 275HX, 24 cœurs
- RAM : 96 Go DDR5-5600
- En face : RTX 5090 desktop, 32 Go GDDR7, 1.79 To/s (2× la bande passante mobile)
La stack
- vLLM :
vllm/vllm-openai:tokenspeed-preview-x86_64-ubuntu2404(0.20.2rc1.dev67+g58c8a5eaa) - Target :
cyankiwi/gemma-4-26B-A4B-it-AWQ-4bit(compressed-tensors WNA16-Marlin, ~17.5 Go) - Drafter :
z-lab/gemma-4-26B-A4B-it-DFlash(~860 Mo safetensors) - KV cache : fp8
- Attention backend :
triton_attn— les tokens multimodaux Gemma 4 interdisentflash_attnmême pour le drafter (partial multimodal token full attention not supported) --max-num-seqs 4 --max-num-batched-tokens 8192 --gpu-memory-utilization 0.92 --enable-prefix-caching
La méthodo
Le prompt standard Space Invaders HTML, 2000 tokens, temp=0.6, top_p=0.95. 2 warmups + 3 à 10 runs mesurés selon le point. Le bench harness tourne dans le pod via kubectl exec pour bypasser le sidecar Envoy.
Le sweep
| n_spec | AVG stable | MAX | n_runs |
|---|---|---|---|
| 6 | 220.61 | 225.95 | 5 |
| 8 | 223.90 | 235.22 | 5 |
| 10 | ~215 | 222.07 | 3 |
| 11 | 212.25 | 217.66 | 5 |
| 12 | ~202 | 206.80 | 3 |
| 13 (était v1.0.0) | ~207 | 213.27 | 5 |
| 15 (reco Tech-Practice) | 201.39 | 211.27 | 3 |
| 17 | ~194 | 195.76 | 3 |
| 20 | 187.33 | 196.12 | 3 |
n=6: ████████████████████████████████ 220.61
n=8: █████████████████████████████████ 223.90 ← peak
n=10: ████████████████████████████████ ~215
n=11: ██████████████████████████████ 212.25
n=12: ██████████████████████████ ~202
n=13: ████████████████████████████ ~207
n=15: █████████████████████████ 201.39
n=17: ████████████████████ ~194
n=20: ██████████████ 187.33
Le peak est sans ambiguïté à n=8. Plus profond (n=15+) coûte ~15% ; plus shallow (n=6) rend ~1,5%.
Pourquoi n=8 en mobile et n=15 en desktop
Deux contraintes poussent le mobile vers des drafts plus shallow :
- Bande passante mémoire : 896 Go/s vs 1,79 To/s. Chaque token spéculatif dans le draft demande des lectures KV supplémentaires pour la passe de vérification du target. Des drafts plus profonds saturent le bus mémoire.
- Budget VRAM : 24 Go vs 32 Go. Les compute buffers du pipeline spec scalent avec n_spec. À n=15, j’avais déjà eu un OOM en
cudagraph_capturequand j’avais bumpmax-num-batched-tokensà 32768.
Le desktop a ~33% de VRAM en plus et ~100% de bande passante en plus — les deux favorisent des drafts plus profonds. En mobile, n_spec=8 c’est le point Pareto-optimal : assez profond pour que le draft amortisse le coût du verify, assez shallow pour que les compute buffers tiennent confortablement.
La partie chelou : un cycle de dégradation reproductible
Quand j’ai étendu le bench n=8 à 10 runs (au lieu de 3) pour valider, j’ai vu ça :
run1 : 221 t/s ← fast (DFlash actif)
run2 : 214 t/s
run3 : 218 t/s
run4 : 222 t/s
run5 : 230 t/s
run6 : 103 t/s ← transition
run7 : 59 t/s ← DÉGRADÉ (pas de spec decoding)
run8 : 62 t/s
run9 : 62 t/s
run10 : 212 t/s ← récupéré
Exactement 5 fast, 1 transition, 3 dégradés, 1 recovery. Reproductible sur plusieurs boots de pod. Le 60 t/s correspond à ce qu’on attendrait de Gemma 4 26B-A4B en decode vanille SANS spec — DFlash est désactivé temporairement par quelque chose dans le pipeline vLLM.
J’ai testé quatre workarounds :
--enable-prefix-cachingOFF → même cycle--max-num-seqs 1→ même cycle--enforce-eager(pas de cudagraph) → cycle retardé du run 6 au run 9, mais eager cap à ~130 t/s — pire au global--no-enable-chunked-prefill→ boot fail (max_num_batched_tokens 8192 < max_model_len 16384)
J’ai aussi tenté de swap l’image vLLM vers cu130-nightly-x86_64 (vLLM 0.19.2) et vllm/vllm-openai:gemma4 (vLLM 0.18.2) — toutes les deux plus anciennes que le tokenspeed-preview que j’utilise (vLLM 0.20.2), donc aucune ne supporte ma combo DFlash + Gemma 4 + triton_attn (l’une erreur avec “non-causal attention not supported”, l’autre n’a même pas DFlash enregistré pour gemma4_text).
Du coup je peux pas tester les PRs récents de hardening Gemma 4 DFlash (#41703, #42102, #40898) sans build une image vLLM custom from main — hors scope pour ce soir.
Mon hypothèse : low-yield spec fallback
Le cycle est trop déterministe pour être du thermal GPU (la carte reste à ~70 °C). Au début je suspectais une re-capture de cudagraph ou une défrag KV, mais en triant les PRs llama.cpp j’ai trouvé une PR closed — #22931 “adaptive low-yield MTP fallback” de leon7609 — qui matche bien mieux.
Cette PR documente le même pattern pathologique sur DeepSeek V4 dans llama.cpp :
- Quand le taux d’acceptance du drafter spéculatif descend sous un seuil, la passe verify du target amplifie son travail par 4.7× parce que chaque token rejeté force du travail supplémentaire côté target.
- Une acceptance basse soutenue produit une régression de throughput de -79.9% vs le régime spec-accepted.
- Ce ratio matche EXACTEMENT mon observation : peak 220 t/s → dégradé 60 t/s ≈ -73%.
Mon modèle actuel :
- z-lab/gemma-4-26B-A4B-it-DFlash a une acceptance bruitée — confirmé dans mon premier bench Gemma 4 DFlash où l’acceptance pos-0 du drafter était seulement ~28% (bien plus bas que les ~60% typiques pour DFlash sur ses targets tunés).
- Après une série de ~5 requêtes avec accept chanceux, l’état KV du drafter diverge assez du target pour que l’acceptance s’effondre temporairement pour les ~4 requêtes suivantes.
- Pendant que l’acceptance est effondrée, l’amplification verify 4.7× produit les ~60 t/s observés.
- Après assez de drafts rejetés, l’état interne du drafter reset et l’acceptance récupère.
Si cette hypothèse est correcte, le fix n’est pas dans vLLM — il est dans l’alignement du drafter. Soit z-lab ship un drafter DFlash mieux aligné pour Gemma 4 (finetuné plus agressivement sur la distribution de sortie du target), soit on accepte le cycle comme une propriété de “DFlash off-the-shelf sur Gemma 4” jusqu’à ce qu’un drafter Gemma 4-specific entraîné conjointement avec le target débarque.
J’aimerais beaucoup un deuxième reproducer de quelqu’un qui run la même stack — si vous confirmez le pattern 5-fast/4-slow avec z-lab/gemma-4-26B-A4B-it-DFlash, pingez-moi et j’ouvre un issue upstream propre avec deux repros indépendants.
Impact réel : mon AVG sur long session de 10 requêtes est ~161 t/s au lieu du 224 t/s peak. En milieu de session, ponctuellement une requête va prendre 30 secondes au lieu de 9 — gênant pour n’importe quel workflow agentique qui streame des tokens.
Ce qui ship dans vllmgemma4dflashone v1.0.4
Le chart Olares Market, un seul diff de config vs v1.0.0 :
SPEC_CONFIG: '{"method":"dflash","model":"z-lab/gemma-4-26B-A4B-it-DFlash","num_speculative_tokens":8}'
Le --attention-backend triton_attn était déjà auto-sélectionné implicitement par vLLM ; je l’ai juste rendu explicite. Le reste (KV fp8, max-num-seqs 4, prefix-caching on) reste inchangé.
Bonus : Gemma 4 Audio One
Pendant que les cycles de bench tournaient, j’ai aussi shippé une nouvelle app : llamacppgemma4audione v1.0.0. Elle utilise la PR llama.cpp #21421 (encoder USM Conformer pour l’input audio Gemma 4, mergée le 12 avril — déjà dans b9101) avec unsloth/gemma-4-E4B-it-GGUF Q4_K_M + le mmproj BF16 (les mmproj F16 et Q8_0 produisent de la sortie répétitive — seul le BF16 marche à cause de la sensibilité numérique des couches ClippableLinear).
~6 Go de VRAM, max 30 secondes d’audio par input, OpenAI-compat chat completions avec content type input_audio. ASR, audio understanding, couche d’input pour agent vocal. Assez léger pour cohabiter avec une autre app GPU.
Reproductible
Chart Helm complet, tag d’image exact, tous les flags, harness de bench — tout est dans orales-one-market. Comme promis sur la home : chaque chiffre ici vient avec la stack exacte pour le reproduire.
Si vous tournez Gemma 4 + DFlash sur du Blackwell consumer (5090M, 5070 Ti, 5080) et que vous voyez le même cycle, pingez-moi — je file le bug upstream vLLM dès que j’ai un deuxième reproducer indépendant.