<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="nl">
  <title>Django de Vreng</title>
  <subtitle>Persoonlijke blog van Django de Vreng over productie-AI, agents, MCP en on-prem.</subtitle>
  <id>https://djangodevreng.nl/blog/</id>
  <link rel="self" type="application/atom+xml" href="https://djangodevreng.nl/atom.xml"/>
  <link rel="alternate" type="text/html" href="https://djangodevreng.nl/blog/"/>
  <updated>2026-06-23T00:00:00.000Z</updated>
  <author><name>Django de Vreng</name><uri>https://djangodevreng.nl/over/</uri></author>
  <entry>
    <title>Gemma-4 v23 op de DGX Spark</title>
    <id>https://djangodevreng.nl/blog/gemma-4-v23-dgx-spark/</id>
    <link rel="alternate" type="text/html" href="https://djangodevreng.nl/blog/gemma-4-v23-dgx-spark/"/>
    <published>2026-06-23T00:00:00.000Z</published>
    <updated>2026-06-23T00:00:00.000Z</updated>
    <author><name>Django de Vreng</name><uri>https://djangodevreng.nl/over/</uri></author>
    <category term="on-prem"/>
    <summary>Nieuwe vLLM v0.23.0-runs voor Gemma-4 op de DGX Spark: BF16, NVFP4 en MTP naast elkaar, met decode, TTFT, tails en praktische grenzen voor lokale agents.</summary>
    <content type="html">&lt;p&gt;NVFP4 blijft de praktische default voor Gemma-4 op de DGX Spark, maar MTP is nu de interessante middenpositie. In de nieuwe vLLM v0.23.0-runs zit NVFP4 nog steeds bovenaan bij chat en multi-turn, terwijl MTP de BF16-run duidelijk voorbij loopt zonder naar de NVIDIA re-quant te wisselen.&lt;/p&gt;
&lt;p&gt;Ik heb dezelfde Gemma-4-26B-A4B-familie opnieuw gedraaid op de &lt;a href=&quot;/dgx-spark/&quot;&gt;DGX Spark&lt;/a&gt;, nu met &lt;code&gt;vllm/vllm-openai:v0.23.0-aarch64-cu129-ubuntu2404&lt;/code&gt;. De ruwe data staat in de benchmark-repo bij commit &lt;a href=&quot;https://github.com/djangodevreng/dgx-spark-benchmarks/commit/605faab6a599d0a76aaf795ad54b3b46ed8f9aa8&quot;&gt;&lt;code&gt;605faab6a599&lt;/code&gt;&lt;/a&gt;. De Arena heeft er drie nieuwe entries bij: BF16 v23, MTP v23 en NVFP4 v23.&lt;/p&gt;
&lt;p&gt;De vorige Gemma-post ging vooral over de prijs van context in BF16. Deze run beantwoordt een andere vraag: wat verandert er als dezelfde machine, hetzelfde modelgebied en dezelfde workloads op vLLM v0.23.0 draaien, met drie serve-profielen naast elkaar?&lt;/p&gt;
&lt;h2&gt;De setup die gelijk bleef&lt;/h2&gt;
&lt;p&gt;Alle drie de runs draaien op dezelfde machine en dezelfde benchmarkvorm:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Onderdeel&lt;/th&gt;
&lt;th&gt;Waarde&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Hardware&lt;/td&gt;
&lt;td&gt;DGX Spark NVIDIA GB10, 128 GB unified memory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;vLLM image&lt;/td&gt;
&lt;td&gt;&lt;code&gt;vllm/vllm-openai:v0.23.0-aarch64-cu129-ubuntu2404&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;KV-cache&lt;/td&gt;
&lt;td&gt;&lt;code&gt;fp8&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prefix caching&lt;/td&gt;
&lt;td&gt;uit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Max model length&lt;/td&gt;
&lt;td&gt;131072&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Benchmark commit&lt;/td&gt;
&lt;td&gt;&lt;code&gt;605faab6a599&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;De drie profielen:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Profiel&lt;/th&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Served name&lt;/th&gt;
&lt;th&gt;Generated&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;BF16 v23&lt;/td&gt;
&lt;td&gt;&lt;code&gt;google/gemma-4-26B-A4B-it&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gemma-4-26b-a4b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2026-06-22T23:16:36+02:00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MTP v23&lt;/td&gt;
&lt;td&gt;&lt;code&gt;google/gemma-4-26B-A4B-it&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gemma-4-26b-a4b-mtp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2026-06-23T03:29:52+02:00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NVFP4 v23&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nvidia/Gemma-4-26B-A4B-NVFP4&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gemma-4-26b-a4b-nvfp4&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2026-06-23T01:35:33+02:00&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;MTP gebruikt dus hetzelfde Google-modelpad als BF16, maar served met het MTP-profiel. NVFP4 gebruikt de NVIDIA re-quant. Dat onderscheid is belangrijk, want anders vergelijk je stiekem twee dingen tegelijk: enginegedrag en modelartefact.&lt;/p&gt;
&lt;h2&gt;Chat: NVFP4 bovenaan, MTP haalt BF16 in&lt;/h2&gt;
&lt;p&gt;De eerste nuttige vergelijking is Run C: 1024 prompttokens, 1024 outputtokens, tien gelijktijdige requests. Dat is een nette chatvorm: niet triviaal kort, maar ook geen contextmonster.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Profiel&lt;/th&gt;
&lt;th&gt;TTFT c10&lt;/th&gt;
&lt;th&gt;Decode/user c10&lt;/th&gt;
&lt;th&gt;Decode totaal c10&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;BF16 v23&lt;/td&gt;
&lt;td&gt;1342.98 ± 449.90 ms&lt;/td&gt;
&lt;td&gt;11.47 ± 0.45 tok/s&lt;/td&gt;
&lt;td&gt;90.83 ± 7.87 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MTP v23&lt;/td&gt;
&lt;td&gt;1400.13 ± 142.07 ms&lt;/td&gt;
&lt;td&gt;17.79 ± 1.55 tok/s&lt;/td&gt;
&lt;td&gt;138.97 ± 6.68 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NVFP4 v23&lt;/td&gt;
&lt;td&gt;1138.26 ± 385.15 ms&lt;/td&gt;
&lt;td&gt;21.59 ± 0.98 tok/s&lt;/td&gt;
&lt;td&gt;151.22 ± 15.96 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Dit is de kern. MTP geeft op deze chat-run ongeveer 55 procent meer per-user decode dan BF16. NVFP4 zit daar nog boven, maar het gat tussen MTP en NVFP4 is veel kleiner dan het gat tussen BF16 en MTP.&lt;/p&gt;
&lt;p&gt;De latency voor de eerste token blijft in dezelfde orde. NVFP4 is hier het snelst, MTP is niet sneller in TTFT dan BF16. Dat past bij het beeld dat deze profielen vooral decode-doorvoer beïnvloeden. Prefill blijft gewoon werk.&lt;/p&gt;
&lt;h2&gt;Multi-turn is waar NVFP4 echt losloopt&lt;/h2&gt;
&lt;p&gt;Run E is voor mij de meest productie-achtige closed-loop test: vijf beurten per gesprek, tien gesprekken parallel, 2048 starttokens en 512 outputtokens per beurt.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Profiel&lt;/th&gt;
&lt;th&gt;TTFT c10&lt;/th&gt;
&lt;th&gt;Decode/user c10&lt;/th&gt;
&lt;th&gt;Decode totaal c10&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;BF16 v23&lt;/td&gt;
&lt;td&gt;2154.60 ± 858.63 ms&lt;/td&gt;
&lt;td&gt;10.69 ± 0.25 tok/s&lt;/td&gt;
&lt;td&gt;98.35 ± 3.95 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MTP v23&lt;/td&gt;
&lt;td&gt;2368.00 ± 789.47 ms&lt;/td&gt;
&lt;td&gt;16.57 ± 1.32 tok/s&lt;/td&gt;
&lt;td&gt;143.47 ± 4.67 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NVFP4 v23&lt;/td&gt;
&lt;td&gt;1966.10 ± 735.30 ms&lt;/td&gt;
&lt;td&gt;20.01 ± 0.80 tok/s&lt;/td&gt;
&lt;td&gt;182.90 ± 6.67 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Hier wordt NVFP4 gewoon lekker. 182.90 tok/s totaal voor tien multi-turn gesprekken op een Spark is geen demo-cijfer, dat is een werkbaar lokaal inference-profiel.&lt;/p&gt;
&lt;p&gt;MTP blijft nuttig. Niet als winnaar, wel als antwoord op: wat als ik het Google BF16-model wil blijven serveren en toch meer decode wil? Dan is 16.57 tok/s per gebruiker een groot verschil met 10.69.&lt;/p&gt;
&lt;h2&gt;Lange output: meer tokens, niet automatisch meer pijn&lt;/h2&gt;
&lt;p&gt;Voor agents en code-generatie is Run G relevant: 256 prompttokens, 4096 outputtokens, tien gelijktijdige requests. Dit is de vorm waarbij je vooral wilt weten of lange generaties de machine laten instorten.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Profiel&lt;/th&gt;
&lt;th&gt;TTFT c10&lt;/th&gt;
&lt;th&gt;Decode/user c10&lt;/th&gt;
&lt;th&gt;Decode totaal c10&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;BF16 v23&lt;/td&gt;
&lt;td&gt;490.95 ± 4.88 ms&lt;/td&gt;
&lt;td&gt;12.47 ± 0.94 tok/s&lt;/td&gt;
&lt;td&gt;87.16 ± 3.88 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MTP v23&lt;/td&gt;
&lt;td&gt;564.16 ± 14.86 ms&lt;/td&gt;
&lt;td&gt;17.67 ± 1.92 tok/s&lt;/td&gt;
&lt;td&gt;127.52 ± 9.05 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NVFP4 v23&lt;/td&gt;
&lt;td&gt;368.83 ± 54.97 ms&lt;/td&gt;
&lt;td&gt;23.69 ± 1.65 tok/s&lt;/td&gt;
&lt;td&gt;120.96 ± 50.17 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Let op de rare vorm: NVFP4 heeft de hoogste per-user decode, maar de totale decode heeft veel meer spreiding. MTP is lager per gebruiker, maar stabieler in deze specifieke run. Ik zou hier dus niet alleen naar de hoogste balk kijken. Voor agents wil je ook voorspelbaarheid, zeker als meerdere runs lang blijven streamen.&lt;/p&gt;
&lt;h2&gt;25k context blijft de muur&lt;/h2&gt;
&lt;p&gt;Quantization en MTP veranderen niet dat grote context vooral prefill is. Bij 25k prompttokens en c10 ziet het er zo uit:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Profiel&lt;/th&gt;
&lt;th&gt;TTFT c10&lt;/th&gt;
&lt;th&gt;Decode/user c10&lt;/th&gt;
&lt;th&gt;Decode totaal c10&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;BF16 v23&lt;/td&gt;
&lt;td&gt;39281.43 ± 20075.74 ms&lt;/td&gt;
&lt;td&gt;5.28 ± 2.13 tok/s&lt;/td&gt;
&lt;td&gt;28.49 ± 0.62 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MTP v23&lt;/td&gt;
&lt;td&gt;45640.37 ± 23247.85 ms&lt;/td&gt;
&lt;td&gt;6.05 ± 3.24 tok/s&lt;/td&gt;
&lt;td&gt;27.62 ± 0.27 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NVFP4 v23&lt;/td&gt;
&lt;td&gt;38575.15 ± 19624.30 ms&lt;/td&gt;
&lt;td&gt;7.40 ± 4.24 tok/s&lt;/td&gt;
&lt;td&gt;33.54 ± 0.03 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Dit is geen chat meer. Bij tien gelijktijdige 25k-prompts wacht je gemiddeld rond de 39 tot 46 seconden op de eerste token. NVFP4 helpt decode nog een beetje, maar de gebruiker voelt vooral leegte voor de stream begint.&lt;/p&gt;
&lt;p&gt;Dat is dezelfde les als in de eerdere &lt;a href=&quot;/blog/gemma-4-dgx-spark-benchmarks/&quot;&gt;Gemma-4 benchmarkpost&lt;/a&gt;, alleen nu met vLLM v0.23.0 erbij: context is geen gratis invoerveld. Als je een lokale agent 25k tokens laat meeslepen, betaal je dat in TTFT.&lt;/p&gt;
&lt;h2&gt;Open-loop: de kantoorvorm blijft bruikbaar&lt;/h2&gt;
&lt;p&gt;De open-loop tests zijn belangrijker voor gevoel dan de closed-loop tabellen. Ze sturen requests volgens een arrival pattern in plaats van alles tegelijk te laten starten.&lt;/p&gt;
&lt;h3&gt;H: kantoor-baseline&lt;/h3&gt;
&lt;p&gt;200 random prompts, request rate 0.3, burstiness 0.7.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Profiel&lt;/th&gt;
&lt;th&gt;OK&lt;/th&gt;
&lt;th&gt;Output tok/s&lt;/th&gt;
&lt;th&gt;P95 TTFT&lt;/th&gt;
&lt;th&gt;P95 TPOT&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;BF16 v23&lt;/td&gt;
&lt;td&gt;200/200&lt;/td&gt;
&lt;td&gt;129.92&lt;/td&gt;
&lt;td&gt;2835.43 ms&lt;/td&gt;
&lt;td&gt;197.57 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MTP v23&lt;/td&gt;
&lt;td&gt;200/200&lt;/td&gt;
&lt;td&gt;132.35&lt;/td&gt;
&lt;td&gt;3394.53 ms&lt;/td&gt;
&lt;td&gt;178.77 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NVFP4 v23&lt;/td&gt;
&lt;td&gt;200/200&lt;/td&gt;
&lt;td&gt;139.05&lt;/td&gt;
&lt;td&gt;2393.78 ms&lt;/td&gt;
&lt;td&gt;77.98 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;NVFP4 is hier duidelijk prettiger. Niet door veel meer output-throughput, want 139.05 versus 129.92 tok/s is geen wereldschok. Het verschil zit in TPOT: 77.98 ms p95 tegenover 197.57 ms bij BF16. De stream voelt veel sneller zodra hij loopt.&lt;/p&gt;
&lt;h3&gt;I: ShareGPT replay&lt;/h3&gt;
&lt;p&gt;250 echte gesprekken, zelfde request rate.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Profiel&lt;/th&gt;
&lt;th&gt;OK&lt;/th&gt;
&lt;th&gt;Output tok/s&lt;/th&gt;
&lt;th&gt;P95 TTFT&lt;/th&gt;
&lt;th&gt;P95 TPOT&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;BF16 v23&lt;/td&gt;
&lt;td&gt;250/250&lt;/td&gt;
&lt;td&gt;60.93&lt;/td&gt;
&lt;td&gt;456.10 ms&lt;/td&gt;
&lt;td&gt;115.31 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MTP v23&lt;/td&gt;
&lt;td&gt;250/250&lt;/td&gt;
&lt;td&gt;61.47&lt;/td&gt;
&lt;td&gt;576.82 ms&lt;/td&gt;
&lt;td&gt;77.32 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NVFP4 v23&lt;/td&gt;
&lt;td&gt;250/250&lt;/td&gt;
&lt;td&gt;61.99&lt;/td&gt;
&lt;td&gt;225.09 ms&lt;/td&gt;
&lt;td&gt;45.30 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Dit is de beste proxy voor normale chat. Korte, echte gesprekken. NVFP4 geeft p95 TTFT van 225.09 ms en p95 TPOT van 45.30 ms. Dat voelt lokaal niet als een compromis.&lt;/p&gt;
&lt;h3&gt;J: maandagochtend-piek&lt;/h3&gt;
&lt;p&gt;300 random prompts, target 1.5 rps, max concurrency 25.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Profiel&lt;/th&gt;
&lt;th&gt;OK&lt;/th&gt;
&lt;th&gt;Output tok/s&lt;/th&gt;
&lt;th&gt;P95 TTFT&lt;/th&gt;
&lt;th&gt;P95 TPOT&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;BF16 v23&lt;/td&gt;
&lt;td&gt;300/300&lt;/td&gt;
&lt;td&gt;132.04&lt;/td&gt;
&lt;td&gt;3006.73 ms&lt;/td&gt;
&lt;td&gt;199.23 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MTP v23&lt;/td&gt;
&lt;td&gt;300/300&lt;/td&gt;
&lt;td&gt;172.32&lt;/td&gt;
&lt;td&gt;3870.47 ms&lt;/td&gt;
&lt;td&gt;235.91 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NVFP4 v23&lt;/td&gt;
&lt;td&gt;300/300&lt;/td&gt;
&lt;td&gt;218.90&lt;/td&gt;
&lt;td&gt;2390.17 ms&lt;/td&gt;
&lt;td&gt;124.58 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Bij overload blijft NVFP4 ook het meest bruikbaar. Alle requests slagen, maar de queue bepaalt wie pijn voelt. BF16 en MTP leveren hier minder fijne tails op. MTP heeft wel meer output-throughput dan BF16, maar de p95 TTFT en p95 TPOT zijn slechter. Dat is precies waarom ik percentielen wil zien en niet alleen tokens per seconde.&lt;/p&gt;
&lt;h2&gt;Wat ik hiermee in de Arena zet&lt;/h2&gt;
&lt;p&gt;Ik heb drie nieuwe Arena entries toegevoegd in plaats van de oude Gemma-4 entries te overschrijven. De oude v0.20.1-runs blijven nuttig als historisch vergelijkingspunt. Deze nieuwe entries zijn expliciet v23:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/arena/gemma-4-26b-a4b-it-bf16-v23/&quot;&gt;&lt;code&gt;gemma-4-26b-a4b-it-bf16-v23&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/arena/gemma-4-26b-a4b-it-mtp-v23/&quot;&gt;&lt;code&gt;gemma-4-26b-a4b-it-mtp-v23&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/arena/gemma-4-26b-a4b-nvfp4-v23/&quot;&gt;&lt;code&gt;gemma-4-26b-a4b-nvfp4-v23&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;De korte rangorde voor mijn eigen gebruik:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;NVFP4 v23 voor lokale chat, agents en kantoor-load.&lt;/li&gt;
&lt;li&gt;MTP v23 als je bij het Google-modelartefact wilt blijven, maar BF16-decode te traag vindt.&lt;/li&gt;
&lt;li&gt;BF16 v23 als controlelijn en voor vergelijkingen waar precisie belangrijker is dan serve-snelheid.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Voor 25k context lost geen van de drie het echte probleem op. Daar moet je aan promptbudget, retrieval, memory compaction en agent-architectuur werken. Niet hopen dat een serve-profiel de wachttijd wegtovert.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>De drie getallen achter een snelle DGX Spark</title>
    <id>https://djangodevreng.nl/blog/drie-getallen-dgx-spark-snelheid/</id>
    <link rel="alternate" type="text/html" href="https://djangodevreng.nl/blog/drie-getallen-dgx-spark-snelheid/"/>
    <published>2026-05-22T00:00:00.000Z</published>
    <updated>2026-05-22T00:00:00.000Z</updated>
    <author><name>Django de Vreng</name><uri>https://djangodevreng.nl/over/</uri></author>
    <category term="on-prem"/>
    <summary>Decode, prefill en queueing: drie getallen bepalen of een DGX Spark snel voelt onder een echte workload, en juist die drie slaan de meeste reviews over.</summary>
    <content type="html">&lt;p&gt;Kun je serieus large language models lokaal draaien op een DGX Spark? Ja. Dat is het saaie antwoord, en het is ook het antwoord dat elke review je geeft: een modelnaam, een getal, tokens per seconde, klaar.&lt;/p&gt;
&lt;p&gt;Het bruikbare antwoord is lastiger. Een model dat één demo-prompt netjes afhandelt zegt niks over een maandagochtend met tien mensen, grote context, agent-flows en iemand die een halve roman in een ticket plakt. Daar gaat het schuren, of niet. En dat hangt niet af van de Spark, het hangt af van je workload.&lt;/p&gt;
&lt;p&gt;Ik heb een Spark in het lab staan en er een stapel modellen op gedraaid, in BF16, FP8 en NVFP4. Negen workloads, twee meetmethodes, en een paar runs opnieuw omdat de eerste verdacht goed was. Wat na al dat meten overbleef is geen scorebord. Het is één manier van kijken die elke keer klopte, en die staat hieronder. De harde cijfers per model staan in de losse posts, en de complete gids met de setup, de kosten en voor wie het werkt staat op &lt;a href=&quot;/dgx-spark/&quot;&gt;LLMs draaien op de DGX Spark&lt;/a&gt;. Dit stuk gaat over die ene lens.&lt;/p&gt;
&lt;h2&gt;Wat het ding eigenlijk is&lt;/h2&gt;
&lt;p&gt;De DGX Spark is NVIDIA&apos;s kleinste Blackwell-machine. Een &lt;a href=&quot;https://www.nvidia.com/en-us/products/workstations/dgx-spark/&quot;&gt;GB10-superchip&lt;/a&gt;, 128 GB unified memory, klein genoeg voor een serverkast. Geen losse videokaart met een eigen geheugenpoel, maar één geheugen dat de CPU en de GPU samen delen. Onthoud dat getal van 128 GB. Het is je hele budget, en alles wat hierna komt is een verdeelsom binnen die 128.&lt;/p&gt;
&lt;p&gt;Eén ding moet je vooraf weten, want het verklaart later de helft van de cijfers. De Spark draait op desktop-Blackwell, SM12.1, en die chip kan niet native in 4-bit rekenen. De grote datacenter-Blackwell, de B200, wel. Gevolg: van 4-bit quantization krijg je op de Spark de volle geheugenwinst, maar niet de volle rekenwinst. vLLM vangt dat op door 4-bit gewichten tijdens het rekenen terug te pakken naar hogere precisie.&lt;/p&gt;
&lt;p&gt;Dat werkt prima. Maar het is precies de reden dat je de mooie FP4-cijfers van een B200 niet zomaar op je eigen Spark moet plakken.&lt;/p&gt;
&lt;h2&gt;Wat past er in 128 GB&lt;/h2&gt;
&lt;p&gt;Kort: de gewichten gaan er eerst in, de rest is KV-cache voor alle gebruikers samen. Precisie is daarom een ontwerpkeuze vooraf, geen knopje achteraf, en ik schreef er een &lt;a href=&quot;/blog/quantization-lokale-llms/&quot;&gt;aparte post&lt;/a&gt; over. De vraag is nooit of een model past, maar wat er overblijft als het past. De volledige verdeelsom staat in de &lt;a href=&quot;/dgx-spark/&quot;&gt;gids&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Hoe snel het echt is&lt;/h2&gt;
&lt;p&gt;Hier gaan de meeste DGX Spark-reviews de fout in. Ze pakken één prompt, meten tokens per seconde, en noemen dat &quot;de snelheid&quot;. Maar snelheid is op deze machine geen getal. Het zijn drie dingen, ze voelen anders en ze gedragen zich anders. Haal ze uit elkaar en de hele Spark valt op zijn plek.&lt;/p&gt;
&lt;h3&gt;Decode is bijna gratis&lt;/h3&gt;
&lt;p&gt;Decode is de tekst die binnenkomt zodra het model eenmaal genereert. Op de Spark is dat saai stabiel, en saai is hier een compliment. Eén gebruiker op een 26B-model haalt tussen de 23 en 24 tokens per seconde in BF16, of je nu 4k of 25k context meegeeft. Tien gebruikers tegelijk: een stuk of 9 à 12 per persoon, en daar blijft het plakken. Decode hangt dus aan hoeveel mensen er tegelijk bezig zijn, niet aan hoe lang hun prompt is.&lt;/p&gt;
&lt;p&gt;En quantization tilt die hele lijn omhoog. &lt;a href=&quot;/blog/gemma-4-nvfp4-vs-bf16-dgx-spark/&quot;&gt;NVFP4 won op decode in alle negen tests&lt;/a&gt;, met 22 tot 92 procent afhankelijk van de workload. Op een lichter MoE-model als Nemotron-3 tikt single-user decode zelfs tegen de 60 t/s aan. Decode is, kortom, het probleem niet.&lt;/p&gt;
&lt;h3&gt;Prefill is de rekening&lt;/h3&gt;
&lt;p&gt;Prefill wel. Prefill is de stilte vóór het eerste token, en dat is wat een gebruiker als &quot;traag&quot; ervaart, niet de tokens daarna.&lt;/p&gt;
&lt;p&gt;Prefill schaalt mee met je promptgrootte, en dat doet pijn. Een korte prompt is binnen een halve seconde verwerkt, ook met tien man tegelijk. Gooi er 25k context tegenaan met diezelfde tien gebruikers en je wacht 35 seconden op het eerste teken. Zelfde machine, zelfde concurrency, alleen een langere prompt. Verdubbel de prompt, verdubbel grofweg de wachttijd.&lt;/p&gt;
&lt;p&gt;En quantization? Helpt hier nauwelijks. Prefill is rekenwerk, en rekenwerk is precies waar die SM12.1-handicap zit. NVFP4 maakt je decode sneller. Je prefill blijft prefill.&lt;/p&gt;
&lt;h3&gt;Onder druk queue&apos;t hij, hij crasht niet&lt;/h3&gt;
&lt;p&gt;Blijft de vraag: wat doet hij als je er gewoon te veel op gooit? Het antwoord is geruststellend saai. Hij valt niet om. Hij gaat in de rij staan.&lt;/p&gt;
&lt;p&gt;In de zwaarste test wilde ik 1,5 requests per seconde door de machine duwen. Hij haalde daar bijna zes keer minder van. En toch faalde geen enkele van de 300 requests. De vertraging ging ook niet naar iedereen, hij ging naar de staart: de doorsnee-gebruiker merkte weinig, de ongelukkige één procent wachtte zes seconden op zijn eerste token.&lt;/p&gt;
&lt;p&gt;Voor on-prem is dat de beste uitkomst die je kunt hopen. Een crash is een telefoontje. Een rij is even geduld. Een kantoor leeft met het tweede, niet met het eerste.&lt;/p&gt;
&lt;p&gt;Dat is het hele model. Decode is bijna gratis, prefill is de rekening, queueing is je vangnet. De cijfers eronder, negen workloads per model en twee meetmethodes, staan in de &lt;a href=&quot;/arena/&quot;&gt;arena&lt;/a&gt; en in de losse posts: de &lt;a href=&quot;/blog/gemma-4-dgx-spark-benchmarks/&quot;&gt;BF16-baseline&lt;/a&gt;, &lt;a href=&quot;/blog/gemma-4-nvfp4-vs-bf16-dgx-spark/&quot;&gt;NVFP4 tegen BF16&lt;/a&gt; en &lt;a href=&quot;/blog/nemotron-3-dgx-spark-precisies/&quot;&gt;Nemotron-3 in drie precisies&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;De rest staat in de gids&lt;/h2&gt;
&lt;p&gt;Welke engine ik draai (vLLM), &lt;a href=&quot;/kosten-dgx-spark/&quot;&gt;wat een Spark kost&lt;/a&gt;, en voor wie dit wel of niet werkt: dat is het complete plaatje, en dat hoort in de &lt;a href=&quot;/dgx-spark/&quot;&gt;gids&lt;/a&gt;, niet in dit ene lens-verhaal. De korte versie van &quot;voor wie&quot;: lokaal wordt pas interessant als de data niet de deur uit mag. Heb je die eis niet en wil je puur de snelste, goedkoopste tokens, dan is een cloud-API het eerlijkere antwoord.&lt;/p&gt;
&lt;p&gt;Lokaal draaien is geen principe. Het is een verdeling: wat moet binnen blijven, en wat mag naar buiten.&lt;/p&gt;
&lt;h2&gt;Doe het zelf na&lt;/h2&gt;
&lt;p&gt;Alles wat hieronder ligt is open. De modellen staan op Hugging Face, vLLM is open source, en de ruwe benchmark-output plus de scripts staan op &lt;a href=&quot;https://github.com/djangodevreng/dgx-spark-benchmarks&quot;&gt;GitHub&lt;/a&gt;. De &lt;a href=&quot;/arena/methodologie/&quot;&gt;methodologie&lt;/a&gt; legt uit welke negen workloads ik draai en waarom.&lt;/p&gt;
&lt;p&gt;Heb je zelf een Spark, dan zou je dezelfde route moeten kunnen lopen en ongeveer dezelfde cijfers moeten krijgen. Lukt dat niet, dan wil ik dat juist weten. &lt;a href=&quot;/contact/&quot;&gt;Mail gerust&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Waarom deze blog en arena bestaan</title>
    <id>https://djangodevreng.nl/blog/waarom-deze-blog-en-arena/</id>
    <link rel="alternate" type="text/html" href="https://djangodevreng.nl/blog/waarom-deze-blog-en-arena/"/>
    <published>2026-05-05T00:00:00.000Z</published>
    <updated>2026-05-05T00:00:00.000Z</updated>
    <author><name>Django de Vreng</name><uri>https://djangodevreng.nl/over/</uri></author>
    <category term="reflecties"/>
    <summary>Ik zocht concrete cijfers voor lokale AI op de DGX Spark en vond ze niet. Dus meet ik ze zelf, en bouw ik de blog en de arena op als open werkbank.</summary>
    <content type="html">&lt;p&gt;Voor klanten van &lt;a href=&quot;https://kamoo.ai&quot;&gt;Kamoo&lt;/a&gt; zet ik AI-systemen op die soms dicht bij huis moeten blijven. Accountants, administraties, kantoren met persoonsgegevens en financiële stukken. Precies het soort data waar je auditor niet rustiger van wordt als je zegt: “we sturen het even naar Amerika”.&lt;/p&gt;
&lt;p&gt;Daarom staat er bij ons een &lt;a href=&quot;https://www.nvidia.com/en-us/products/workstations/dgx-spark/&quot;&gt;DGX Spark&lt;/a&gt;. 128 GB unified memory, klein genoeg voor een serverkast, groot genoeg om serieus lokale modellen te draaien via &lt;a href=&quot;https://docs.vllm.ai/&quot;&gt;vLLM&lt;/a&gt;. Wat er praktisch op past, verzamel ik op &lt;a href=&quot;/dgx-spark/&quot;&gt;de overzichtspagina over lokale modellen op de DGX Spark&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Toen begon de praktische vraag.&lt;/p&gt;
&lt;p&gt;Welk model gebruik je waarvoor op deze machine? Welke precisie kies je? Hoeveel context past nog? Waar valt concurrency om? Wat gebeurt er op een gewone maandag met tien mensen die niet tegelijk een benchmark draaien, maar gewoon hun werk doen?&lt;/p&gt;
&lt;p&gt;Ik zocht cijfers voor precies die vragen. Geen algemene leaderboard met een score die vooral goed staat in een screenshot. Gewoon: deze chip, deze modellen, deze engines, deze workloads, deze grenzen.&lt;/p&gt;
&lt;p&gt;Die vond ik niet.&lt;/p&gt;
&lt;p&gt;Dus bouw ik ze zelf op.&lt;/p&gt;
&lt;h2&gt;De arena is de meetbank&lt;/h2&gt;
&lt;p&gt;Op dit moment staan er tien benchmarkprofielen in de &lt;a href=&quot;/arena/&quot;&gt;arena&lt;/a&gt;, met runs voor onder meer context-scaling, concurrency, output-throughput, RAG-achtige workloads en Maandagochtend-piek.&lt;/p&gt;
&lt;p&gt;Die arena moet één ding goed doen: laten zien wat je op een DGX Spark praktisch kunt verwachten. Niet welk model “het beste” is in abstracte zin, maar welk model op deze hardware bruikbaar blijft onder de workloads die ik in klantwerk tegenkom.&lt;/p&gt;
&lt;p&gt;Bij een paar runs schreef ik al op wat er misging en wat ik eruit haalde. Bijvoorbeeld &lt;a href=&quot;/blog/gemma-4-dgx-spark-benchmarks/&quot;&gt;waar Gemma-4 op de Spark begint te schuren&lt;/a&gt;, &lt;a href=&quot;/blog/gemma-4-nvfp4-vs-bf16-dgx-spark/&quot;&gt;wat NVFP4 wint van BF16 als de bugs eenmaal weg zijn&lt;/a&gt;, en &lt;a href=&quot;/blog/nemotron-3-dgx-spark-precisies/&quot;&gt;hoe drie precisies van Nemotron-3 zich verhouden&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;De ruwe output staat publiek op GitHub: &lt;a href=&quot;https://github.com/djangodevreng/dgx-spark-benchmarks&quot;&gt;djangodevreng/dgx-spark-benchmarks&lt;/a&gt;. Dat is bewust. Als je zelf een Spark hebt, moet je dezelfde route kunnen lopen en ongeveer dezelfde cijfers kunnen krijgen. Als dat niet lukt, is dat ook interessante data.&lt;/p&gt;
&lt;p&gt;De arena is dus geen statisch lijstje. Het is een werkbank. Nieuwe modellen erin, andere precisies ernaast, workloads aanscherpen, rare resultaten opnieuw draaien. Precies saai genoeg om nuttig te worden.&lt;/p&gt;
&lt;h2&gt;De blog is de context eromheen&lt;/h2&gt;
&lt;p&gt;Cijfers zijn handig, maar ze vertellen niet het hele verhaal.&lt;/p&gt;
&lt;p&gt;Een benchmark kan zeggen dat NVFP4 sneller is dan BF16. De blog kan vertellen dat de eerste runs stukliepen op vLLM-bugs, dat een parameter verkeerd stond, dat een model pas bruikbaar werd nadat de contextlengte omlaag ging, of dat de tail-latency erger voelde dan het gemiddelde deed vermoeden.&lt;/p&gt;
&lt;p&gt;Dat is de laag die ik zelf miste toen ik begon. Niet alleen “hier is een score”, maar: dit probeerde ik, dit brak, dit heb ik aangepast, en dit zou ik de volgende keer anders doen.&lt;/p&gt;
&lt;p&gt;Daarom staan de blog en arena naast elkaar. De arena geeft de meetpunten. De blog geeft de redenering, de fouten en de praktische keuzes erachter.&lt;/p&gt;
&lt;h2&gt;Waarom lokaal&lt;/h2&gt;
&lt;p&gt;Privacy is meestal de nette uitleg. Die klopt ook. De praktischere reden: sommige klanten hebben geen keuze.&lt;/p&gt;
&lt;p&gt;Een accountantskantoor kan klantdata niet behandelen alsof het voorbeeldtekst in een demo is. Gemeentes hebben regels. Financiële documenten hebben regels. Persoonsgegevens hebben regels. In de praktijk komt dat allemaal neer op dezelfde vraag: kun je dit opzetten zonder dat legal, compliance en audit meteen de deur dichttrekken?&lt;/p&gt;
&lt;p&gt;Dan heb je twee opties. AI past daar niet, of je maakt het lokaal.&lt;/p&gt;
&lt;p&gt;Wij kiezen voor lokaal waar dat nodig is. De Spark maakt dat ineens minder exotisch. Hij is niet goedkoop, maar wel behapbaar voor een MKB-kantoor dat iets serieus wil doen zonder meteen een eigen datacenter te bouwen.&lt;/p&gt;
&lt;p&gt;Daar zit voor mij het interessante werk: modellen draaien, latency meten, prompts testen, documenten door een pipeline trekken, en kijken waar het breekt.&lt;/p&gt;
&lt;p&gt;Meestal breekt het ergens saais. Dat zijn de beste plekken.&lt;/p&gt;
&lt;h2&gt;Wat ik wil kunnen beantwoorden&lt;/h2&gt;
&lt;p&gt;De arena moet uiteindelijk antwoord geven op vragen die in projecten steeds terugkomen.&lt;/p&gt;
&lt;p&gt;Welk model is snel genoeg voor interne documentvragen? Welke precisie geeft genoeg ruimte voor meerdere gebruikers tegelijk? Wanneer is NVFP4 prima, wanneer wil je FP8, en wanneer is BF16 vooral een dure default? Hoeveel context kun je geven voordat latency vervelend wordt? Welke engine past beter bij welke workload: vLLM, TensorRT-LLM of SGLang?&lt;/p&gt;
&lt;p&gt;Dat zijn geen academische vragen. Ze bepalen hoe je een on-prem setup ontwerpt. Hoeveel hardware je nodig hebt. Welke data lokaal blijft. Welke stappen je eventueel naar een hosted model stuurt. En waar je de grens trekt tussen “werkt in een demo” en “houdt stand op maandagochtend”.&lt;/p&gt;
&lt;p&gt;Die laatste grens is de hele reden dat deze site bestaat.&lt;/p&gt;
&lt;h2&gt;Waarom ik dit publiek opschrijf&lt;/h2&gt;
&lt;p&gt;Alles wat ik hiervoor gebruik is open of publiek: vLLM, modellen op Hugging Face, benchmark-scripts, losse JSON, de site zelf. Het geheim zit niet in toegang tot een magisch dashboard. Het zit in uren proberen, meten, opnieuw draaien, bugs zoeken en daarna nog eens meten omdat je eerste run verdacht goed was.&lt;/p&gt;
&lt;p&gt;Dat kostte mij inmiddels tientallen uren. Modellen laten draaien, runs herhalen, rare resultaten uitvogelen, en daarna nog een keer meten omdat de eerste run verdacht goed was.&lt;/p&gt;
&lt;p&gt;Als iemand anders dezelfde route loopt, hoeft die niet opnieuw door alle stoeptegels heen. En als iemand mijn cijfers tegenspreekt met betere runs: mooi. Dan wordt de arena beter.&lt;/p&gt;
&lt;p&gt;Er zit ook een tweede reden onder. Deze site is zelf onderdeel van het experiment. De blog, de arena, de flow van benchmark-output naar gestructureerde JSON naar pagina’s: dat is grotendeels in een paar weken gebouwd met agents die meeschrijven en meebouwen. De kleine versie daarvan beschreef ik eerder in &lt;a href=&quot;/blog/openclaw-op-raspberry-pi/&quot;&gt;de OpenClaw-setup op een Raspberry Pi&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Die workflow is inmiddels onderdeel van het werk. Ik dump ruwe bevindingen in Slack, laat een agent de repo en schrijfgids lezen, krijg een branch met voorstel terug, draai checks en review zelf de diff. Dat scheelt geen denken. Het verplaatst wel veel voorbereiding naar een laag die gewoon doorwerkt.&lt;/p&gt;
&lt;p&gt;Schrijven over dat proces dwingt me om het minder rommelig te maken dan mijn terminal history. Dat helpt. Niet altijd leuk, wel nodig.&lt;/p&gt;
&lt;h2&gt;Wat ik hierna wil bouwen&lt;/h2&gt;
&lt;p&gt;Eerst meer benchmarks. vLLM was het startpunt, omdat het snel werkt en breed gebruikt wordt. TensorRT-LLM ligt al op de werkbank voor Nemotron-3. SGLang wil ik daarna naast dezelfde workloads leggen. Pas met meerdere engines zie je of je model traag is, je engine dwarsligt, of jij gewoon iets doms hebt gedaan.&lt;/p&gt;
&lt;p&gt;Daarna wil ik &lt;code&gt;bench-spark&lt;/code&gt; publiek maken: de benchmark-runner zoals ik hem nu gebruik. Geen perfect framework. Wel iets waarmee iemand met dezelfde hardware dezelfde vragen kan stellen zonder eerst mijn fouten na te bouwen.&lt;/p&gt;
&lt;p&gt;Ook wil ik een Nederlandse eval-suite voor lokale LLMs maken. Geen Engelse reasoning-benchmark erbij, maar kantoorwerk: accountancy-jargon, juridische teksten, financiële stukken, documenten met rare opmaak. Precies de dingen waar lokale AI in Nederland op afgerekend wordt.&lt;/p&gt;
&lt;p&gt;En er komt meer werk rond lokaal RAG op grote documentsets. Geen platform-pitch. Gewoon uitzoeken hoe je meer dan een miljoen documenten door een on-prem setup krijgt zonder dat opslag, retrieval of OCR je langzaam gaat haten.&lt;/p&gt;
&lt;h2&gt;Wat ik oversla&lt;/h2&gt;
&lt;p&gt;Geen dagelijkse AI-nieuwsbrief. Daar zijn al genoeg plekken voor, sommige zelfs expres.&lt;/p&gt;
&lt;p&gt;Geen general-purpose “wij doen alles met AI”-verhaal. Te breed, en meestal betekent het niets.&lt;/p&gt;
&lt;p&gt;Geen thought-leader-act. Ik bouw liever iets dat kraakt dan een mening die soepel klinkt.&lt;/p&gt;
&lt;p&gt;Ook geen platform bouwen zoals OpenClaw. Ik gebruik het, ik schrijf erover, ik bouw er flows mee. Maar die laag zelf laat ik aan de mensen die daar elke dag in zitten.&lt;/p&gt;
&lt;h2&gt;Wat dit moet worden&lt;/h2&gt;
&lt;p&gt;Voor klanten moet dit laten zien wat lokale AI praktisch kost: hardware, latency, precisie, onderhoud, rare randgevallen. Voor mij is het de plek waar ik mijn eigen aannames vastzet voordat de volgende benchmark ze onderuit haalt.&lt;/p&gt;
&lt;p&gt;Ik probeer het ritme vast te houden. Geen belofte per week. Als er niets te melden is, staat hier niets. Als er bugs, runs en rare grafieken zijn, staat hier waarschijnlijk te veel.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Gemma-4 op de DGX Spark: NVFP4 vs BF16</title>
    <id>https://djangodevreng.nl/blog/gemma-4-nvfp4-vs-bf16-dgx-spark/</id>
    <link rel="alternate" type="text/html" href="https://djangodevreng.nl/blog/gemma-4-nvfp4-vs-bf16-dgx-spark/"/>
    <published>2026-05-03T00:00:00.000Z</published>
    <updated>2026-05-08T00:00:00.000Z</updated>
    <author><name>Django de Vreng</name><uri>https://djangodevreng.nl/over/</uri></author>
    <category term="on-prem"/>
    <summary>Negen identieke benchmarks, twee precisies. NVFP4 is 22 tot 92 procent sneller per token, en de capaciteit groeit 69 procent op piekuren op de Spark.</summary>
    <content type="html">&lt;p&gt;import BenchCard from &quot;../../components/post/BenchCard.astro&quot;;
import BenchCardRow from &quot;../../components/post/BenchCardRow.astro&quot;;
import Note from &quot;../../components/post/Note.astro&quot;;&lt;/p&gt;
&lt;p&gt;In de &lt;a href=&quot;/blog/gemma-4-dgx-spark-benchmarks/&quot;&gt;BF16-baseline van Gemma-4 op de DGX Spark&lt;/a&gt; deed ik negen benchmarks met Gemma-4-26B-A4B in BF16. Decode-snelheid hield prima stand, prefill bepaalde wanneer de muur kwam, en het systeem queue&apos;de netjes onder druk in plaats van te crashen. Dat verhaal leek af, totdat NVIDIA een NVFP4-quantized versie van datzelfde model uitbracht.&lt;/p&gt;
&lt;p&gt;Zelfde architectuur en fine-tune, zelfde server-config, alleen de precisie verandert. Van BF16 (16 bits per parameter) naar NVFP4 (4 bits per parameter, NVIDIA&apos;s variant op FP4). Vier keer kleiner per gewicht, en als de Blackwell-kernels meewerken ook flink sneller op compute-zware taken.&lt;/p&gt;
&lt;p&gt;Op papier dus mooi. In de praktijk: de officiële vLLM v0.20.1-release herkent dit checkpoint zonder gedoe, en de cijfers waren over de hele linie sneller dan de BF16-baseline. Beide tests vallen onder de gids &lt;a href=&quot;/dgx-spark/&quot;&gt;LLMs draaien op de DGX Spark&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Waarom dit überhaupt onderzoeken&lt;/h2&gt;
&lt;p&gt;Voor een kantoor met een lokale AI-machine is geheugen-budget het meest beperkende ding na rekenkracht. Een 26B model in BF16 neemt ~48 GB GPU-geheugen voor weights alleen. Op een Spark met 128 GB unified memory blijft er zo&apos;n 65 GB over voor KV-cache. Voldoende voor het kantoorscenario uit de eerste blog, maar niet veel ruimte om bijvoorbeeld 30+ gebruikers met grote context naast elkaar te draaien.&lt;/p&gt;
&lt;p&gt;NVFP4 reduceert dat tot ~18 GB voor weights. Niet vier keer minder dan BF16 (de vision-encoder blijft BF16, en scale-factors kosten ook ruimte), maar wel ongeveer 2.7× minder. Dat geeft je richting 95 GB KV-cache headroom, wat in theorie veel hogere concurrency moet ondersteunen. Daar komt nog bij dat er minder geheugenverkeer per forward pass nodig is, dus per definitie minder bandwidth-druk, en dat was in BF16 al de bottleneck bij multi-user load. De vraag was dus simpel: hoeveel van die theoretische winst overleeft de praktijk?&lt;/p&gt;
&lt;h2&gt;Wat NVFP4 eigenlijk is&lt;/h2&gt;
&lt;p&gt;NVFP4 is NVIDIA&apos;s variant op FP4: floating-point getallen met 4 bits per waarde. Vier bits, niet vier bytes, dus een factor 4 minder per parameter dan BF16. Door per groep gewichten een scaling factor mee op te slaan blijft de accuracy redelijk overeind.&lt;/p&gt;
&lt;p&gt;Voor Blackwell ligt het zo. NVIDIA&apos;s datacenter-kaarten (B100, B200, SM10.0) hebben tensor cores die &lt;em&gt;natively&lt;/em&gt; met 4-bit waarden kunnen rekenen, en dat is veel sneller dan dezelfde berekening in FP16 of BF16. De DGX Spark daarentegen is desktop-Blackwell (GB10, SM12.1) en die architectuur heeft géén native FP4-compute.&amp;lt;Note&amp;gt;Op een datacenter B200 (SM10.0) zou je hier nog 2 tot 3× bovenop verwachten dankzij native FP4 tensor cores. Spark mist die hardware-pad, dus alle winst komt uit geheugen-bandwidth, niet uit compute.&amp;lt;/Note&amp;gt; Wat je in dat geval krijgt is &quot;weight-only&quot; FP4: de gewichten zijn fysiek 4-bit opgeslagen (vandaar de geheugen-winst), maar tijdens compute worden ze on-the-fly gedecodeerd naar FP16 voor de matrix-multiplications. Een vLLM-warning maakt dat expliciet:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Your GPU does not have native support for FP4 computation but FP4 quantization
is being used. Weight-only FP4 compression will be used leveraging the Marlin kernel.
This may degrade performance for compute-heavy workloads.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Geheugen-winst krijg je dus volledig, compute-winst maar gedeeltelijk. De Marlin INT4 GEMM kernel is geoptimaliseerd, maar niet zo snel als native FP4 op SM10.0 zou zijn. Goed om in te calculeren bij de cijfers verderop.&lt;/p&gt;
&lt;h2&gt;De testopstelling&lt;/h2&gt;
&lt;p&gt;Server-config identiek aan de eerste blog, alleen het model wisselt:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker run -d --name vllm-bench \
  --gpus all --ipc=host \
  -v appliance_hf-cache:/root/.cache/huggingface \
  -p 8000:8000 \
  vllm/vllm-openai:v0.20.1 \
  --model nvidia/Gemma-4-26B-A4B-NVFP4 \
  --served-model-name gemma-4-26b-a4b-nvfp4 \
  --max-model-len 131072 \
  --gpu-memory-utilization 0.95 \
  --kv-cache-dtype fp8 \
  --limit-mm-per-prompt &apos;{&quot;image&quot;:0,&quot;audio&quot;:0}&apos; \
  --async-scheduling \
  --no-enable-prefix-caching \
  --host 0.0.0.0 \
  --port 8000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Tests zijn één-op-één gelijk aan de eerste blog: zelfde commands, zelfde concurrency-niveaus, zelfde datasets voor de open-loop tests, zelfde seed. Dat is opzettelijk, want wil je het effect van een geïsoleerde variabele meten (in dit geval de precisie), dan moet alles eromheen gelijk blijven. Hoe ik die concurrency-niveaus, seeds en open-loop-aankomsten precies meet staat in de &lt;a href=&quot;/arena/methodologie/&quot;&gt;meetmethode van de Arena&lt;/a&gt;.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Vergelijking&lt;/th&gt;
&lt;th&gt;BF16&lt;/th&gt;
&lt;th&gt;NVFP4&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Model&lt;/td&gt;
&lt;td&gt;google/gemma-4-26B-A4B-it&lt;/td&gt;
&lt;td&gt;nvidia/Gemma-4-26B-A4B-NVFP4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Active params&lt;/td&gt;
&lt;td&gt;4B&lt;/td&gt;
&lt;td&gt;4B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total params&lt;/td&gt;
&lt;td&gt;26B&lt;/td&gt;
&lt;td&gt;26B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model memory&lt;/td&gt;
&lt;td&gt;~48 GB&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~18 GB&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;KV-cache headroom&lt;/td&gt;
&lt;td&gt;~65 GB&lt;/td&gt;
&lt;td&gt;~95 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MoE backend&lt;/td&gt;
&lt;td&gt;(default)&lt;/td&gt;
&lt;td&gt;MARLIN (geforceerd)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Drie cijfers vatten waar het op uitkomt samen. Klik door voor de volledige run in de Arena, met alle seeds, concurrency-niveaus en commands:&lt;/p&gt;
&lt;p&gt;&amp;lt;BenchCardRow&amp;gt;
&amp;lt;BenchCard
model=&quot;gemma-4-26b-a4b-nvfp4&quot;
bench=&quot;long-output&quot;
label=&quot;Decode @ c=10 (256→4k)&quot;
value=&quot;22.5&quot;
unit=&quot;tok/s&quot;
delta=&quot;+92% vs BF16&quot;
/&amp;gt;
&amp;lt;BenchCard
model=&quot;gemma-4-26b-a4b-nvfp4&quot;
bench=&quot;monday-peak&quot;
label=&quot;Maandag-piek RPS&quot;
value=&quot;0.44&quot;
delta=&quot;+69% vs BF16&quot;
/&amp;gt;
&amp;lt;BenchCard
model=&quot;gemma-4-26b-a4b-nvfp4&quot;
bench=&quot;sharegpt&quot;
label=&quot;ShareGPT TPOT P50&quot;
value=&quot;39&quot;
unit=&quot;ms&quot;
delta=&quot;−59% vs BF16&quot;
/&amp;gt;
&amp;lt;/BenchCardRow&amp;gt;&lt;/p&gt;
&lt;p&gt;Een interactieve versie van alle cijfers staat op de &lt;a href=&quot;/arena/gemma-4-26b-a4b-nvfp4/&quot;&gt;arena-pagina van Gemma-4-26B-A4B-NVFP4&lt;/a&gt;, inclusief commands en TTFT-percentielen voor alle 9 tests.&lt;/p&gt;
&lt;p&gt;&amp;lt;details&amp;gt;
&amp;lt;summary&amp;gt;Run A: context-scaling van 4k naar 25k&amp;lt;/summary&amp;gt;&lt;/p&gt;
&lt;p&gt;Decode per gebruiker bij groeiende context, c=1/5/10:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Context&lt;/th&gt;
&lt;th&gt;Users&lt;/th&gt;
&lt;th&gt;BF16 d/u&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;NVFP4 d/u&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;Winst&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;4k&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;24.08&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;29.80&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;+24%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4k&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;12.55&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;22.01&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;+75%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4k&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;9.48&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;16.94&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;+79%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8k&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;23.69&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;29.31&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;+24%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8k&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;11.48&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;19.28&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;+68%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8k&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;8.52&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;14.35&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;+68%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16k&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;23.34&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;28.55&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;+22%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16k&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;10.05&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;15.67&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;+56%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16k&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;6.79&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;10.06&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;+48%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;25k&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;22.75&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;27.70&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;+22%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;25k&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;8.46&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;12.46&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;+47%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;25k&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;5.40&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;7.55&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;+40%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Bij c=1 is de winst stabiel rond +22-24% door alle contexts heen. Geheugen-bandwidth speelt bij single-user nauwelijks, dus de winst zit hier in het compute-pad zelf. Marlin&apos;s INT4-decode plus FP16-matmul is iets sneller dan BF16&apos;s directe FP16-matmul, ondanks dat het twee stappen zijn.&lt;/p&gt;
&lt;p&gt;Bij c=10 schaalt het verschil veel sterker met workload-type, van +40% bij 25k context tot +79% bij 4k. Dat komt doordat bij multi-user de geheugen-bandwidth de bottleneck wordt, en NVFP4 leest minder bytes per forward pass. Hoe meer concurrent, hoe meer dat telt, totdat je weer aan de KV-cache memory limits zit (25k context met meerdere users) en de winst afvlakt.&lt;/p&gt;
&lt;p&gt;TTFT (eerste token) is ook beter:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Context&lt;/th&gt;
&lt;th&gt;Users&lt;/th&gt;
&lt;th&gt;BF16 TTFT&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;NVFP4 TTFT&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;4k&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;4.46s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4.20s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8k&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;7.99s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;7.84s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16k&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;18.92s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;18.69s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;25k&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;35.67s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;35.65s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Op TTFT is de winst klein. Dat is logisch: prefill is compute-zwaar, en op SM12.1 zonder native FP4-tensorcores moet Marlin de gewichten on-the-fly decoderen voor de matmul. Dat kost terug wat de geheugen-bandwidth opleverde. Voor decode telt bandwidth zwaarder dan compute, voor prefill andersom.&lt;/p&gt;
&lt;p&gt;&amp;lt;/details&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;details&amp;gt;
&amp;lt;summary&amp;gt;Run B: 25k context, concurrency tot 20&amp;lt;/summary&amp;gt;&lt;/p&gt;
&lt;p&gt;De stress-test uit deel één:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Users&lt;/th&gt;
&lt;th&gt;BF16 d/u&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;NVFP4 d/u&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;BF16 TTFT&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;NVFP4 TTFT&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;8.51 t/s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;12.43 t/s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;19.86s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;19.72s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;5.37 t/s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;7.56 t/s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;35.44s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;35.51s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;3.16 t/s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4.26 t/s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;67.37s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;67.40s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Aggregate decode plateau verschuift van 32 t/s naar 36 t/s op c=20: een 12% hoger plafond bij 25k context onder maximale druk. TTFT is praktisch identiek tussen BF16 en NVFP4 omdat prefill hier de muur is en die niet veel sneller wordt op SM12.1. Decode per gebruiker is wel duidelijk beter: bij twintig parallelle 25k-prompts haal je 4.26 in plaats van 3.16 t/s, +35%. Nog steeds geen chat-snelheid, maar wel een merkbaar verschil zodra de tokens beginnen te stromen.&lt;/p&gt;
&lt;p&gt;&amp;lt;/details&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;details&amp;gt;
&amp;lt;summary&amp;gt;Run C: 1k prompt, 1k output&amp;lt;/summary&amp;gt;&lt;/p&gt;
&lt;p&gt;De korte-prompt + lange-antwoord workload, dichtbij agent-flows en code-generatie:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Users&lt;/th&gt;
&lt;th&gt;BF16 d/u&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;NVFP4 d/u&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;Winst&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;23.86&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;29.45&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;+23%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;13.59&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;24.69&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;+82%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;10.92&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;20.88&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;+91%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Bij c=10 zit per-user decode op ruim 20 t/s, boven leessnelheid en dichtbij comfortabele streaming-UI. Aggregate decode bij c=10 tikt op 209 t/s in plaats van 86 t/s in BF16, bijna een verdubbeling.&lt;/p&gt;
&lt;p&gt;&amp;lt;/details&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;details&amp;gt;
&amp;lt;summary&amp;gt;Run E: multi-turn (depth 4)&amp;lt;/summary&amp;gt;&lt;/p&gt;
&lt;p&gt;Vijf opeenvolgende beurten per gesprek, tien gesprekken parallel: de meest realistische kantoor-shape.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Users&lt;/th&gt;
&lt;th&gt;BF16 d/u&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;NVFP4 d/u&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;BF16 TTFT&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;NVFP4 TTFT&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;23.97&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;29.61&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.53s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.33s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;13.07&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;23.98&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1.32s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.11s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;10.43&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;19.51&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2.13s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.94s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Voor tien parallelle 5-turn gesprekken: 1.94 seconden tot eerste token, 19.51 t/s per gebruiker. Dat past comfortabel binnen wat een lezer als chat ervaart, en is 87% sneller per token dan BF16 in dezelfde test.&lt;/p&gt;
&lt;p&gt;&amp;lt;/details&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;details&amp;gt;
&amp;lt;summary&amp;gt;Run F: RAG-mix (8k prompt)&amp;lt;/summary&amp;gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Users&lt;/th&gt;
&lt;th&gt;BF16 d/u&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;NVFP4 d/u&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;BF16 TTFT&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;NVFP4 TTFT&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;12.11&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;20.91&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;4.32s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4.28s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;9.31&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;15.96&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;7.99s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;8.00s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;6.05&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;10.57&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;14.61s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;14.45s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;8k context is ongeveer wat een RAG-flow met vier chunks van 2k tokens binnenkrijgt. Bij tien gebruikers wacht je 8 seconden tot eerste token (vrijwel gelijk aan BF16, want compute-bottleneck), daarna 16 t/s streamen. Voor &quot;vraag iets over je documenten&quot;-flows ruim werkbaar, en waar de winst zit: in decode-snelheid, niet in TTFT.&lt;/p&gt;
&lt;p&gt;&amp;lt;/details&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;details&amp;gt;
&amp;lt;summary&amp;gt;Run G: korte instructie, 4096 outputtokens&amp;lt;/summary&amp;gt;&lt;/p&gt;
&lt;p&gt;De agent / code-generatie shape:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Users&lt;/th&gt;
&lt;th&gt;BF16 d/u&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;NVFP4 d/u&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;BF16 TTFT&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;NVFP4 TTFT&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;24.17&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;29.59&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.24s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.11s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;14.32&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;25.79&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.38s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.23s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;11.75&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;22.54&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.48s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.37s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;TTFT van 110 milliseconden bij single-user is heel laag, lager dan de meeste hosted APIs over het netwerk halen. En 22.54 t/s per user bij c=10 is ruim genoeg voor agent-streams. Aggregate decode bij c=10 in deze test komt uit op 225 t/s versus 84 t/s in BF16, bijna 2.7× zo veel. Voor een team dat tien gelijktijdige agents draait die elk lange gestructureerde output produceren is dit het belangrijkste cijfer.&lt;/p&gt;
&lt;p&gt;&amp;lt;/details&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;details&amp;gt;
&amp;lt;summary&amp;gt;Run H: open-loop, random 4k workload&amp;lt;/summary&amp;gt;&lt;/p&gt;
&lt;p&gt;De synthetische kantoor-baseline met Poisson-aankomsten:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;BF16&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;NVFP4&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Achieved RPS&lt;/td&gt;
&lt;td&gt;0.27&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.29&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Peak concurrent&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;16&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TTFT P50&lt;/td&gt;
&lt;td&gt;1286 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1006 ms&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TTFT P99&lt;/td&gt;
&lt;td&gt;3316 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2893 ms&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TPOT P50&lt;/td&gt;
&lt;td&gt;182 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;64 ms&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total tok/s&lt;/td&gt;
&lt;td&gt;1215&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1302&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Wat opvalt is dat de peak concurrent zakt van 36 naar 16 bij identieke arrival rate (0.3 rps) en identieke prompts. Doordat NVFP4 elk verzoek sneller afhandelt blijft de queue korter, en dat is een belangrijk inzicht voor capaciteits-planning: NVFP4 geeft je niet alleen lagere latency per request, maar ook minder queue-druk bij dezelfde arrival rate. Tegelijk zakt TPOT P50 van 182ms naar 64ms. Mediaan inter-token latency dus bijna drie keer sneller. Voor een chat-UI die token-streaming toont is dat het verschil tussen kunstmatig wachten op een antwoord en gewoon meelezen.&lt;/p&gt;
&lt;p&gt;&amp;lt;/details&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;details&amp;gt;
&amp;lt;summary&amp;gt;Run I: ShareGPT replay (echte gesprekken)&amp;lt;/summary&amp;gt;&lt;/p&gt;
&lt;p&gt;Echte multi-turn conversation data:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;BF16&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;NVFP4&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Peak concurrent&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TTFT P50&lt;/td&gt;
&lt;td&gt;353 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;152 ms&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TTFT P99&lt;/td&gt;
&lt;td&gt;637 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;265 ms&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TPOT P50&lt;/td&gt;
&lt;td&gt;95 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;39 ms&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;P99 TTFT van 265 milliseconden, voor 99 procent van de gebruikers. TPOT van 39 ms komt neer op 25.6 t/s per gebruiker. Dat mag je gerust realtime chat noemen voor 25 medewerkers met realistische ShareGPT-stijl prompts.&lt;/p&gt;
&lt;p&gt;&amp;lt;/details&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;details&amp;gt;
&amp;lt;summary&amp;gt;Run J: Maandagochtend-piek&amp;lt;/summary&amp;gt;&lt;/p&gt;
&lt;p&gt;Het zwaarste scenario uit deel één: overbelaste server, 1.5 rps target met max 25 gelijktijdige requests.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;BF16&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;NVFP4&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Configured RPS&lt;/td&gt;
&lt;td&gt;1.50&lt;/td&gt;
&lt;td&gt;1.50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Achieved RPS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.26&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.44&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TTFT P50&lt;/td&gt;
&lt;td&gt;1132 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;920 ms&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TTFT P99&lt;/td&gt;
&lt;td&gt;6157 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;6054 ms&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TPOT P50&lt;/td&gt;
&lt;td&gt;187 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;108 ms&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total tok/s&lt;/td&gt;
&lt;td&gt;1173&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1984&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Het meest meetbare cijfer van de hele dag is dat de achieved RPS van 0.26 naar 0.44 gaat. Hetzelfde target, dezelfde concurrency-cap, dezelfde Poisson-aankomsten, en NVFP4 verwerkt 69% meer verzoeken per seconde voordat de queue dichtslibt.&lt;/p&gt;
&lt;p&gt;P99 TTFT verschuift maar marginaal (6.16s naar 6.05s). Dat klopt met het patroon: prefill is compute-bound op SM12.1, en daar is NVFP4 niet veel sneller. Maar TPOT P50 zakt van 187ms naar 108ms, en aggregate token throughput groeit van 1173 naar 1984 t/s. Voor een 25-persoons-kantoor in piekuren is dat het verschil tussen genoeg en knel: meer requests per seconde verwerkt, met snellere streaming voor wie aan de beurt is.&lt;/p&gt;
&lt;p&gt;&amp;lt;/details&amp;gt;&lt;/p&gt;
&lt;h2&gt;Wat dit betekent voor on-prem AI&lt;/h2&gt;
&lt;p&gt;Als je een Spark hebt en Gemma-4-26B draait, dan is NVFP4 de upgrade. In alle 9 tests is NVFP4 de winnaar, en het laat 30 GB geheugen vrij voor andere doeleinden zoals meer KV-cache, een tweede klein model ernaast, of batch-jobs. Bij Kamoo staat deze NVFP4-config nu naast de BF16-baseline in &lt;code&gt;bench-spark/&lt;/code&gt;, en één commando schakelt tussen de twee.&lt;/p&gt;
&lt;p&gt;Voor een 25-persoons-kantoor met realistische ShareGPT-achtige prompts merk je het direct. TPOT P50 zakt van 95 ms naar 39 ms, P99 TTFT van 637 ms naar 265 ms. En als er piekbelasting komt, levert het systeem 69% meer verzoeken per seconde voordat het vol komt te staan. Voor agent-flows en code-generatie (Run G shape) staat de Spark in NVFP4 op zijn sterkst: tien parallelle agents, elk 4096 tokens output, 22.5 t/s per gebruiker met TTFT onder 400 ms.&lt;/p&gt;
&lt;p&gt;Voor 25k context-stress (Run B) blijft het de muur. NVFP4 verlaagt &apos;m nauwelijks (TTFT verschilt minder dan een seconde), want prefill blijft prefill, en tien parallelle 25k-prompts wachten 35 seconden op het eerste token. Daar verandert quantization niets aan op deze hardware. Wel decode-snelheid: 7.56 t/s/user in plaats van 5.37, dus zodra de tokens komen, lopen ze sneller.&lt;/p&gt;
&lt;h2&gt;Wat deze run niet zegt&lt;/h2&gt;
&lt;p&gt;Dit is geen NVFP4 op SM10.0 (datacenter Blackwell). Daar zou native FP4-compute het verschil veel groter maken, met een verwachting van een verdere 2-3× speedup bovenop wat we hier zien. Op een H100 of B200 zijn deze cijfers dus &lt;em&gt;niet&lt;/em&gt; representatief; de Spark heeft een specifieke SM12.1-handicap (geen native FP4) die in de cloud niet bestaat.&lt;/p&gt;
&lt;p&gt;Dit is ook geen vergelijking met dense Gemma-4-31B in NVFP4. Dense ondergaat een ander code-pad door vLLM&apos;s loader. Voor een vervolg-blog zou dense-NVFP4 met dezelfde testsuite een derde datapunt opleveren.&lt;/p&gt;
&lt;p&gt;En dit is geen lange-termijn accuracy-vergelijking. NVFP4 quantization heeft potentieel kleine accuracy-effecten. Voor de typische taken in een kantoor (samenvatting, ticket-classificatie, RAG) zelden merkbaar, voor edge-cases mogelijk wel.&lt;/p&gt;
&lt;p&gt;Wat NVIDIA wél heeft gepubliceerd staat in de &lt;a href=&quot;https://huggingface.co/nvidia/Gemma-4-26B-A4B-NVFP4&quot;&gt;NVFP4-model-card&lt;/a&gt;: op MMLU-Pro, GPQA-Diamond en LiveCodeBench zit NVFP4 binnen 0.2 tot 0.7 punten van hun eigen BF16-baseline.&amp;lt;Note&amp;gt;NVIDIA&apos;s eigen BF16-baseline wijkt zelf af van Google&apos;s officiële Gemma-4-card cijfers. Eval-harnesses verschillen meer dan precisie zelf, dus kruisvergelijking tussen vendors zonder identieke harness is wankel.&amp;lt;/Note&amp;gt; Dat valt binnen run-to-run-variance, geen echte degradatie. Curieus aan diezelfde tabel is dat NVIDIA&apos;s BF16-baseline weer afwijkt van wat Google in de officiële Gemma-4-card publiceert: MMLU-Pro 85.0 vs 82.6, GPQA 80.3 vs 82.3, LiveCodeBench 80.5 vs 77.1. Niet omdat quantization beter wordt dan het origineel, maar omdat eval-harness blijkbaar meer uitmaakt dan de precisie zelf. Andere prompts, andere temperature, andere stop-criteria. Kruisvergelijkingen tussen vendors zijn dus zonder dezelfde harness lastig hard te maken.&lt;/p&gt;
&lt;h2&gt;Wat blijft hangen&lt;/h2&gt;
&lt;p&gt;Decode verkoopt de benchmark, prefill bepaalt de ervaring. Dat klopte in deel één en dat klopt nog steeds. Wat NVFP4 toevoegt is dat decode in elke workload sneller wordt, en het meest waar het ertoe doet: bij grotere context en meer gebruikers tegelijk. TTFT blijft op SM12.1 grofweg gelijk omdat prefill compute-bound is en de Spark geen native FP4-tensorcores heeft. Voor wat de gebruiker voelt zodra de tokens beginnen te stromen, is NVFP4 op deze hardware fors beter dan BF16, en het kost niets aan setup-pijn: één officiële vLLM-image, één model-flag, en het draait.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Nemotron-3 op de DGX Spark: BF16 vs FP8 vs NVFP4</title>
    <id>https://djangodevreng.nl/blog/nemotron-3-dgx-spark-precisies/</id>
    <link rel="alternate" type="text/html" href="https://djangodevreng.nl/blog/nemotron-3-dgx-spark-precisies/"/>
    <published>2026-05-03T00:00:00.000Z</published>
    <updated>2026-05-05T00:00:00.000Z</updated>
    <author><name>Django de Vreng</name><uri>https://djangodevreng.nl/over/</uri></author>
    <category term="on-prem"/>
    <summary>Eén model, drie precisies, dezelfde Spark. Wat geheugen-budget, decode-snelheid en tail-latency doen wanneer je van 16 bit naar 8 bit naar 4 bit gaat.</summary>
    <content type="html">&lt;p&gt;In de vorige posts draaide ik Gemma-4 op de DGX Spark. Eerst &lt;a href=&quot;/blog/gemma-4-dgx-spark-benchmarks/&quot;&gt;alleen BF16 als baseline&lt;/a&gt;, daarna &lt;a href=&quot;/blog/gemma-4-nvfp4-vs-bf16-dgx-spark/&quot;&gt;NVFP4 vs BF16 over dezelfde test-suite&lt;/a&gt;. Dat gaf één model in twee precisies. Nuttig, maar nog geen echt beeld van de keuze die je in productie moet maken.&lt;/p&gt;
&lt;p&gt;Voor dit stuk draai ik drie varianten van hetzelfde model naast elkaar: &lt;strong&gt;BF16, FP8 en NVFP4&lt;/strong&gt; van Nemotron-3-Nano-Omni-30B-A3B-Reasoning. Zelfde Spark. Zelfde vLLM-versie. Zelfde prompts. Zelfde benchmark-suite. Zo dicht bij een eerlijke quantization-vergelijking als ik hem op deze machine kan krijgen.&lt;/p&gt;
&lt;p&gt;De korte versie: &lt;strong&gt;NVFP4 wint op snelheid en throughput, FP8 wint vaker op tail-latency, BF16 is vooral nog nuttig als baseline&lt;/strong&gt;. Dat is minder netjes dan &quot;4 bit is altijd beter&quot;. Gelukkig maar, anders was deze post kort geweest. Onderdeel van de gids &lt;a href=&quot;/dgx-spark/&quot;&gt;LLMs draaien op de DGX Spark&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Waarom dit experiment&lt;/h2&gt;
&lt;p&gt;De Gemma-post liet vooral zien dat NVFP4 op de Spark werkt. Wel met pijn. Vijf vLLM-bugs, een nightly build en genoeg flags om een command-regel eruit te laten zien als een kleine bekentenis.&lt;/p&gt;
&lt;p&gt;Maar Gemma beantwoordde niet de vraag die ik voor klanten nodig heb: wat kies je als je vandaag een lokaal model op een Spark wil draaien? BF16 omdat dat de originele weights zijn? FP8 omdat Blackwell daar native goed in is? Of NVFP4 omdat je veel meer model en KV-cache in hetzelfde geheugen krijgt?&lt;/p&gt;
&lt;p&gt;Daarom deze run. Eén model in drie precisies. Geen leaderboard-score, maar workloads die lijken op kantoorwerk: chat, RAG, langere antwoorden, meerdere gebruikers tegelijk en een maandagochtend waarop iedereen ineens denkt dat AI toch handig is.&lt;/p&gt;
&lt;h2&gt;Wat BF16, FP8 en NVFP4 hier betekenen&lt;/h2&gt;
&lt;p&gt;BF16 is de baseline: 16 bits per parameter, ongeveer 2 bytes. Voor dit model betekent dat grofweg 61,5 GB aan checkpoint-size. Dat past op de Spark, maar het eet veel van je 128 GB unified memory op voordat er ook maar één gebruiker context in de KV-cache heeft staan.&lt;/p&gt;
&lt;p&gt;FP8 halveert dat gewicht ongeveer. De checkpoint is 32,8 GB. Op Blackwell is FP8 een logische keuze: minder geheugen, native ondersteuning, en meestal weinig gedoe in vLLM.&lt;/p&gt;
&lt;p&gt;NVFP4 gaat nog verder. De checkpoint is 20,9 GB. Niet vier keer kleiner dan BF16, omdat de vision- en audio-encoders in BF16 blijven, maar klein genoeg om de Spark anders te laten voelen. Meer ruimte voor KV-cache, meer batching, meer concurrency.&lt;/p&gt;
&lt;p&gt;De nuance: de DGX Spark draait op desktop Blackwell SM12.1. Daar is NVFP4 niet hetzelfde feest als op datacenter-Blackwell. vLLM gebruikt Marlin om FP4 weights te decoderen richting FP16 tijdens compute. Je krijgt de geheugenwinst volledig. De compute-winst is minder zuiver.&lt;/p&gt;
&lt;p&gt;Voor deze post maakt dat juist interessant. Dit is geen theoretische quantization-post. Dit is: wat gebeurt er op deze machine, met deze stack, als je de drie opties echt draait?&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Precisie&lt;/th&gt;
&lt;th&gt;Model size&lt;/th&gt;
&lt;th&gt;Geheugen-budget over van 128 GB&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;BF16&lt;/td&gt;
&lt;td&gt;61.5 GB&lt;/td&gt;
&lt;td&gt;~66 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FP8&lt;/td&gt;
&lt;td&gt;32.8 GB&lt;/td&gt;
&lt;td&gt;~95 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NVFP4&lt;/td&gt;
&lt;td&gt;20.9 GB&lt;/td&gt;
&lt;td&gt;~107 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;De testopstelling&lt;/h2&gt;
&lt;p&gt;Alle runs draaien via Docker op de DGX Spark met &lt;code&gt;vllm/vllm-openai:v0.20.0&lt;/code&gt;. Officiële release, geen patches.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker run -d --name vllm-bench \
  --gpus all --ipc=host \
  -v appliance_hf-cache:/root/.cache/huggingface \
  -p 8000:8000 \
  -e HF_TOKEN=&quot;***&quot; \
  vllm/vllm-openai:v0.20.0 \
  vllm serve nvidia/Nemotron-3-Nano-Omni-30B-A3B-Reasoning-NVFP4 \
  --max-model-len 131072 \
  --gpu-memory-utilization 0.95 \
  --max-num-seqs 256 \
  --max-num-batched-tokens 8192 \
  --trust-remote-code \
  --video-pruning-rate 0.5 \
  --reasoning-parser nemotron_v3 \
  --enable-auto-tool-choice \
  --tool-call-parser qwen3_coder \
  --limit-mm-per-prompt &apos;{&quot;image&quot;:0,&quot;audio&quot;:0}&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Voor FP8 gebruik ik hetzelfde profiel met &lt;code&gt;--kv-cache-dtype fp8&lt;/code&gt;. BF16 draait zonder die KV-cache-flag. Verder blijft de test gelijk.&lt;/p&gt;
&lt;p&gt;De benchmark-suite staat beschreven op de &lt;a href=&quot;/arena/methodologie/&quot;&gt;arena-methodologie&lt;/a&gt;. Kort gezegd: closed-loop tests voor decode en TTFT per gebruiker, plus open-loop tests met Poisson-aankomsten om te zien hoe de server zich gedraagt als requests niet netjes op elkaar wachten.&lt;/p&gt;
&lt;h2&gt;Setup&lt;/h2&gt;
&lt;p&gt;Ik begon verkeerd met &lt;code&gt;nvcr.io/nvidia/vllm:26.02-py3&lt;/code&gt;, NVIDIA&apos;s eigen vLLM-container. Die had vLLM 0.15.1 en kende de &lt;code&gt;NemotronH_Nano_Omni_Reasoning_V3&lt;/code&gt; architectuur nog niet.&lt;/p&gt;
&lt;p&gt;De oplossing was saaier: &lt;code&gt;vllm/vllm-openai:v0.20.0&lt;/code&gt;. Officiële release, juiste flashinfer-versies, eerste run werkend.&lt;/p&gt;
&lt;p&gt;Onze eigen &lt;code&gt;bench-spark&lt;/code&gt; CLI had nog twee kleine fixes nodig: de NVIDIA-entrypoint omzeilen met &lt;code&gt;--entrypoint vllm&lt;/code&gt;, en &lt;code&gt;HF_TOKEN&lt;/code&gt; automatisch doorgeven aan de container. Daarna liep de suite.&lt;/p&gt;
&lt;p&gt;Les: begin met de stable release die de architectuur ondersteunt.&lt;/p&gt;
&lt;p&gt;&amp;lt;details&amp;gt;
&amp;lt;summary&amp;gt;Run A: context-scaling&amp;lt;/summary&amp;gt;&lt;/p&gt;
&lt;p&gt;Deze run is de basis: wat gebeurt er als de prompt langer wordt, terwijl het aantal gebruikers oploopt van één naar tien? Dat raakt direct aan kantoorwerk. Een korte chat is makkelijk. Een RAG-vraag met 25k context en meerdere mensen tegelijk is waar de Spark laat zien hoeveel ruimte er echt over is.&lt;/p&gt;
&lt;p&gt;Hier kijk ik naar twee dingen. Eerst decode per gebruiker: hoe snel komt tekst terug zodra de generatie loopt? Daarna TTFT: hoe lang wacht je op het eerste token? Bij lange context is TTFT vaak de pijn die gebruikers als eerste voelen. Ze zien geen tokens, dus het voelt alsof het systeem vastzit.&lt;/p&gt;
&lt;p&gt;Single-user is vooral een pure snelheidsmeting. Daar verdubbelt NVFP4 bijna BF16. Bij tien gebruikers wordt het interessanter: de kleinere weights geven vLLM meer ruimte om te batchen, en dan wordt BF16 gewoon zwaar.&lt;/p&gt;
&lt;h3&gt;Decode/user (tg256), c=1&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Context&lt;/th&gt;
&lt;th&gt;BF16&lt;/th&gt;
&lt;th&gt;FP8&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;NVFP4&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;NVFP4 vs BF16&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;4k&lt;/td&gt;
&lt;td&gt;29.23&lt;/td&gt;
&lt;td&gt;51.68&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;60.30&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+106%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8k&lt;/td&gt;
&lt;td&gt;28.59&lt;/td&gt;
&lt;td&gt;49.82&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;55.72&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;+95%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16k&lt;/td&gt;
&lt;td&gt;28.24&lt;/td&gt;
&lt;td&gt;47.52&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;55.24&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;+96%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;25k&lt;/td&gt;
&lt;td&gt;28.24&lt;/td&gt;
&lt;td&gt;48.85&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;54.98&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;+95%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;BF16 blijft netjes vlak rond 28-29 tokens per seconde. Dat is stabiel, maar niet snel. FP8 zet daar ongeveer 50 t/s tegenover. NVFP4 zit rond 55-60 t/s. Voor één gebruiker is dat het verschil tussen &quot;prima&quot; en &quot;dit voelt lokaal maar niet lokaal-traag&quot;.&lt;/p&gt;
&lt;h3&gt;Decode/user (tg256), c=10&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Context&lt;/th&gt;
&lt;th&gt;BF16&lt;/th&gt;
&lt;th&gt;FP8&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;NVFP4&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;NVFP4 vs BF16&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;4k&lt;/td&gt;
&lt;td&gt;7.76&lt;/td&gt;
&lt;td&gt;13.45&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;19.69&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+154%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8k&lt;/td&gt;
&lt;td&gt;7.13&lt;/td&gt;
&lt;td&gt;11.14&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;17.90&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;+151%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16k&lt;/td&gt;
&lt;td&gt;6.30&lt;/td&gt;
&lt;td&gt;10.73&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;14.99&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;+138%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;25k&lt;/td&gt;
&lt;td&gt;5.56&lt;/td&gt;
&lt;td&gt;8.59&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;12.99&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;+134%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Bij tien gebruikers is NVFP4 niet &quot;wat sneller&quot;. Het is een andere klasse. Op 25k context doet BF16 5,56 tok/s/user. NVFP4 doet 12,99. Dat is nog steeds geen cloud-GPU-cluster, maar het verschil in gevoel is groot: BF16 wordt wachten, NVFP4 blijft werken.&lt;/p&gt;
&lt;h3&gt;TTFT (eerste token), c=10&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Context&lt;/th&gt;
&lt;th&gt;BF16&lt;/th&gt;
&lt;th&gt;FP8&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;NVFP4&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;4k&lt;/td&gt;
&lt;td&gt;3.90s&lt;/td&gt;
&lt;td&gt;2.91s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2.45s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8k&lt;/td&gt;
&lt;td&gt;6.49s&lt;/td&gt;
&lt;td&gt;5.93s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4.03s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16k&lt;/td&gt;
&lt;td&gt;12.63s&lt;/td&gt;
&lt;td&gt;10.55s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;8.01s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;25k&lt;/td&gt;
&lt;td&gt;19.82s&lt;/td&gt;
&lt;td&gt;16.89s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;12.71s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Dit is de tabel die ik voor echte gebruikers het meest serieus neem. Bij 25k context en tien gebruikers wacht je met BF16 bijna 20 seconden op het eerste token. Met NVFP4 is dat 12,7 seconden. Nog steeds lang, maar niet hetzelfde soort lang.&lt;/p&gt;
&lt;p&gt;&amp;lt;/details&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;details&amp;gt;
&amp;lt;summary&amp;gt;Run B: 25k context, concurrency tot 20&amp;lt;/summary&amp;gt;&lt;/p&gt;
&lt;p&gt;Run A laat zien hoe contextlengte schaalt. Run B houdt de context zwaar en verhoogt alleen de concurrency. Dit is de &quot;iedereen stelt tegelijk een grote vraag&quot;-test.&lt;/p&gt;
&lt;p&gt;In de praktijk gebeurt dit niet elk uur. Tien tot twintig mensen klikken zelden exact tegelijk met 25k context op verzenden. Maar als je een lokale AI-machine voor een team neerzet, wil je weten hoe hij faalt. Rustig langzamer worden is acceptabel. Een queue die voelt alsof hij dood is, niet.&lt;/p&gt;
&lt;p&gt;NVFP4 houdt hier de meeste lucht. Niet omdat het model slimmer wordt, maar omdat de server met kleinere weights meer ruimte heeft voor batching en KV-cache.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Gebruikers&lt;/th&gt;
&lt;th&gt;BF16 d/u&lt;/th&gt;
&lt;th&gt;FP8 d/u&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;NVFP4 d/u&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;NVFP4 vs BF16&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;9.06&lt;/td&gt;
&lt;td&gt;15.33&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;20.75&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;+129%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;5.65&lt;/td&gt;
&lt;td&gt;9.18&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;12.99&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;+130%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;3.70&lt;/td&gt;
&lt;td&gt;5.97&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;7.79&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;+110%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Gebruikers&lt;/th&gt;
&lt;th&gt;BF16 TTFT&lt;/th&gt;
&lt;th&gt;FP8 TTFT&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;NVFP4 TTFT&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;11.01s&lt;/td&gt;
&lt;td&gt;8.89s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;7.21s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;19.75s&lt;/td&gt;
&lt;td&gt;15.82s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;12.74s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;37.88s&lt;/td&gt;
&lt;td&gt;29.91s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;24.08s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Twintig gebruikers met 25k context is expres onaardig. Toch is het nuttig. BF16 zit op 37,88 seconden TTFT. Dat voelt stuk. NVFP4 zit op 24,08 seconden. Ook niet gezellig, maar nog steeds ruim dertien seconden sneller.&lt;/p&gt;
&lt;p&gt;Aggregate decode laat hetzelfde beeld zien:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Gebruikers&lt;/th&gt;
&lt;th&gt;BF16&lt;/th&gt;
&lt;th&gt;FP8&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;NVFP4&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;34 t/s&lt;/td&gt;
&lt;td&gt;53 t/s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;71 t/s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;38 t/s&lt;/td&gt;
&lt;td&gt;59 t/s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;77 t/s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;44 t/s&lt;/td&gt;
&lt;td&gt;66 t/s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;84 t/s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Het plafond verschuift van 44 t/s naar 84 t/s. Voor een enkele gebruiker is dat abstract. Voor een team betekent het dat de queue sneller leegloopt.&lt;/p&gt;
&lt;p&gt;&amp;lt;/details&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;details&amp;gt;
&amp;lt;summary&amp;gt;Run C: korte prompt, lange output&amp;lt;/summary&amp;gt;&lt;/p&gt;
&lt;p&gt;Dit is de workload voor agents, code-generatie en langere antwoorden: weinig input, veel output. De prompt is maar 1024 tokens, dus prefill is hier niet het probleem. De vraag is vooral hoe snel het model blijft doortikken zodra de output lang wordt.&lt;/p&gt;
&lt;p&gt;Daarom kijk ik hier naar decode per gebruiker. TTFT moet laag blijven, maar het echte verschil voel je pas na een paar honderd tokens. Een model dat snel begint maar daarna op 8 tok/s blijft hangen, voelt alsnog traag.&lt;/p&gt;
&lt;p&gt;NVFP4 wint hier duidelijk. Bij tien parallelle gebruikers blijft het model op 22,90 tok/s/user zitten. BF16 zakt naar 7,84. Dat is nog leesbaar, maar voor een agent-flow voelt het alsof iemand met de hand meetypt.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Gebruikers&lt;/th&gt;
&lt;th&gt;BF16 d/u&lt;/th&gt;
&lt;th&gt;FP8 d/u&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;NVFP4 d/u&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;28.65&lt;/td&gt;
&lt;td&gt;49.85&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;55.55&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;12.19&lt;/td&gt;
&lt;td&gt;21.32&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;30.97&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;7.84&lt;/td&gt;
&lt;td&gt;15.26&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;22.90&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Voor deze workload is NVFP4 de logische default. FP8 is netjes, maar je levert hier vooral snelheid in zonder dat tail-latency de hoofdrol speelt.&lt;/p&gt;
&lt;p&gt;&amp;lt;/details&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;details&amp;gt;
&amp;lt;summary&amp;gt;Run E: multi-turn, depth 4&amp;lt;/summary&amp;gt;&lt;/p&gt;
&lt;p&gt;Multi-turn is dichter bij echt gebruik dan één losse prompt. Vijf beurten per gesprek, meerdere gesprekken parallel. Dat lijkt op een medewerker die niet één vraag stelt, maar doorvraagt, corrigeert en context meeneemt.&lt;/p&gt;
&lt;p&gt;Hier wil ik niet alleen hoge throughput zien. Ik wil vooral dat de server niet elke beurt opnieuw voelt alsof hij uit een koude start komt. Bij tien gesprekken tegelijk wordt dat relevant: de context groeit per gesprek, de scheduler moet blijven delen, en de gebruiker verwacht dat de chat blijft lopen.&lt;/p&gt;
&lt;p&gt;Dit is voor mij de belangrijkste kantoor-run. Niet omdat hij perfect echt is, maar omdat hij het dichtst in de buurt komt van &quot;25 mensen gebruiken dit verspreid over de dag&quot;.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Gebruikers&lt;/th&gt;
&lt;th&gt;BF16 d/u&lt;/th&gt;
&lt;th&gt;FP8 d/u&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;NVFP4 d/u&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;NVFP4 TTFT&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;28.69&lt;/td&gt;
&lt;td&gt;49.72&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;56.18&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;596 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;11.50&lt;/td&gt;
&lt;td&gt;20.87&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;30.55&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1032 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;7.68&lt;/td&gt;
&lt;td&gt;14.88&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;21.58&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1359 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Bij tien parallelle gesprekken zit NVFP4 op 21,58 tok/s/user. FP8 zit op 14,88. BF16 op 7,68. Dat laatste werkt technisch, maar het voelt niet meer als een vlotte chat. NVFP4 blijft ruim boven de grens waar je antwoord als vloeiend ervaart.&lt;/p&gt;
&lt;p&gt;&amp;lt;/details&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;details&amp;gt;
&amp;lt;summary&amp;gt;Run F: RAG-mix met 8k prompt&amp;lt;/summary&amp;gt;&lt;/p&gt;
&lt;p&gt;RAG is meestal geen 25k context, maar ook geen korte chat. Deze run gebruikt 8k prompt en 512 outputtokens. Denk aan vier chunks van ongeveer 2k tokens, plus vraag en instructie.&lt;/p&gt;
&lt;p&gt;Bij RAG telt prefill meer dan bij Run C. Je stopt elke keer een flinke lap context in het model voordat er iets terugkomt. Daarna wil je genoeg decode overhouden om het antwoord bruikbaar snel te maken.&lt;/p&gt;
&lt;p&gt;De vraag is dus: blijft quantization helpen als de prompt zwaarder wordt? Ja. NVFP4 blijft duidelijk voor, ook bij twintig gebruikers.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Gebruikers&lt;/th&gt;
&lt;th&gt;BF16 d/u&lt;/th&gt;
&lt;th&gt;FP8 d/u&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;NVFP4 d/u&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;12.50&lt;/td&gt;
&lt;td&gt;21.02&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;27.77&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;8.11&lt;/td&gt;
&lt;td&gt;14.37&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;19.65&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;5.51&lt;/td&gt;
&lt;td&gt;9.82&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;14.09&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Bij twintig gebruikers levert NVFP4 14,09 tok/s/user. BF16 zit op 5,51. Voor batch-processing kan dat nog. Voor real-time RAG op een kantoor voelt BF16 krap, zeker als documenten rommelig zijn en prompts langer worden dan je had gehoopt. Dat worden ze altijd.&lt;/p&gt;
&lt;p&gt;&amp;lt;/details&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;details&amp;gt;
&amp;lt;summary&amp;gt;Run G: korte instructie, 4096 outputtokens&amp;lt;/summary&amp;gt;&lt;/p&gt;
&lt;p&gt;Run G lijkt op Run C, maar trekt de output veel verder door: 4096 tokens. Dit is de shape van agents die plannen uitschrijven, code genereren, lange analyses maken of meerdere bestanden samenvatten.&lt;/p&gt;
&lt;p&gt;Bij dit soort workloads is de eerste token bijna bijzaak. Als het antwoord lang is, bepaalt decode-snelheid de ervaring. Tien seconden verschil aan het begin is vervelend. Minutenlang op output wachten is erger.&lt;/p&gt;
&lt;p&gt;NVFP4 blijft hier het sterkst. Belangrijker: het blijft ook bij tien gebruikers boven 25 tok/s/user. Dat is voor lokale hardware op een bureau-machine gewoon bruikbaar.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Gebruikers&lt;/th&gt;
&lt;th&gt;BF16 d/u&lt;/th&gt;
&lt;th&gt;FP8 d/u&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;NVFP4 d/u&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;NVFP4 TTFT&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;28.68&lt;/td&gt;
&lt;td&gt;49.75&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;55.44&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;179 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;14.32&lt;/td&gt;
&lt;td&gt;25.56&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;34.63&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;427 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;9.51&lt;/td&gt;
&lt;td&gt;18.40&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;25.18&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;363 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Voor agent-flows is dit vrij hard: BF16 is niet stuk, maar je betaalt elke lange output dubbel. Eerst in geheugen, daarna in wachttijd.&lt;/p&gt;
&lt;p&gt;&amp;lt;/details&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;details&amp;gt;
&amp;lt;summary&amp;gt;Run H: open-loop kantoor-baseline&amp;lt;/summary&amp;gt;&lt;/p&gt;
&lt;p&gt;Vanaf hier verandert de interpretatie. De vorige runs sturen gecontroleerde batches door het model. Run H gebruikt open-loop traffic: requests komen binnen volgens een Poisson-verdeling. De server moet dus omgaan met aankomsten die niet netjes wachten tot de vorige klaar is.&lt;/p&gt;
&lt;p&gt;Dit lijkt meer op een kantoor. Niet perfect, wel beter dan iedereen tegelijk of juist volledig sequentieel. De metrics zijn ook anders. TPOT vertelt hoe snel tokens komen zodra je aan de beurt bent. TTFT P50 vertelt de normale ervaring. TTFT P99 vertelt wat de pechvogel merkt.&lt;/p&gt;
&lt;p&gt;Hier wordt FP8 interessant. NVFP4 wint de mediaan en TPOT, maar FP8 wint de tail. Dat is precies waarom ik niet wil eindigen met &quot;NVFP4 is altijd beter&quot;.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;BF16&lt;/th&gt;
&lt;th&gt;FP8&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;NVFP4&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Achieved RPS&lt;/td&gt;
&lt;td&gt;0.26&lt;/td&gt;
&lt;td&gt;0.28&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.29&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Peak concurrent&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;42&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TTFT P50&lt;/td&gt;
&lt;td&gt;1229 ms&lt;/td&gt;
&lt;td&gt;732 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;618 ms&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TTFT P99&lt;/td&gt;
&lt;td&gt;2996 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2008 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;3235 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TPOT P50&lt;/td&gt;
&lt;td&gt;203 ms&lt;/td&gt;
&lt;td&gt;74 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;39 ms&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Aggregate tok/s&lt;/td&gt;
&lt;td&gt;1203&lt;/td&gt;
&lt;td&gt;1297&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1329&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Die peak concurrent van BF16 lijkt op papier goed, maar is het niet. De queue loopt op omdat BF16 hem minder snel leeg krijgt. NVFP4 verwerkt sneller, dus er staan minder requests tegelijk open. Dat is geen lagere capaciteit, dat is minder file.&lt;/p&gt;
&lt;p&gt;De echte keuze zit tussen NVFP4 en FP8. Wil je de beste mediaan en snelste output, dan NVFP4. Wil je de netste P99 op deze workload, dan FP8.&lt;/p&gt;
&lt;p&gt;&amp;lt;/details&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;details&amp;gt;
&amp;lt;summary&amp;gt;Run I: ShareGPT replay&amp;lt;/summary&amp;gt;&lt;/p&gt;
&lt;p&gt;ShareGPT replay is rommeliger en daardoor nuttig. Echte gesprekken hebben wisselende lengtes, vervolgvragen, korte antwoorden, lange antwoorden en prompts die niet door een benchmark-auteur netjes zijn gladgestreken.&lt;/p&gt;
&lt;p&gt;Dit is de run die ik het meest vertrouw voor chatgevoel. Niet voor bedrijfsdocumenten, wel voor de vraag: hoe voelt dit als meerdere mensen door de dag heen gesprekken voeren?&lt;/p&gt;
&lt;p&gt;Het patroon uit Run H blijft staan. NVFP4 is het snelst voor de doorsnee gebruiker. FP8 heeft de betere P99.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;BF16&lt;/th&gt;
&lt;th&gt;FP8&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;NVFP4&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Peak concurrent&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TTFT P50&lt;/td&gt;
&lt;td&gt;433 ms&lt;/td&gt;
&lt;td&gt;220 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;157 ms&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TTFT P99&lt;/td&gt;
&lt;td&gt;713 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;422 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1361 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TPOT P50&lt;/td&gt;
&lt;td&gt;118 ms&lt;/td&gt;
&lt;td&gt;38 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;26 ms&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;NVFP4 voelt instant voor de meeste gebruikers: 157 ms TTFT P50 en 26 ms TPOT P50. Maar de P99 is 1361 ms, waar FP8 op 422 ms blijft. Dat is een fors verschil.&lt;/p&gt;
&lt;p&gt;Voor een interne chat waar een enkele tragere request geen ramp is, kies ik NVFP4. Voor een product-UI met harde latency-belofte zou ik FP8 serieuzer nemen.&lt;/p&gt;
&lt;p&gt;&amp;lt;/details&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;details&amp;gt;
&amp;lt;summary&amp;gt;Run J: maandagochtend-piek&amp;lt;/summary&amp;gt;&lt;/p&gt;
&lt;p&gt;Run J is oversubscribe. Het target is 1,5 requests per seconde met een concurrency-cap van 25. Dit is niet de normale werkdag. Dit is de test voor wat er gebeurt als de vraag groter is dan de server netjes kan bijhouden.&lt;/p&gt;
&lt;p&gt;Bij oversubscribe kijk ik eerst naar achieved RPS. Niet naar configured RPS, want die is voor iedereen hetzelfde. De vraag is hoeveel requests de server daadwerkelijk verwerkt terwijl hij onder druk staat.&lt;/p&gt;
&lt;p&gt;Daar wint NVFP4 duidelijk. FP8 houdt de tail netter, maar NVFP4 krijgt veel meer werk door de machine.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;BF16&lt;/th&gt;
&lt;th&gt;FP8&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;NVFP4&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Configured RPS&lt;/td&gt;
&lt;td&gt;1.50&lt;/td&gt;
&lt;td&gt;1.50&lt;/td&gt;
&lt;td&gt;1.50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Achieved RPS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.25&lt;/td&gt;
&lt;td&gt;0.43&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.58&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Peak concurrent&lt;/td&gt;
&lt;td&gt;28&lt;/td&gt;
&lt;td&gt;28&lt;/td&gt;
&lt;td&gt;28&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TTFT P50&lt;/td&gt;
&lt;td&gt;1130 ms&lt;/td&gt;
&lt;td&gt;757 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;687 ms&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TTFT P99&lt;/td&gt;
&lt;td&gt;5184 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3388 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;4462 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TPOT P50&lt;/td&gt;
&lt;td&gt;197 ms&lt;/td&gt;
&lt;td&gt;112 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;82 ms&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Aggregate tok/s&lt;/td&gt;
&lt;td&gt;1118&lt;/td&gt;
&lt;td&gt;1951&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2622&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Concreet: NVFP4 verwerkt ongeveer 35 requests per minuut. BF16 ongeveer 15. Dat is het verschil tussen een queue die langzaam leegloopt en een queue die gebruikers aan het twijfelen brengt of ze nog een keer moeten klikken. Niet klikken. Nooit helpen die tweede klikken.&lt;/p&gt;
&lt;p&gt;&amp;lt;/details&amp;gt;&lt;/p&gt;
&lt;h2&gt;De drie precisies naast elkaar&lt;/h2&gt;
&lt;p&gt;Als ik één realistische chat-run moet kiezen, pak ik ShareGPT replay. Daar zie je het onderscheid het schoonst: NVFP4 wint de normale ervaring, FP8 wint de tail, BF16 doet mee maar nergens overtuigend.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;BF16&lt;/th&gt;
&lt;th&gt;FP8&lt;/th&gt;
&lt;th&gt;NVFP4&lt;/th&gt;
&lt;th&gt;Beste keuze&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;TPOT P50&lt;/td&gt;
&lt;td&gt;118 ms&lt;/td&gt;
&lt;td&gt;38 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;26 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;NVFP4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TTFT P50&lt;/td&gt;
&lt;td&gt;433 ms&lt;/td&gt;
&lt;td&gt;220 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;157 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;NVFP4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TTFT P99&lt;/td&gt;
&lt;td&gt;713 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;422 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1361 ms&lt;/td&gt;
&lt;td&gt;FP8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Peak concurrent&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;NVFP4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Achieved RPS&lt;/td&gt;
&lt;td&gt;0.30&lt;/td&gt;
&lt;td&gt;0.30&lt;/td&gt;
&lt;td&gt;0.30&lt;/td&gt;
&lt;td&gt;gelijk&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Bij oversubscribe wordt het verschil harder:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;BF16&lt;/th&gt;
&lt;th&gt;FP8&lt;/th&gt;
&lt;th&gt;NVFP4&lt;/th&gt;
&lt;th&gt;Beste keuze&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Achieved RPS&lt;/td&gt;
&lt;td&gt;0.25&lt;/td&gt;
&lt;td&gt;0.43&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.58&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;NVFP4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TTFT P50&lt;/td&gt;
&lt;td&gt;1130 ms&lt;/td&gt;
&lt;td&gt;757 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;687 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;NVFP4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TTFT P99&lt;/td&gt;
&lt;td&gt;5184 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3388 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;4462 ms&lt;/td&gt;
&lt;td&gt;FP8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TPOT P50&lt;/td&gt;
&lt;td&gt;197 ms&lt;/td&gt;
&lt;td&gt;112 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;82 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;NVFP4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Aggregate tok/s&lt;/td&gt;
&lt;td&gt;1118&lt;/td&gt;
&lt;td&gt;1951&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2622&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;NVFP4&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Dat maakt de keuze praktischer dan ik vooraf dacht. NVFP4 is de default als je throughput en normale gebruikerservaring wil. FP8 is de keuze als je P99 belangrijker vindt dan mediaan. BF16 is de baseline waarmee je checkt of quantization je accuracy sloopt.&lt;/p&gt;
&lt;h2&gt;Waarom FP8 de P99 wint&lt;/h2&gt;
&lt;p&gt;Mijn hypothese: NVFP4 geeft vLLM meer geheugenruimte en daarmee meer batchingruimte. Dat verhoogt throughput en verlaagt TPOT, maar individuele requests kunnen soms langer wachten voordat ze netjes in een batch vallen.&lt;/p&gt;
&lt;p&gt;FP8 heeft minder headroom dan NVFP4, maar nog genoeg voor deze workload. Daardoor lijkt de scheduler voorspelbaarder. Minder agressief, minder snel in mediaan, beter in de tail.&lt;/p&gt;
&lt;p&gt;BF16 heeft het slechtste van beide werelden: grote weights, minder KV-cache-headroom en lagere decode. De queue wordt voller, maar niet omdat de server zo veel tegelijk aankan. Hij komt er gewoon minder snel doorheen.&lt;/p&gt;
&lt;p&gt;Dit wil ik nog verder uitzoeken met scheduler-instellingen en prefix caching. De ruwe cijfers en de testdefinities staan in de &lt;a href=&quot;/arena/&quot;&gt;arena&lt;/a&gt; zodat ik toekomstige runs naast dezelfde lat kan leggen.&lt;/p&gt;
&lt;h2&gt;Vergelijking met Gemma-4-26B-A4B&lt;/h2&gt;
&lt;p&gt;Nemotron-NVFP4 is single-user bijna twee keer sneller dan Gemma-NVFP4. Bij multi-user wordt het verschil kleiner, maar blijft het meestal positief.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Workload&lt;/th&gt;
&lt;th&gt;Gemma-NVFP4 d/u&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Nemotron-NVFP4 d/u&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;Ratio&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;pp4096 c=1&lt;/td&gt;
&lt;td&gt;30.01&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;60.30&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2.0×&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pp8192 c=1&lt;/td&gt;
&lt;td&gt;29.35&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;55.72&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1.9×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pp25000 c=1&lt;/td&gt;
&lt;td&gt;28.00&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;54.98&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2.0×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pp4096 c=10&lt;/td&gt;
&lt;td&gt;17.05&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;19.69&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1.2×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pp25000 c=10&lt;/td&gt;
&lt;td&gt;7.61&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;12.99&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1.7×&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Dat patroon klopt bij wat het model is. Nemotron heeft 3B active params, Gemma 4B active params. Bij single-user helpt dat hard. Bij multi-user schuift de bottleneck richting geheugen-bandwidth en scheduling, en dan wordt het verschil kleiner.&lt;/p&gt;
&lt;h2&gt;Wat dit betekent voor on-prem AI&lt;/h2&gt;
&lt;p&gt;Mijn default keuze voor deze Spark is &lt;strong&gt;NVFP4&lt;/strong&gt;. Niet omdat 4 bit principieel mooier is, maar omdat de cijfers bij deze workloads het dragen: hoogste throughput, snelste mediaan, laagste TPOT, kleinste footprint.&lt;/p&gt;
&lt;p&gt;Ik kies &lt;strong&gt;FP8&lt;/strong&gt; wanneer tail-latency belangrijker is dan mediaan. Denk aan een UI waar je wil kunnen zeggen dat 99 procent van de requests binnen een bepaalde grens start. In Run H, I en J wint FP8 consequent op P99 TTFT.&lt;/p&gt;
&lt;p&gt;Ik kies &lt;strong&gt;BF16&lt;/strong&gt; alleen nog als baseline of voor accuracy-kritische validatie. Niet als productie-default. Daarvoor is het op de Spark te duur: ongeveer drie keer zoveel geheugen als NVFP4 en grofweg de helft van de snelheid.&lt;/p&gt;
&lt;p&gt;Voor een 25-persoons-kantoor met chat- en RAG-achtige workload zou ik NVFP4 draaien, met een eigen eval-suite ernaast. Voor een externe chatbot met strakke latency-belofte zou ik FP8 testen. Voor BF16 zou ik vooral een korte run bewaren om te zien wat quantization inhoudelijk verandert.&lt;/p&gt;
&lt;h2&gt;Wat deze runs niet zeggen&lt;/h2&gt;
&lt;p&gt;Geen accuracy-tests. FP8 en NVFP4 kunnen inhoudelijk afwijken van BF16. Voor productie moet je dat meten op je eigen documenten, je eigen prompts en je eigen fouttolerantie.&lt;/p&gt;
&lt;p&gt;Geen multimodal-benchmarks. Nemotron-3-Nano-Omni is multimodal-aware, maar deze runs zijn text-only. Vision en audio blijven hier buiten beeld.&lt;/p&gt;
&lt;p&gt;Geen vergelijking met dense modellen. Dit is een MoE-model. Dense modellen voelen anders, vooral bij output-snelheid en hoe vLLM ermee omgaat.&lt;/p&gt;
&lt;p&gt;Geen definitieve scheduler-conclusie. De FP8-vs-NVFP4-tail is interessant genoeg om apart te testen met andere batching- en scheduling-instellingen.&lt;/p&gt;
&lt;h2&gt;Waar ik land&lt;/h2&gt;
&lt;p&gt;De precisie-keuze is geen detail. Op de Spark bepaalt hij of dezelfde machine voelt als een lokaal experiment of als iets dat je aan collega&apos;s kunt geven zonder elke vijf minuten uitleg te moeten geven.&lt;/p&gt;
&lt;p&gt;NVFP4 verdubbelt in veel runs de bruikbare ervaring ten opzichte van BF16. FP8 is minder spectaculair, maar voorspelbaarder in de tail. BF16 blijft nuttig als referentiepunt, niet als eindstation.&lt;/p&gt;
&lt;p&gt;De praktische les uit deze drie posts samen: volg de vendor recipes, draai de stable image en meet je eigen workload. Niet zelf knutselen tenzij je daar een goede reden voor hebt. Ik had bij Gemma een reden. Achteraf was hij middelmatig.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Gemma-4 op de DGX Spark: de prijs van context</title>
    <id>https://djangodevreng.nl/blog/gemma-4-dgx-spark-benchmarks/</id>
    <link rel="alternate" type="text/html" href="https://djangodevreng.nl/blog/gemma-4-dgx-spark-benchmarks/"/>
    <published>2026-05-01T00:00:00.000Z</published>
    <updated>2026-05-02T00:00:00.000Z</updated>
    <author><name>Django de Vreng</name><uri>https://djangodevreng.nl/over/</uri></author>
    <category term="on-prem"/>
    <summary>Negen benchmarks van Gemma-4-26B-A4B-it op de DGX Spark met llama-benchy en vLLM. Decode houdt stand; prefill en queueing bepalen het gevoel.</summary>
    <content type="html">&lt;p&gt;Ik wilde weten hoe goed een DGX Spark zich houdt als lokale AI-machine voor een kantooromgeving.&lt;/p&gt;
&lt;p&gt;Niet theoretisch. Gewoon: Gemma-4-26B-A4B-it laden in vLLM, llama-benchy ertegenaan, context windows groter maken, output langer, concurrency omhoog, multi-turn erbij, en kijken waar het prettig blijft en waar de wachttijd pijn gaat doen. En toen dat verhaal zich begon af te tekenen kwam er een tweede vraag: wat als ik niet meer in lockstep test, maar verzoeken organisch laat aankomen zoals in een echt kantoor? Daarvoor pakte ik vLLM&apos;s eigen benchmark-suite erbij, die wel doet wat llama-benchy niet doet: Poisson-aankomsten, percentielen, echte conversation-data. Hoe ik dit allemaal meet staat in de &lt;a href=&quot;/arena/methodologie/&quot;&gt;methodologie&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;De korte versie: voor normaal kantoorgebruik ziet dit er goed uit. Korte tot middelgrote prompts, langere outputs, en zelfs gesprekken over meerdere beurten blijven snel aanvoelen, ook met tien gebruikers tegelijk. Bij grote context windows wordt niet tokens per seconde het probleem, maar hoe lang iemand naar een leeg chatvenster kijkt voordat de eerste token komt. En als je de machine echt overbelast, schaalt &apos;ie niet, hij queue&apos;t.&lt;/p&gt;
&lt;p&gt;Dat maakt dit geen &quot;kan de DGX Spark het wel of niet&quot;-verhaal. Het maakt het een workload-verhaal. Negen tests, twee methodes, één machine. Het is een van de build-logs onder de gids &lt;a href=&quot;/dgx-spark/&quot;&gt;LLMs draaien op de DGX Spark&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Waarom deze test&lt;/h2&gt;
&lt;p&gt;Bij on-prem AI praat je al snel over privacy, data dichterbij houden en minder afhankelijk zijn van hosted modellen. Dat klopt allemaal, maar uiteindelijk komt er een plattere vraag achteraan.&lt;/p&gt;
&lt;p&gt;Kan de machine het aan?&lt;/p&gt;
&lt;p&gt;Een lokaal model dat één demo-prompt netjes beantwoordt is leuk. Maar productie lijkt daar zelden op. Daar heb je meerdere gebruikers, grotere context, agent-flows, tool-calls, retries en soms iemand die een halve roman in een ticket plakt.&lt;/p&gt;
&lt;p&gt;Daarom wilde ik niet alleen tokens per seconde meten bij één prompt. Ik wilde zien wat er gebeurt als je de machine vanuit verschillende hoeken belast: van &quot;tien gebruikers, korte prompts, lange antwoorden&quot; tot &quot;tien gebruikers, gesprekken van vijf beurten, groeiend geheugen&quot; tot &quot;verzoeken die organisch aankomen zoals in een echt kantoor, niet allemaal tegelijk en niet allemaal hetzelfde formaat&quot;.&lt;/p&gt;
&lt;p&gt;Voor deze benchmarks testte ik één model:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;google/gemma-4-26B-A4B-it&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;BF16&lt;/li&gt;
&lt;li&gt;DGX Spark, NVIDIA GB10, 128 GB unified memory&lt;/li&gt;
&lt;li&gt;vLLM als OpenAI-compatible endpoint&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Dense komt later. MoE vs dense ook. Dit stuk gaat alleen over Gemma-4-26B-A4B-it op de DGX Spark. Deze run draait op BF16; wat er met dezelfde Gemma-4 gebeurt als je naar &lt;a href=&quot;/blog/gemma-4-nvfp4-vs-bf16-dgx-spark/&quot;&gt;NVFP4 quantiseert&lt;/a&gt; is een apart verhaal.&lt;/p&gt;
&lt;h2&gt;De verwachting vooraf&lt;/h2&gt;
&lt;p&gt;Mijn verwachting was simpel: MoE zou redelijk goed blijven bij concurrent requests, maar ik dacht dat de DGX Spark sneller tegen zijn grenzen zou lopen zodra de context groot werd.&lt;/p&gt;
&lt;p&gt;Vooral bij 25k context.&lt;/p&gt;
&lt;p&gt;Context is duur. Je betaalt niet alleen voor de prompt die binnenkomt, maar ook voor de KV-cache die vLLM moet bijhouden. Als je dat vermenigvuldigt met meerdere gebruikers, wordt het ineens een geheugenvraagstuk én een wachtrijvraagstuk.&lt;/p&gt;
&lt;p&gt;Ik was benieuwd naar vijf dingen:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;blijft decode nog bruikbaar als context groeit?&lt;/li&gt;
&lt;li&gt;hoeveel doet prefill met de tijd tot de eerste token?&lt;/li&gt;
&lt;li&gt;wat gebeurt er als de prompt kort is, maar de output lang?&lt;/li&gt;
&lt;li&gt;hoe gedraagt het zich bij multi-turn gesprekken, waar context per beurt aandikt?&lt;/li&gt;
&lt;li&gt;en (pas later toegevoegd) hoe ziet dat er allemaal uit als verzoeken niet in lockstep komen, maar organisch?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Die laatste vraag bleek de helft van het verhaal.&lt;/p&gt;
&lt;h2&gt;De testopstelling&lt;/h2&gt;
&lt;p&gt;De server draaide in Docker met de officiële vLLM-image:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker run -d --name vllm-bench \
  --gpus all --ipc=host \
  -v appliance_hf-cache:/root/.cache/huggingface \
  -p 8000:8000 \
  vllm/vllm-openai:v0.20.1 \
  --model google/gemma-4-26B-A4B-it \
  --served-model-name gemma-4-26b-a4b-bf16 \
  --max-model-len 131072 \
  --gpu-memory-utilization 0.95 \
  --kv-cache-dtype fp8 \
  --limit-mm-per-prompt &apos;{&quot;image&quot;:0,&quot;audio&quot;:0}&apos; \
  --async-scheduling \
  --no-enable-prefix-caching \
  --host 0.0.0.0 \
  --port 8000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Een paar details doen ertoe.&lt;/p&gt;
&lt;p&gt;Prefix caching staat bewust uit. Ik wilde eerst de rauwe prefill-kosten zien, niet een benchmark die mooier wordt omdat prompts op elkaar lijken.&lt;/p&gt;
&lt;p&gt;De KV-cache draait op fp8. Zonder dat wordt 128k context met meerdere requests tegelijk al snel een geheugenoefening waar je weinig aan hebt.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Alle negen tests hieronder gebruiken precies deze server-config.&lt;/strong&gt; Geen herstart, geen tussentijdse aanpassing. Wat varieert is de workload: prompt-grootte, output-grootte, concurrency, depth, en bij de open-loop tests ook arrival rate en burstiness.&lt;/p&gt;
&lt;p&gt;Wat de Spark hiervan maakt:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Onderdeel&lt;/th&gt;
&lt;th&gt;Waarde&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Model weights (BF16)&lt;/td&gt;
&lt;td&gt;~48 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;KV-cache headroom (fp8)&lt;/td&gt;
&lt;td&gt;~65 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Theoretisch parallel @ 128k&lt;/td&gt;
&lt;td&gt;~4 requests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Theoretisch parallel @ 8k&lt;/td&gt;
&lt;td&gt;~50 requests&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Bij volle context per request is het geheugen krap. In de praktijk gebruikt geen enkele test 128k tegelijk per gebruiker, dus de bottleneck verschuift naar prefill-compute en scheduler-batching. Dat zien we hieronder terug.&lt;/p&gt;
&lt;h2&gt;Run A: context groter maken&lt;/h2&gt;
&lt;p&gt;De eerste run liet de context groeien van 4k naar 25k. Concurrency ging mee van 1 naar 5 en 10. Closed-loop, dus N gebruikers in lockstep.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;uvx llama-benchy \
  --base-url http://localhost:8000/v1 \
  --model gemma-4-26b-a4b-bf16 \
  --pp 4096 8192 16384 25000 \
  --tg 256 \
  --depth 0 \
  --concurrency 1 5 10 \
  --runs 3 \
  --latency-mode generation \
  --format md
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;pp&lt;/code&gt; is prefill, oftewel hoeveel prompttokens erin gaan. &lt;code&gt;tg&lt;/code&gt; is decode, oftewel hoeveel tokens het model daarna genereert. llama-benchy rapporteert mean ± stddev. Geen p95. Dat is belangrijk om te onthouden, want bij latency wil je jezelf anders al snel rijk rekenen.&lt;/p&gt;
&lt;p&gt;Dit is de samenvatting uit Run A:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Context&lt;/th&gt;
&lt;th&gt;Users&lt;/th&gt;
&lt;th&gt;Prefill total&lt;/th&gt;
&lt;th&gt;Decode/user&lt;/th&gt;
&lt;th&gt;Decode total&lt;/th&gt;
&lt;th&gt;TTFT&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;4k&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3677.85 ± 1259.27 tok/s&lt;/td&gt;
&lt;td&gt;24.08 ± 0.02 tok/s&lt;/td&gt;
&lt;td&gt;24.08 ± 0.02 tok/s&lt;/td&gt;
&lt;td&gt;1.37 ± 0.52s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4k&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;5722.96 ± 94.70 tok/s&lt;/td&gt;
&lt;td&gt;12.55 ± 0.49 tok/s&lt;/td&gt;
&lt;td&gt;57.07 ± 2.64 tok/s&lt;/td&gt;
&lt;td&gt;2.29 ± 0.82s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4k&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;5475.53 ± 888.14 tok/s&lt;/td&gt;
&lt;td&gt;9.48 ± 0.73 tok/s&lt;/td&gt;
&lt;td&gt;84.40 ± 3.08 tok/s&lt;/td&gt;
&lt;td&gt;4.46 ± 2.38s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8k&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;6121.87 ± 62.31 tok/s&lt;/td&gt;
&lt;td&gt;23.69 ± 0.02 tok/s&lt;/td&gt;
&lt;td&gt;23.69 ± 0.02 tok/s&lt;/td&gt;
&lt;td&gt;1.39 ± 0.01s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8k&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;5444.57 ± 12.82 tok/s&lt;/td&gt;
&lt;td&gt;11.48 ± 0.92 tok/s&lt;/td&gt;
&lt;td&gt;49.42 ± 1.60 tok/s&lt;/td&gt;
&lt;td&gt;4.34 ± 1.91s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8k&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;5478.98 ± 11.48 tok/s&lt;/td&gt;
&lt;td&gt;8.52 ± 1.10 tok/s&lt;/td&gt;
&lt;td&gt;67.72 ± 0.91 tok/s&lt;/td&gt;
&lt;td&gt;7.99 ± 4.03s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16k&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4607.64 ± 23.05 tok/s&lt;/td&gt;
&lt;td&gt;23.34 ± 0.05 tok/s&lt;/td&gt;
&lt;td&gt;23.34 ± 0.05 tok/s&lt;/td&gt;
&lt;td&gt;3.42 ± 0.00s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16k&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;4466.35 ± 27.19 tok/s&lt;/td&gt;
&lt;td&gt;10.05 ± 1.75 tok/s&lt;/td&gt;
&lt;td&gt;38.41 ± 0.12 tok/s&lt;/td&gt;
&lt;td&gt;10.43 ± 4.69s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16k&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;4453.92 ± 18.19 tok/s&lt;/td&gt;
&lt;td&gt;6.79 ± 1.62 tok/s&lt;/td&gt;
&lt;td&gt;45.76 ± 0.43 tok/s&lt;/td&gt;
&lt;td&gt;18.92 ± 9.43s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;25k&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3621.25 ± 18.50 tok/s&lt;/td&gt;
&lt;td&gt;22.75 ± 0.08 tok/s&lt;/td&gt;
&lt;td&gt;22.75 ± 0.08 tok/s&lt;/td&gt;
&lt;td&gt;6.39 ± 0.05s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;25k&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;3561.78 ± 9.23 tok/s&lt;/td&gt;
&lt;td&gt;8.46 ± 2.36 tok/s&lt;/td&gt;
&lt;td&gt;27.93 ± 0.08 tok/s&lt;/td&gt;
&lt;td&gt;19.63 ± 8.87s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;25k&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;3565.35 ± 8.21 tok/s&lt;/td&gt;
&lt;td&gt;5.40 ± 2.00 tok/s&lt;/td&gt;
&lt;td&gt;30.73 ± 0.12 tok/s&lt;/td&gt;
&lt;td&gt;35.67 ± 18.00s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;lt;figure class=&quot;breakout-wide&quot;&amp;gt;
&amp;lt;img src=&quot;/blog/gemma-4-dgx-spark/run-a-ttfr.webp&quot; width=&quot;1425&quot; height=&quot;878&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; alt=&quot;Run A: TTFT vs context, lijn per gelijktijdige users (1, 5, 10). TTFT loopt op van ~1.4 seconden bij 4k tot 36 seconden bij 25k context met 10 users.&quot; /&amp;gt;
&amp;lt;figcaption&amp;gt;Run A: Wachttijd voor de eerste token, per gelijktijdige users. Verdubbel de prompt en je verdubbelt de wachttijd.&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;figure class=&quot;breakout-wide&quot;&amp;gt;
&amp;lt;img src=&quot;/blog/gemma-4-dgx-spark/run-a-decode.webp&quot; width=&quot;1425&quot; height=&quot;878&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; alt=&quot;Run A: Decode-snelheid per gebruiker vs context. Bij c=1 blijft decode tussen 22.7 en 24.1 tokens per seconde, bij c=10 zakt het van 9.5 naar 5.4 tokens per seconde.&quot; /&amp;gt;
&amp;lt;figcaption&amp;gt;Run A: Decode per gebruiker. Bij één gebruiker blijft het bijna vlak; pas met meerdere users en grote context valt het in.&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;h2&gt;Run B: 25k context vasthouden, concurrency omhoog&lt;/h2&gt;
&lt;p&gt;Daarna draaide ik dezelfde 25k-context zwaarder. Niet meer variëren in context, alleen gebruikers erbij.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;uvx llama-benchy \
  --base-url http://localhost:8000/v1 \
  --model gemma-4-26b-a4b-bf16 \
  --pp 25000 \
  --tg 256 \
  --depth 0 \
  --concurrency 5 10 20 \
  --runs 3 \
  --latency-mode generation \
  --exit-on-first-fail \
  --format md
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Geen OOM. Geen crash. De DGX Spark overleefde 20 gelijktijdige requests met 25k context.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Users&lt;/th&gt;
&lt;th&gt;Prefill total&lt;/th&gt;
&lt;th&gt;Decode/user&lt;/th&gt;
&lt;th&gt;Decode total&lt;/th&gt;
&lt;th&gt;TTFT&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;3559.17 ± 6.72 tok/s&lt;/td&gt;
&lt;td&gt;8.51 ± 2.40 tok/s&lt;/td&gt;
&lt;td&gt;27.88 ± 0.05 tok/s&lt;/td&gt;
&lt;td&gt;19.86 ± 9.00s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;3569.77 ± 2.99 tok/s&lt;/td&gt;
&lt;td&gt;5.37 ± 1.99 tok/s&lt;/td&gt;
&lt;td&gt;30.68 ± 0.09 tok/s&lt;/td&gt;
&lt;td&gt;35.44 ± 17.95s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;3563.64 ± 8.78 tok/s&lt;/td&gt;
&lt;td&gt;3.16 ± 1.41 tok/s&lt;/td&gt;
&lt;td&gt;32.26 ± 0.10 tok/s&lt;/td&gt;
&lt;td&gt;67.37 ± 36.44s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;lt;figure class=&quot;breakout-wide&quot;&amp;gt;
&amp;lt;img src=&quot;/blog/gemma-4-dgx-spark/run-b-prefill-wall.webp&quot; width=&quot;1522&quot; height=&quot;843&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; alt=&quot;Run B: TTFT groeit lineair met concurrency: 19.9s bij 5 users, 35.4s bij 10, 67.4s bij 20. Aggregate decode plakt rond 30 tok/s.&quot; /&amp;gt;
&amp;lt;figcaption&amp;gt;Run B: Aggregate decode plakt op ~30 tok/s; alle extra wachttijd gaat in TTFT zitten.&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;Dit is de stress-rand van de benchmark. Aggregate decode plakt rond 30 tok/s, ongeacht of je 5, 10 of 20 gebruikers neerzet. Per gebruiker zakt het van 8.51 naar 3.16 tok/s. Maar het echte probleem is TTFT: bij 20 gebruikers wacht de gemiddelde request 67 seconden voordat de eerste token komt. De server is dan niet stuk. De workload past alleen niet meer bij een realtime chatverwachting.&lt;/p&gt;
&lt;h2&gt;Run C: korte prompt, lange output&lt;/h2&gt;
&lt;p&gt;Run C draaide de vorm om. Niet 25k context met korte output, maar 1024 prompttokens en 1024 outputtokens.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Users&lt;/th&gt;
&lt;th&gt;Prefill total&lt;/th&gt;
&lt;th&gt;Decode/user&lt;/th&gt;
&lt;th&gt;Decode total&lt;/th&gt;
&lt;th&gt;TTFT&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4627.12 ± 374.91 tok/s&lt;/td&gt;
&lt;td&gt;23.86 ± 0.03 tok/s&lt;/td&gt;
&lt;td&gt;23.86 ± 0.03 tok/s&lt;/td&gt;
&lt;td&gt;0.31 ± 0.02s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;5701.55 ± 561.36 tok/s&lt;/td&gt;
&lt;td&gt;13.59 ± 1.05 tok/s&lt;/td&gt;
&lt;td&gt;54.67 ± 4.90 tok/s&lt;/td&gt;
&lt;td&gt;0.76 ± 0.11s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;6346.87 ± 64.52 tok/s&lt;/td&gt;
&lt;td&gt;10.92 ± 0.73 tok/s&lt;/td&gt;
&lt;td&gt;86.46 ± 1.74 tok/s&lt;/td&gt;
&lt;td&gt;1.26 ± 0.40s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;lt;figure class=&quot;breakout-wide&quot;&amp;gt;
&amp;lt;img src=&quot;/blog/gemma-4-dgx-spark/run-c-grouped.webp&quot; width=&quot;1227&quot; height=&quot;777&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; alt=&quot;Run C: per-user decode zakt van 23.9 (c=1) naar 10.9 (c=10), aggregate decode loopt op naar 86.5 tok/s.&quot; /&amp;gt;
&amp;lt;figcaption&amp;gt;Run C: korte prompt, lange output. Aggregate decode schaalt netjes naar 86 tok/s, per-user blijft ruim leesbaar.&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;Bij tien gebruikers tegelijk blijft TTFT op 1.3 seconden. Dat voelt als chat.&lt;/p&gt;
&lt;h2&gt;Run G: nóg langere output&lt;/h2&gt;
&lt;p&gt;Run A, B en C lieten genoeg zien om het verhaal &quot;decode is stabiel, prefill bepaalt de wachttijd&quot; plausibel te maken. Maar er bleef één scenario open: wat als de output nog véél langer is? Een agent die code genereert. Een tool-call met gestructureerde output. Een lange samenvatting.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Users&lt;/th&gt;
&lt;th&gt;Prefill total&lt;/th&gt;
&lt;th&gt;Decode/user&lt;/th&gt;
&lt;th&gt;Decode total&lt;/th&gt;
&lt;th&gt;TTFT&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1993.94 ± 262.05 tok/s&lt;/td&gt;
&lt;td&gt;24.17 ± 0.02 tok/s&lt;/td&gt;
&lt;td&gt;24.17 ± 0.02 tok/s&lt;/td&gt;
&lt;td&gt;0.24 ± 0.01s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;3048.28 ± 496.15 tok/s&lt;/td&gt;
&lt;td&gt;14.32 ± 2.18 tok/s&lt;/td&gt;
&lt;td&gt;46.11 ± 11.57 tok/s&lt;/td&gt;
&lt;td&gt;0.38 ± 0.07s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;4800.80 ± 50.75 tok/s&lt;/td&gt;
&lt;td&gt;11.75 ± 0.68 tok/s&lt;/td&gt;
&lt;td&gt;83.77 ± 4.04 tok/s&lt;/td&gt;
&lt;td&gt;0.48 ± 0.01s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;lt;figure class=&quot;breakout-wide&quot;&amp;gt;
&amp;lt;img src=&quot;/blog/gemma-4-dgx-spark/run-g-grouped.webp&quot; width=&quot;1227&quot; height=&quot;777&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; alt=&quot;Run G: per-user decode 24.2 (c=1), 14.3 (c=5), 11.8 (c=10); aggregate 24.2, 46.1, 83.8 tok/s.&quot; /&amp;gt;
&amp;lt;figcaption&amp;gt;Run G: 4k output: lange generaties zijn alleen langer, niet trager. Per-user staat dichtbij Run C.&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;Decode/user over 4096 tokens zakt nauwelijks weg vergeleken met C&apos;s 1024 tokens. Bij c=1 is het 24.17 (G) vs 23.86 (C). Bij c=10 is het 11.75 (G) vs 10.92 (C). Lange generaties compounderen niet, ze duren alleen proportioneel langer. En TTFT is hier het laagst: onder een halve seconde bij tien gebruikers tegelijk.&lt;/p&gt;
&lt;h2&gt;Run F: middelgrote context, meer gebruikers&lt;/h2&gt;
&lt;p&gt;Tussen Run C (1k context) en Run B (25k context) zat een gat dat dichter bij realiteit ligt. Een typische RAG-flow met vier chunks van ~2k tokens komt uit op zo&apos;n 8k.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Users&lt;/th&gt;
&lt;th&gt;Prefill total&lt;/th&gt;
&lt;th&gt;Decode/user&lt;/th&gt;
&lt;th&gt;Decode total&lt;/th&gt;
&lt;th&gt;TTFT&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;5439.51 ± 32.60 tok/s&lt;/td&gt;
&lt;td&gt;12.11 ± 0.51 tok/s&lt;/td&gt;
&lt;td&gt;55.21 ± 1.49 tok/s&lt;/td&gt;
&lt;td&gt;4.32 ± 1.90s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;5466.71 ± 15.65 tok/s&lt;/td&gt;
&lt;td&gt;9.31 ± 0.77 tok/s&lt;/td&gt;
&lt;td&gt;78.36 ± 1.61 tok/s&lt;/td&gt;
&lt;td&gt;7.99 ± 4.02s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;5532.74 ± 5.39 tok/s&lt;/td&gt;
&lt;td&gt;6.05 ± 0.62 tok/s&lt;/td&gt;
&lt;td&gt;97.35 ± 3.50 tok/s&lt;/td&gt;
&lt;td&gt;14.61 ± 7.72s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;lt;figure class=&quot;breakout-wide&quot;&amp;gt;
&amp;lt;img src=&quot;/blog/gemma-4-dgx-spark/run-f-ttfr.webp&quot; width=&quot;1522&quot; height=&quot;843&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; alt=&quot;Run F: 8k context. TTFT loopt van 4.3s (c=5) naar 8.0s (c=10) naar 14.6s (c=20); aggregate decode haalt 97.4 tok/s.&quot; /&amp;gt;
&amp;lt;figcaption&amp;gt;Run F: 8k context. TTFT groeit lineair met concurrency, aggregate decode blijft schalen tot bijna 100 tok/s.&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;Drie observaties.&lt;/p&gt;
&lt;p&gt;Prefill-throughput zit op een vlakke 5.5k tok/s, ongeacht of het 5, 10 of 20 gebruikers zijn. De machine is bij 8k context al gesatureerd op prefill-niveau. Aggregate decode blijft schalen: in Run B (25k) plateauerde dit op ~30 t/s, hier loopt het door tot 97.4 t/s. En het belangrijkste: TTFT bij 8k context is grofweg een kwart van wat het bij 25k is. Dezelfde concurrency, dezelfde machine, andere prompt-grootte.&lt;/p&gt;
&lt;h2&gt;Run E: multi-turn als realistisch kantoorwerk&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;--depth 4&lt;/code&gt; betekent: per request vijf turns achter elkaar (initieel + vier vervolgvragen). Concurrency op 10 betekent: tien zulke gesprekken parallel.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Users&lt;/th&gt;
&lt;th&gt;Prefill total&lt;/th&gt;
&lt;th&gt;Decode/user&lt;/th&gt;
&lt;th&gt;Decode total&lt;/th&gt;
&lt;th&gt;TTFT&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4716.21 ± 542.88 tok/s&lt;/td&gt;
&lt;td&gt;23.97 ± 0.10 tok/s&lt;/td&gt;
&lt;td&gt;23.97 ± 0.10 tok/s&lt;/td&gt;
&lt;td&gt;0.53 ± 0.06s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;5693.39 ± 128.08 tok/s&lt;/td&gt;
&lt;td&gt;13.07 ± 0.16 tok/s&lt;/td&gt;
&lt;td&gt;59.48 ± 2.26 tok/s&lt;/td&gt;
&lt;td&gt;1.32 ± 0.39s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;6096.81 ± 56.92 tok/s&lt;/td&gt;
&lt;td&gt;10.43 ± 0.35 tok/s&lt;/td&gt;
&lt;td&gt;92.42 ± 3.33 tok/s&lt;/td&gt;
&lt;td&gt;2.13 ± 0.83s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;lt;figure class=&quot;breakout-wide&quot;&amp;gt;
&amp;lt;img src=&quot;/blog/gemma-4-dgx-spark/run-e-multiturn.webp&quot; width=&quot;1242&quot; height=&quot;777&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; alt=&quot;Run E: multi-turn. Per-user 24.0/13.1/10.4 tok/s, aggregate 24.0/59.5/92.4 tok/s, hoogste aggregate van alle closed-loop runs.&quot; /&amp;gt;
&amp;lt;figcaption&amp;gt;Run E: multi-turn (depth = 4) bij 2k startcontext. Aggregate van 92 tok/s is het hoogste cijfer in alle zes closed-loop runs.&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;Drie dingen vielen op die ik vooraf niet had verwacht.&lt;/p&gt;
&lt;p&gt;Per-user decode bij multi-turn is identiek aan single-turn. Multi-turn maakt de tokens niet langzamer, alleen het aantal prefills neemt toe. Aggregate decode op c=10 is 92.42 t/s, het hoogste van élke closed-loop run. vLLM krijgt bij multi-turn een dichtere stroom afhankelijke requests aangeleverd, en kan die efficiënter batchen dan tien losse single-shot prompts. En TTFT op c=10 is gemiddeld 2.13 seconden over alle vijf turns. Onder drie seconden voelt nog steeds als chat.&lt;/p&gt;
&lt;h2&gt;Wat de zes closed-loop runs samen laten zien&lt;/h2&gt;
&lt;p&gt;Eén tabel die alles bij c=10 naast elkaar zet:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Run&lt;/th&gt;
&lt;th&gt;Prompt&lt;/th&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;th&gt;Depth&lt;/th&gt;
&lt;th&gt;TTFT (c=10)&lt;/th&gt;
&lt;th&gt;Decode/user (c=10)&lt;/th&gt;
&lt;th&gt;Aggregate decode (c=10)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;G&lt;/td&gt;
&lt;td&gt;256&lt;/td&gt;
&lt;td&gt;4096&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0.48s&lt;/td&gt;
&lt;td&gt;11.75 t/s&lt;/td&gt;
&lt;td&gt;83.8 t/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C&lt;/td&gt;
&lt;td&gt;1024&lt;/td&gt;
&lt;td&gt;1024&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1.26s&lt;/td&gt;
&lt;td&gt;10.92 t/s&lt;/td&gt;
&lt;td&gt;86.5 t/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;E&lt;/td&gt;
&lt;td&gt;2048&lt;/td&gt;
&lt;td&gt;512&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;2.13s&lt;/td&gt;
&lt;td&gt;10.43 t/s&lt;/td&gt;
&lt;td&gt;92.4 t/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;F&lt;/td&gt;
&lt;td&gt;8192&lt;/td&gt;
&lt;td&gt;512&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;7.99s&lt;/td&gt;
&lt;td&gt;9.31 t/s&lt;/td&gt;
&lt;td&gt;78.4 t/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;16384&lt;/td&gt;
&lt;td&gt;256&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;18.92s&lt;/td&gt;
&lt;td&gt;6.79 t/s&lt;/td&gt;
&lt;td&gt;45.8 t/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A/B&lt;/td&gt;
&lt;td&gt;25000&lt;/td&gt;
&lt;td&gt;256&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;35.67s&lt;/td&gt;
&lt;td&gt;5.40 t/s&lt;/td&gt;
&lt;td&gt;30.7 t/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;lt;figure class=&quot;breakout-wide&quot;&amp;gt;
&amp;lt;img src=&quot;/blog/gemma-4-dgx-spark/summary-c10.webp&quot; width=&quot;1569&quot; height=&quot;944&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; alt=&quot;Scatter van alle zes closed-loop runs bij c=10. Y-as decode/user (5 tot 12 tok/s), X-as TTFT logaritmisch (0.5s tot 49s). G en C linksboven, A-25k rechtsonder.&quot; /&amp;gt;
&amp;lt;figcaption&amp;gt;Alle zes closed-loop runs bij 10 gelijktijdige users. Decode per user beweegt nauwelijks tot 8k context. TTFT beweegt overal.&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;Twee patronen springen eruit.&lt;/p&gt;
&lt;p&gt;Decode/user beweegt nauwelijks tot 8k context. Tussen Run G en Run F zit een factor 32 in prompt-grootte en een factor 8 in output-grootte. Toch zit decode/user daar tussen 9.3 en 11.8 tok/s. Pas bij 16k+ valt die strook in elkaar.&lt;/p&gt;
&lt;p&gt;TTFT beweegt overal en is bijna een functie van prompt-grootte alleen. Verdubbel de prompt en de TTFT verdubbelt grofweg mee. Output-grootte en depth doen er voor TTFT bijna niets toe.&lt;/p&gt;
&lt;p&gt;Dat is de closed-loop conclusie. Hij klopt, en hij vertelt een echt deel van het verhaal. Maar er zit een gat in.&lt;/p&gt;
&lt;h2&gt;Maar dit zijn synthetische tests&lt;/h2&gt;
&lt;p&gt;De zes runs hierboven testen capaciteit. &lt;em&gt;Plafonds.&lt;/em&gt; Allemaal in dezelfde vorm: N gebruikers in lockstep, allemaal hetzelfde prompt-formaat, allemaal tegelijk verzendknopjes indrukkend. Dat is een prima manier om te meten waar het breekt. Het is een slechte manier om te meten hoe een echt kantoor voelt.&lt;/p&gt;
&lt;p&gt;Want een echt kantoor heeft 25 medewerkers waarvan er gemiddeld een paar tegelijk wat doen. De ene collega vraagt een korte vraag. De andere is mid-RAG met 8k context. De derde zit in turn 4 van een gesprek. En verzoeken arriveren niet in lockstep. Ze arriveren als een Poisson-proces met af en toe een burst, omdat iemand net een mail af heeft en drie collega&apos;s tegelijk aan koffie willen.&lt;/p&gt;
&lt;p&gt;Dat is wat &lt;strong&gt;vLLM&apos;s eigen &lt;code&gt;vllm bench serve&lt;/code&gt;&lt;/strong&gt; wel kan en llama-benchy niet:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Open-loop&lt;/strong&gt; met arrival rate. Verzoeken dispatchen volgens een Poisson- of Gamma-distributie, in plaats van lockstep.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Percentielen.&lt;/strong&gt; P50, P90, P95, P99 op TTFT, TPOT (time per output token), ITL (inter-token latency) en E2E. Geen mean ± stddev meer.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Realistische datasets.&lt;/strong&gt; ShareGPT replay van 94k+ echte gesprekken met natuurlijk variërende prompt-lengtes en multi-turn structuur.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mixed workloads.&lt;/strong&gt; Prompts uit een distributie sampelen in plaats van één vaste shape testen.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Drie tests hieronder, dezelfde server (geen herstart), maar met die andere bril op.&lt;/p&gt;
&lt;h2&gt;Test H: realistische kantoor-baseline&lt;/h2&gt;
&lt;p&gt;Het scenario: 25 mensen actief gemiddeld, elk stuurt zo&apos;n keer per 1–2 minuten een prompt, prompts variëren sterk in lengte. Aankomsten zijn licht clumpy.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker exec vllm-bench vllm bench serve \
  --backend openai-chat \
  --base-url http://localhost:8000 \
  --endpoint /v1/chat/completions \
  --model google/gemma-4-26B-A4B-it \
  --tokenizer google/gemma-4-26B-A4B-it \
  --served-model-name gemma-4-26b-a4b-bf16 \
  --dataset-name random \
  --random-input-len 4000 \
  --random-output-len 500 \
  --random-range-ratio 0.9 \
  --num-prompts 200 \
  --request-rate 0.3 \
  --burstiness 0.7 \
  --percentile-metrics ttft,tpot,itl,e2el \
  --metric-percentiles 50,90,95,99 \
  --seed 42
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Met &lt;code&gt;--random-range-ratio 0.9&lt;/code&gt; variëren input-lengtes van 399 tot 7600 tokens, outputs van 49 tot 950. &lt;code&gt;--burstiness 0.7&lt;/code&gt; is iets clumpier dan pure Poisson. Mensen drukken vaak in burstjes op enter, niet als een metronoom. Target rate van 0.3 req/s = ~18 prompts/min over 25 gebruikers.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Successful requests&lt;/td&gt;
&lt;td&gt;200 / 200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Achieved RPS&lt;/td&gt;
&lt;td&gt;0.27 (target 0.30)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Peak concurrent requests&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;36&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total token throughput&lt;/td&gt;
&lt;td&gt;1215 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Mean&lt;/th&gt;
&lt;th&gt;P50&lt;/th&gt;
&lt;th&gt;P90&lt;/th&gt;
&lt;th&gt;P95&lt;/th&gt;
&lt;th&gt;P99&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TTFT (ms)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1395&lt;/td&gt;
&lt;td&gt;1286&lt;/td&gt;
&lt;td&gt;2284&lt;/td&gt;
&lt;td&gt;2644&lt;/td&gt;
&lt;td&gt;3316&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TPOT (ms)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;177&lt;/td&gt;
&lt;td&gt;182&lt;/td&gt;
&lt;td&gt;193&lt;/td&gt;
&lt;td&gt;202&lt;/td&gt;
&lt;td&gt;214&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;E2E (ms)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;85921&lt;/td&gt;
&lt;td&gt;85306&lt;/td&gt;
&lt;td&gt;150192&lt;/td&gt;
&lt;td&gt;162375&lt;/td&gt;
&lt;td&gt;171351&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Mediaan-gebruiker krijgt eerste token in 1.29s. Voelt nog als chat. De tail blijft binnen de perken: P99 wacht 3.3 seconden, ruim twee keer het gemiddelde.&lt;/p&gt;
&lt;p&gt;En kijk naar peak concurrent: &lt;strong&gt;36&lt;/strong&gt;. Bij target rate van slechts 0.3 req/s. Geen enkele closed-loop run zat in die buurt. De Poisson-burstiness alleen al, gecombineerd met gemiddelde response-tijd van ~86 seconden, zorgt voor pieken die heftiger zijn dan welke Run B-stress-test ook had. Dat is het ding dat closed-loop letterlijk niet kan laten zien.&lt;/p&gt;
&lt;h2&gt;Test I: echte gesprekken (ShareGPT replay)&lt;/h2&gt;
&lt;p&gt;Identieke aankomst-pattern als Test H, maar nu met 250 echte multi-turn gesprekken uit ShareGPT V3 als prompts. Sommige zijn 1 turn van 200 tokens, andere zijn 15 turns met steeds groeiende context.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker exec vllm-bench vllm bench serve \
  ... \
  --dataset-name sharegpt \
  --dataset-path /tmp/ShareGPT_V3.json \
  --num-prompts 250 \
  --request-rate 0.3 \
  --burstiness 0.7
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Successful requests&lt;/td&gt;
&lt;td&gt;250 / 250&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Achieved RPS&lt;/td&gt;
&lt;td&gt;0.30 (target 0.30)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Peak concurrent requests&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;17&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total token throughput&lt;/td&gt;
&lt;td&gt;133 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Mean&lt;/th&gt;
&lt;th&gt;P50&lt;/th&gt;
&lt;th&gt;P90&lt;/th&gt;
&lt;th&gt;P95&lt;/th&gt;
&lt;th&gt;P99&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TTFT (ms)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;376&lt;/td&gt;
&lt;td&gt;353&lt;/td&gt;
&lt;td&gt;469&lt;/td&gt;
&lt;td&gt;509&lt;/td&gt;
&lt;td&gt;637&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TPOT (ms)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;93&lt;/td&gt;
&lt;td&gt;95&lt;/td&gt;
&lt;td&gt;117&lt;/td&gt;
&lt;td&gt;123&lt;/td&gt;
&lt;td&gt;135&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;E2E (ms)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;19600&lt;/td&gt;
&lt;td&gt;10923&lt;/td&gt;
&lt;td&gt;49525&lt;/td&gt;
&lt;td&gt;63036&lt;/td&gt;
&lt;td&gt;82596&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Dit is een ander universum dan Test H. &lt;strong&gt;TTFT P99 = 637 ms.&lt;/strong&gt; 99% van de gebruikers ziet binnen 650 milliseconden de eerste token. Dat is écht chat-snelheid.&lt;/p&gt;
&lt;p&gt;Identieke aankomst-pattern als Test H, totaal andere ervaring. Het verschil zit volledig in prompt-grootte: ShareGPT-gesprekken zijn gemiddeld 228 tokens, niet 4000. Korte prompt = goedkope prefill = geen queue-druk = sub-seconde TTFT.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Test H (random 4k)&lt;/th&gt;
&lt;th&gt;Test I (ShareGPT)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Achieved RPS&lt;/td&gt;
&lt;td&gt;0.27&lt;/td&gt;
&lt;td&gt;0.30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Peak concurrent&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TTFT P50&lt;/td&gt;
&lt;td&gt;1286 ms&lt;/td&gt;
&lt;td&gt;353 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TTFT P99&lt;/td&gt;
&lt;td&gt;3316 ms&lt;/td&gt;
&lt;td&gt;637 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TPOT P50&lt;/td&gt;
&lt;td&gt;182 ms&lt;/td&gt;
&lt;td&gt;95 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Dit is ook een waarschuwing: de synthetische workload van Test H &lt;em&gt;overdrijft&lt;/em&gt; hoe zwaar een gemiddeld kantoor-prompt is. Real-world conversations zijn lichter dan onze 4k random baseline, dus de praktijk-cijfers zitten vermoedelijk dichter bij Test I dan bij Test H.&lt;/p&gt;
&lt;h2&gt;Test J: maandagochtend-piek&lt;/h2&gt;
&lt;p&gt;Wat als iedereen tegelijk binnenkomt en op verzendknopjes drukt? Vijfvoudige load, max 25 gelijktijdige requests om een echt kantoor te modelleren.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker exec vllm-bench vllm bench serve \
  ... \
  --dataset-name random \
  --random-input-len 4000 \
  --random-output-len 500 \
  --random-range-ratio 0.9 \
  --num-prompts 300 \
  --request-rate 1.5 \
  --burstiness 1.0 \
  --max-concurrency 25
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Successful requests&lt;/td&gt;
&lt;td&gt;300 / 300&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Configured RPS&lt;/td&gt;
&lt;td&gt;1.50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Achieved RPS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.26&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Peak concurrent requests&lt;/td&gt;
&lt;td&gt;27&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total token throughput&lt;/td&gt;
&lt;td&gt;1173 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Mean&lt;/th&gt;
&lt;th&gt;P50&lt;/th&gt;
&lt;th&gt;P90&lt;/th&gt;
&lt;th&gt;P95&lt;/th&gt;
&lt;th&gt;P99&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TTFT (ms)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1370&lt;/td&gt;
&lt;td&gt;1132&lt;/td&gt;
&lt;td&gt;1932&lt;/td&gt;
&lt;td&gt;2961&lt;/td&gt;
&lt;td&gt;6157&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TPOT (ms)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;185&lt;/td&gt;
&lt;td&gt;187&lt;/td&gt;
&lt;td&gt;195&lt;/td&gt;
&lt;td&gt;199&lt;/td&gt;
&lt;td&gt;221&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;E2E (ms)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;92752&lt;/td&gt;
&lt;td&gt;91099&lt;/td&gt;
&lt;td&gt;165179&lt;/td&gt;
&lt;td&gt;172073&lt;/td&gt;
&lt;td&gt;179139&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Dit is het sleutelcijfer: &lt;strong&gt;achieved rate 0.26 bij target 1.5&lt;/strong&gt;. Het systeem is &lt;strong&gt;bijna 6× throttled&lt;/strong&gt;. Niet omdat &apos;ie crasht (alle 300 requests slagen, geen failures), maar omdat de queue zich vult tot 25 en daar verzoeken vasthoudt totdat er ruimte is.&lt;/p&gt;
&lt;p&gt;Vergelijk Test H (target 0.3) en Test J (target 1.5):&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Test H (0.3 rps)&lt;/th&gt;
&lt;th&gt;Test J (1.5 rps)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Achieved RPS&lt;/td&gt;
&lt;td&gt;0.27&lt;/td&gt;
&lt;td&gt;0.26&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TTFT P50&lt;/td&gt;
&lt;td&gt;1286 ms&lt;/td&gt;
&lt;td&gt;1132 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TTFT P95&lt;/td&gt;
&lt;td&gt;2644 ms&lt;/td&gt;
&lt;td&gt;2961 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TTFT P99&lt;/td&gt;
&lt;td&gt;3316 ms&lt;/td&gt;
&lt;td&gt;6157 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TPOT P50&lt;/td&gt;
&lt;td&gt;182 ms&lt;/td&gt;
&lt;td&gt;187 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Mediaan-ervaring is bij Test J zelfs &lt;em&gt;iets beter&lt;/em&gt; dan bij Test H (1.13s vs 1.29s). De cap zorgt voor een gelijkmatigere stroom. Maar de tail is dramatisch erger: P99 verdubbelt van 3.3s naar 6.2s.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure class=&quot;breakout-wide&quot;&amp;gt;
&amp;lt;img src=&quot;/blog/gemma-4-dgx-spark/open-loop-ttft.webp&quot; width=&quot;1425&quot; height=&quot;882&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; alt=&quot;Open-loop TTFT-percentielen voor H (random 4k 0.3 rps), I (ShareGPT 0.3 rps) en J (random 4k 1.5 rps). I blijft sub-seconde overal; H loopt op tot 6.4s P99; J schiet door naar 14.8s P99.&quot; /&amp;gt;
&amp;lt;figcaption&amp;gt;Open-loop TTFT-percentielen. Mediaan zegt weinig; de tail vertelt waar overload pijn doet.&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;De Spark schaalt niet onder oversubscribe, hij &lt;strong&gt;queue&apos;t&lt;/strong&gt;. Dat is goed nieuws: graceful degradation in plaats van crashes. Voor on-prem AI is dat eigenlijk de beste failure-mode.&lt;/p&gt;
&lt;h2&gt;Wat closed-loop verbergt, wat open-loop overdrijft&lt;/h2&gt;
&lt;p&gt;De twee methodes vertellen elk een ander deel van het verhaal. Allebei waar, allebei onvolledig.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Closed-loop onderschat queue-diepte.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In Run F testte ik c=10 als &quot;tien gebruikers tegelijk&quot;. Dat klinkt als een redelijk drukke kantoorsituatie. Maar Test H toont dat een organische 0.3 req/s arrival rate al genoeg is om pieken van 36 gelijktijdige requests te produceren. De closed-loop &quot;10 gebruikers&quot; claim is dus optimistischer dan de praktijk laat zien.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Open-loop met synthetisch overdrijft de werkelijke load.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Tegelijk: Test H gebruikt random 4k-prompts. Een echt kantoor stelt geen 25 gemiddelde 4k-prompts per minuut. ShareGPT (Test I) is een veel betere proxy voor &quot;wat mensen typen&quot;, gemiddeld 228 tokens. Bij die workload-shape is peak concurrent 17 in plaats van 36, en P99 TTFT 637ms in plaats van 3.3s.&lt;/p&gt;
&lt;p&gt;De praktijk zit dus tussen Run F en Test I in:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Bron&lt;/th&gt;
&lt;th&gt;TTFT (P50 of mean)&lt;/th&gt;
&lt;th&gt;Peak concurrent&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Run F (closed-loop, 10 users, 8k)&lt;/td&gt;
&lt;td&gt;7.99 s&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Test H (open-loop, 0.3 rps, 4k random)&lt;/td&gt;
&lt;td&gt;1.29 s P50 / 3.3s P99&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Test I (open-loop, 0.3 rps, ShareGPT)&lt;/td&gt;
&lt;td&gt;0.35 s P50 / 0.64s P99&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Test J (open-loop, 1.5 rps, 4k random, cap 25)&lt;/td&gt;
&lt;td&gt;1.13 s P50 / 6.2s P99&lt;/td&gt;
&lt;td&gt;27&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Voor een kantoor met realistische prompts en realistische arrival pattern is Test I het dichtst bij wat mensen voelen. Voor capaciteitsplanning (&quot;wat als iedereen tegelijk een 8k RAG-vraag stelt?&quot;) is Run F het dichtst bij wat de machine kan verstouwen.&lt;/p&gt;
&lt;h2&gt;De tail vertelt wat het gemiddelde verbergt&lt;/h2&gt;
&lt;p&gt;llama-benchy gaf alleen mean ± stddev. Dat klinkt als veel informatie, maar het verbergt het deel dat er voor je gebruikers het meest toe doet: de tail.&lt;/p&gt;
&lt;p&gt;Test I&apos;s mean TTFT is 376ms. Klinkt prima. Maar wat zegt dat over de 1% gebruikers waar de queue net pikte? Niets. Daarvoor heb je P99 nodig, en die zit op 637ms. In dit geval geen probleem (allebei sub-seconde), maar het &lt;em&gt;principe&lt;/em&gt; dat je moet kennen.&lt;/p&gt;
&lt;p&gt;Test H&apos;s mean TTFT is 1395ms. P99 is 3316ms. Ruim twee keer slechter dan het gemiddelde voor de unlucky 1%.&lt;/p&gt;
&lt;p&gt;Test J&apos;s mean TTFT is 1370ms. P99 is &lt;strong&gt;6157ms&lt;/strong&gt;. Ruim vier keer het gemiddelde.&lt;/p&gt;
&lt;p&gt;Voor SLA-beslissingen (&quot;ons systeem geeft binnen 3 seconden antwoord aan 95% van requests&quot;) heb je deze percentielen nodig. Mean ± stddev kan een SLA suggereren die je niet haalt op de momenten dat het er het meest toe doet, namelijk wanneer er druk is.&lt;/p&gt;
&lt;p&gt;Dat is waarom de blog niet alleen op llama-benchy kan landen. Capaciteit testen is één ding. Tail-latency rapporteren is een ander.&lt;/p&gt;
&lt;h2&gt;Decode is het probleem niet&lt;/h2&gt;
&lt;p&gt;Bij één gebruiker blijft decode bijna vlak.&lt;/p&gt;
&lt;p&gt;4k context haalt 24.08 tok/s per gebruiker. 25k context haalt 22.75 tok/s. 4096 outputtokens (Run G, c=1) haalt 24.17 tok/s. Multi-turn met depth 4 (Run E, c=1) haalt 23.97 tok/s. Vier verschillende workloads, allemaal binnen 6 procent van elkaar.&lt;/p&gt;
&lt;p&gt;Bij tien gebruikers tegelijk gebeurt iets vergelijkbaars, alleen op een lagere lijn. Run G: 11.75 tok/s/user. Run C: 10.92. Run E: 10.43. Run F: 9.31. En in de open-loop tests: Test I geeft TPOT P50 = 95ms = ~10.5 tok/s/user. Test H en J geven TPOT P50 = ~185ms = ~5.4 tok/s/user (omdat pieken daar 25+ concurrent halen).&lt;/p&gt;
&lt;p&gt;Kortom: per-token decode-snelheid is een functie van &lt;strong&gt;gemiddelde concurrent load&lt;/strong&gt;, niet van prompt-lengte, output-lengte, multi-turn, of arrival pattern. Pas bij 16k+ context gecombineerd met meerdere users (Run A) zakt het echt door 7 t/s/user.&lt;/p&gt;
&lt;p&gt;Concurrency op zichzelf is niet het probleem. Lange output ook niet. Multi-turn ook niet. Pas grote context tegelijk met meerdere gebruikers eet decode op.&lt;/p&gt;
&lt;h2&gt;Prefill is de muur&lt;/h2&gt;
&lt;p&gt;Wat je als eerste voelt, is wachten.&lt;/p&gt;
&lt;p&gt;Bij één gebruiker op 25k context duurt het ruim 6 seconden voordat de eerste response komt. Bij vijf gebruikers wordt dat 19.9 seconden. Bij tien wordt het 35.4 seconden. Bij twintig wordt het 67.4 seconden.&lt;/p&gt;
&lt;p&gt;Run F laat zien dat dit lineair is in &lt;em&gt;zowel&lt;/em&gt; concurrency als context. 8k context bij 20 gebruikers geeft 14.6 seconden, ongeveer een kwart van de 67.4 seconden bij 25k context, voor dezelfde concurrency. Halveer de prompt, halveer de wachttijd.&lt;/p&gt;
&lt;p&gt;En Test J laat zien: zodra je het systeem voorbij zijn doorvoer-plafond pusht, gaat al die extra wachttijd in de &lt;strong&gt;tail&lt;/strong&gt; zitten. Mediaan TTFT blijft stabiel rond 1.1-1.3s, maar P99 schiet naar 6 seconden. De pijn van overbelasting valt op een kleine groep, niet op iedereen.&lt;/p&gt;
&lt;p&gt;Daar zit de echte grens.&lt;/p&gt;
&lt;p&gt;Niet: kan de DGX Spark tokens genereren? Ja.&lt;/p&gt;
&lt;p&gt;Niet: kan de KV-cache 20 × 25k aan? Ook ja.&lt;/p&gt;
&lt;p&gt;Niet: stopt het bij overload? Nee, het queue&apos;t netjes door.&lt;/p&gt;
&lt;p&gt;Maar: voelt dit nog als chat? Niet voor 25k. Voor 8k al wel grensgebied. Voor 2k met multi-turn gewoon prima. Voor ShareGPT-realistische prompts met 25 gebruikers organisch verspreid: glashelder ja.&lt;/p&gt;
&lt;h2&gt;Waar dit wel past&lt;/h2&gt;
&lt;p&gt;Deze benchmarks maken de on-prem keuze concreter.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ja voor een kantooromgeving waar 10 tot 25 mensen verspreid over de dag lokale AI gebruiken.&lt;/strong&gt; Test I is het bewijs: 250 echte ShareGPT-gesprekken, 0.3 req/s aankomst-rate, P99 TTFT van 637ms. Mediaan-gebruiker ziet de eerste token in 353 milliseconden. Dat is precies het kantoor-scenario, en dit is wat het voelt.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ja voor RAG-flows met middelgrote context.&lt;/strong&gt; Run F gaf de cijfers vooraf: 8k prompt, 10 users, 8s TTFT, 9.3 tok/s streamen. Test H bevestigt dat de open-loop variant nog steeds werkbaar is: P99 TTFT 3.3s. Niet realtime, wel binnen wachtbare grenzen.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ja voor agents en code-generatie.&lt;/strong&gt; Run G is de bevestiging: korte instructie, 4k+ tokens output, tien parallelle taken. TTFT onder een halve seconde, 11.75 tok/s/user.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ja voor multi-turn gesprekken.&lt;/strong&gt; Run E geeft 2.1s TTFT bij 10 parallelle 5-turn gesprekken. Decode hetzelfde als single-turn.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Voorzichtig bij 5+ gebruikers met 25k context tegelijk.&lt;/strong&gt; 19.9 seconden TTFT is geen chat meer, wel werkbaar voor analyses.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Voorzichtig met SLA-claims op basis van gemiddeldes.&lt;/strong&gt; Test H&apos;s mean TTFT van 1.4s zou als acceptabel kunnen klinken, maar P99 zit op 3.3s. Beslissingen op basis van percentielen, niet op mean.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Nee voor support-chat waarbij tien tot twintig gebruikers tegelijk 25k context per sessie sturen en allemaal realtime antwoord verwachten.&lt;/strong&gt; Of: support-chat onder Test J-achtige load (1.5 rps van 4k-prompts). Dat kan technisch draaien (geen failures), maar P99 TTFT van 6 seconden is een grensgeval voor chat.&lt;/p&gt;
&lt;h2&gt;Wat deze tests niet zeggen&lt;/h2&gt;
&lt;p&gt;Dit is geen MoE-vs-dense vergelijking. Dat wil ik apart testen, en dan niet alleen met throughput. Als je MoE en dense vergelijkt, moet je ook prompts testen: samenvatten, codevragen, tool-keuze, ticket-classificatie, lang contextstuk met vervolgstappen. Anders meet je alleen hoe hard de motor draait, niet of hij de goede kant op rijdt.&lt;/p&gt;
&lt;p&gt;Dit is ook geen test met prefix caching aan. Dat is bewust. Ik wilde de rauwe prefill-kosten zien, niet een benchmark die mooier wordt omdat prompts op elkaar lijken. In een volgend stuk gaat dat erbij: diezelfde 8k en 25k context-runs en de open-loop tests met &lt;code&gt;--enable-prefix-caching&lt;/code&gt;. Mijn vermoeden: Test H en J profiteren matig (random data, weinig overlap), Test I profiteert behoorlijk (echte gesprekken hebben overlappende system prompts en context), en Run F gaat substantieel sneller. Maar dat moet gemeten worden.&lt;/p&gt;
&lt;h2&gt;Waar ik land&lt;/h2&gt;
&lt;p&gt;Mijn verwachting vooraf was dat de DGX Spark met dit MoE-model eerder zou vollopen bij grote context windows. Dat gebeurde, maar anders dan ik dacht.&lt;/p&gt;
&lt;p&gt;Geheugen was niet de showstopper. Run B haalde 20 gebruikers met 25k context zonder OOM. Test J overleefde 1.5 req/s zonder een enkele failed request. De praktische grens zat altijd in prefill-latency, niet in capaciteit.&lt;/p&gt;
&lt;p&gt;En na negen tests blijkt: dat is eigenlijk de enige grens die je voelt.&lt;/p&gt;
&lt;p&gt;Decode/user is bijna een constante voor deze machine. Tussen 9 en 12 tokens per seconde bij tien gelijktijdige gebruikers, in zes verschillende closed-loop workloads. In open-loop met realistische ShareGPT-prompts: 10.5 t/s/user. Pas bij 16k context of bij synthetische pieken van 25+ concurrent valt dat onder de 7 t/s.&lt;/p&gt;
&lt;p&gt;Wat varieert is hoe lang iemand wacht voordat de tekst begint. Op 256 prompttokens is dat een halve seconde, ook met tien gebruikers. Op 2048 prompttokens met vijf turns gemiddeld 2.1 seconden. Op 8192 prompttokens met tien gebruikers acht seconden. Op 25k met tien gebruikers 35 seconden. Op realistische 0.3 rps ShareGPT-belasting: 353 milliseconden voor de mediaan, 637 milliseconden voor de unlucky 1%.&lt;/p&gt;
&lt;p&gt;En zodra je het systeem boven zijn capaciteit duwt, schaalt &apos;ie niet, hij queue&apos;t. Test J liet zien dat 1.5 req/s target wordt gethrottled tot 0.26 achieved, met de pijn volledig in de tail (P99 6.2s) terwijl de mediaan stabiel blijft. Voor on-prem AI is dat de beste failure-mode die je kunt hopen: niemand crasht, sommigen wachten langer.&lt;/p&gt;
&lt;p&gt;Dat is geen &quot;kan deze machine het wel of niet&quot;. Dat is &quot;kies de workload die past bij wat de gebruiker verwacht, en accepteer dat 1% van de requests een onaangename wachttijd heeft op piekmomenten&quot;.&lt;/p&gt;
&lt;p&gt;Voor één tot drie gebruikers met grote context is hij bruikbaar. Voor tien gebruikers met middelgrote context is hij prima. Voor tien gebruikers met multi-turn gesprekken is hij eigenlijk op zijn best. Voor een 25-persoons-kantoor met realistische prompts en organische arrival pattern is hij verbluffend goed: sub-seconde TTFT voor 99% van requests, gemeten op echte conversation-data.&lt;/p&gt;
&lt;p&gt;Voor agent-flows met lange outputs is hij sterk. Voor twintig gelijktijdige 25k-prompts of voor 1.5 rps oversubscribe is het geen realtime chat meer. Daar moet je queue&apos;en, prefix caching aanzetten, of dat type werk anders routeren.&lt;/p&gt;
&lt;p&gt;Twee methodes meten twee dingen. Closed-loop benchmarks tonen wat de machine kán. Open-loop replay toont wat de gebruiker voelt. De DGX Spark is een sterke lokale AI-machine voor kantoorwerk, zolang je weet welke knop bepaalt wat je voelt.&lt;/p&gt;
&lt;p&gt;Decode verkoopt de benchmark. Prefill bepaalt de ervaring. En zodra je de plank voorbij gaat, queue&apos;t de Spark in plaats van te breken, en dat is het derde cijfer dat een on-prem-keuze moet kunnen lezen.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Ik zette een 24/7 assistent op een Raspberry Pi</title>
    <id>https://djangodevreng.nl/blog/openclaw-op-raspberry-pi/</id>
    <link rel="alternate" type="text/html" href="https://djangodevreng.nl/blog/openclaw-op-raspberry-pi/"/>
    <published>2026-05-01T00:00:00.000Z</published>
    <updated>2026-05-05T00:00:00.000Z</updated>
    <author><name>Django de Vreng</name><uri>https://djangodevreng.nl/over/</uri></author>
    <category term="on-prem"/>
    <summary>Een build-log over OpenClaw op een Raspberry Pi 5: Slack als interface, GPT-5.5 als model, en de Pi als always-on agent-laag naast de DGX Spark.</summary>
    <content type="html">&lt;p&gt;Ik wilde geen betere chatbot. Ik wilde een agent die uit zichzelf werk kan oppakken: het internet op, tickets lezen, een repo induiken, een eerste voorstel voor code-wijzigingen maken en daarna terugrapporteren waar mijn team toch al werkt.&lt;/p&gt;
&lt;p&gt;De ingang moest Slack zijn. Daar zitten de vragen, threads, bestanden en half afgemaakte ideeën. De agent moest tools kunnen gebruiken, bestanden kunnen lezen, branches kunnen klaarzetten en blijven draaien als mijn laptop dichtgaat.&lt;/p&gt;
&lt;p&gt;Daarom draait er nu een Raspberry Pi 5 met 4 GB RAM in mijn netwerk. Daarop draait &lt;a href=&quot;https://openclaw.ai/&quot;&gt;OpenClaw&lt;/a&gt;. Slack ervoor, GPT-5.5 erachter, &lt;a href=&quot;https://tailscale.com/&quot;&gt;Tailscale&lt;/a&gt; als toegangspoort wanneer ik niet thuis ben.&lt;/p&gt;
&lt;p&gt;Dat klinkt groter dan het is. De Pi draait geen lokaal taalmodel. OpenClaw gebruikt de Pi als always-on Gateway: de laag die Slack-berichten ontvangt, sessies en workspace-context beheert, een agent-run start, tools beschikbaar maakt en het antwoord weer terugstuurt naar dezelfde thread. Het model draait in deze setup via OpenAI.&lt;/p&gt;
&lt;p&gt;Dat onderscheid is belangrijk. Voor volledig lokale inference gebruik ik &lt;a href=&quot;/dgx-spark/&quot;&gt;de DGX Spark&lt;/a&gt;, en daar schreef ik eerder over in &lt;a href=&quot;/blog/quantization-lokale-llms/&quot;&gt;de quantization-post&lt;/a&gt;. Deze Pi is de agent-laag ernaast: altijd aan, bereikbaar in Slack, dicht bij mijn bestanden en workflows.&lt;/p&gt;
&lt;h2&gt;Het ding dat ik miste&lt;/h2&gt;
&lt;p&gt;Ik gebruik al genoeg AI-tools. Claude Code voor bouwen. ChatGPT voor losse vragen. Voor klantprojecten werk ik met model-API’s of lokale modellen, afhankelijk van wat de data en infrastructuur toelaten.&lt;/p&gt;
&lt;p&gt;De ontbrekende laag zat tussen die tools in: een agent die werk ziet binnenkomen en alvast begint. In Slack kan dat klein starten. Ik typ een rommelige opdracht, de agent leest de repo, pakt de juiste tone-of-voice-regels erbij en komt terug met iets dat ik kan beoordelen.&lt;/p&gt;
&lt;p&gt;Publiceren blijft handwerk. Vertrouwen ook. Het eerste voorwerk mag wel automatisch gebeuren.&lt;/p&gt;
&lt;p&gt;De richting is groter dan drafts schrijven. Ik wil uiteindelijk een ticket kunnen aanwijzen en zeggen: zoek uit wat hier nodig is. De agent leest de context, checkt documentatie, kijkt in de codebase, stelt een aanpak voor en zet eventueel alvast een branch klaar.&lt;/p&gt;
&lt;p&gt;Dat werk blijft vaak liggen omdat het nergens netjes past. Te klein voor een sprint. Te groot om “even tussendoor” te doen. Voor je het weet staat zo’n ticket een week later nog open met dezelfde drie vage opmerkingen eronder.&lt;/p&gt;
&lt;h2&gt;Wat er op de Pi draait&lt;/h2&gt;
&lt;p&gt;De basis is klein:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Raspberry Pi 5, 4 GB RAM&lt;/li&gt;
&lt;li&gt;OpenClaw Gateway lokaal op de Pi&lt;/li&gt;
&lt;li&gt;OpenAI GPT-5.5 als model in deze setup&lt;/li&gt;
&lt;li&gt;Slack als interface&lt;/li&gt;
&lt;li&gt;Tailscale voor remote toegang&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;De Pi is hier vooral beschikbaar. Dat is zijn talent.&lt;/p&gt;
&lt;p&gt;OpenClaw knoopt de lagen aan elkaar: channel, sessie, agent-runtime, model-provider en tools. Een Slack-bericht komt binnen via de channel-laag. OpenClaw zet daar een agent-turn van klaar, met de juiste context en tools. De runtime voert die turn uit met het gekozen model. Daarna bezorgt OpenClaw het antwoord weer terug via Slack.&lt;/p&gt;
&lt;p&gt;Op die manier kan dezelfde agent files lezen, shell-commands draaien, webpagina’s ophalen, git-status bekijken of een PR voorbereiden, afhankelijk van welke tools je toestaat. De Pi is dus geen mini-GPU. Hij is de lokale controle-laag.&lt;/p&gt;
&lt;p&gt;Tailscale houdt het praktisch. Ik kan bij de Pi als ik onderweg ben. Een publieke poort openzetten voor een build-log zou wat veel eer zijn.&lt;/p&gt;
&lt;h2&gt;Slack als werkvloer&lt;/h2&gt;
&lt;p&gt;Slack was de makkelijkste keuze omdat ik er al de hele dag in zit. Mijn bedrijven hebben workspaces, channels, threads, bestanden en notificaties. Een extra dashboard zou vooral extra tabblad-stof verzamelen.&lt;/p&gt;
&lt;p&gt;Voor mij is dit de kern: de agent moet beschikbaar zijn waar het team werkt. Als hij iets uitzoekt op basis van een ticket, wil ik het antwoord terug in dezelfde flow. De analyse hoort naast de vraag, in dezelfde thread.&lt;/p&gt;
&lt;p&gt;OpenClaw ondersteunt meer ingangen dan Slack. Het werkt ook via onder meer Telegram, Microsoft Teams, Google Chat, WhatsApp, Discord en iMessage. Slack is mijn ingang. Het bredere idee is agents op bestaande communicatiekanalen, met tools en geheugen erachter.&lt;/p&gt;
&lt;h2&gt;De installatie was minder spannend dan gehoopt&lt;/h2&gt;
&lt;p&gt;De installatie was minder dramatisch dan ik had verwacht. Dat is prettig voor mij en slecht voor het genre “build-log met vuur”.&lt;/p&gt;
&lt;p&gt;De meeste tijd zat in lezen. OpenClaw heeft veel documentatie, en je moet even uitzoeken welk deel bij jouw setup hoort. Slack, Gateway, agents, runtimes, channels, tools: het zijn losse lagen die uiteindelijk samen één assistent vormen.&lt;/p&gt;
&lt;p&gt;Slack instellen kostte ook aandacht. Je bepaalt welke gebruikers de bot mogen DM’en, in welke channels hij mag praten en of hij in groepschannels op elk bericht reageert of alleen bij een @mention. Dat zijn geen details voor later. Je moet die regels vooraf kiezen en met je team delen, anders snapt niemand wanneer de agent wel of niet mee gaat doen.&lt;/p&gt;
&lt;p&gt;Na ongeveer twee uur werkte het. Ik typte in Slack, de Pi ving het bericht op, OpenClaw startte een run, GPT-5.5 dacht mee en het antwoord kwam terug in dezelfde thread.&lt;/p&gt;
&lt;p&gt;Een hoop plumbing voor een tekstbericht. Alleen kan dat tekstbericht nu wel tools gebruiken.&lt;/p&gt;
&lt;h2&gt;Eerste test: deze site&lt;/h2&gt;
&lt;p&gt;De eerste plek waar ik dit voor gebruik is djangodevreng.nl.&lt;/p&gt;
&lt;p&gt;De inhoud moet uit echt werk komen: wat we bouwden, wat brak, welke keuzes bleven staan, waar een tool mooi leek tot hij onder load begon te zweten. De agent mag helpen met vorm en uitvoering.&lt;/p&gt;
&lt;p&gt;Zodra die ruwe input er is, kan hij veel doen. Een dump structureren. Een eerste outline maken. Een draft herschrijven in mijn toon. Marketing-taal eruit halen. Checken of een post klinkt alsof hij uit een generieke LinkedIn-carrousel is gevallen.&lt;/p&gt;
&lt;p&gt;De workflow voor deze site begint meestal rommelig. Ik dump in Slack wat ik wil zeggen: een paar observaties, een half idee, soms alleen feedback op een bestaande post. De agent zoekt daarna de juiste repo erbij, leest de relevante files en pakt de schrijfgids uit de workspace.&lt;/p&gt;
&lt;p&gt;Daarna vraag ik hem om een concrete wijziging: “herschrijf de intro”, “haal de marketing-taal eruit” of “maak deze technische uitleg preciezer”. Hoe scherper de opdracht, hoe bruikbaarder de diff. Hij past de markdown aan op een branch, draait de checks en pusht de wijziging naar een PR.&lt;/p&gt;
&lt;p&gt;Daar begint mijn deel weer. Ik lees de diff, geef feedback in Slack en laat hem de volgende ronde verwerken. Pas als de post klopt, merge ik zelf. De agent doet het voorbereidende werk. Ik blijf verantwoordelijk voor wat er live gaat.&lt;/p&gt;
&lt;p&gt;Een agent die publiceert zonder dat ik kijk, is geen workflow. Dat is een gokautomaat met commit-rechten.&lt;/p&gt;
&lt;h2&gt;Waarom dit anders voelt dan chat&lt;/h2&gt;
&lt;p&gt;Veel AI-tools voelen alsof je je werk naar een chatvenster moet brengen. Je kopieert context, plakt logs, legt voor de derde keer uit waar het repo-pad staat en hoopt dat het model doet alsof het erbij was.&lt;/p&gt;
&lt;p&gt;Deze setup draait dichter op de context. De agent kan zelfstandig beginnen omdat hij de workspace ziet, de branch kent, de regels voor de site kan lezen en weet welke checks gedraaid moeten worden.&lt;/p&gt;
&lt;p&gt;Dat maakt hem nog geen autonome developer. Hij schuift vooral het eerste saaie stuk naar voren.&lt;/p&gt;
&lt;p&gt;Voor mij is dat de interessante agent-laag: alvast meelezen, een eerste versie maken, aanwijzen waar het wringt. Een junior collega met oneindig geduld, geen agenda en soms een verontrustend vertrouwen in zijn eigen zinnen.&lt;/p&gt;
&lt;p&gt;Ik ga hier nog een losse post over schrijven, want OpenClaw verdient eigenlijk meer uitleg dan in deze build-log past. Welke kanalen het ondersteunt. Welke tools je eraan hangt. En vooral: waarom dit interessant wordt.&lt;/p&gt;
&lt;p&gt;We schuiven langzaam van AI als sparringpartner naar AI als uitvoerende laag. De afgelopen jaren praatten we vooral met modellen: brainstormen, samenvatten, herschrijven, meedenken. Dat blijft nuttig, maar het echte verschil zit in agents die werk kunnen uitvoeren in bestaande systemen.&lt;/p&gt;
&lt;p&gt;Agents nemen het werk van mensen niet één-op-één over. Zo simpel is het niet, gelukkig. De verschuiving zit in workflows: tickets uitzoeken, context verzamelen, drafts voorbereiden, code-wijzigingen voorstellen, checks draaien, terugrapporteren. Werk waar je normaal iemand voor vraagt omdat het tijd kost, terwijl het weinig diepe menselijke oordeelskracht nodig heeft.&lt;/p&gt;
&lt;h2&gt;Volgende stap: tickets en MCP&lt;/h2&gt;
&lt;p&gt;De volgende stap is &lt;a href=&quot;https://modelcontextprotocol.io/&quot;&gt;MCP&lt;/a&gt;. Ik wil tools netjes aan deze workflow hangen, te beginnen met Linear.&lt;/p&gt;
&lt;p&gt;Het scenario is simpel: er komt een ticket binnen, de agent leest de relevante repo-context, zoekt de waarschijnlijke files, schrijft een korte analyse en komt terug met een voorstel of een lijst vragen.&lt;/p&gt;
&lt;p&gt;Autonoom mergen sla ik over. Eerst wil ik weten waar de grens ligt tussen nuttige voorbereiding en gevaarlijke dadendrang.&lt;/p&gt;
&lt;p&gt;Daarna komen GitHub, repo-context en misschien een lokale knowledge base. Sommige context hoort gewoon beschikbaar te zijn, zonder dat ik hem steeds opnieuw in een prompt plak.&lt;/p&gt;
&lt;h2&gt;Workflow voor workflow&lt;/h2&gt;
&lt;p&gt;Deze Pi-setup is klein. Dat is precies waarom ik hem prettig vind.&lt;/p&gt;
&lt;p&gt;Klein genoeg om te begrijpen. Echt genoeg om iets van te leren. Goedkoop genoeg om altijd aan te laten. Lokaal genoeg om dichtbij mijn werk te staan, zonder te doen alsof het model zelf lokaal draait.&lt;/p&gt;
&lt;p&gt;Voor productie-AI bij klanten is dit hooguit één laag in de architectuur. Voor mijn eigen workflow werkt hij al prima: Slack als ingang, OpenClaw als Gateway, OpenAI als model-provider, GitHub als plek waar werk klaar komt te staan.&lt;/p&gt;
&lt;p&gt;De komende tijd ga ik hier lekker mee rommelen. Eerst deze site. Daarna tickets. Daarna MCP-tools. Daarna waarschijnlijk iets waarvan ik nu nog denk dat het te specifiek is om te automatiseren.&lt;/p&gt;
&lt;p&gt;Dat is de interessante route: workflow voor workflow vervangen door een agent die voorwerk doet, context verzamelt en voorstellen klaarzet. Stap voor stap bouw ik mijn OpenClaw-setup uit. Gewoon als praktische assistent die steeds iets meer werk uit mijn handen haalt.&lt;/p&gt;
&lt;p&gt;En als het stukloopt, staat hij dichtbij genoeg om de stekker eruit te trekken.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Wat quantization werd na drie benchmarkrondes</title>
    <id>https://djangodevreng.nl/blog/quantization-lokale-llms/</id>
    <link rel="alternate" type="text/html" href="https://djangodevreng.nl/blog/quantization-lokale-llms/"/>
    <published>2026-05-01T00:00:00.000Z</published>
    <updated>2026-05-05T00:00:00.000Z</updated>
    <author><name>Django de Vreng</name><uri>https://djangodevreng.nl/over/</uri></author>
    <category term="on-prem"/>
    <summary>Een praktische terugblik op quantization op de DGX Spark: wat BF16, FP8 en NVFP4 doen met geheugen, snelheid en tail-latency, na drie benchmarkrondes met vLLM.</summary>
    <content type="html">&lt;p&gt;Dit was de eerste blogpost die ik live zette op deze site. Toen ik hem schreef, had ik net twee modellen op de DGX Spark draaien: Gemma-4-26B-A4B-it, een MoE-model, en een 31B dense model. Allebei lokaal, allebei via &lt;a href=&quot;https://docs.vllm.ai/&quot;&gt;vLLM&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Op dat moment was quantization voor mij nog vooral een vraag. Ik kende de term, ik snapte ongeveer waar het over ging, maar ik had nog te weinig eigen metingen om er hard iets over te zeggen.&lt;/p&gt;
&lt;p&gt;Inmiddels zijn we een paar benchmarkrondes verder. Eerst &lt;a href=&quot;/blog/gemma-4-dgx-spark-benchmarks/&quot;&gt;Gemma-4 op de DGX Spark&lt;/a&gt;. Daarna &lt;a href=&quot;/blog/gemma-4-nvfp4-vs-bf16-dgx-spark/&quot;&gt;NVFP4 vs BF16 op datzelfde model&lt;/a&gt;. En daarna &lt;a href=&quot;/blog/nemotron-3-dgx-spark-precisies/&quot;&gt;Nemotron-3 in BF16, FP8 en NVFP4&lt;/a&gt;. Samen vormen ze de gids &lt;a href=&quot;/dgx-spark/&quot;&gt;LLMs draaien op de DGX Spark&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Daardoor is deze post eigenlijk veranderd. Hij gaat minder over “wat is quantization?” en meer over wat er gebeurt als quantization van een model-card-term verandert in een architectuurkeuze.&lt;/p&gt;
&lt;h2&gt;De eerste vraag was gewoon: past het?&lt;/h2&gt;
&lt;p&gt;Bij hosted modellen begin je vaak bij kwaliteit. Welke is slimmer, welke volgt instructies beter, welke schrijft betere code?&lt;/p&gt;
&lt;p&gt;Lokaal begin je botter: past het?&lt;/p&gt;
&lt;p&gt;Dat klinkt bijna te simpel, maar op eigen hardware is dat de eerste grens. Een modelnaam en een model card zijn papierwerk; de gewichten moeten echt in memory. Daarna wil je ook nog context kwijt, meerdere requests tegelijk verwerken, en liefst binnen seconden iets terugzien.&lt;/p&gt;
&lt;p&gt;Bij de DGX Spark voel je dat meteen. Je ziet vLLM bezig: downloaden, laden, memory reserveren, warm worden. Daarna pas begint de discussie over throughput, latency en bruikbaarheid.&lt;/p&gt;
&lt;p&gt;Dat is een ander gevoel dan een API-call naar Claude of GPT-5.5. Daar bestaat de infrastructuur vooral als abstractie. Je stuurt tekst heen en krijgt tekst terug. Bij lokaal draaien zie je de achterkant. Soms is dat leuk. Soms duurt het vooral lang.&lt;/p&gt;
&lt;p&gt;Precies daar komt quantization binnen.&lt;/p&gt;
&lt;h2&gt;Mijn eerste beeld was te smal&lt;/h2&gt;
&lt;p&gt;Mijn eerste werkdefinitie was netjes genoeg: quantization slaat modelgewichten compacter op. FP16 of BF16 gebruikt meer ruimte dan 8-bit of 4-bit. Minder bits betekent minder geheugen. Minder geheugen betekent dat een model eerder past, sneller geladen kan worden, of ruimte overlaat voor meer context en meer requests.&lt;/p&gt;
&lt;p&gt;Dat klopt, maar het is te klein.&lt;/p&gt;
&lt;p&gt;Na de benchmarks kijk ik er anders naar. De vraag “past dit model op deze machine?” is pas het begin. Daarna komt de vraag wat je met die machine kunt doen zodra het model past.&lt;/p&gt;
&lt;p&gt;Eén request draaien is de demo. Meerdere requests draaien is de workflow.&lt;/p&gt;
&lt;p&gt;Daar zit voor mij het verschil. Een lokaal model dat één prompt netjes beantwoordt, is leuk. Een lokaal model dat meerdere gebruikers, agents of taken tegelijk aankan zonder dat latency instort, wordt bruikbaar.&lt;/p&gt;
&lt;p&gt;Quantization bepaalt dus hoeveel speelruimte je overhoudt.&lt;/p&gt;
&lt;h2&gt;vLLM maakt het concreet&lt;/h2&gt;
&lt;p&gt;Ik gebruik vLLM omdat één request tegelijk niet de situatie is waar ik naartoe wil. Een lokale chatbot starten is prima om te testen, maar zodra je over agents praat krijg je ander verkeer.&lt;/p&gt;
&lt;p&gt;Een agent haalt context op, roept tools aan, splitst werk op, vraagt soms parallel dingen uit en wacht tussendoor op resultaten. Ondertussen wil je dat een tweede request niet hoeft te wachten tot de eerste helemaal klaar is.&lt;/p&gt;
&lt;p&gt;Daar wordt serving belangrijk.&lt;/p&gt;
&lt;p&gt;vLLM is de laag die dit concreet maakt: batching, scheduling, memory efficiënter gebruiken en meerdere concurrent requests afhandelen. Het maakt ook zichtbaar dat lokaal draaien een systeem is. Het model, de precisie, de context-lengte, het aantal gelijktijdige requests en de scheduler trekken allemaal aan dezelfde hardware.&lt;/p&gt;
&lt;p&gt;Dat was voor mij de eerste echte les. Quantization is geen los trucje onderaan de stack. Het beïnvloedt hoe de hele stack zich gedraagt.&lt;/p&gt;
&lt;h2&gt;BF16 voelde eerst als de veilige keuze&lt;/h2&gt;
&lt;p&gt;Als je nog niet gemeten hebt, voelt hogere precisie al snel veiliger. BF16 klinkt degelijk. Meer detail, minder kwaliteitsrisico, minder kans dat het model vreemd gedrag gaat vertonen.&lt;/p&gt;
&lt;p&gt;Dat was ook mijn eerste reflex. Als de hardware het aankan, waarom zou je dan lager gaan zitten?&lt;/p&gt;
&lt;p&gt;De metingen maakten dat minder vanzelfsprekend. Op de DGX Spark bleek BF16 in de latere runs vaak de minst praktische keuze. BF16 is niet “slecht”; de hardware en workload wegen alleen zwaarder dan het nette gevoel van hogere precisie.&lt;/p&gt;
&lt;p&gt;Als een lagere precisie veel meer ruimte geeft voor concurrency, context of throughput, dan kan dat in de praktijk beter zijn. Zeker voor workloads waar snelheid en gelijktijdigheid zwaarder tellen dan het laatste beetje modelkwaliteit.&lt;/p&gt;
&lt;p&gt;Dat vond ik de interessante draai. De hoogste precisie voelt intuïtief als de serieuze keuze. Op deze machine was dat vaak vooral de duurste keuze.&lt;/p&gt;
&lt;h2&gt;NVFP4 veranderde de Spark&lt;/h2&gt;
&lt;p&gt;De grootste verschuiving kwam bij NVFP4. In de benchmarkposts en de &lt;a href=&quot;/arena/&quot;&gt;arena&lt;/a&gt; zie je dat NVFP4 de DGX Spark voor veel workloads bijna verdubbelt. Dat is geen kleine optimalisatie meer. Dat verandert wat je met dezelfde machine durft te proberen.&lt;/p&gt;
&lt;p&gt;Voor on-prem AI is dat precies het punt. Je koopt hardware voor workflow, niet voor één mooie prompt. Je wilt weten hoeveel echt werk je op die doos kwijt kunt.&lt;/p&gt;
&lt;p&gt;Als NVFP4 betekent dat je meer requests tegelijk kunt draaien, meer ruimte overhoudt en minder snel tegen geheugenlimieten botst, dan is dat geen detail in een tabel. Dan verandert je architectuur.&lt;/p&gt;
&lt;p&gt;Je kunt taken anders verdelen. Je kunt meer lokaal houden. Je kunt sneller experimenteren met agent-stappen die anders meteen naar een hosted model zouden gaan.&lt;/p&gt;
&lt;p&gt;Daarmee werd quantization voor mij praktischer dan ik vooraf dacht. Het ging niet meer over een kleiner model, maar over een andere workflow mogelijk maken.&lt;/p&gt;
&lt;h2&gt;FP8 had een ander soort voordeel&lt;/h2&gt;
&lt;p&gt;FP8 zat niet simpelweg “tussen BF16 en NVFP4 in”. In de Nemotron-3-runs werd vooral tail-latency interessant. Dat trekt minder aandacht dan een grote throughput-sprong, maar in gebruik telt het minstens zo hard.&lt;/p&gt;
&lt;p&gt;Gemiddelden liegen niet per se, maar ze stellen je gerust op de verkeerde momenten. Een workflow voelt traag door de paar requests die blijven hangen.&lt;/p&gt;
&lt;p&gt;Daarom is tail-latency zo praktisch. Als een agent-workflow uit meerdere stappen bestaat, stapelen vertragingen. Eén trage stap is vervelend. Drie trage stappen achter elkaar voelen alsof het systeem nadenkt over zijn levenskeuzes.&lt;/p&gt;
&lt;p&gt;FP8 lijkt in die hoek nuttig: minder extreem dan NVFP4, maar interessant wanneer voorspelbaarheid belangrijker is dan maximaal zoveel mogelijk tegelijk draaien.&lt;/p&gt;
&lt;p&gt;Dat is de nuance die ik in de eerste versie nog niet had. Precision is geen ladder waarbij lager altijd sneller en slechter is. Het is een set keuzes met verschillende trade-offs.&lt;/p&gt;
&lt;h2&gt;Kwaliteit blijft de open vraag&lt;/h2&gt;
&lt;p&gt;De benchmarks geven antwoord op memory, throughput en latency. Ze zeggen minder over gedrag.&lt;/p&gt;
&lt;p&gt;Dat blijft de lastige kant van quantization. Je ziet kwaliteitsverlies niet altijd netjes in één metric. Soms wordt een antwoord vlakker. Soms gaat code net vaker mis. Soms kiest een agent de verkeerde tool. Soms merk je niets, tot je taak net anders is dan je testset.&lt;/p&gt;
&lt;p&gt;Voor simpele taken kan dat prima zijn. Denk aan classificatie, routing, eerste samenvattingen, embeddings of een lichte pass over interne documenten. Daar hoeft niet altijd het zwaarste model op.&lt;/p&gt;
&lt;p&gt;Voor code-generatie en agent-workflows ligt dat gevoeliger. Kleine fouten stapelen. Eén matige redenering is vervelend. Een verkeerde tool-call is een ander soort probleem.&lt;/p&gt;
&lt;p&gt;Daarom wil ik quantized modellen niet alleen benchmarken op snelheid. Ik wil weten waar ik ze durf in te zetten.&lt;/p&gt;
&lt;p&gt;Dat is een andere vraag. En eerlijk gezegd ook de enige die telt.&lt;/p&gt;
&lt;h2&gt;De split wordt duidelijker&lt;/h2&gt;
&lt;p&gt;Mijn verwachting is nog steeds dat de beste on-prem setup een mix wordt. “Alles lokaal” klinkt stoer, maar meestal ook onnodig streng.&lt;/p&gt;
&lt;p&gt;De logische split wordt eerder:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;embeddings lokaal&lt;/li&gt;
&lt;li&gt;gevoelige documenten lokaal&lt;/li&gt;
&lt;li&gt;routing en classificatie lokaal&lt;/li&gt;
&lt;li&gt;simpele agent-stappen lokaal&lt;/li&gt;
&lt;li&gt;zware redenering naar Claude of GPT-5.5 wanneer dat nodig is&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Quantization bepaalt hoe groot dat lokale deel kan worden. Hoe meer taken lokaal betrouwbaar en snel genoeg draaien, hoe minder je naar buiten hoeft te sturen.&lt;/p&gt;
&lt;p&gt;Dat is voor klantwerk belangrijk. Niet omdat elke token per se binnen vier muren moet blijven, maar omdat sommige data daar wel hoort te blijven. En omdat latency, &lt;a href=&quot;/kosten-dgx-spark/&quot;&gt;kosten&lt;/a&gt; en controle in productie gewoon meetellen.&lt;/p&gt;
&lt;p&gt;Een on-prem setup is geen geloofsovertuiging. Het is een verdeling van werk.&lt;/p&gt;
&lt;h2&gt;Wat ik nu anders zou meten&lt;/h2&gt;
&lt;p&gt;In de eerste versie van deze post had ik vooral een lijst vragen. Hoe lang duurt downloaden? Hoe lang duurt laden? Hoeveel VRAM blijft over? Hoeveel concurrent requests kan ik sturen voordat latency vervelend wordt?&lt;/p&gt;
&lt;p&gt;Die vragen blijven nuttig, maar ze zijn het begin. Hoe ik die metingen op de Spark precies opzet, staat in de &lt;a href=&quot;/arena/methodologie/&quot;&gt;arena-methodologie&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Nu zou ik per precisie drie dingen naast elkaar leggen:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;systeemgedrag: laden, memory, throughput, latency en tail-latency&lt;/li&gt;
&lt;li&gt;modelgedrag: Nederlandse output, codevragen, langere context, tool-use&lt;/li&gt;
&lt;li&gt;workflowgeschiktheid: welke taken durf ik hiermee lokaal te draaien&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Dat laatste mis je snel als je alleen naar benchmarktabellen kijkt. Een model kan technisch draaien en toch onhandig zijn. Of juist minder mooi scoren, maar precies goed genoeg zijn voor routing of samenvatten.&lt;/p&gt;
&lt;p&gt;Voor productie maakt dat verschil uit. Niemand koopt iets aan “tokens per seconde” alleen. Je koopt ruimte in een workflow.&lt;/p&gt;
&lt;h2&gt;Wat ik nu snap&lt;/h2&gt;
&lt;p&gt;Mijn werkdefinitie is verschoven.&lt;/p&gt;
&lt;p&gt;Quantization maakt een model kleiner, maar dat is slechts de ingang. Het verandert hoeveel werk je uit dezelfde hardware krijgt, welke latency je accepteert en welke taken je lokaal durft te houden.&lt;/p&gt;
&lt;p&gt;Op de DGX Spark lijkt de hoogste precisie zelden automatisch de beste keuze. NVFP4 maakt de machine voor veel workloads veel bruikbaarder. FP8 is interessant wanneer tail-latency belangrijk wordt. BF16 blijft nuttig als referentiepunt, maar voelt op deze hardware minder vaak als de praktische default.&lt;/p&gt;
&lt;p&gt;Dat is precies waarom ik deze metingen wilde doen. Een universele ranglijst helpt weinig; betere architectuurkeuzes wel.&lt;/p&gt;
&lt;p&gt;De vraag is niet: welk quantization-niveau wint?&lt;/p&gt;
&lt;p&gt;De vraag is: welke taak mag op welke precisie, op welke machine, met hoeveel risico?&lt;/p&gt;
&lt;p&gt;Daar begint on-prem AI voor mij interessant te worden: bij de verdeling van werk.&lt;/p&gt;
</content>
  </entry>
</feed>
