Skip to content
/ airelien.dev
Go back
Aurélien AMSELLEM

166 t/s sur Nemotron-Labs 30B-A3B NVFP4 — le nouveau LLM le plus rapide sur Olares One, caché derrière un flag CUDA-graph

NVIDIA a sorti Nemotron-Labs Elastic 30B-A3B avec quantization NVFP4 native il y a deux semaines. Sur Olares One (RTX 5090M consumer mobile sm_120, 24 GB), la config par défaut de vLLM OOM au load. Avec un seul flag CUDA-graph bien réglé — mode PIECEWISE et capture_sizes explicites [1,2,4] — le modèle boot et tourne à 165,91 t/s. +22% vs Gemma 4, +55% vs BeeLlama sur Qwen3.6 27B, +124% vs mon build MTP-master. Nouveau champion.

Deux passes. La première a OOM. La seconde a tourné à 166 t/s. La différence : un flag CUDA-graph.

Voici l’histoire.

Le modèle

NVIDIA a fait une release discrète le 8 mai : nvidia/NVIDIA-Nemotron-Labs-3-Elastic-30B-A3B-NVFP4. 4k+ downloads/semaine, très peu de bruit communautaire. Les morceaux intéressants :

C’est le premier modèle poussé par NVIDIA ciblant Blackwell consumer qui soit vraiment sur HuggingFace. (Nemotron-3 Super d’avril est toujours gated.) Ça mérite un test.

Passe 1 — defaults vLLM, OOM

image: vllm/vllm-openai:nightly
args:
  - nvidia/NVIDIA-Nemotron-Labs-3-Elastic-30B-A3B-NVFP4
  - --max-model-len 32768
  - --gpu-memory-utilization 0.92
  - --trust-remote-code

Boot :

CUDA out of memory. Tried to allocate 316.00 MiB. GPU 0 has a total capacity
of 23.42 GiB of which 416.38 MiB is free. Including non-PyTorch memory, this
process has 23.13 GiB memory in use.

Le calcul :

Ça ne rentre pas, à quelques centaines de MB près.

Workaround #1 : --enforce-eager — désactive entièrement les CUDA graphs. Pod boot. Bench : 32,60 t/s (10 runs, σ ≈ 0,08 — extrêmement stable). Mais le mode eager coûte ~moitié la vitesse d’un path graph-capturé sur du décodage à petit batch. C’est le plancher, pas le plafond.

Filed away. Passé à autre chose. (Puis revenu dessus quand l’utilisateur a demandé pourquoi eager et que la réponse “les graphs OOM” semblait être quelque chose qu’on pouvait résoudre.)

Passe 2 — PIECEWISE graphs avec capture_sizes restreints

Le CLI vLLM n’expose pas --cudagraph-mode directement. Le setting vit dans --compilation-config, en JSON :

args:
  - nvidia/NVIDIA-Nemotron-Labs-3-Elastic-30B-A3B-NVFP4
  - --max-model-len 4096
  - --gpu-memory-utilization 0.95
  - --max-num-seqs 1
  - --compilation-config
  - '{"cudagraph_mode": 1, "max_cudagraph_capture_size": 4, "cudagraph_capture_sizes": [1, 2, 4]}'
  - --trust-remote-code

Ce qui change :

Log de boot :

Capturing CUDA graphs (mixed prefill-decode, PIECEWISE): 100%|██████████| 3/3 [00:00<00:00, 8.30it/s]
CUDA graph pool memory: 0.04 GiB (actual), 0.05 GiB (estimated)

0,04 GiB. 40 mégaoctets. C’est tout le pool de CUDA graphs. Contre les ~4 GiB du default. Réduction de 100× sur la mémoire des graphs, et vLLM capture quand même trois tailles utiles.

Le pod load proprement. 23,13 GB utilisés / 23,42 GB disponibles. 290 MB de marge.

Bench

10 runs de completion Space Invaders HTML (2000 tokens chacun), temp=0,6 top_k=20 min_p=0.

Runt/sTemps wall
1 (warmup capture graphs / JIT)23,2885,89s
2166,6212,00s
3166,4612,02s
4165,9112,05s
5165,4612,09s
6165,3112,10s
7165,2512,10s
8165,4012,09s
9166,4112,02s
10166,3912,02s

Post-warmup : 165,91 t/s AVG, σ ≈ 0,5 (range 165,25 – 166,62).

C’est 5,1× le bench eager. Presque toute la différence vient des CUDA graphs — mêmes poids modèle, mêmes backend kernels, même fenêtre de contexte. Le coût host-CPU roundtrip sur du décodage small-batch est juste tellement brutal sans graphs.

Le nouveau classement sur Olares One

Voici mon leaderboard complet Olares One ce soir (single user, single-stream, completion 2000 tokens) :

Stackt/s AVGModèleQuant
Nemotron-Labs Elastic 30B-A3B NVFP4 + vLLM + PIECEWISE165,91NemotronH 30B-A3BNVFP4 ModelOpt
Gemma 4 26B-A4B vLLM135,97Gemma 4 26B-A4BAWQ-INT4
BeeLlama Qwen3.6 27B + DFlash + turbo3 KV107,54Qwen3.6 27B denseQ3_K_XL
llama.cpp MTP master ad2775774,28Qwen3.6 27B denseQ3_K_XL
Nemotron-Labs eager (no graphs)32,60NemotronH 30B-A3BNVFP4 ModelOpt

vs l’ancien champion Gemma 4 : +22%. vs mon meilleur path Qwen3.6 (BeeLlama) : +55%. vs Qwen3.6 + MTP-master : +124%.

Pour référence, les reports bench Reddit r/LocalLLaMA pour la même classe de modèle sur hardware desktop :

Le 5090M Laptop fait ~50% de la bande passante mémoire du 5090 desktop sur le papier. Atteindre 10% près du throughput desktop 5090 sur un modèle 30B c’est quelque chose que les kernels NVFP4 natifs peuvent faire que les formats GGUF quantizés ne peuvent pas — il n’y a pas d’étape dequantize dans la boucle d’inférence.

Pourquoi ça marche si bien

Trois choses se composent pour nous donner +22% sur l’ancien champion :

  1. NVFP4 natif sur tensor cores Blackwell. AWQ-INT4 (path Gemma 4) et Q3_K_XL (Qwen3.6 GGUF) ont tous deux besoin d’un dequantize-and-multiply : prendre du 4-bit, expand en FP16/BF16, puis lancer le GEMM. Les tensor cores FP4 de Blackwell sautent ça. vLLM pick FlashInferCutlassNvFp4LinearKernel pour le GEMM et FLASHINFER_CUTLASS pour le MoE — les deux écrits spécifiquement pour le path NVFP4. Le même modèle 30B en BF16 ne rentrerait pas du tout dans 24 GB.

  2. Routing MoE-A3B sur FlashInfer. 128 experts, 3B actifs par token. Le count de params actifs est comparable à un modèle dense 3B, mais on a la breadth d’un modèle 30B quand le router en a besoin. Le backend FlashInfer CUTLASS MoE est tuné pour exactement ce pattern ; le surcoût de routing est petit (~5%) comparé aux savings de ne pas faire tourner les 30B params par token.

  3. Hybride Mamba+Attention. La moitié des couches sont state-space (Mamba) — O(n) par token quelle que soit la taille de contexte. À 4K context ça compte moins, mais ça veut dire que roughly la moitié du coût décodage est constant-par-token au lieu de scaler avec la taille du KV cache.

Contraintes

max_model_len = 4096. C’est la douloureuse. Le KV cache est ce qui reste après modèle + graphs + compute buffers. À 32K context on aurait besoin de ~4 GB d’espace KV en plus qui ne rentrent pas. Options pour pousser :

Pour mon usage — appels Hermes Agent, completion de code, Q&A single-shot — 4K suffit la plupart du temps. Les longs PDF et les sessions coding multi-turn ont besoin d’un autre path.

max_num_seqs = 1. Single-stream. Pour des workflows multi-user / agentiques concurrents on veut plus, mais sur 24 GB avec ce modèle on ne peut pas se payer plus de KV cache. Même contrainte que max_model_len.

Warmup : Run 1 = 23 t/s. Capture des CUDA graphs + compilation JIT + autotune kernels premier pass. Les runs suivants hit 165 immédiatement. Pour des évals one-shot la première réponse est lente ; pour de l’usage soutenu, pas d’impact.

Qualité NVFP4. vLLM warn “Detected ModelOpt NVFP4 checkpoint. Please note that the format is experimental and could change in future.” Pour Kimi K2.5 / K2.6 NVFP4 (les seuls autres modèles NVFP4 sortis pour l’instant), les divergences MMLU publiées par NVIDIA étaient à 1% près de la baseline INT4. Pour ce 30B je n’ai pas encore fait d’éval qualité — il faut un sweep MMLU / HumanEval avant de le recommander pour la prod.

Ce que je ship ensuite

En train de packager ça comme nemotronlabselastic30bnvfp4one (ou un nom plus court que je peux faire rentrer) dans la source de marché Olares. Une fois le chart up, ce sera un install en un clic : pull image, download des poids, boot avec la compilation-config ci-dessus.

Deux trucs à surveiller :

Si tu es sur Blackwell consumer (5090 desktop, 5090M mobile, 5080) et que tu veux essayer ça, le trick de compilation-config est la clé. Les args vLLM par défaut OOM. cudagraph_mode: 1 + capture_sizes restreints c’est l’unlock. Même recette marche sur n’importe quel modèle NVFP4 où le problème est la capture de graphs qui bouffe trop de VRAM.


Hardware : Olares One — RTX 5090M Laptop (24 GB GDDR7, sm_120 Blackwell consumer mobile), Intel Core Ultra 9 275HX 24-core, 96 GB DDR5. Software : vLLM v0.19.2rc1.dev107+g4eafc7292 nightly (2026-05-20 06:22 UTC). Bench prompt : completion du jeu Space Invaders HTML, 2000 tokens, temp=0,6 top_k=20 min_p=0. Dix runs single-stream, single-user.

Share this post on:

Commentaires