Salut les amis ! Aujourd’hui on parle inférence locale et speculative decoding, parce que je viens de passer un week-end là-dessus et j’ai des chiffres à vous montrer.
À la base, je suis tombé sur le post de u/Kindly-Cantaloupe978 qui annonce 80 t/s à 218K de contexte et l’article Medium de Wasif Basharat à 85 t/s sur RTX 3090. Je me suis dit : si ça tourne sur 32 Go desktop et sur du 24 Go Ampere, est-ce que je peux reproduire ça sur mon Olares One — ma petite box d’IA maison avec un RTX 5090 Laptop GPU (24 Go, ~896 Go/s, sm_120 Blackwell) ? Spoiler : oui, mais il y a des pièges spécifiques au Blackwell consumer mobile qu’on ne voit dans aucun des deux articles.
Après plusieurs itérations, voilà où j’en suis : ~85-100 t/s soutenus, pic à 99.7 t/s, contexte max 75K, MTP n=3 avec 92-95 % d’acceptation une fois chaud. C’est environ 3 fois plus rapide que llama.cpp sur la même carte (33-36 t/s avec le meilleur GGUF NVFP4) et ça matche, voire ça bat, les références 32 Go desktop.
TL;DR — les chiffres
| Setup | Hardware | t/s |
|---|---|---|
| llama.cpp UD-Q4_K_XL ou GGUF NVFP4 | RTX 5090M 24 Go | 33-36 |
| vLLM v0.17 NVFP4 (sans MTP) | RTX 5090M 24 Go | 39 |
| vLLM v0.19.1 NVFP4 + MTP n=1 | RTX 5090M 24 Go | OOM (modèle OK, head MTP 2.37 Gio qui ne passe pas) |
| vLLM 0.19.1 + Lorbus AutoRound + MTP n=1 | RTX 5090M 24 Go | 65 |
| vLLM 0.19.1 + Lorbus AutoRound + MTP n=3 | RTX 5090M 24 Go | 85-100 |
| Référence : même recette sur 5090 desktop 32 Go | RTX 5090 32 Go | 78-80 |
| Référence : la stack de Wasif sur 3090 24 Go | RTX 3090 24 Go | 85 |
Cinq pièges spécifiques au Blackwell mobile 24 Go
1. NVFP4 + MTP = OOM en 24 Go
J’ai d’abord essayé sakamakismile/Qwen3.6-27B-Text-NVFP4-MTP. Sur le papier c’est parfait : NVFP4 c’est 2× le throughput FP8 sur les tensor cores Blackwell, et le nom du modèle dit qu’il inclut MTP. Le chargement passe sans broncher, et puis :
torch.OutOfMemoryError: Tried to allocate 2.37 GiB. GPU has 2.25 GiB free.
Pile le même problème que Wasif documente. Le loader Qwen3_5MTP de vLLM alloue un buffer BF16 tout neuf de 2.37 Gio pour mtp.fc parce que NVFP4 quantize tout dans le fichier. Sur 32 Go ça passe, sur 24 Go ça casse.
Le fix : passer à Lorbus/Qwen3.6-27B-int4-AutoRound, qui dé-quantize uniquement mtp.fc en BF16 directement dans le fichier (~280 Mio). vLLM le trouve sur disque, plus de buffer alloué à la volée.
Le trade-off : AutoRound INT4 utilise les kernels Marlin (tunés pour Ampere) au lieu des tensor cores NVFP4 natifs. Mais MTP n=3 apporte largement plus de vitesse que ce que l’accélération NVFP4 aurait gagné sur une carte consumer bandwidth-bound. Bref, on signe sans trembler.
2. --kv-cache-dtype fp8_e5m2 rejeté avec les checkpoints NVFP4
ValueError: fp8_e5m2 kv-cache is not supported with fp8 checkpoints.
AutoRound INT4 n’est pas dans la famille FP8, donc fp8_e5m2 passe. Bonus : ça donne plus de pool KV que fp8_e4m3 (l’Olares One se retrouve avec 23 760 tokens cachés en fp8_e5m2 + gpu-mem-util 0.97).
3. La PR vllm#36325 (Blackwell TMA fix) est obligatoire sur sm_12x
Sans elle, l’autotuner Triton OOM au warmup. is_tma_supported renvoie True pour toute compute capability ≥ 9 mais le Blackwell consumer ne fait pas vraiment de TMA — les allocations de descriptor buffer font exploser la VRAM. La PR cap à < 12. C’est un patch de 4 lignes que j’ai cherry-pické dans une image custom.
4. patch_tolist_cudagraph.py est maintenant public
Le patch précédemment privé de l’article de Wasif est maintenant dans noonghunna/qwen36-27b-single-3090/patches/. 165 lignes, qui fixent un sync CPU .tolist() qui casse la capture de CUDA graph pendant la simulation de continuation-chunk au warmup quand spec-decode + chunked-prefill se combinent. Obligatoire même avec fp8 KV (pas seulement avec TurboQuant). Merci à eux d’avoir publié !
5. MTP n=3 passe vraiment en 24 Go avec Lorbus
Je m’attendais à ce que n=3 OOM (l’article de Wasif prévient pour 24 Go avec sakamakismile). Avec le mtp.fc dé-quantizé de Lorbus et --gpu-memory-utilization 0.97, n=3 passe sans souci. La longueur d’acceptance pique à 3.86/3.0 (98 %/96 %/92 % par position), le throughput de génération pique à 99.7 t/s. Tout simplement.
La recette
Image Docker custom (FROM vllm/vllm-openai:v0.19.1-cu130) :
- Appliquer
vllm-project/vllm#36325.diffau build - Mounter
patch_tolist_cudagraph.pyet le lancer avantvllm servevia un entrypoint wrapper
Args vLLM :
--model Lorbus/Qwen3.6-27B-int4-AutoRound
--quantization auto_round
--dtype float16
--attention-backend flashinfer
--kv-cache-dtype fp8_e5m2
--max-model-len 75000
--gpu-memory-utilization 0.97
--max-num-seqs 1
--max-num-batched-tokens 2048
--language-model-only
--enable-prefix-caching
--enable-chunked-prefill
--enable-auto-tool-choice
--tool-call-parser qwen3_coder
--reasoning-parser qwen3
--speculative-config '{"method":"mtp","num_speculative_tokens":3}'
Variables d’env :
VLLM_USE_FLASHINFER_SAMPLER=1
VLLM_MEMORY_PROFILER_ESTIMATE_CUDAGRAPHS=1
VLLM_ALLOW_LONG_MAX_MODEL_LEN=1
VLLM_MARLIN_USE_ATOMIC_ADD=1
VLLM_FLOAT32_MATMUL_PRECISION=high
PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True,max_split_size_mb:512
NCCL_CUMEM_ENABLE=0
NCCL_P2P_DISABLE=1
OMP_NUM_THREADS=1
CUDA_DEVICE_MAX_CONNECTIONS=8
Métriques en steady state
Avg generation throughput: 85-100 t/s (varie selon le contenu)
Peak : 99.7 t/s
Longueur d'acceptance moyenne : 3.20 → 3.86 (sur 3 max)
Acceptation par position : 98 % / 93 % / 88 %
Taux d'acceptation des drafts : 92-95 %
Chargement du modèle : 16.87 Gio
Pool KV : 23 760 tokens (3.24 Gio)
Utilisation KV cache pendant la génération : 21-31 %
Quelques notes en vrac
- La variance, parlons-en : la vitesse tombe à 65-70 t/s sur du texte créatif ou de transition où l’acceptation MTP descend à ~70 %, et remonte à 95+ t/s sur les patterns prévisibles (boilerplate code, output structuré). C’est exactement la “MTP variance” que Wasif documente.
- Pourquoi on bat les références 32 Go ? Probablement parce que la combinaison Lorbus + flashinfer + chunked-prefill à n=3 tombe juste, et que la bande passante plus faible de la carte laptop est masquée par la haute acceptation MTP. Les maths : 60 % de la bande passante du 5090 desktop (896 vs 1500 Go/s) → plafond ~50 t/s sans spec, × ~2 d’acceptation length → ~100 t/s atteignable, ce qu’on voit.
- NVFP4 pourrait-il encore aider ? Si quelqu’un publie un quant Qwen3.6-27B NVFP4 avec
mtp.fcdé-quantizé dans le fichier (le truc à la Lorbus mais appliqué au NVFP4), le Blackwell mobile 24 Go passerait probablement les 100 t/s. La vitesse 2× sur tensor cores se compounderait avec MTP n=3. Si jamais quelqu’un se lance, faites-moi signe !
Crédits
- u/Kindly-Cantaloupe978 pour la recette Reddit sur 5090 32 Go
- Wasif Basharat pour l’article Medium sur 3090 24 Go
- noonghunna/qwen36-27b-single-3090 pour avoir publié les patches
- vllm-project/vllm#36325 pour le fix Blackwell TMA
- Lorbus pour le quant AutoRound avec le truc du head MTP dé-quantizé
Voilà ! Si quelqu’un veut le Dockerfile custom ou le chart Helm, faites-moi signe — je partage volontiers. Et si vous tournez sur 5090M, 4080M ou 3090 24 Go et que vous arrivez à reproduire ces chiffres (ou pas), je suis curieux d’avoir vos retours. À 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.