<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="fr">
  <title>Django de Vreng</title>
  <subtitle>Blog personnel de Django de Vreng sur l&apos;IA en production, les agents, MCP et l&apos;on-prem.</subtitle>
  <id>https://djangodevreng.nl/fr/blog/</id>
  <link rel="self" type="application/atom+xml" href="https://djangodevreng.nl/fr/atom.xml"/>
  <link rel="alternate" type="text/html" href="https://djangodevreng.nl/fr/blog/"/>
  <updated>2026-06-23T00:00:00.000Z</updated>
  <author><name>Django de Vreng</name><uri>https://djangodevreng.nl/fr/a-propos/</uri></author>
  <entry>
    <title>Gemma-4 v23 sur le DGX Spark</title>
    <id>https://djangodevreng.nl/fr/blog/gemma-4-v23-dgx-spark/</id>
    <link rel="alternate" type="text/html" href="https://djangodevreng.nl/fr/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/fr/a-propos/</uri></author>
    <category term="on-prem"/>
    <summary>Nouveaux runs vLLM v0.23.0 pour Gemma-4 sur DGX Spark : BF16, NVFP4 et MTP comparés sur decode, TTFT, tails et limites pratiques pour agents locaux.</summary>
    <content type="html">&lt;p&gt;NVFP4 reste le choix pratique par défaut pour Gemma-4 sur le DGX Spark, mais MTP devient la position intermédiaire intéressante. Dans les nouveaux runs vLLM v0.23.0, NVFP4 reste devant en chat et en multi-turn, tandis que MTP dépasse nettement le run BF16 sans passer au re-quant NVIDIA.&lt;/p&gt;
&lt;p&gt;J&apos;ai relancé la même famille Gemma-4-26B-A4B sur le &lt;a href=&quot;/fr/dgx-spark/&quot;&gt;DGX Spark&lt;/a&gt;, cette fois avec &lt;code&gt;vllm/vllm-openai:v0.23.0-aarch64-cu129-ubuntu2404&lt;/code&gt;. Les données brutes sont dans le repo de benchmark au 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;. L&apos;Arena a maintenant trois nouvelles entrées : BF16 v23, MTP v23 et NVFP4 v23.&lt;/p&gt;
&lt;p&gt;Le précédent article Gemma parlait surtout du prix du contexte en BF16. Ce run répond à une autre question : qu&apos;est-ce qui change quand la même machine, la même famille de modèles et les mêmes workloads tournent sur vLLM v0.23.0, avec trois profils de serving côte à côte ?&lt;/p&gt;
&lt;h2&gt;Le setup resté identique&lt;/h2&gt;
&lt;p&gt;Les trois runs utilisent la même machine et la même forme de benchmark :&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Composant&lt;/th&gt;
&lt;th&gt;Valeur&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;désactivé&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;Les trois profils :&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Profil&lt;/th&gt;
&lt;th&gt;Modèle&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 utilise donc le même chemin de modèle Google que BF16, mais servi avec le profil MTP. NVFP4 utilise le re-quant NVIDIA. Cette distinction compte, sinon tu compares discrètement deux choses à la fois : le comportement de l&apos;engine et l&apos;artefact modèle.&lt;/p&gt;
&lt;h2&gt;Chat : NVFP4 devant, MTP rattrape BF16&lt;/h2&gt;
&lt;p&gt;La première comparaison utile est le Run C : 1024 prompttokens, 1024 outputtokens, dix requêtes concurrentes. C&apos;est une forme de chat propre : pas trivialement courte, mais pas non plus un monstre de contexte.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Profil&lt;/th&gt;
&lt;th&gt;TTFT c10&lt;/th&gt;
&lt;th&gt;Decode/user c10&lt;/th&gt;
&lt;th&gt;Decode total 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;C&apos;est le coeur du résultat. MTP donne environ 55 pourcent de decode par utilisateur en plus que BF16 sur ce run chat. NVFP4 reste au-dessus, mais l&apos;écart entre MTP et NVFP4 est beaucoup plus petit que l&apos;écart entre BF16 et MTP.&lt;/p&gt;
&lt;p&gt;La latence jusqu&apos;au premier token reste dans le même ordre de grandeur. NVFP4 est le plus rapide ici, MTP n&apos;est pas plus rapide que BF16 en TTFT. Cela colle au pattern : ces profils changent surtout le débit de decode. Le prefill reste du travail.&lt;/p&gt;
&lt;h2&gt;Le multi-turn est l&apos;endroit où NVFP4 s&apos;ouvre vraiment&lt;/h2&gt;
&lt;p&gt;Le Run E est pour moi le test closed-loop le plus proche de la production : cinq tours par conversation, dix conversations en parallèle, 2048 tokens de départ et 512 outputtokens par tour.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Profil&lt;/th&gt;
&lt;th&gt;TTFT c10&lt;/th&gt;
&lt;th&gt;Decode/user c10&lt;/th&gt;
&lt;th&gt;Decode total 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;C&apos;est là que NVFP4 devient vraiment naturel. 182.90 tok/s au total pour dix conversations multi-turn sur un Spark, ce n&apos;est pas un chiffre de démo, c&apos;est un profil d&apos;inference locale utilisable.&lt;/p&gt;
&lt;p&gt;MTP reste utile. Pas comme gagnant, mais comme réponse à : et si je veux garder l&apos;artefact Google BF16 tout en obtenant plus de decode ? Dans ce cas, 16.57 tok/s par utilisateur change beaucoup par rapport à 10.69.&lt;/p&gt;
&lt;h2&gt;Output long : plus de tokens, pas automatiquement plus de douleur&lt;/h2&gt;
&lt;p&gt;Pour les agents et la génération de code, le Run G compte : 256 prompttokens, 4096 outputtokens, dix requêtes concurrentes. Cette forme indique si les longues générations font s&apos;écrouler la machine.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Profil&lt;/th&gt;
&lt;th&gt;TTFT c10&lt;/th&gt;
&lt;th&gt;Decode/user c10&lt;/th&gt;
&lt;th&gt;Decode total 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;Note la forme un peu étrange : NVFP4 a le meilleur decode par utilisateur, mais le decode total a beaucoup plus de variance. MTP est plus bas par utilisateur, mais plus stable dans ce run précis. Je ne regarderais donc pas seulement la barre la plus haute. Pour des agents, tu veux aussi de la prévisibilité, surtout si plusieurs runs continuent de streamer longtemps.&lt;/p&gt;
&lt;h2&gt;25k de contexte reste le mur&lt;/h2&gt;
&lt;p&gt;Quantization et MTP ne changent pas le fait qu&apos;un grand contexte est surtout du prefill. À 25k prompttokens et c10, cela donne ceci :&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Profil&lt;/th&gt;
&lt;th&gt;TTFT c10&lt;/th&gt;
&lt;th&gt;Decode/user c10&lt;/th&gt;
&lt;th&gt;Decode total 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;Ce n&apos;est plus du chat. Avec dix prompts concurrents de 25k, tu attends en moyenne entre 39 et 46 secondes avant le premier token. NVFP4 aide encore un peu le decode, mais l&apos;utilisateur ressent surtout une fenêtre vide avant que le stream démarre.&lt;/p&gt;
&lt;p&gt;C&apos;est la même leçon que dans le précédent &lt;a href=&quot;/fr/blog/gemma-4-dgx-spark-benchmarks/&quot;&gt;article de benchmark Gemma-4&lt;/a&gt;, maintenant avec vLLM v0.23.0 en plus : le contexte n&apos;est pas un champ d&apos;entrée gratuit. Si tu fais porter 25k tokens à un agent local, tu le paies en TTFT.&lt;/p&gt;
&lt;h2&gt;Open-loop : la forme bureau reste utilisable&lt;/h2&gt;
&lt;p&gt;Les tests open-loop comptent plus pour le ressenti que les tableaux closed-loop. Ils envoient les requêtes selon un pattern d&apos;arrivée au lieu de tout lancer en même temps.&lt;/p&gt;
&lt;h3&gt;H : baseline bureau&lt;/h3&gt;
&lt;p&gt;200 prompts random, request rate 0.3, burstiness 0.7.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Profil&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 est nettement plus agréable ici. Pas grâce à un output throughput beaucoup plus élevé, car 139.05 contre 129.92 tok/s n&apos;est pas une révolution. La différence est dans le TPOT : 77.98 ms p95 contre 197.57 ms pour BF16. Le stream paraît beaucoup plus rapide dès qu&apos;il commence.&lt;/p&gt;
&lt;h3&gt;I : replay ShareGPT&lt;/h3&gt;
&lt;p&gt;250 vraies conversations, même request rate.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Profil&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;C&apos;est le meilleur proxy pour le chat normal. Des conversations courtes et réelles. NVFP4 donne un p95 TTFT de 225.09 ms et un p95 TPOT de 45.30 ms. En local, cela ne ressemble pas à un compromis.&lt;/p&gt;
&lt;h3&gt;J : pic du lundi matin&lt;/h3&gt;
&lt;p&gt;300 prompts random, target 1.5 rps, max concurrency 25.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Profil&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;Sous surcharge, NVFP4 reste aussi le plus utilisable. Toutes les requêtes réussissent, mais la queue décide qui ressent la douleur. BF16 et MTP donnent ici des tails moins agréables. MTP a plus d&apos;output throughput que BF16, mais un p95 TTFT et un p95 TPOT plus mauvais. C&apos;est exactement pourquoi je veux voir des percentiles, pas seulement des tokens par seconde.&lt;/p&gt;
&lt;h2&gt;Ce que je mets dans l&apos;Arena&lt;/h2&gt;
&lt;p&gt;J&apos;ai ajouté trois nouvelles entrées Arena au lieu d&apos;écraser les anciennes entrées Gemma-4. Les anciens runs v0.20.1 restent utiles comme points de comparaison historiques. Ces nouvelles entrées sont explicitement v23 :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/fr/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;/fr/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;/fr/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;Le classement court pour mon propre usage :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;NVFP4 v23 pour le chat local, les agents et la charge bureau.&lt;/li&gt;
&lt;li&gt;MTP v23 si tu veux garder l&apos;artefact modèle Google, mais que le decode BF16 est trop lent.&lt;/li&gt;
&lt;li&gt;BF16 v23 comme ligne de contrôle et pour les comparaisons où la précision compte plus que la vitesse de serving.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Pour 25k de contexte, aucun des trois ne règle le vrai problème. Là, il faut travailler sur le budget de prompt, le retrieval, la compaction de mémoire et l&apos;architecture d&apos;agent. Pas espérer qu&apos;un profil de serving fasse disparaître l&apos;attente.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Les trois chiffres d&apos;une DGX Spark rapide</title>
    <id>https://djangodevreng.nl/fr/blog/trois-chiffres-vitesse-dgx-spark/</id>
    <link rel="alternate" type="text/html" href="https://djangodevreng.nl/fr/blog/trois-chiffres-vitesse-dgx-spark/"/>
    <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/fr/a-propos/</uri></author>
    <category term="on-prem"/>
    <summary>Decode, prefill et queueing : trois chiffres décident si une DGX Spark semble rapide sous une vraie charge, et ce sont eux que la plupart des tests oublient.</summary>
    <content type="html">&lt;p&gt;Peux-tu faire tourner sérieusement des large language models en local sur une DGX Spark ? Oui. C&apos;est la réponse ennuyeuse, et c&apos;est aussi celle que te donne chaque test : un nom de modèle, un chiffre, des tokens par seconde, terminé.&lt;/p&gt;
&lt;p&gt;La réponse utile est plus difficile. Un modèle qui gère proprement une seule prompt de démo ne dit rien d&apos;un lundi matin avec dix personnes, un gros contexte, des agent-flows et quelqu&apos;un qui colle un demi-roman dans un ticket. C&apos;est là que ça coince, ou pas. Et ça ne dépend pas de la Spark, ça dépend de ta charge.&lt;/p&gt;
&lt;p&gt;J&apos;ai une Spark dans le lab et j&apos;y ai fait tourner une pile de modèles, en BF16, FP8 et NVFP4. Neuf charges de travail, deux méthodes de mesure, et quelques runs refaits parce que les premiers étaient suspects de bons. Ce qui restait après toute cette mesure n&apos;est pas un tableau de scores. C&apos;est une façon de regarder qui a tenu à chaque fois, et elle est ci-dessous. Les chiffres bruts par modèle sont dans les posts séparés, et le guide complet avec la configuration, le coût et pour qui ça marche est sur &lt;a href=&quot;/fr/dgx-spark/&quot;&gt;Faire tourner des LLMs sur la DGX Spark&lt;/a&gt;. Ce texte parle de cette seule loupe.&lt;/p&gt;
&lt;h2&gt;Ce qu&apos;est vraiment l&apos;engin&lt;/h2&gt;
&lt;p&gt;La DGX Spark est la plus petite machine Blackwell de NVIDIA. Une &lt;a href=&quot;https://www.nvidia.com/en-us/products/workstations/dgx-spark/&quot;&gt;puce GB10&lt;/a&gt;, 128 GB de unified memory, assez petite pour une baie serveur. Pas de carte graphique séparée avec son propre pool mémoire, mais une seule mémoire que le CPU et le GPU partagent ensemble. Retiens ce chiffre, 128 GB. C&apos;est tout ton budget, et tout ce qui suit est une division à l&apos;intérieur de ces 128.&lt;/p&gt;
&lt;p&gt;Une chose à savoir d&apos;avance, parce qu&apos;elle explique la moitié des chiffres plus loin. La Spark tourne sur du Blackwell desktop, SM12.1, et cette puce ne sait pas calculer nativement en 4-bit. Le gros Blackwell datacenter, le B200, oui. Conséquence : de la quantization 4-bit tu obtiens sur la Spark tout le gain mémoire, mais pas tout le gain de calcul. vLLM contourne ça en ramenant les poids 4-bit à une précision plus haute pendant le calcul.&lt;/p&gt;
&lt;p&gt;Ça marche très bien. Mais c&apos;est justement pour ça que tu ne dois pas coller bêtement les jolis chiffres FP4 d&apos;un B200 sur ta propre Spark.&lt;/p&gt;
&lt;h2&gt;Ce qui rentre dans 128 GB&lt;/h2&gt;
&lt;p&gt;En bref : les poids rentrent en premier, le reste est de la KV-cache pour tous les utilisateurs ensemble. La précision est donc un choix de conception à l&apos;avance, pas un bouton après coup, et j&apos;ai écrit un &lt;a href=&quot;/fr/blog/quantization-llms-locaux/&quot;&gt;post séparé&lt;/a&gt; là-dessus. La question n&apos;est jamais de savoir si un modèle rentre, mais ce qui reste quand il rentre. La division complète est dans le &lt;a href=&quot;/fr/dgx-spark/&quot;&gt;guide&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;À quelle vitesse c&apos;est vraiment&lt;/h2&gt;
&lt;p&gt;C&apos;est là que la plupart des tests de la DGX Spark se trompent. Ils prennent une seule prompt, mesurent les tokens par seconde, et appellent ça « la vitesse ». Mais sur cette machine la vitesse n&apos;est pas un chiffre. Ce sont trois choses, elles se ressentent différemment et se comportent différemment. Sépare-les et toute la Spark se met en place.&lt;/p&gt;
&lt;h3&gt;Le decode est presque gratuit&lt;/h3&gt;
&lt;p&gt;Le decode, c&apos;est le texte qui arrive une fois que le modèle génère vraiment. Sur la Spark c&apos;est d&apos;une stabilité ennuyeuse, et ennuyeux est un compliment ici. Un utilisateur sur un modèle 26B atteint entre 23 et 24 tokens par seconde en BF16, que tu lui donnes 4k ou 25k de contexte. Dix utilisateurs en même temps : entre 9 et 12 chacun, et ça reste collé là. Le decode dépend donc du nombre de gens occupés en même temps, pas de la longueur de leur prompt.&lt;/p&gt;
&lt;p&gt;Et la quantization tire toute cette ligne vers le haut. &lt;a href=&quot;/fr/blog/gemma-4-nvfp4-vs-bf16-dgx-spark/&quot;&gt;NVFP4 a gagné sur le decode dans les neuf tests&lt;/a&gt;, de 22 à 92 pour cent selon la charge. Sur un modèle MoE plus léger comme Nemotron-3, le decode single-user frôle même les 60 t/s. Le decode, en somme, n&apos;est pas le problème.&lt;/p&gt;
&lt;h3&gt;Le prefill est la facture&lt;/h3&gt;
&lt;p&gt;Le prefill, lui, l&apos;est. Le prefill, c&apos;est le silence avant le premier token, et c&apos;est ça qu&apos;un utilisateur ressent comme « lent », pas les tokens d&apos;après.&lt;/p&gt;
&lt;p&gt;Le prefill grimpe avec la taille de ta prompt, et ça fait mal. Une prompt courte est traitée en une demi-seconde, même à dix en même temps. Balance-lui 25k de contexte avec ces mêmes dix utilisateurs et tu attends 35 secondes le premier caractère. Même machine, même concurrency, juste une prompt plus longue. Double la prompt, double grosso modo l&apos;attente.&lt;/p&gt;
&lt;p&gt;Et la quantization ? Elle n&apos;aide presque pas ici. Le prefill, c&apos;est du calcul, et le calcul est précisément là où se trouve ce handicap SM12.1. NVFP4 rend ton decode plus rapide. Ton prefill reste du prefill.&lt;/p&gt;
&lt;h3&gt;Sous pression elle met en file, elle ne plante pas&lt;/h3&gt;
&lt;p&gt;Reste la question : que fait-elle quand tu lui balances simplement trop de choses ? La réponse est rassurante d&apos;ennui. Elle ne tombe pas. Elle se met dans la file.&lt;/p&gt;
&lt;p&gt;Dans le test le plus lourd je voulais pousser 1,5 requests par seconde à travers la machine. Elle en a encaissé presque six fois moins. Et pourtant aucune des 300 requests n&apos;a échoué. Le ralentissement n&apos;est pas non plus allé à tout le monde, il est allé à la queue : l&apos;utilisateur moyen a peu remarqué, le malchanceux un pour cent a attendu six secondes son premier token.&lt;/p&gt;
&lt;p&gt;Pour l&apos;on-prem c&apos;est le meilleur résultat que tu puisses espérer. Un crash, c&apos;est un coup de fil. Une file, c&apos;est un peu de patience. Un bureau vit avec le second, pas avec le premier.&lt;/p&gt;
&lt;p&gt;C&apos;est tout le modèle. Le decode est presque gratuit, le prefill est la facture, le queueing est ton filet de sécurité. Les chiffres en dessous, neuf charges par modèle et deux méthodes de mesure, sont dans l&apos;&lt;a href=&quot;/fr/arena/&quot;&gt;arène&lt;/a&gt; et dans les posts séparés : la &lt;a href=&quot;/fr/blog/gemma-4-dgx-spark-benchmarks/&quot;&gt;baseline BF16&lt;/a&gt;, &lt;a href=&quot;/fr/blog/gemma-4-nvfp4-vs-bf16-dgx-spark/&quot;&gt;NVFP4 contre BF16&lt;/a&gt; et &lt;a href=&quot;/fr/blog/nemotron-3-dgx-spark-precisions/&quot;&gt;Nemotron-3 en trois précisions&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Le reste est dans le guide&lt;/h2&gt;
&lt;p&gt;Quel moteur je fais tourner (vLLM), &lt;a href=&quot;/fr/cout-dgx-spark/&quot;&gt;ce que coûte une Spark&lt;/a&gt;, et pour qui ça marche ou non : c&apos;est le tableau complet, et ça a sa place dans le &lt;a href=&quot;/fr/dgx-spark/&quot;&gt;guide&lt;/a&gt;, pas dans cette seule histoire de loupe. La version courte de « pour qui » : le local ne devient intéressant que lorsque les données n&apos;ont pas le droit de sortir du bâtiment. Si tu n&apos;as pas cette exigence et que tu veux juste les tokens les plus rapides et les moins chers, alors une API cloud est la réponse plus honnête.&lt;/p&gt;
&lt;p&gt;Faire tourner en local n&apos;est pas un principe. C&apos;est une répartition : ce qui doit rester dedans, et ce qui a le droit de sortir.&lt;/p&gt;
&lt;h2&gt;Refais-le toi-même&lt;/h2&gt;
&lt;p&gt;Tout ce qui est en dessous est ouvert. Les modèles sont sur Hugging Face, vLLM est open source, et la sortie brute des benchmarks plus les scripts sont sur &lt;a href=&quot;https://github.com/djangodevreng/dgx-spark-benchmarks&quot;&gt;GitHub&lt;/a&gt;. La &lt;a href=&quot;/fr/arena/methodologie/&quot;&gt;méthodologie&lt;/a&gt; explique quelles neuf charges je fais tourner et pourquoi.&lt;/p&gt;
&lt;p&gt;Si tu as toi-même une Spark, tu devrais pouvoir suivre la même route et obtenir à peu près les mêmes chiffres. Si ça ne marche pas, c&apos;est justement ce que je veux savoir. &lt;a href=&quot;/fr/contact/&quot;&gt;Écris-moi sans souci&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Pourquoi ce blog et cette arena existent</title>
    <id>https://djangodevreng.nl/fr/blog/pourquoi-ce-blog-et-arena/</id>
    <link rel="alternate" type="text/html" href="https://djangodevreng.nl/fr/blog/pourquoi-ce-blog-et-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/fr/a-propos/</uri></author>
    <category term="reflecties"/>
    <summary>Je cherchais des chiffres concrets sur l&apos;IA locale sur le DGX Spark, sans en trouver. Alors je les mesure moi-meme et je batis le blog et l&apos;arena en etabli.</summary>
    <content type="html">&lt;p&gt;Pour les clients de &lt;a href=&quot;https://kamoo.ai&quot;&gt;Kamoo&lt;/a&gt;, je mets en place des systèmes d&apos;IA qui doivent parfois rester proches de la maison. Des comptables, des bureaux administratifs, des cabinets avec des données personnelles et des documents financiers. Exactement le genre de données qui ne rendent pas ton auditeur plus serein quand tu lui dis : &quot;on envoie ça vite fait en Amérique&quot;.&lt;/p&gt;
&lt;p&gt;C&apos;est pour ça qu&apos;on a un &lt;a href=&quot;https://www.nvidia.com/en-us/products/workstations/dgx-spark/&quot;&gt;DGX Spark&lt;/a&gt; ici. 128 GB de unified memory, assez petit pour une armoire serveur, assez grand pour faire tourner des modèles locaux sérieux via &lt;a href=&quot;https://docs.vllm.ai/&quot;&gt;vLLM&lt;/a&gt;. Ce qui tient dessus en pratique, je le rassemble sur &lt;a href=&quot;/fr/dgx-spark/&quot;&gt;la page de synthèse sur les modèles locaux sur le DGX Spark&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Puis la question pratique a commencé.&lt;/p&gt;
&lt;p&gt;Quel modèle utilises-tu pour quoi sur cette machine ? Quelle précision choisis-tu ? Combien de contexte tient encore ? Où la concurrency s&apos;effondre-t-elle ? Que se passe-t-il un lundi ordinaire avec dix personnes qui ne lancent pas toutes un benchmark en même temps, mais font simplement leur travail ?&lt;/p&gt;
&lt;p&gt;Je cherchais des chiffres pour exactement ces questions. Pas un leaderboard général avec un score qui fait surtout bonne figure dans une capture d&apos;écran. Juste : cette puce, ces modèles, ces engines, ces workloads, ces limites.&lt;/p&gt;
&lt;p&gt;Je ne les ai pas trouvés.&lt;/p&gt;
&lt;p&gt;Alors je les construis moi-même.&lt;/p&gt;
&lt;h2&gt;L&apos;arena est l&apos;établi de mesure&lt;/h2&gt;
&lt;p&gt;En ce moment, il y a dix profils de benchmarks dans l&apos;&lt;a href=&quot;/fr/arena/&quot;&gt;arena&lt;/a&gt;, avec des runs pour entre autres le context-scaling, la concurrency, l&apos;output-throughput, des workloads façon RAG et un pic du lundi matin.&lt;/p&gt;
&lt;p&gt;Cette arena doit faire une chose bien : montrer ce que tu peux attendre en pratique sur un DGX Spark. Pas quel modèle est &quot;le meilleur&quot; dans un sens abstrait, mais quel modèle reste utilisable sur ce matériel sous les workloads que je rencontre dans le travail client.&lt;/p&gt;
&lt;p&gt;Pour quelques runs, j&apos;ai déjà noté ce qui a foiré et ce que j&apos;en ai tiré. Par exemple &lt;a href=&quot;/fr/blog/gemma-4-dgx-spark-benchmarks/&quot;&gt;où Gemma-4 commence à coincer sur le Spark&lt;/a&gt;, &lt;a href=&quot;/fr/blog/gemma-4-nvfp4-vs-bf16-dgx-spark/&quot;&gt;ce que NVFP4 gagne sur BF16 une fois les bugs partis&lt;/a&gt;, et &lt;a href=&quot;/fr/blog/nemotron-3-dgx-spark-precisions/&quot;&gt;comment trois précisions de Nemotron-3 se comparent&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;L&apos;output brut est public sur GitHub : &lt;a href=&quot;https://github.com/djangodevreng/dgx-spark-benchmarks&quot;&gt;djangodevreng/dgx-spark-benchmarks&lt;/a&gt;. C&apos;est volontaire. Si tu as un Spark toi-même, tu dois pouvoir suivre le même chemin et obtenir à peu près les mêmes chiffres. Si ça ne marche pas, c&apos;est aussi une donnée intéressante.&lt;/p&gt;
&lt;p&gt;L&apos;arena n&apos;est donc pas une petite liste statique. C&apos;est un etabli. De nouveaux modèles dedans, d&apos;autres précisions à côté, des workloads resserrés, des résultats bizarres relancés. Juste assez ennuyeux pour devenir utile.&lt;/p&gt;
&lt;h2&gt;Le blog est le contexte autour&lt;/h2&gt;
&lt;p&gt;Les chiffres sont pratiques, mais ils ne racontent pas toute l&apos;histoire.&lt;/p&gt;
&lt;p&gt;Un benchmark peut dire que NVFP4 est plus rapide que BF16. Le blog peut raconter que les premiers runs ont cassé sur des bugs de vLLM, qu&apos;un paramètre était mal réglé, qu&apos;un modèle n&apos;est devenu utilisable qu&apos;après avoir baissé la longueur de contexte, ou que la tail-latency se ressentait pire que la moyenne ne le laissait penser.&lt;/p&gt;
&lt;p&gt;C&apos;est la couche qui me manquait moi-même quand j&apos;ai commencé. Pas seulement &quot;voici un score&quot;, mais : voilà ce que j&apos;ai essayé, ça a cassé, voilà ce que j&apos;ai changé, et voilà ce que je ferais autrement la prochaine fois.&lt;/p&gt;
&lt;p&gt;C&apos;est pour ça que le blog et l&apos;arena sont côte à côte. L&apos;arena donne les points de mesure. Le blog donne le raisonnement, les erreurs et les choix pratiques derrière.&lt;/p&gt;
&lt;h2&gt;Pourquoi local&lt;/h2&gt;
&lt;p&gt;La vie privée est en général l&apos;explication polie. Elle est vraie aussi. La raison plus pratique : certains clients n&apos;ont pas le choix.&lt;/p&gt;
&lt;p&gt;Un cabinet comptable ne peut pas traiter les données client comme si c&apos;était du texte d&apos;exemple dans une demo. Les communes ont des règles. Les documents financiers ont des règles. Les données personnelles ont des règles. En pratique, tout ça revient à la même question : peux-tu mettre ça en place sans que le juridique, la compliance et l&apos;audit ne claquent aussitôt la porte ?&lt;/p&gt;
&lt;p&gt;Alors tu as deux options. L&apos;IA n&apos;a pas sa place là, ou tu le fais en local.&lt;/p&gt;
&lt;p&gt;On choisit le local quand c&apos;est nécessaire. Le Spark rend ça soudain moins exotique. Il n&apos;est pas bon marché, mais il reste abordable pour un cabinet de PME qui veut faire quelque chose de sérieux sans aussitôt construire son propre data center.&lt;/p&gt;
&lt;p&gt;C&apos;est là que se trouve le travail intéressant pour moi : faire tourner des modèles, mesurer la latency, tester des prompts, faire passer des documents dans une pipeline, et regarder où ça casse.&lt;/p&gt;
&lt;p&gt;D&apos;habitude, ça casse quelque part d&apos;ennuyeux. Ce sont les meilleurs endroits.&lt;/p&gt;
&lt;h2&gt;Ce à quoi je veux pouvoir répondre&lt;/h2&gt;
&lt;p&gt;L&apos;arena doit finalement répondre à des questions qui reviennent sans cesse dans les projets.&lt;/p&gt;
&lt;p&gt;Quel modèle est assez rapide pour des questions internes sur documents ? Quelle précision laisse assez de marge pour plusieurs utilisateurs en même temps ? Quand NVFP4 suffit, quand veux-tu du FP8, et quand BF16 est-il surtout un default cher ? Combien de contexte peux-tu donner avant que la latency devienne pénible ? Quel engine convient mieux à quel workload : vLLM, TensorRT-LLM ou SGLang ?&lt;/p&gt;
&lt;p&gt;Ce ne sont pas des questions académiques. Elles déterminent comment tu conçois un setup on-prem. Combien de matériel il te faut. Quelles données restent en local. Quelles étapes tu envoies éventuellement vers un modèle hébergé. Et où tu traces la ligne entre &quot;marche dans une demo&quot; et &quot;tient le coup le lundi matin&quot;.&lt;/p&gt;
&lt;p&gt;Cette dernière ligne est toute la raison pour laquelle ce site existe.&lt;/p&gt;
&lt;h2&gt;Pourquoi j&apos;écris ça en public&lt;/h2&gt;
&lt;p&gt;Tout ce que j&apos;utilise pour ça est open ou public : vLLM, des modèles sur Hugging Face, des scripts de benchmark, du JSON en vrac, le site lui-même. Le secret n&apos;est pas l&apos;accès à un dashboard magique. Il est dans des heures à essayer, mesurer, relancer, chasser des bugs et ensuite mesurer encore parce que ton premier run était suspectement bon.&lt;/p&gt;
&lt;p&gt;Ça m&apos;a coûté des dizaines d&apos;heures jusqu&apos;ici. Faire tourner des modèles, répéter des runs, démêler des résultats bizarres, et ensuite mesurer encore parce que le premier run était suspectement bon.&lt;/p&gt;
&lt;p&gt;Si quelqu&apos;un d&apos;autre suit le même chemin, il n&apos;a pas à trébucher sur tous les mêmes pavés. Et si quelqu&apos;un contredit mes chiffres avec de meilleurs runs : tant mieux. L&apos;arena en devient meilleure.&lt;/p&gt;
&lt;p&gt;Il y a aussi une deuxième raison en dessous. Ce site fait lui-même partie de l&apos;expérience. Le blog, l&apos;arena, le flux de l&apos;output de benchmark vers du JSON structuré vers des pages : tout ça a été largement construit en quelques semaines avec des agents qui écrivent et construisent avec moi. J&apos;ai décrit la petite version de ça plus tôt dans &lt;a href=&quot;/fr/blog/openclaw-sur-raspberry-pi/&quot;&gt;le setup OpenClaw sur un Raspberry Pi&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Ce workflow fait partie du travail maintenant. Je balance des trouvailles brutes dans Slack, je laisse un agent lire le repo et le guide d&apos;écriture, je récupère une branche avec une proposition, je lance les checks et je relis la diff moi-même. Ça ne m&apos;épargne pas la réflexion. Mais ça déplace beaucoup de préparation vers une couche qui continue simplement de tourner.&lt;/p&gt;
&lt;p&gt;Écrire sur ce processus m&apos;oblige à le rendre moins brouillon que mon historique de terminal. Ça aide. Pas toujours sympa, mais nécessaire.&lt;/p&gt;
&lt;h2&gt;Ce que je veux construire ensuite&lt;/h2&gt;
&lt;p&gt;D&apos;abord, plus de benchmarks. vLLM était le point de départ, parce qu&apos;il marche vite et est largement utilisé. TensorRT-LLM est déjà sur l&apos;etabli pour Nemotron-3. SGLang, c&apos;est ce que je veux mettre à côté des mêmes workloads ensuite. C&apos;est seulement avec plusieurs engines que tu vois si ton modèle est lent, si ton engine te met des bâtons dans les roues, ou si tu as juste fait une bêtise.&lt;/p&gt;
&lt;p&gt;Ensuite, je veux rendre &lt;code&gt;bench-spark&lt;/code&gt; public : le benchmark-runner tel que je l&apos;utilise aujourd&apos;hui. Pas un framework parfait. Mais quelque chose avec quoi quelqu&apos;un sur le même matériel peut poser les mêmes questions sans d&apos;abord reconstruire mes erreurs.&lt;/p&gt;
&lt;p&gt;Je veux aussi faire une eval-suite néerlandaise pour les LLMs locaux. Pas un benchmark de reasoning anglais de plus, mais du travail de bureau : jargon comptable, textes juridiques, documents financiers, documents avec une mise en forme bizarre. Exactement les choses sur lesquelles l&apos;IA locale est jugée aux Pays-Bas.&lt;/p&gt;
&lt;p&gt;Et il y aura plus de travail autour du RAG local sur de grands jeux de documents. Pas de pitch de plateforme. Juste comprendre comment faire passer plus d&apos;un million de documents dans un setup on-prem sans que le stockage, le retrieval ou l&apos;OCR se mette lentement à te détester.&lt;/p&gt;
&lt;h2&gt;Ce que je laisse de côté&lt;/h2&gt;
&lt;p&gt;Pas de newsletter IA quotidienne. Il y a déjà assez d&apos;endroits pour ça, certains même exprès.&lt;/p&gt;
&lt;p&gt;Pas d&apos;histoire general-purpose &quot;on fait tout avec l&apos;IA&quot;. Trop large, et d&apos;habitude ça ne veut rien dire.&lt;/p&gt;
&lt;p&gt;Pas de numéro de thought-leader. Je préfère construire quelque chose qui craque qu&apos;une opinion qui sonne lisse.&lt;/p&gt;
&lt;p&gt;Pas non plus de construction d&apos;une plateforme comme OpenClaw. Je l&apos;utilise, j&apos;écris dessus, je construis des flux avec. Mais cette couche elle-même, je la laisse aux gens qui vivent dedans tous les jours.&lt;/p&gt;
&lt;h2&gt;Ce que ça doit devenir&lt;/h2&gt;
&lt;p&gt;Pour les clients, ça doit montrer ce que l&apos;IA locale coûte en pratique : matériel, latency, précision, maintenance, cas limites bizarres. Pour moi, c&apos;est l&apos;endroit où je fixe mes propres suppositions avant que le prochain benchmark ne les renverse.&lt;/p&gt;
&lt;p&gt;J&apos;essaie de tenir le rythme. Pas de promesse par semaine. S&apos;il n&apos;y a rien à signaler, rien ne s&apos;affiche ici. S&apos;il y a des bugs, des runs et des graphiques bizarres, il y a sans doute trop ici.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Gemma-4 sur la DGX Spark : NVFP4 vs BF16</title>
    <id>https://djangodevreng.nl/fr/blog/gemma-4-nvfp4-vs-bf16-dgx-spark/</id>
    <link rel="alternate" type="text/html" href="https://djangodevreng.nl/fr/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/fr/a-propos/</uri></author>
    <category term="on-prem"/>
    <summary>Neuf benchmarks identiques, deux précisions. NVFP4 est 22 à 92 pour cent plus rapide par token, et la capacité grimpe de 69 pour cent aux heures de pointe.</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;Dans la &lt;a href=&quot;/fr/blog/gemma-4-dgx-spark-benchmarks/&quot;&gt;baseline BF16 de Gemma-4 sur la DGX Spark&lt;/a&gt;, j&apos;ai fait neuf benchmarks avec Gemma-4-26B-A4B en BF16. La vitesse de décodage tenait très bien, le prefill décidait du moment où le mur arrivait, et le système faisait sagement la queue sous pression au lieu de planter. Cette histoire semblait bouclée, jusqu&apos;à ce que NVIDIA sorte une version NVFP4-quantized de ce même modèle.&lt;/p&gt;
&lt;p&gt;Même architecture et même fine-tune, même config serveur, seule la précision change. De BF16 (16 bits par paramètre) à NVFP4 (4 bits par paramètre, la variante de NVIDIA sur FP4). Quatre fois plus petit par poids, et si les kernels Blackwell jouent le jeu, nettement plus rapide aussi sur les tâches compute-heavy.&lt;/p&gt;
&lt;p&gt;Sur le papier, c&apos;est beau. En pratique : la release officielle vLLM v0.20.1 reconnaît ce checkpoint sans broncher, et les chiffres étaient plus rapides sur toute la ligne que la baseline BF16. Les deux tests tombent sous le guide &lt;a href=&quot;/fr/dgx-spark/&quot;&gt;faire tourner des LLMs sur la DGX Spark&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Pourquoi se pencher là-dessus&lt;/h2&gt;
&lt;p&gt;Pour un bureau avec une machine IA locale, le budget mémoire est la chose la plus limitante après la puissance de calcul. Un modèle 26B en BF16 prend ~48 Go de mémoire GPU rien que pour les weights. Sur une Spark avec 128 Go de unified memory, il reste environ 65 Go pour le KV-cache. Suffisant pour le scénario de bureau du premier blog, mais pas beaucoup de marge pour faire tourner, disons, 30+ utilisateurs avec un gros contexte côte à côte.&lt;/p&gt;
&lt;p&gt;NVFP4 réduit ça à ~18 Go pour les weights. Pas quatre fois moins que BF16 (le vision-encoder reste en BF16, et les scale-factors coûtent aussi de la place), mais environ 2,7× moins. Ça te donne vers 95 Go de KV-cache headroom, ce qui en théorie devrait supporter une concurrency bien plus élevée. À ça s&apos;ajoute qu&apos;il faut moins de trafic mémoire par forward pass, donc par définition moins de pression sur la bandwidth, et c&apos;était déjà le bottleneck en BF16 sous charge multi-utilisateurs. La question était donc simple : combien de ce gain théorique survit en pratique ?&lt;/p&gt;
&lt;h2&gt;Ce qu&apos;est vraiment NVFP4&lt;/h2&gt;
&lt;p&gt;NVFP4 est la variante de NVIDIA sur FP4 : des nombres en virgule flottante avec 4 bits par valeur. Quatre bits, pas quatre octets, donc un facteur 4 de moins par paramètre que BF16. En stockant un scaling factor par groupe de weights, l&apos;accuracy reste raisonnablement préservée.&lt;/p&gt;
&lt;p&gt;Pour Blackwell, ça marche comme ça. Les cartes datacenter de NVIDIA (B100, B200, SM10.0) ont des tensor cores qui peuvent calculer &lt;em&gt;nativement&lt;/em&gt; avec des valeurs 4-bit, et c&apos;est bien plus rapide que le même calcul en FP16 ou BF16. La DGX Spark, en revanche, c&apos;est du Blackwell desktop (GB10, SM12.1) et cette architecture n&apos;a pas de compute FP4 natif.&amp;lt;Note&amp;gt;Sur un B200 datacenter (SM10.0), tu attendrais encore 2 à 3× par-dessus grâce aux tensor cores FP4 natifs. La Spark n&apos;a pas ce chemin matériel, donc tout le gain vient de la bandwidth mémoire, pas du compute.&amp;lt;/Note&amp;gt; Ce que tu obtiens dans ce cas, c&apos;est du FP4 &quot;weight-only&quot; : les weights sont physiquement stockés en 4-bit (d&apos;où le gain mémoire), mais pendant le compute ils sont décodés à la volée vers FP16 pour les matrix-multiplications. Un warning vLLM le dit explicitement :&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;Tu obtiens donc le gain mémoire en entier, le gain compute seulement en partie. Le kernel Marlin INT4 GEMM est optimisé, mais pas aussi rapide que le FP4 natif sur SM10.0 le serait. Bon à intégrer dans le calcul quand tu regardes les chiffres plus bas.&lt;/p&gt;
&lt;h2&gt;Le montage de test&lt;/h2&gt;
&lt;p&gt;Config serveur identique au premier blog, seul le modèle change :&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;Les tests sont un à un identiques au premier blog : mêmes commandes, mêmes niveaux de concurrency, mêmes datasets pour les tests open-loop, même seed. C&apos;est volontaire, car si tu veux mesurer l&apos;effet d&apos;une variable isolée (ici la précision), tout le reste autour doit rester pareil. La façon exacte dont je mesure ces niveaux de concurrency, ces seeds et ces arrivées open-loop est décrite dans la &lt;a href=&quot;/fr/arena/methodologie/&quot;&gt;méthode de mesure de l&apos;Arena&lt;/a&gt;.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Comparaison&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;Modèle&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 Go&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~18 Go&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 Go&lt;/td&gt;
&lt;td&gt;~95 Go&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 (forcé)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Trois chiffres résument où ça aboutit. Clique pour la run complète dans l&apos;Arena, avec tous les seeds, niveaux de concurrency et commandes :&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;RPS pic du lundi&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;Une version interactive de tous les chiffres se trouve sur la &lt;a href=&quot;/fr/arena/gemma-4-26b-a4b-nvfp4/&quot;&gt;page Arena de Gemma-4-26B-A4B-NVFP4&lt;/a&gt;, commandes et percentiles TTFT inclus pour les 9 tests.&lt;/p&gt;
&lt;p&gt;&amp;lt;details&amp;gt;
&amp;lt;summary&amp;gt;Run A : scaling du contexte de 4k à 25k&amp;lt;/summary&amp;gt;&lt;/p&gt;
&lt;p&gt;Décodage par utilisateur quand le contexte grandit, 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;Gain&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;À c=1, le gain est stable autour de +22-24% à travers tous les contextes. La bandwidth mémoire ne joue presque pas en single-user, donc le gain ici se trouve dans le compute-path lui-même. Le décodage INT4 de Marlin plus le matmul FP16 est un peu plus rapide que le matmul FP16 direct de BF16, malgré les deux étapes.&lt;/p&gt;
&lt;p&gt;À c=10, l&apos;écart scale beaucoup plus fort selon le type de workload, de +40% à 25k de contexte à +79% à 4k. C&apos;est parce qu&apos;en multi-utilisateurs la bandwidth mémoire devient le bottleneck, et NVFP4 lit moins d&apos;octets par forward pass. Plus c&apos;est concurrent, plus ça compte, jusqu&apos;à ce que tu retombes sur les KV-cache memory limits (25k de contexte avec plusieurs utilisateurs) et que le gain s&apos;aplatisse.&lt;/p&gt;
&lt;p&gt;Le TTFT (premier token) est meilleur aussi :&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;Sur le TTFT, le gain est faible. C&apos;est logique : le prefill est compute-heavy, et sur SM12.1 sans tensor cores FP4 natifs, Marlin doit décoder les weights à la volée pour le matmul. Ça reprend une partie de ce que la bandwidth mémoire avait rapporté. Pour le décodage, la bandwidth compte plus que le compute ; pour le prefill, c&apos;est l&apos;inverse.&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 de contexte, concurrency jusqu&apos;à 20&amp;lt;/summary&amp;gt;&lt;/p&gt;
&lt;p&gt;Le stress-test de la première partie :&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;Le plateau de décodage agrégé passe de 32 t/s à 36 t/s à c=20 : un plafond 12% plus haut à 25k de contexte sous pression maximale. Le TTFT est pratiquement identique entre BF16 et NVFP4 parce que le prefill est le mur ici et qu&apos;il n&apos;accélère pas beaucoup sur SM12.1. Le décodage par utilisateur est par contre nettement meilleur : avec vingt prompts 25k en parallèle, tu obtiens 4.26 au lieu de 3.16 t/s, +35%. Toujours pas de la vitesse de chat, mais une différence perceptible dès que les tokens commencent à couler.&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 : prompt 1k, output 1k&amp;lt;/summary&amp;gt;&lt;/p&gt;
&lt;p&gt;La workload prompt-court + réponse-longue, proche des agent-flows et de la génération de code :&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;Gain&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;À c=10, le décodage par utilisateur est bien au-dessus de 20 t/s, au-dessus de la vitesse de lecture et proche d&apos;une UI de streaming confortable. Le décodage agrégé à c=10 atteint 209 t/s au lieu de 86 t/s en BF16, presque le double.&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;Cinq tours consécutifs par conversation, dix conversations en parallèle : la shape de bureau la plus réaliste.&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;Pour dix conversations 5-turn en parallèle : 1.94 seconde jusqu&apos;au premier token, 19.51 t/s par utilisateur. Ça rentre confortablement dans ce qu&apos;un lecteur ressent comme du chat, et c&apos;est 87% plus rapide par token que BF16 dans le même 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 : mix RAG (prompt 8k)&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 de contexte, c&apos;est à peu près ce qu&apos;un RAG-flow avec quatre chunks de 2k tokens reçoit. À dix utilisateurs, tu attends 8 secondes jusqu&apos;au premier token (quasi pareil que BF16, car bottleneck compute), puis 16 t/s en streaming. Pour les flows &quot;pose une question sur tes documents&quot;, c&apos;est largement exploitable, et là où le gain se trouve : dans la vitesse de décodage, pas dans le 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 : instruction courte, 4096 tokens d&apos;output&amp;lt;/summary&amp;gt;&lt;/p&gt;
&lt;p&gt;La shape agent / génération de code :&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;Un TTFT de 110 millisecondes en single-user, c&apos;est très bas, plus bas que ce que la plupart des hosted APIs atteignent à travers le réseau. Et 22.54 t/s par utilisateur à c=10, c&apos;est largement assez pour des agent-streams. Le décodage agrégé à c=10 dans ce test sort à 225 t/s contre 84 t/s en BF16, presque 2,7× autant. Pour une équipe qui fait tourner dix agents simultanés produisant chacun de longues sorties structurées, c&apos;est le chiffre le plus important.&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, workload 4k aléatoire&amp;lt;/summary&amp;gt;&lt;/p&gt;
&lt;p&gt;La baseline de bureau synthétique avec des arrivées Poisson :&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;Ce qui frappe, c&apos;est que le peak concurrent tombe de 36 à 16 à arrival rate identique (0.3 rps) et prompts identiques. Comme NVFP4 traite chaque requête plus vite, la queue reste plus courte, et c&apos;est un point important pour la planification de capacité : NVFP4 te donne non seulement une latency plus basse par requête, mais aussi moins de pression de queue au même arrival rate. En parallèle, le TPOT P50 tombe de 182ms à 64ms. La latency médiane inter-token est donc presque trois fois plus rapide. Pour une UI de chat qui montre le token-streaming, c&apos;est la différence entre attendre artificiellement une réponse et simplement lire au fil de l&apos;eau.&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 : replay ShareGPT (vraies conversations)&amp;lt;/summary&amp;gt;&lt;/p&gt;
&lt;p&gt;De vraies données de conversation multi-turn :&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;Un P99 TTFT de 265 millisecondes, pour 99 pour cent des utilisateurs. Un TPOT de 39 ms revient à 25.6 t/s par utilisateur. Tu peux tranquillement appeler ça du chat en temps réel pour 25 collaborateurs avec des prompts réalistes de style ShareGPT.&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 : pic du lundi matin&amp;lt;/summary&amp;gt;&lt;/p&gt;
&lt;p&gt;Le scénario le plus lourd de la première partie : serveur surchargé, target de 1.5 rps avec max 25 requêtes simultanées.&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;Le chiffre le plus mesurable de toute la journée, c&apos;est que l&apos;achieved RPS passe de 0.26 à 0.44. Même target, même cap de concurrency, mêmes arrivées Poisson, et NVFP4 traite 69% de requêtes en plus par seconde avant que la queue ne se bouche.&lt;/p&gt;
&lt;p&gt;Le P99 TTFT ne bouge que marginalement (6.16s à 6.05s). Ça colle au schéma : le prefill est compute-bound sur SM12.1, et NVFP4 n&apos;y est pas beaucoup plus rapide. Mais le TPOT P50 tombe de 187ms à 108ms, et le throughput agrégé de tokens grimpe de 1173 à 1984 t/s. Pour un bureau de 25 personnes aux heures de pointe, c&apos;est la différence entre du confortable et du serré : plus de requêtes par seconde traitées, avec un streaming plus rapide pour qui est servi.&lt;/p&gt;
&lt;p&gt;&amp;lt;/details&amp;gt;&lt;/p&gt;
&lt;h2&gt;Ce que ça veut dire pour l&apos;IA on-prem&lt;/h2&gt;
&lt;p&gt;Si tu as une Spark et que tu fais tourner Gemma-4-26B, NVFP4 est l&apos;upgrade. Sur les 9 tests, NVFP4 est le gagnant, et il libère 30 Go de mémoire pour d&apos;autres usages comme plus de KV-cache, un deuxième petit modèle à côté, ou des batch-jobs. Chez Kamoo, cette config NVFP4 est maintenant à côté de la baseline BF16 dans &lt;code&gt;bench-spark/&lt;/code&gt;, et une seule commande bascule entre les deux.&lt;/p&gt;
&lt;p&gt;Pour un bureau de 25 personnes avec des prompts réalistes de style ShareGPT, tu le remarques tout de suite. Le TPOT P50 tombe de 95 ms à 39 ms, le P99 TTFT de 637 ms à 265 ms. Et quand un pic de charge arrive, le système délivre 69% de requêtes en plus par seconde avant de se remplir. Pour les agent-flows et la génération de code (shape Run G), la Spark en NVFP4 est à son meilleur : dix agents en parallèle, chacun 4096 tokens d&apos;output, 22.5 t/s par utilisateur avec un TTFT sous 400 ms.&lt;/p&gt;
&lt;p&gt;Pour le stress à 25k de contexte (Run B), ça reste le mur. NVFP4 ne le baisse presque pas (le TTFT diffère de moins d&apos;une seconde), parce que le prefill reste le prefill, et dix prompts 25k en parallèle attendent 35 secondes le premier token. La quantization n&apos;y change rien sur ce matériel. La vitesse de décodage, par contre, si : 7.56 t/s/utilisateur au lieu de 5.37, donc dès que les tokens arrivent, ils défilent plus vite.&lt;/p&gt;
&lt;h2&gt;Ce que cette run ne dit pas&lt;/h2&gt;
&lt;p&gt;Ce n&apos;est pas du NVFP4 sur SM10.0 (Blackwell datacenter). Là, le compute FP4 natif rendrait la différence bien plus grande, avec l&apos;attente d&apos;un speedup supplémentaire de 2-3× par-dessus ce qu&apos;on voit ici. Sur un H100 ou un B200, ces chiffres ne sont donc &lt;em&gt;pas&lt;/em&gt; représentatifs ; la Spark a un handicap spécifique SM12.1 (pas de FP4 natif) qui n&apos;existe pas dans le cloud.&lt;/p&gt;
&lt;p&gt;Ce n&apos;est pas non plus une comparaison avec Gemma-4-31B dense en NVFP4. Le dense passe par un autre code-path dans le loader de vLLM. Pour un blog de suite, le dense-NVFP4 avec la même suite de tests fournirait un troisième point de données.&lt;/p&gt;
&lt;p&gt;Et ce n&apos;est pas une comparaison d&apos;accuracy à long terme. La quantization NVFP4 a des effets d&apos;accuracy potentiellement petits. Pour les tâches typiques d&apos;un bureau (résumé, classification de tickets, RAG) rarement perceptibles, pour les edge-cases peut-être bien.&lt;/p&gt;
&lt;p&gt;Ce que NVIDIA a publié, lui, se trouve dans la &lt;a href=&quot;https://huggingface.co/nvidia/Gemma-4-26B-A4B-NVFP4&quot;&gt;model-card NVFP4&lt;/a&gt; : sur MMLU-Pro, GPQA-Diamond et LiveCodeBench, NVFP4 reste à 0,2 à 0,7 point de leur propre baseline BF16.&amp;lt;Note&amp;gt;La propre baseline BF16 de NVIDIA s&apos;écarte elle-même des chiffres de la card officielle Gemma-4 de Google. Les eval-harnesses diffèrent plus que la précision elle-même, donc une comparaison croisée entre vendors sans harness identique est branlante.&amp;lt;/Note&amp;gt; Ça tombe dans la run-to-run-variance, pas de vraie dégradation. Ce qui est curieux dans ce même tableau, c&apos;est que la baseline BF16 de NVIDIA s&apos;écarte à son tour de ce que Google publie dans la card officielle Gemma-4 : MMLU-Pro 85.0 vs 82.6, GPQA 80.3 vs 82.3, LiveCodeBench 80.5 vs 77.1. Pas parce que la quantization devient meilleure que l&apos;original, mais parce que l&apos;eval-harness compte visiblement plus que la précision elle-même. Autres prompts, autre temperature, autres critères d&apos;arrêt. Les comparaisons croisées entre vendors sont donc difficiles à établir solidement sans le même harness.&lt;/p&gt;
&lt;h2&gt;Ce qui reste&lt;/h2&gt;
&lt;p&gt;Le décodage vend le benchmark, le prefill décide de l&apos;expérience. C&apos;était vrai en première partie et ça l&apos;est toujours. Ce que NVFP4 ajoute, c&apos;est que le décodage devient plus rapide dans chaque workload, et le plus là où ça compte : à plus grand contexte et avec plus d&apos;utilisateurs en même temps. Le TTFT reste à peu près pareil sur SM12.1 parce que le prefill est compute-bound et que la Spark n&apos;a pas de tensor cores FP4 natifs. Pour ce que l&apos;utilisateur ressent dès que les tokens commencent à couler, NVFP4 sur ce matériel est nettement meilleur que BF16, et ça ne coûte rien en douleur d&apos;installation : une image officielle vLLM, un flag de modèle, et ça tourne.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Nemotron-3 sur le DGX Spark : BF16 vs FP8 vs NVFP4</title>
    <id>https://djangodevreng.nl/fr/blog/nemotron-3-dgx-spark-precisions/</id>
    <link rel="alternate" type="text/html" href="https://djangodevreng.nl/fr/blog/nemotron-3-dgx-spark-precisions/"/>
    <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/fr/a-propos/</uri></author>
    <category term="on-prem"/>
    <summary>Un modèle, trois précisions, le même Spark. Ce que font budget mémoire, vitesse de decode et tail-latency quand tu passes de 16 à 8 puis 4 bits.</summary>
    <content type="html">&lt;p&gt;Dans les posts précédents, j&apos;ai fait tourner Gemma-4 sur le DGX Spark. D&apos;abord &lt;a href=&quot;/fr/blog/gemma-4-dgx-spark-benchmarks/&quot;&gt;juste BF16 comme baseline&lt;/a&gt;, puis &lt;a href=&quot;/fr/blog/gemma-4-nvfp4-vs-bf16-dgx-spark/&quot;&gt;NVFP4 vs BF16 sur la même suite de tests&lt;/a&gt;. Ça donnait un modèle dans deux précisions. Utile, mais pas encore une vraie image du choix que tu dois faire en production.&lt;/p&gt;
&lt;p&gt;Pour cet article, je fais tourner trois variantes du même modèle côte à côte : &lt;strong&gt;BF16, FP8 et NVFP4&lt;/strong&gt; de Nemotron-3-Nano-Omni-30B-A3B-Reasoning. Même Spark. Même version de vLLM. Mêmes prompts. Même suite de benchmarks. Aussi proche d&apos;une comparaison de quantization honnête que je peux l&apos;obtenir sur cette machine.&lt;/p&gt;
&lt;p&gt;La version courte : &lt;strong&gt;NVFP4 gagne sur la vitesse et le throughput, FP8 gagne plus souvent sur la tail-latency, BF16 reste surtout utile comme baseline&lt;/strong&gt;. C&apos;est moins net que &quot;4 bits c&apos;est toujours mieux&quot;. Heureusement, sinon ce post aurait été court. Fait partie du guide &lt;a href=&quot;/fr/dgx-spark/&quot;&gt;faire tourner des LLMs sur le DGX Spark&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Pourquoi cette expérience&lt;/h2&gt;
&lt;p&gt;Le post sur Gemma montrait surtout que NVFP4 fonctionne sur le Spark. Avec de la douleur. Cinq bugs vLLM, une nightly build et assez de flags pour qu&apos;une ligne de commande ressemble à un petit aveu.&lt;/p&gt;
&lt;p&gt;Mais Gemma ne répondait pas à la question dont j&apos;ai besoin pour les clients : que choisis-tu si tu veux faire tourner un modèle local sur un Spark aujourd&apos;hui ? BF16 parce que ce sont les weights originaux ? FP8 parce que Blackwell y est nativement bon ? Ou NVFP4 parce que tu fais tenir beaucoup plus de modèle et de KV-cache dans la même mémoire ?&lt;/p&gt;
&lt;p&gt;D&apos;où cette run. Un modèle dans trois précisions. Pas un score de leaderboard, mais des workloads qui ressemblent au travail de bureau : chat, RAG, réponses plus longues, plusieurs utilisateurs en même temps, et un lundi matin où tout le monde décide soudain que l&apos;IA est bien pratique finalement.&lt;/p&gt;
&lt;h2&gt;Ce que BF16, FP8 et NVFP4 signifient ici&lt;/h2&gt;
&lt;p&gt;BF16 est la baseline : 16 bits par paramètre, environ 2 octets. Pour ce modèle ça veut dire à peu près 61,5 GB de checkpoint size. Ça tient sur le Spark, mais ça grignote une grande partie de tes 128 GB de mémoire unifiée avant qu&apos;un seul utilisateur n&apos;ait du contexte dans le KV-cache.&lt;/p&gt;
&lt;p&gt;FP8 divise ce poids à peu près par deux. Le checkpoint fait 32,8 GB. Sur Blackwell, FP8 est un choix logique : moins de mémoire, support natif, et en général peu d&apos;embêtements dans vLLM.&lt;/p&gt;
&lt;p&gt;NVFP4 va plus loin. Le checkpoint fait 20,9 GB. Pas quatre fois plus petit que BF16, parce que les encoders vision et audio restent en BF16, mais assez petit pour rendre le Spark différent. Plus de place pour le KV-cache, plus de batching, plus de concurrency.&lt;/p&gt;
&lt;p&gt;La nuance : le DGX Spark tourne sur du desktop Blackwell SM12.1. Là, NVFP4 n&apos;est pas la même fête que sur du datacenter Blackwell. vLLM utilise Marlin pour décoder les weights FP4 vers FP16 pendant le compute. Tu obtiens le gain mémoire en entier. Le gain compute est moins pur.&lt;/p&gt;
&lt;p&gt;Pour ce post, c&apos;est justement ce qui le rend intéressant. Ce n&apos;est pas un post théorique sur la quantization. C&apos;est : que se passe-t-il sur cette machine, avec cette stack, quand tu fais vraiment tourner les trois options ?&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Précision&lt;/th&gt;
&lt;th&gt;Model size&lt;/th&gt;
&lt;th&gt;Budget mémoire restant sur 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;Le banc de test&lt;/h2&gt;
&lt;p&gt;Toutes les runs passent par Docker sur le DGX Spark avec &lt;code&gt;vllm/vllm-openai:v0.20.0&lt;/code&gt;. Release officielle, pas de 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;Pour FP8, j&apos;utilise le même profil avec &lt;code&gt;--kv-cache-dtype fp8&lt;/code&gt;. BF16 tourne sans ce flag KV-cache. Le reste du test reste identique.&lt;/p&gt;
&lt;p&gt;La suite de benchmarks est décrite sur la &lt;a href=&quot;/fr/arena/methodologie/&quot;&gt;méthodologie de l&apos;arena&lt;/a&gt;. En bref : tests closed-loop pour le decode et le TTFT par utilisateur, plus des tests open-loop avec des arrivées de Poisson pour voir comment le serveur se comporte quand les requests n&apos;attendent pas gentiment l&apos;une l&apos;autre.&lt;/p&gt;
&lt;h2&gt;Setup&lt;/h2&gt;
&lt;p&gt;J&apos;ai mal commencé avec &lt;code&gt;nvcr.io/nvidia/vllm:26.02-py3&lt;/code&gt;, le container vLLM de NVIDIA. Il avait vLLM 0.15.1 et ne connaissait pas encore l&apos;architecture &lt;code&gt;NemotronH_Nano_Omni_Reasoning_V3&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;La solution était plus ennuyeuse : &lt;code&gt;vllm/vllm-openai:v0.20.0&lt;/code&gt;. Release officielle, bonnes versions de flashinfer, première run fonctionnelle.&lt;/p&gt;
&lt;p&gt;Notre propre CLI &lt;code&gt;bench-spark&lt;/code&gt; avait encore besoin de deux petits fixes : contourner l&apos;entrypoint NVIDIA avec &lt;code&gt;--entrypoint vllm&lt;/code&gt;, et passer &lt;code&gt;HF_TOKEN&lt;/code&gt; automatiquement au container. Après ça, la suite a tourné.&lt;/p&gt;
&lt;p&gt;Leçon : commence par la release stable qui supporte l&apos;architecture.&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;Cette run est la base : que se passe-t-il quand le prompt devient plus long, pendant que le nombre d&apos;utilisateurs grimpe de un à dix ? Ça touche directement au travail de bureau. Un chat court est facile. Une question RAG avec 25k de contexte et plusieurs personnes en même temps, c&apos;est là que le Spark montre combien de place il reste vraiment.&lt;/p&gt;
&lt;p&gt;Ici, je regarde deux choses. D&apos;abord le decode par utilisateur : à quelle vitesse le texte revient une fois que la génération tourne ? Ensuite le TTFT : combien de temps attends-tu le premier token ? Avec un long contexte, le TTFT est souvent la douleur que les utilisateurs ressentent en premier. Ils ne voient aucun token, donc on a l&apos;impression que le système est bloqué.&lt;/p&gt;
&lt;p&gt;Single-user est surtout une mesure de vitesse pure. Là, NVFP4 double presque BF16. À dix utilisateurs, ça devient plus intéressant : les weights plus petits donnent à vLLM plus de place pour batcher, et là BF16 devient simplement lourd.&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;Contexte&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 reste bien plat autour de 28-29 tokens par seconde. C&apos;est stable, mais pas rapide. FP8 met environ 50 t/s en face. NVFP4 se situe autour de 55-60 t/s. Pour un seul utilisateur, c&apos;est la différence entre &quot;correct&quot; et &quot;ça fait local mais pas local-lent&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;Contexte&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;À dix utilisateurs, NVFP4 n&apos;est pas &quot;un peu plus rapide&quot;. C&apos;est une autre classe. À 25k de contexte, BF16 fait 5,56 tok/s/user. NVFP4 fait 12,99. Ce n&apos;est toujours pas un cluster de GPU cloud, mais la différence de ressenti est grande : BF16 devient de l&apos;attente, NVFP4 continue de travailler.&lt;/p&gt;
&lt;h3&gt;TTFT (premier token), c=10&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Contexte&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;C&apos;est le tableau que je prends le plus au sérieux pour de vrais utilisateurs. À 25k de contexte et dix utilisateurs, tu attends presque 20 secondes le premier token avec BF16. Avec NVFP4 c&apos;est 12,7 secondes. Toujours long, mais pas le même genre de long.&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 de contexte, concurrency jusqu&apos;à 20&amp;lt;/summary&amp;gt;&lt;/p&gt;
&lt;p&gt;La Run A montre comment la longueur de contexte scale. La Run B garde le contexte lourd et augmente seulement la concurrency. C&apos;est le test &quot;tout le monde pose une grosse question en même temps&quot;.&lt;/p&gt;
&lt;p&gt;En pratique, ça n&apos;arrive pas toutes les heures. Dix à vingt personnes cliquent rarement sur envoyer exactement au même moment avec 25k de contexte. Mais si tu poses une machine IA locale devant une équipe, tu veux savoir comment elle échoue. Ralentir calmement est acceptable. Une queue qui a l&apos;air morte, non.&lt;/p&gt;
&lt;p&gt;NVFP4 garde le plus d&apos;air ici. Pas parce que le modèle devient plus malin, mais parce que le serveur avec des weights plus petits a plus de place pour le batching et le KV-cache.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Utilisateurs&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;Utilisateurs&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;Vingt utilisateurs avec 25k de contexte, c&apos;est exprès méchant. Pourtant c&apos;est utile. BF16 est à 37,88 secondes de TTFT. Ça fait cassé. NVFP4 est à 24,08 secondes. Pas confortable non plus, mais quand même bien treize secondes plus rapide.&lt;/p&gt;
&lt;p&gt;L&apos;aggregate decode montre la même image :&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Utilisateurs&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;Le plafond passe de 44 t/s à 84 t/s. Pour un seul utilisateur, c&apos;est abstrait. Pour une équipe, ça veut dire que la queue se vide plus vite.&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 : prompt court, output longue&amp;lt;/summary&amp;gt;&lt;/p&gt;
&lt;p&gt;C&apos;est le workload pour les agents, la génération de code et les réponses plus longues : peu d&apos;input, beaucoup d&apos;output. Le prompt ne fait que 1024 tokens, donc le prefill n&apos;est pas le problème ici. La question est surtout : à quelle vitesse le modèle continue de tourner une fois que l&apos;output devient longue ?&lt;/p&gt;
&lt;p&gt;Donc ici, je regarde le decode par utilisateur. Le TTFT doit rester bas, mais la vraie différence, tu ne la sens qu&apos;après quelques centaines de tokens. Un modèle qui démarre vite mais reste ensuite coincé à 8 tok/s donne quand même une impression de lenteur.&lt;/p&gt;
&lt;p&gt;NVFP4 gagne clairement ici. À dix utilisateurs en parallèle, le modèle reste à 22,90 tok/s/user. BF16 tombe à 7,84. C&apos;est encore lisible, mais pour un flow d&apos;agent, on dirait que quelqu&apos;un tape à la main en même temps.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Utilisateurs&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;Pour ce workload, NVFP4 est le default logique. FP8 est correct, mais ici tu cèdes surtout de la vitesse sans que la tail-latency joue le rôle principal.&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;Le multi-turn est plus proche de l&apos;usage réel qu&apos;un prompt isolé. Cinq tours par conversation, plusieurs conversations en parallèle. Ça ressemble à un employé qui ne pose pas une question, mais relance, corrige et garde le contexte.&lt;/p&gt;
&lt;p&gt;Ici, je ne veux pas seulement voir du throughput élevé. Je veux surtout que le serveur ne donne pas l&apos;impression de sortir d&apos;un cold start à chaque tour. Avec dix conversations en même temps, ça devient pertinent : le contexte grandit par conversation, le scheduler doit continuer à partager, et l&apos;utilisateur s&apos;attend à ce que le chat continue de tourner.&lt;/p&gt;
&lt;p&gt;C&apos;est pour moi la run de bureau la plus importante. Pas parce qu&apos;elle est parfaitement réelle, mais parce qu&apos;elle se rapproche le plus de &quot;25 personnes utilisent ça réparties sur la journée&quot;.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Utilisateurs&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;À dix conversations en parallèle, NVFP4 est à 21,58 tok/s/user. FP8 est à 14,88. BF16 à 7,68. Ce dernier fonctionne techniquement, mais ça ne donne plus l&apos;impression d&apos;un chat fluide. NVFP4 reste bien au-dessus de la ligne où tu perçois une réponse comme fluide.&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 : mix RAG avec prompt de 8k&amp;lt;/summary&amp;gt;&lt;/p&gt;
&lt;p&gt;Le RAG n&apos;est en général pas 25k de contexte, mais pas non plus un chat court. Cette run utilise un prompt de 8k et 512 tokens d&apos;output. Pense à quatre chunks d&apos;environ 2k tokens, plus la question et l&apos;instruction.&lt;/p&gt;
&lt;p&gt;Avec le RAG, le prefill compte plus que dans la Run C. Tu enfournes à chaque fois une bonne tranche de contexte dans le modèle avant que quelque chose revienne. Ensuite, tu veux garder assez de decode pour rendre la réponse utilement rapide.&lt;/p&gt;
&lt;p&gt;La question est donc : la quantization continue-t-elle d&apos;aider quand le prompt s&apos;alourdit ? Oui. NVFP4 reste clairement devant, même à vingt utilisateurs.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Utilisateurs&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;À vingt utilisateurs, NVFP4 délivre 14,09 tok/s/user. BF16 est à 5,51. Pour du batch processing, ça peut encore aller. Pour du RAG temps réel dans un bureau, BF16 fait juste, surtout quand les documents sont en désordre et que les prompts deviennent plus longs que tu l&apos;espérais. Ils le deviennent toujours.&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 : instruction courte, 4096 tokens d&apos;output&amp;lt;/summary&amp;gt;&lt;/p&gt;
&lt;p&gt;La Run G ressemble à la Run C, mais pousse l&apos;output bien plus loin : 4096 tokens. C&apos;est la shape des agents qui rédigent des plans, génèrent du code, font de longues analyses ou résument plusieurs fichiers.&lt;/p&gt;
&lt;p&gt;Pour ce genre de workload, le premier token est presque secondaire. Si la réponse est longue, la vitesse de decode détermine l&apos;expérience. Dix secondes de différence au début, c&apos;est agaçant. Attendre l&apos;output pendant des minutes, c&apos;est pire.&lt;/p&gt;
&lt;p&gt;NVFP4 reste le plus fort ici. Plus important : il reste aussi au-dessus de 25 tok/s/user à dix utilisateurs. Pour du hardware local sur une machine de bureau, c&apos;est tout simplement utilisable.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Utilisateurs&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;Pour les flows d&apos;agents, c&apos;est assez tranché : BF16 n&apos;est pas cassé, mais tu paies chaque output longue deux fois. D&apos;abord en mémoire, ensuite en temps d&apos;attente.&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 : baseline bureau open-loop&amp;lt;/summary&amp;gt;&lt;/p&gt;
&lt;p&gt;À partir d&apos;ici, l&apos;interprétation change. Les runs précédentes poussent des batches contrôlés à travers le modèle. La Run H utilise du trafic open-loop : les requests arrivent selon une distribution de Poisson. Le serveur doit donc gérer des arrivées qui n&apos;attendent pas gentiment que la précédente soit finie.&lt;/p&gt;
&lt;p&gt;Ça ressemble plus à un bureau. Pas parfait, mais mieux que tout le monde en même temps ou tout à fait séquentiel. Les metrics sont différentes aussi. Le TPOT dit à quelle vitesse les tokens arrivent une fois que c&apos;est ton tour. Le TTFT P50 dit l&apos;expérience normale. Le TTFT P99 dit ce que remarque le malchanceux.&lt;/p&gt;
&lt;p&gt;Ici, FP8 devient intéressant. NVFP4 gagne la médiane et le TPOT, mais FP8 gagne la tail. C&apos;est exactement pour ça que je ne veux pas finir avec &quot;NVFP4 est toujours mieux&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;Ce peak concurrent de BF16 a l&apos;air bon sur le papier, mais ne l&apos;est pas. La queue monte parce que BF16 la vide moins vite. NVFP4 traite plus vite, donc moins de requests sont ouvertes en même temps. Ce n&apos;est pas une capacité plus faible, c&apos;est moins de file d&apos;attente.&lt;/p&gt;
&lt;p&gt;Le vrai choix est entre NVFP4 et FP8. Tu veux la meilleure médiane et l&apos;output la plus rapide, alors NVFP4. Tu veux le P99 le plus propre sur ce workload, alors 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 : replay ShareGPT&amp;lt;/summary&amp;gt;&lt;/p&gt;
&lt;p&gt;Le replay ShareGPT est plus brouillon et donc utile. Les vraies conversations ont des longueurs variables, des questions de suivi, des réponses courtes, des réponses longues et des prompts qui n&apos;ont pas été gentiment lissés par un auteur de benchmark.&lt;/p&gt;
&lt;p&gt;C&apos;est la run en laquelle j&apos;ai le plus confiance pour le ressenti chat. Pas pour des documents d&apos;entreprise, mais pour la question : qu&apos;est-ce que ça donne quand plusieurs personnes mènent des conversations tout au long de la journée ?&lt;/p&gt;
&lt;p&gt;Le pattern de la Run H tient. NVFP4 est le plus rapide pour l&apos;utilisateur moyen. FP8 a le meilleur 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 donne une impression instantanée pour la plupart des utilisateurs : 157 ms de TTFT P50 et 26 ms de TPOT P50. Mais le P99 est de 1361 ms, là où FP8 reste à 422 ms. C&apos;est une grosse différence.&lt;/p&gt;
&lt;p&gt;Pour un chat interne où une seule request plus lente n&apos;est pas un drame, je choisis NVFP4. Pour une UI produit avec une promesse de latence dure, je prendrais FP8 plus au sérieux.&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 : pic du lundi matin&amp;lt;/summary&amp;gt;&lt;/p&gt;
&lt;p&gt;La Run J est en oversubscribe. La cible est de 1,5 requests par seconde avec une concurrency-cap de 25. Ce n&apos;est pas la journée de travail normale. C&apos;est le test de ce qui se passe quand la demande est plus grande que ce que le serveur peut suivre proprement.&lt;/p&gt;
&lt;p&gt;En oversubscribe, je regarde d&apos;abord l&apos;achieved RPS. Pas le configured RPS, parce qu&apos;il est le même pour tout le monde. La question est : combien de requests le serveur traite réellement pendant qu&apos;il est sous pression ?&lt;/p&gt;
&lt;p&gt;Là, NVFP4 gagne clairement. FP8 garde la tail plus propre, mais NVFP4 fait passer beaucoup plus de travail par la 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;Concrètement : NVFP4 traite environ 35 requests par minute. BF16 environ 15. C&apos;est la différence entre une queue qui se vide lentement et une queue qui fait douter les utilisateurs s&apos;ils doivent cliquer encore une fois. Ne clique pas. Ce deuxième clic n&apos;aide jamais.&lt;/p&gt;
&lt;p&gt;&amp;lt;/details&amp;gt;&lt;/p&gt;
&lt;h2&gt;Les trois précisions côte à côte&lt;/h2&gt;
&lt;p&gt;Si je dois choisir une run de chat réaliste, je prends le replay ShareGPT. C&apos;est là que tu vois la distinction le plus proprement : NVFP4 gagne l&apos;expérience normale, FP8 gagne la tail, BF16 participe mais ne convainc nulle part.&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;Meilleur choix&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;égalité&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;En oversubscribe, la différence se durcit :&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;Meilleur choix&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;Ça rend le choix plus pratique que je ne le pensais au départ. NVFP4 est le default si tu veux du throughput et une expérience utilisateur normale. FP8 est le choix si tu trouves le P99 plus important que la médiane. BF16 est la baseline qui te sert à vérifier si la quantization démolit ton accuracy.&lt;/p&gt;
&lt;h2&gt;Pourquoi FP8 gagne le P99&lt;/h2&gt;
&lt;p&gt;Mon hypothèse : NVFP4 donne à vLLM plus de place mémoire et donc plus de place pour le batching. Ça augmente le throughput et baisse le TPOT, mais des requests individuelles peuvent parfois attendre plus longtemps avant de tomber proprement dans un batch.&lt;/p&gt;
&lt;p&gt;FP8 a moins de headroom que NVFP4, mais encore assez pour ce workload. Du coup, le scheduler semble plus prévisible. Moins agressif, moins rapide en médiane, meilleur dans la tail.&lt;/p&gt;
&lt;p&gt;BF16 a le pire des deux mondes : gros weights, moins de headroom KV-cache et un decode plus bas. La queue se remplit, mais pas parce que le serveur encaisse tant en même temps. Il en vient juste à bout moins vite.&lt;/p&gt;
&lt;p&gt;Je veux creuser ça davantage avec des réglages de scheduler et du prefix caching. Les chiffres bruts et les définitions des tests sont dans l&apos;&lt;a href=&quot;/fr/arena/&quot;&gt;arena&lt;/a&gt; pour que je puisse mesurer les futures runs à la même barre.&lt;/p&gt;
&lt;h2&gt;Comparaison avec Gemma-4-26B-A4B&lt;/h2&gt;
&lt;p&gt;Nemotron-NVFP4 est en single-user presque deux fois plus rapide que Gemma-NVFP4. En multi-user, la différence se réduit, mais reste en général positive.&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;Ce pattern colle à ce qu&apos;est le modèle. Nemotron a 3B de params actifs, Gemma 4B de params actifs. En single-user, ça aide beaucoup. En multi-user, le bottleneck se déplace vers la memory bandwidth et le scheduling, et là la différence se réduit.&lt;/p&gt;
&lt;h2&gt;Ce que ça veut dire pour l&apos;IA on-prem&lt;/h2&gt;
&lt;p&gt;Mon choix par défaut pour ce Spark, c&apos;est &lt;strong&gt;NVFP4&lt;/strong&gt;. Pas parce que 4 bits est par principe plus joli, mais parce que les chiffres sur ces workloads le portent : throughput le plus élevé, médiane la plus rapide, TPOT le plus bas, footprint le plus petit.&lt;/p&gt;
&lt;p&gt;Je choisis &lt;strong&gt;FP8&lt;/strong&gt; quand la tail-latency compte plus que la médiane. Pense à une UI où tu veux pouvoir dire que 99 pour cent des requests démarrent dans une certaine limite. Dans les Runs H, I et J, FP8 gagne de façon constante sur le TTFT P99.&lt;/p&gt;
&lt;p&gt;Je choisis &lt;strong&gt;BF16&lt;/strong&gt; seulement comme baseline ou pour de la validation accuracy-critique. Pas comme default de production. Pour ça, c&apos;est trop cher sur le Spark : environ trois fois plus de mémoire que NVFP4 et grosso modo la moitié de la vitesse.&lt;/p&gt;
&lt;p&gt;Pour un bureau de 25 personnes avec un workload chat et type RAG, je ferais tourner NVFP4, avec une suite d&apos;eval maison à côté. Pour un chatbot externe avec une promesse de latence serrée, je testerais FP8. Pour BF16, je garderais surtout une run courte pour voir ce que la quantization change sur le fond.&lt;/p&gt;
&lt;h2&gt;Ce que ces runs ne disent pas&lt;/h2&gt;
&lt;p&gt;Pas de tests d&apos;accuracy. FP8 et NVFP4 peuvent diverger sur le fond de BF16. Pour la production, tu dois mesurer ça sur tes propres documents, tes propres prompts et ta propre tolérance aux erreurs.&lt;/p&gt;
&lt;p&gt;Pas de benchmarks multimodal. Nemotron-3-Nano-Omni est multimodal-aware, mais ces runs sont text-only. La vision et l&apos;audio restent hors champ ici.&lt;/p&gt;
&lt;p&gt;Pas de comparaison avec des modèles dense. C&apos;est un modèle MoE. Les modèles dense donnent un ressenti différent, surtout sur la vitesse d&apos;output et la façon dont vLLM les gère.&lt;/p&gt;
&lt;p&gt;Pas de conclusion définitive sur le scheduler. La tail FP8-vs-NVFP4 est assez intéressante pour la tester à part avec d&apos;autres réglages de batching et de scheduling.&lt;/p&gt;
&lt;h2&gt;Où j&apos;atterris&lt;/h2&gt;
&lt;p&gt;Le choix de précision n&apos;est pas un détail. Sur le Spark, il détermine si la même machine donne l&apos;impression d&apos;une expérience locale ou de quelque chose que tu peux confier à des collègues sans devoir expliquer toutes les cinq minutes.&lt;/p&gt;
&lt;p&gt;NVFP4 double dans beaucoup de runs l&apos;expérience utilisable par rapport à BF16. FP8 est moins spectaculaire, mais plus prévisible dans la tail. BF16 reste utile comme point de référence, pas comme terminus.&lt;/p&gt;
&lt;p&gt;La leçon pratique de ces trois posts pris ensemble : suis les recettes du vendor, fais tourner l&apos;image stable et mesure ton propre workload. Ne bricole pas toi-même sauf si tu as une bonne raison. Avec Gemma, j&apos;avais une raison. Avec le recul, elle était médiocre.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Gemma-4 sur le DGX Spark : le prix du contexte</title>
    <id>https://djangodevreng.nl/fr/blog/gemma-4-dgx-spark-benchmarks/</id>
    <link rel="alternate" type="text/html" href="https://djangodevreng.nl/fr/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/fr/a-propos/</uri></author>
    <category term="on-prem"/>
    <summary>Neuf benchmarks de Gemma-4-26B-A4B-it sur le DGX Spark avec llama-benchy et vLLM. Le decode tient ; le prefill et la file d&apos;attente decident du ressenti.</summary>
    <content type="html">&lt;p&gt;Je voulais savoir comment un DGX Spark se comporte comme machine d&apos;IA locale pour un environnement de bureau.&lt;/p&gt;
&lt;p&gt;Pas en théorie. Juste : charger Gemma-4-26B-A4B-it dans vLLM, lui balancer llama-benchy, agrandir les context windows, allonger l&apos;output, monter la concurrency, ajouter du multi-turn, et regarder où ça reste agréable et où l&apos;attente commence à faire mal. Et quand cette histoire a commencé à se dessiner, une deuxième question est arrivée : et si je n&apos;effectue plus mes tests en lockstep, mais que je laisse les requêtes arriver de façon organique, comme dans un vrai bureau ? Pour ça j&apos;ai pris la suite de benchmark de vLLM elle-même, qui fait ce que llama-benchy ne fait pas : arrivées de Poisson, percentiles, vraies données de conversation. Comment je mesure tout ça, c&apos;est dans la &lt;a href=&quot;/fr/arena/methodologie/&quot;&gt;methodologie&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;La version courte : pour un usage de bureau normal, ça a l&apos;air bon. Des prompts courts à moyens, des outputs plus longs, et même des conversations sur plusieurs tours restent rapides au ressenti, même avec dix utilisateurs en même temps. Avec de grands context windows, le problème n&apos;est pas les tokens par seconde, mais combien de temps quelqu&apos;un fixe une fenêtre de chat vide avant que le premier token n&apos;arrive. Et si tu surcharges vraiment la machine, elle ne scale pas, elle met en file.&lt;/p&gt;
&lt;p&gt;Ça n&apos;en fait pas une histoire de &quot;le DGX Spark y arrive ou pas&quot;. Ça en fait une histoire de workload. Neuf tests, deux méthodes, une machine. C&apos;est l&apos;un des build logs sous le guide &lt;a href=&quot;/fr/dgx-spark/&quot;&gt;faire tourner des LLMs sur le DGX Spark&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Pourquoi ce test&lt;/h2&gt;
&lt;p&gt;Avec l&apos;IA on-prem, tu en viens vite à parler de vie privée, de garder les données plus près et d&apos;être moins dépendant de modèles hébergés. Tout ça est vrai, mais finalement une question plus plate suit.&lt;/p&gt;
&lt;p&gt;La machine peut-elle encaisser ?&lt;/p&gt;
&lt;p&gt;Un modèle local qui répond proprement à un seul prompt de démo, c&apos;est sympa. Mais la production ressemble rarement à ça. Là tu as plusieurs utilisateurs, un contexte plus grand, des agent flows, des tool-calls, des retries et parfois quelqu&apos;un qui colle un demi-roman dans un ticket.&lt;/p&gt;
&lt;p&gt;C&apos;est pourquoi je ne voulais pas mesurer uniquement les tokens par seconde sur un seul prompt. Je voulais voir ce qui se passe quand on charge la machine sous différents angles : de &quot;dix utilisateurs, prompts courts, longues réponses&quot; à &quot;dix utilisateurs, conversations de cinq tours, mémoire croissante&quot; jusqu&apos;à &quot;des requêtes qui arrivent de façon organique comme dans un vrai bureau, pas toutes en même temps et pas toutes du même format&quot;.&lt;/p&gt;
&lt;p&gt;Pour ces benchmarks j&apos;ai testé un seul modèle :&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 comme endpoint compatible OpenAI&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Le dense viendra plus tard. MoE vs dense aussi. Cet article ne parle que de Gemma-4-26B-A4B-it sur le DGX Spark. Ce run tourne en BF16 ; ce qui arrive au même Gemma-4 quand tu &lt;a href=&quot;/fr/blog/gemma-4-nvfp4-vs-bf16-dgx-spark/&quot;&gt;quantises en NVFP4&lt;/a&gt; est une autre histoire.&lt;/p&gt;
&lt;h2&gt;Ce que j&apos;attendais au départ&lt;/h2&gt;
&lt;p&gt;Mon attente était simple : le MoE resterait raisonnablement bon sous requêtes concurrentes, mais je pensais que le DGX Spark atteindrait ses limites plus vite dès que le contexte deviendrait grand.&lt;/p&gt;
&lt;p&gt;Surtout à 25k de contexte.&lt;/p&gt;
&lt;p&gt;Le contexte coûte cher. Tu payes non seulement le prompt qui entre, mais aussi le KV-cache que vLLM doit tenir à jour. Multiplie ça par plusieurs utilisateurs et ça devient d&apos;un coup un problème de mémoire et un problème de file d&apos;attente.&lt;/p&gt;
&lt;p&gt;J&apos;étais curieux de cinq choses :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;le decode reste-t-il utilisable quand le contexte grandit ?&lt;/li&gt;
&lt;li&gt;combien le prefill ajoute-t-il au temps jusqu&apos;au premier token ?&lt;/li&gt;
&lt;li&gt;que se passe-t-il quand le prompt est court mais l&apos;output long ?&lt;/li&gt;
&lt;li&gt;comment se comporte-t-il avec des conversations multi-turn, où le contexte s&apos;épaissit à chaque tour ?&lt;/li&gt;
&lt;li&gt;et (ajouté seulement plus tard) à quoi ressemble tout ça quand les requêtes n&apos;arrivent pas en lockstep, mais de façon organique ?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Cette dernière question s&apos;est révélée être la moitié de l&apos;histoire.&lt;/p&gt;
&lt;h2&gt;L&apos;installation de test&lt;/h2&gt;
&lt;p&gt;Le serveur tournait dans Docker avec l&apos;image officielle de vLLM :&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;Quelques détails comptent.&lt;/p&gt;
&lt;p&gt;Le prefix caching est volontairement coupé. Je voulais d&apos;abord voir le coût brut du prefill, pas un benchmark qui s&apos;embellit parce que les prompts se ressemblent.&lt;/p&gt;
&lt;p&gt;Le KV-cache tourne en fp8. Sans ça, 128k de contexte avec plusieurs requêtes en même temps devient vite un exercice de mémoire dont tu ne tires pas grand-chose.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Les neuf tests ci-dessous utilisent exactement cette config serveur.&lt;/strong&gt; Pas de redémarrage, pas d&apos;ajustement en cours de route. Ce qui varie, c&apos;est le workload : taille du prompt, taille de l&apos;output, concurrency, depth, et pour les tests open-loop aussi l&apos;arrival rate et la burstiness.&lt;/p&gt;
&lt;p&gt;Ce que le Spark en fait :&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Composant&lt;/th&gt;
&lt;th&gt;Valeur&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;Parallele theorique @ 128k&lt;/td&gt;
&lt;td&gt;~4 requests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Parallele theorique @ 8k&lt;/td&gt;
&lt;td&gt;~50 requests&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;À plein contexte par requête, la mémoire est juste. En pratique aucun test n&apos;utilise 128k en même temps par utilisateur, donc le goulot se déplace vers le prefill-compute et le scheduler-batching. On le retrouve ci-dessous.&lt;/p&gt;
&lt;h2&gt;Run A : agrandir le contexte&lt;/h2&gt;
&lt;p&gt;Le premier run faisait grandir le contexte de 4k à 25k. La concurrency suivait de 1 à 5 et 10. Closed-loop, donc N utilisateurs en 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; c&apos;est le prefill, c&apos;est-à-dire combien de prompt tokens entrent. &lt;code&gt;tg&lt;/code&gt; c&apos;est le decode, c&apos;est-à-dire combien de tokens le modèle génère ensuite. llama-benchy rapporte mean ± stddev. Pas de p95. C&apos;est important à retenir, car sur la latence tu te racontes sinon vite des histoires.&lt;/p&gt;
&lt;p&gt;Voici le résumé du Run A :&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Contexte&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 contexte, une ligne par utilisateurs concurrents (1, 5, 10). Le TTFT grimpe de ~1.4 secondes a 4k jusqu&apos;a 36 secondes a 25k de contexte avec 10 users.&quot; /&amp;gt;
&amp;lt;figcaption&amp;gt;Run A : temps d&apos;attente du premier token, par utilisateurs concurrents. Double le prompt et tu doubles l&apos;attente.&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 : vitesse de decode par utilisateur vs contexte. A c=1 le decode reste entre 22.7 et 24.1 tokens par seconde, a c=10 il descend de 9.5 a 5.4 tokens par seconde.&quot; /&amp;gt;
&amp;lt;figcaption&amp;gt;Run A : decode par utilisateur. Avec un seul utilisateur ça reste presque plat ; ce n&apos;est qu&apos;avec plusieurs users et un grand contexte que ça s&apos;effondre.&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;h2&gt;Run B : tenir 25k de contexte, monter la concurrency&lt;/h2&gt;
&lt;p&gt;Ensuite j&apos;ai poussé le même contexte 25k plus fort. Plus de variation du contexte, juste ajouter des utilisateurs.&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;Pas d&apos;OOM. Pas de crash. Le DGX Spark a survécu à 20 requêtes concurrentes à 25k de contexte.&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 : le TTFT croit lineairement avec la concurrency : 19.9s a 5 users, 35.4s a 10, 67.4s a 20. Le decode agrege reste autour de 30 tok/s.&quot; /&amp;gt;
&amp;lt;figcaption&amp;gt;Run B : le decode agrégé reste à ~30 tok/s ; toute l&apos;attente supplémentaire passe dans le TTFT.&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;C&apos;est le bord de stress du benchmark. Le decode agrégé reste autour de 30 tok/s, que tu mettes 5, 10 ou 20 utilisateurs. Par utilisateur il descend de 8.51 à 3.16 tok/s. Mais le vrai problème c&apos;est le TTFT : à 20 utilisateurs la requête moyenne attend 67 secondes avant que le premier token n&apos;arrive. Le serveur n&apos;est pas cassé pour autant. Le workload ne colle juste plus à une attente de chat en temps réel.&lt;/p&gt;
&lt;h2&gt;Run C : prompt court, output long&lt;/h2&gt;
&lt;p&gt;Le Run C a inversé la forme. Pas 25k de contexte avec output court, mais 1024 prompt tokens et 1024 output tokens.&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 : le decode par utilisateur descend de 23.9 (c=1) a 10.9 (c=10), le decode agrege grimpe a 86.5 tok/s.&quot; /&amp;gt;
&amp;lt;figcaption&amp;gt;Run C : prompt court, output long. Le decode agrégé scale proprement jusqu&apos;à 86 tok/s, le par-utilisateur reste largement lisible.&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;À dix utilisateurs en même temps, le TTFT reste à 1.3 seconde. Ça ressemble à du chat.&lt;/p&gt;
&lt;h2&gt;Run G : output encore plus long&lt;/h2&gt;
&lt;p&gt;Les Run A, B et C montraient assez pour rendre plausible l&apos;histoire &quot;le decode est stable, le prefill décide de l&apos;attente&quot;. Mais un scénario restait ouvert : et si l&apos;output est encore beaucoup plus long ? Un agent qui génère du code. Un tool-call avec output structuré. Un long résumé.&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 : decode par utilisateur 24.2 (c=1), 14.3 (c=5), 11.8 (c=10) ; agrege 24.2, 46.1, 83.8 tok/s.&quot; /&amp;gt;
&amp;lt;figcaption&amp;gt;Run G : output 4k : les longues générations sont seulement plus longues, pas plus lentes. Le par-utilisateur reste proche du Run C.&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;Le decode/user sur 4096 tokens baisse à peine comparé aux 1024 tokens du C. À c=1 c&apos;est 24.17 (G) vs 23.86 (C). À c=10 c&apos;est 11.75 (G) vs 10.92 (C). Les longues générations ne se cumulent pas, elles durent juste proportionnellement plus longtemps. Et le TTFT est le plus bas ici : sous la demi-seconde à dix utilisateurs en même temps.&lt;/p&gt;
&lt;h2&gt;Run F : contexte moyen, plus d&apos;utilisateurs&lt;/h2&gt;
&lt;p&gt;Entre le Run C (1k de contexte) et le Run B (25k de contexte) se trouvait un trou plus proche de la réalité. Un flow RAG typique avec quatre chunks de ~2k tokens arrive autour de 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 de contexte. Le TTFT monte de 4.3s (c=5) a 8.0s (c=10) a 14.6s (c=20) ; le decode agrege atteint 97.4 tok/s.&quot; /&amp;gt;
&amp;lt;figcaption&amp;gt;Run F : 8k de contexte. Le TTFT croît linéairement avec la concurrency, le decode agrégé continue de scaler jusqu&apos;à presque 100 tok/s.&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;Trois observations.&lt;/p&gt;
&lt;p&gt;Le prefill throughput reste à un plat 5.5k tok/s, que ce soit 5, 10 ou 20 utilisateurs. À 8k de contexte la machine est déjà saturée au niveau du prefill. Le decode agrégé continue de scaler : dans le Run B (25k) ça plafonnait à ~30 t/s, ici ça monte jusqu&apos;à 97.4 t/s. Et le plus important : le TTFT à 8k de contexte est grosso modo un quart de ce qu&apos;il est à 25k. Même concurrency, même machine, taille de prompt différente.&lt;/p&gt;
&lt;h2&gt;Run E : le multi-turn comme vrai travail de bureau&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;--depth 4&lt;/code&gt; signifie : cinq tours d&apos;affilée par requête (initial + quatre questions de suivi). Concurrency à 10 signifie : dix de ces conversations en parallèle.&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. Par-utilisateur 24.0/13.1/10.4 tok/s, agrege 24.0/59.5/92.4 tok/s, le plus haut agrege de tous les runs closed-loop.&quot; /&amp;gt;
&amp;lt;figcaption&amp;gt;Run E : multi-turn (depth = 4) à 2k de contexte de départ. L&apos;agrégé de 92 tok/s est le plus haut chiffre des six runs closed-loop.&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;Trois choses ont attiré l&apos;attention, que je n&apos;avais pas prévues au départ.&lt;/p&gt;
&lt;p&gt;Le decode par utilisateur en multi-turn est identique au single-turn. Le multi-turn ne rend pas les tokens plus lents, seul le nombre de prefills augmente. Le decode agrégé à c=10 est de 92.42 t/s, le plus haut de n&apos;importe quel run closed-loop. En multi-turn, vLLM reçoit un flux plus dense de requêtes dépendantes, et peut les batcher plus efficacement que dix prompts single-shot séparés. Et le TTFT à c=10 est en moyenne de 2.13 secondes sur les cinq tours. Sous trois secondes, ça ressemble encore à du chat.&lt;/p&gt;
&lt;h2&gt;Ce que les six runs closed-loop montrent ensemble&lt;/h2&gt;
&lt;p&gt;Un tableau qui met tout côte à côte à c=10 :&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;Decode agrege (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;Nuage de points des six runs closed-loop a c=10. Axe Y decode/user (5 a 12 tok/s), axe X TTFT logarithmique (0.5s a 49s). G et C en haut a gauche, A-25k en bas a droite.&quot; /&amp;gt;
&amp;lt;figcaption&amp;gt;Les six runs closed-loop à 10 utilisateurs concurrents. Le decode par utilisateur bouge à peine jusqu&apos;à 8k de contexte. Le TTFT bouge partout.&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;Deux motifs ressortent.&lt;/p&gt;
&lt;p&gt;Le decode/user bouge à peine jusqu&apos;à 8k de contexte. Entre le Run G et le Run F il y a un facteur 32 sur la taille du prompt et un facteur 8 sur la taille de l&apos;output. Pourtant le decode/user y reste entre 9.3 et 11.8 tok/s. Ce n&apos;est qu&apos;à 16k+ que cette bande s&apos;effondre.&lt;/p&gt;
&lt;p&gt;Le TTFT bouge partout et est presque une fonction de la seule taille du prompt. Double le prompt et le TTFT double grosso modo avec. La taille de l&apos;output et le depth ne comptent presque rien pour le TTFT.&lt;/p&gt;
&lt;p&gt;C&apos;est la conclusion closed-loop. Elle tient, et elle raconte une vraie partie de l&apos;histoire. Mais il y a un trou dedans.&lt;/p&gt;
&lt;h2&gt;Mais ce sont des tests synthétiques&lt;/h2&gt;
&lt;p&gt;Les six runs ci-dessus testent la capacité. &lt;em&gt;Des plafonds.&lt;/em&gt; Tous dans la même forme : N utilisateurs en lockstep, tous le même format de prompt, tous appuyant sur les boutons d&apos;envoi en même temps. C&apos;est une bonne façon de mesurer où ça casse. C&apos;est une mauvaise façon de mesurer ce que ressent un vrai bureau.&lt;/p&gt;
&lt;p&gt;Parce qu&apos;un vrai bureau a 25 employés dont quelques-uns en moyenne font quelque chose en même temps. Un collègue pose une question courte. Un autre est en plein RAG avec 8k de contexte. Le troisième est au tour 4 d&apos;une conversation. Et les requêtes n&apos;arrivent pas en lockstep. Elles arrivent comme un processus de Poisson avec de temps en temps un burst, parce que quelqu&apos;un vient de finir un mail et que trois collègues veulent un café en même temps.&lt;/p&gt;
&lt;p&gt;C&apos;est ce que &lt;strong&gt;&lt;code&gt;vllm bench serve&lt;/code&gt; de vLLM&lt;/strong&gt; sait faire et que llama-benchy ne sait pas :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Open-loop&lt;/strong&gt; avec arrival rate. Dispatcher les requêtes selon une distribution de Poisson ou de Gamma, au lieu du lockstep.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Percentiles.&lt;/strong&gt; P50, P90, P95, P99 sur TTFT, TPOT (time per output token), ITL (inter-token latency) et E2E. Fini les mean ± stddev.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Datasets réalistes.&lt;/strong&gt; Replay ShareGPT de 94k+ vraies conversations avec des longueurs de prompt qui varient naturellement et une structure multi-turn.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Workloads mixtes.&lt;/strong&gt; Échantillonner des prompts depuis une distribution au lieu de tester une seule forme fixe.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Trois tests ci-dessous, le même serveur (pas de redémarrage), mais avec ces autres lunettes sur le nez.&lt;/p&gt;
&lt;h2&gt;Test H : baseline de bureau réaliste&lt;/h2&gt;
&lt;p&gt;Le scénario : 25 personnes actives en moyenne, chacune envoie un prompt à peu près une fois toutes les 1-2 minutes, les prompts varient fortement en longueur. Les arrivées sont légèrement 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;Avec &lt;code&gt;--random-range-ratio 0.9&lt;/code&gt;, les longueurs d&apos;input varient de 399 à 7600 tokens, les outputs de 49 à 950. &lt;code&gt;--burstiness 0.7&lt;/code&gt; est un peu plus clumpy que du Poisson pur. Les gens appuient souvent sur entrée par petites rafales, pas comme un métronome. Target rate de 0.3 req/s = ~18 prompts/min sur 25 utilisateurs.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Valeur&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;L&apos;utilisateur médian reçoit le premier token en 1.29s. Ça ressemble encore à du chat. Le tail reste dans les clous : le P99 attend 3.3 secondes, largement sous le double de la moyenne.&lt;/p&gt;
&lt;p&gt;Et regarde le peak concurrent : &lt;strong&gt;36&lt;/strong&gt;. À un target rate de seulement 0.3 req/s. Aucun run closed-loop n&apos;en approchait. La seule burstiness de Poisson, combinée à un temps de réponse moyen de ~86 secondes, produit des pics plus violents que n&apos;importe quel stress-test du Run B. C&apos;est la chose que le closed-loop ne peut littéralement pas montrer.&lt;/p&gt;
&lt;h2&gt;Test I : vraies conversations (replay ShareGPT)&lt;/h2&gt;
&lt;p&gt;Pattern d&apos;arrivée identique au Test H, mais maintenant avec 250 vraies conversations multi-turn de ShareGPT V3 comme prompts. Certaines sont 1 tour de 200 tokens, d&apos;autres sont 15 tours avec un contexte qui grandit à chaque fois.&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;Valeur&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;C&apos;est un autre univers que le Test H. &lt;strong&gt;TTFT P99 = 637 ms.&lt;/strong&gt; 99% des utilisateurs voient le premier token en moins de 650 millisecondes. C&apos;est vraiment une vitesse de chat.&lt;/p&gt;
&lt;p&gt;Pattern d&apos;arrivée identique au Test H, expérience totalement différente. La différence tient entièrement à la taille du prompt : les conversations ShareGPT font en moyenne 228 tokens, pas 4000. Prompt court = prefill bon marché = pas de pression de file = TTFT sous la seconde.&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;C&apos;est aussi un avertissement : le workload synthétique du Test H &lt;em&gt;exagère&lt;/em&gt; le poids d&apos;un prompt de bureau moyen. Les conversations du monde réel sont plus légères que notre baseline random 4k, donc les chiffres de la pratique sont probablement plus proches du Test I que du Test H.&lt;/p&gt;
&lt;h2&gt;Test J : pic du lundi matin&lt;/h2&gt;
&lt;p&gt;Et si tout le monde arrive en même temps et se met à appuyer sur les boutons d&apos;envoi ? Cinq fois la charge, max 25 requêtes concurrentes pour modéliser un vrai bureau.&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;Valeur&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;C&apos;est le chiffre clé : &lt;strong&gt;achieved rate 0.26 à un target de 1.5&lt;/strong&gt;. Le système est &lt;strong&gt;throttle de presque 6x&lt;/strong&gt;. Pas parce qu&apos;il crashe (les 300 requêtes réussissent, aucun échec), mais parce que la file se remplit jusqu&apos;à 25 et y retient les requêtes jusqu&apos;à ce qu&apos;il y ait de la place.&lt;/p&gt;
&lt;p&gt;Compare le Test H (target 0.3) et le 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;L&apos;expérience médiane est même &lt;em&gt;légèrement meilleure&lt;/em&gt; au Test J qu&apos;au Test H (1.13s vs 1.29s). Le cap crée un flux plus régulier. Mais le tail est dramatiquement pire : le P99 double, de 3.3s à 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;Percentiles de TTFT open-loop pour H (random 4k 0.3 rps), I (ShareGPT 0.3 rps) et J (random 4k 1.5 rps). I reste sous la seconde partout ; H monte jusqu&apos;a 6.4s P99 ; J file jusqu&apos;a 14.8s P99.&quot; /&amp;gt;
&amp;lt;figcaption&amp;gt;Percentiles de TTFT open-loop. La médiane dit peu ; le tail raconte où la surcharge fait mal.&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;Le Spark ne scale pas sous oversubscribe, il &lt;strong&gt;met en file&lt;/strong&gt;. C&apos;est une bonne nouvelle : degradation gracieuse au lieu de crashes. Pour l&apos;IA on-prem c&apos;est vraiment le meilleur failure mode.&lt;/p&gt;
&lt;h2&gt;Ce que le closed-loop cache, ce que l&apos;open-loop exagère&lt;/h2&gt;
&lt;p&gt;Les deux méthodes racontent chacune une partie différente de l&apos;histoire. Toutes les deux vraies, toutes les deux incomplètes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Le closed-loop sous-estime la profondeur de la file.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Dans le Run F j&apos;ai testé c=10 comme &quot;dix utilisateurs en même temps&quot;. Ça sonne comme une situation de bureau raisonnablement chargée. Mais le Test H montre qu&apos;un arrival rate organique de 0.3 req/s suffit déjà à produire des pics de 36 requêtes concurrentes. La revendication closed-loop &quot;10 utilisateurs&quot; est donc plus optimiste que ce que la pratique montre.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;L&apos;open-loop avec du synthétique exagère la charge réelle.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;En même temps : le Test H utilise des prompts random 4k. Un vrai bureau ne pose pas 25 prompts moyens de 4k par minute. ShareGPT (Test I) est un bien meilleur proxy de &quot;ce que les gens tapent&quot;, en moyenne 228 tokens. Avec cette forme de workload, le peak concurrent est de 17 au lieu de 36, et le P99 TTFT de 637ms au lieu de 3.3s.&lt;/p&gt;
&lt;p&gt;La pratique se situe donc entre le Run F et le Test I :&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;th&gt;TTFT (P50 ou 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;Pour un bureau avec des prompts réalistes et un pattern d&apos;arrivée réaliste, le Test I est le plus proche de ce que ressentent les gens. Pour la planification de capacité (&quot;et si tout le monde pose une question RAG de 8k en même temps ?&quot;), le Run F est le plus proche de ce que la machine peut digérer.&lt;/p&gt;
&lt;h2&gt;Le tail raconte ce que la moyenne cache&lt;/h2&gt;
&lt;p&gt;llama-benchy donnait seulement mean ± stddev. Ça a l&apos;air de beaucoup d&apos;information, mais ça cache la partie qui compte le plus pour tes utilisateurs : le tail.&lt;/p&gt;
&lt;p&gt;Le mean TTFT du Test I est de 376ms. Ça a l&apos;air bien. Mais qu&apos;est-ce que ça dit du 1% d&apos;utilisateurs où la file a justement grimpé ? Rien. Pour ça il te faut le P99, et il est à 637ms. Dans ce cas pas de problème (les deux sous la seconde), mais c&apos;est le &lt;em&gt;principe&lt;/em&gt; que tu dois connaître.&lt;/p&gt;
&lt;p&gt;Le mean TTFT du Test H est de 1395ms. Le P99 est de 3316ms. Largement plus de deux fois pire que la moyenne pour le 1% malchanceux.&lt;/p&gt;
&lt;p&gt;Le mean TTFT du Test J est de 1370ms. Le P99 est de &lt;strong&gt;6157ms&lt;/strong&gt;. Largement quatre fois la moyenne.&lt;/p&gt;
&lt;p&gt;Pour les décisions de SLA (&quot;notre système répond en moins de 3 secondes à 95% des requêtes&quot;) tu as besoin de ces percentiles. Mean ± stddev peut suggérer un SLA que tu ne tiens pas aux moments qui comptent le plus, c&apos;est-à-dire quand c&apos;est chargé.&lt;/p&gt;
&lt;p&gt;C&apos;est pourquoi le blog ne peut pas se reposer uniquement sur llama-benchy. Tester la capacité est une chose. Rapporter la tail latency en est une autre.&lt;/p&gt;
&lt;h2&gt;Le decode n&apos;est pas le problème&lt;/h2&gt;
&lt;p&gt;Avec un seul utilisateur, le decode reste presque plat.&lt;/p&gt;
&lt;p&gt;4k de contexte atteint 24.08 tok/s par utilisateur. 25k de contexte atteint 22.75 tok/s. 4096 output tokens (Run G, c=1) atteint 24.17 tok/s. Multi-turn avec depth 4 (Run E, c=1) atteint 23.97 tok/s. Quatre workloads différents, tous à moins de 6 pourcent les uns des autres.&lt;/p&gt;
&lt;p&gt;À dix utilisateurs en même temps il se passe quelque chose de comparable, juste sur une ligne plus basse. Run G : 11.75 tok/s/user. Run C : 10.92. Run E : 10.43. Run F : 9.31. Et dans les tests open-loop : le Test I donne TPOT P50 = 95ms = ~10.5 tok/s/user. Les Test H et J donnent TPOT P50 = ~185ms = ~5.4 tok/s/user (parce que les pics y atteignent 25+ concurrent).&lt;/p&gt;
&lt;p&gt;Bref : la vitesse de decode par token est une fonction de la &lt;strong&gt;charge concurrente moyenne&lt;/strong&gt;, pas de la longueur du prompt, de la longueur de l&apos;output, du multi-turn ou du pattern d&apos;arrivée. Ce n&apos;est qu&apos;à 16k+ de contexte combiné à plusieurs utilisateurs (Run A) qu&apos;elle descend vraiment sous 7 t/s/user.&lt;/p&gt;
&lt;p&gt;La concurrency en soi n&apos;est pas le problème. Le long output non plus. Le multi-turn non plus. Seul un grand contexte en même temps que plusieurs utilisateurs mange le decode.&lt;/p&gt;
&lt;h2&gt;Le prefill est le mur&lt;/h2&gt;
&lt;p&gt;Ce que tu sens en premier, c&apos;est attendre.&lt;/p&gt;
&lt;p&gt;Avec un seul utilisateur à 25k de contexte, il faut un bon 6 secondes avant que la première réponse arrive. À cinq utilisateurs ça devient 19.9 secondes. À dix ça devient 35.4 secondes. À vingt ça devient 67.4 secondes.&lt;/p&gt;
&lt;p&gt;Le Run F montre que c&apos;est linéaire &lt;em&gt;à la fois&lt;/em&gt; en concurrency et en contexte. 8k de contexte à 20 utilisateurs donne 14.6 secondes, environ un quart des 67.4 secondes à 25k de contexte, pour la même concurrency. Coupe le prompt en deux, coupe l&apos;attente en deux.&lt;/p&gt;
&lt;p&gt;Et le Test J montre : dès que tu pousses le système au-delà de son plafond de débit, toute cette attente supplémentaire passe dans le &lt;strong&gt;tail&lt;/strong&gt;. Le TTFT médian reste stable autour de 1.1-1.3s, mais le P99 file à 6 secondes. La douleur de la surcharge tombe sur un petit groupe, pas sur tout le monde.&lt;/p&gt;
&lt;p&gt;C&apos;est là que se trouve la vraie limite.&lt;/p&gt;
&lt;p&gt;Pas : le DGX Spark peut-il générer des tokens ? Oui.&lt;/p&gt;
&lt;p&gt;Pas : le KV-cache peut-il encaisser 20 × 25k ? Oui aussi.&lt;/p&gt;
&lt;p&gt;Pas : s&apos;arrête-t-il sous surcharge ? Non, il met gentiment en file.&lt;/p&gt;
&lt;p&gt;Mais : ça ressemble-t-il encore à du chat ? Pas pour 25k. Pour 8k c&apos;est déjà la zone grise. Pour 2k avec multi-turn tout simplement bien. Pour des prompts réalistes façon ShareGPT avec 25 utilisateurs répartis de façon organique : un oui limpide.&lt;/p&gt;
&lt;h2&gt;Où ça convient&lt;/h2&gt;
&lt;p&gt;Ces benchmarks rendent le choix on-prem plus concret.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Oui pour un environnement de bureau où 10 à 25 personnes utilisent de l&apos;IA locale répartie sur la journée.&lt;/strong&gt; Le Test I est la preuve : 250 vraies conversations ShareGPT, arrival rate de 0.3 req/s, P99 TTFT de 637ms. L&apos;utilisateur médian voit le premier token en 353 millisecondes. C&apos;est exactement le scénario de bureau, et voilà ce que ça donne au ressenti.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Oui pour des flows RAG avec contexte moyen.&lt;/strong&gt; Le Run F a donné les chiffres au départ : prompt 8k, 10 users, TTFT 8s, streaming 9.3 tok/s. Le Test H confirme que la variante open-loop reste utilisable : P99 TTFT 3.3s. Pas du temps réel, mais dans des limites attendables.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Oui pour les agents et la génération de code.&lt;/strong&gt; Le Run G est la confirmation : instruction courte, 4k+ tokens d&apos;output, dix tâches parallèles. TTFT sous la demi-seconde, 11.75 tok/s/user.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Oui pour les conversations multi-turn.&lt;/strong&gt; Le Run E donne 2.1s de TTFT à 10 conversations parallèles de 5 tours. Decode identique au single-turn.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Prudence avec 5+ utilisateurs à 25k de contexte en même temps.&lt;/strong&gt; 19.9 secondes de TTFT ce n&apos;est plus du chat, mais c&apos;est utilisable pour des analyses.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Prudence avec les revendications de SLA basées sur des moyennes.&lt;/strong&gt; Le mean TTFT de 1.4s du Test H pourrait sonner acceptable, mais le P99 est à 3.3s. Des décisions basées sur les percentiles, pas sur la moyenne.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Non pour un support-chat où dix à vingt utilisateurs envoient 25k de contexte par session en même temps et attendent tous une réponse en temps réel.&lt;/strong&gt; Ou : un support-chat sous une charge façon Test J (1.5 rps de prompts 4k). Ça peut techniquement tourner (aucun échec), mais un P99 TTFT de 6 secondes est un cas limite pour du chat.&lt;/p&gt;
&lt;h2&gt;Ce que ces tests ne disent pas&lt;/h2&gt;
&lt;p&gt;Ce n&apos;est pas une comparaison MoE-vs-dense. Je veux tester ça à part, et alors pas seulement avec du throughput. Si tu compares MoE et dense, tu dois aussi tester des prompts : résumer, questions de code, choix d&apos;outil, classification de tickets, un long morceau de contexte avec étapes de suivi. Sinon tu mesures seulement à quel point le moteur tourne fort, pas s&apos;il roule dans la bonne direction.&lt;/p&gt;
&lt;p&gt;Ce n&apos;est pas non plus un test avec le prefix caching activé. C&apos;est volontaire. Je voulais voir le coût brut du prefill, pas un benchmark qui s&apos;embellit parce que les prompts se ressemblent. Un prochain article l&apos;ajoutera : ces mêmes runs de contexte 8k et 25k et les tests open-loop avec &lt;code&gt;--enable-prefix-caching&lt;/code&gt;. Mon intuition : les Test H et J en profitent modestement (données random, peu de recouvrement), le Test I en profite sérieusement (les vraies conversations ont des system prompts et du contexte qui se recouvrent), et le Run F va sensiblement plus vite. Mais il faut le mesurer.&lt;/p&gt;
&lt;h2&gt;Là où j&apos;atterris&lt;/h2&gt;
&lt;p&gt;Mon attente au départ était que le DGX Spark avec ce modèle MoE se remplirait plus tôt sur de grands context windows. C&apos;est arrivé, mais autrement que je pensais.&lt;/p&gt;
&lt;p&gt;La mémoire n&apos;était pas le show-stopper. Le Run B a tenu 20 utilisateurs à 25k de contexte sans OOM. Le Test J a survécu à 1.5 req/s sans une seule requête échouée. La limite pratique était toujours dans la prefill-latency, pas dans la capacité.&lt;/p&gt;
&lt;p&gt;Et après neuf tests il s&apos;avère : c&apos;est en fait la seule limite que tu sens.&lt;/p&gt;
&lt;p&gt;Le decode/user est presque une constante pour cette machine. Entre 9 et 12 tokens par seconde à dix utilisateurs concurrents, dans six workloads closed-loop différents. En open-loop avec des prompts ShareGPT réalistes : 10.5 t/s/user. Ce n&apos;est qu&apos;à 16k de contexte ou à des pics synthétiques de 25+ concurrent que ça descend sous 7 t/s.&lt;/p&gt;
&lt;p&gt;Ce qui varie, c&apos;est combien de temps quelqu&apos;un attend avant que le texte commence. À 256 prompt tokens c&apos;est une demi-seconde, même avec dix utilisateurs. À 2048 prompt tokens avec cinq tours en moyenne 2.1 secondes. À 8192 prompt tokens avec dix utilisateurs huit secondes. À 25k avec dix utilisateurs 35 secondes. Sous une charge ShareGPT réaliste de 0.3 rps : 353 millisecondes pour la médiane, 637 millisecondes pour le 1% malchanceux.&lt;/p&gt;
&lt;p&gt;Et dès que tu pousses le système au-delà de sa capacité, il ne scale pas, il met en file. Le Test J a montré qu&apos;un target de 1.5 req/s se fait throttle à 0.26 achieved, avec la douleur entièrement dans le tail (P99 6.2s) tandis que la médiane reste stable. Pour l&apos;IA on-prem c&apos;est le meilleur failure mode que tu puisses espérer : personne ne crashe, certains attendent plus longtemps.&lt;/p&gt;
&lt;p&gt;Ce n&apos;est pas un &quot;cette machine y arrive ou pas&quot;. C&apos;est &quot;choisis le workload qui correspond à ce que l&apos;utilisateur attend, et accepte que 1% des requêtes ait une attente désagréable aux moments de pic&quot;.&lt;/p&gt;
&lt;p&gt;Pour un à trois utilisateurs avec un grand contexte il est utilisable. Pour dix utilisateurs avec un contexte moyen il est très bien. Pour dix utilisateurs avec des conversations multi-turn il est en fait à son meilleur. Pour un bureau de 25 personnes avec des prompts réalistes et un pattern d&apos;arrivée organique il est étonnamment bon : TTFT sous la seconde pour 99% des requêtes, mesuré sur de vraies données de conversation.&lt;/p&gt;
&lt;p&gt;Pour des agent-flows avec de longs outputs il est solide. Pour vingt prompts 25k concurrents ou pour 1.5 rps d&apos;oversubscribe ce n&apos;est plus du chat en temps réel. Là tu dois mettre en file, activer le prefix caching, ou router ce type de travail autrement.&lt;/p&gt;
&lt;p&gt;Deux méthodes mesurent deux choses. Les benchmarks closed-loop montrent ce que la machine peut faire. Le replay open-loop montre ce que l&apos;utilisateur ressent. Le DGX Spark est une machine d&apos;IA locale solide pour le travail de bureau, tant que tu sais quel bouton décide de ce que tu ressens.&lt;/p&gt;
&lt;p&gt;Le decode vend le benchmark. Le prefill décide de l&apos;expérience. Et dès que tu dépasses la limite, le Spark met en file au lieu de casser, et c&apos;est le troisième chiffre qu&apos;un choix on-prem doit pouvoir lire.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>J&apos;ai mis un assistant 24/7 sur un Raspberry Pi</title>
    <id>https://djangodevreng.nl/fr/blog/openclaw-sur-raspberry-pi/</id>
    <link rel="alternate" type="text/html" href="https://djangodevreng.nl/fr/blog/openclaw-sur-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/fr/a-propos/</uri></author>
    <category term="on-prem"/>
    <summary>Un build-log sur OpenClaw sur un Raspberry Pi 5 : Slack comme interface, GPT-5.5 comme modèle, et le Pi comme couche agent always-on à côté du DGX Spark.</summary>
    <content type="html">&lt;p&gt;Je ne voulais pas un meilleur chatbot. Je voulais un agent qui prend du travail tout seul : aller sur internet, lire des tickets, plonger dans un repo, faire une première proposition de modifications de code et ensuite rendre compte là où mon équipe travaille déjà de toute façon.&lt;/p&gt;
&lt;p&gt;L&apos;entrée devait être Slack. C&apos;est là que vivent les questions, les threads, les fichiers et les idées à moitié finies. L&apos;agent devait pouvoir utiliser des tools, lire des fichiers, préparer des branches et continuer à tourner quand mon laptop se ferme.&lt;/p&gt;
&lt;p&gt;Du coup il y a maintenant un Raspberry Pi 5 avec 4 GB RAM dans mon réseau. Dessus tourne &lt;a href=&quot;https://openclaw.ai/&quot;&gt;OpenClaw&lt;/a&gt;. Slack devant, GPT-5.5 derrière, &lt;a href=&quot;https://tailscale.com/&quot;&gt;Tailscale&lt;/a&gt; comme porte d&apos;accès quand je ne suis pas chez moi.&lt;/p&gt;
&lt;p&gt;Ça sonne plus gros que ce que c&apos;est. Le Pi ne fait pas tourner de modèle de langage local. OpenClaw utilise le Pi comme Gateway always-on : la couche qui reçoit les messages Slack, gère les sessions et le contexte du workspace, lance un agent-run, met des tools à disposition et renvoie la réponse dans le même thread. Dans cette config, le modèle tourne via OpenAI.&lt;/p&gt;
&lt;p&gt;Cette distinction est importante. Pour de l&apos;inference entièrement locale j&apos;utilise &lt;a href=&quot;/fr/dgx-spark/&quot;&gt;le DGX Spark&lt;/a&gt;, et j&apos;en ai parlé plus tôt dans &lt;a href=&quot;/fr/blog/quantization-llms-locaux/&quot;&gt;le post sur la quantization&lt;/a&gt;. Ce Pi est la couche agent à côté : toujours allumé, joignable dans Slack, proche de mes fichiers et de mes workflows.&lt;/p&gt;
&lt;h2&gt;Le truc qui me manquait&lt;/h2&gt;
&lt;p&gt;J&apos;utilise déjà assez d&apos;outils AI. Claude Code pour construire. ChatGPT pour des questions isolées. Pour les projets clients je travaille avec des API de modèles ou des modèles locaux, selon ce que les données et l&apos;infrastructure permettent.&lt;/p&gt;
&lt;p&gt;La couche manquante se trouvait entre ces outils : un agent qui voit le travail arriver et commence déjà. Dans Slack ça peut démarrer petit. Je tape une instruction brouillonne, l&apos;agent lit le repo, va chercher les bonnes règles de tone-of-voice et revient avec quelque chose que je peux évaluer.&lt;/p&gt;
&lt;p&gt;Publier reste du travail manuel. La confiance aussi. Le premier travail préparatoire a le droit de se faire automatiquement.&lt;/p&gt;
&lt;p&gt;La direction est plus grande que d&apos;écrire des drafts. À terme je veux pouvoir désigner un ticket et dire : trouve ce qu&apos;il faut ici. L&apos;agent lit le contexte, vérifie la documentation, regarde dans la codebase, propose une approche et prépare éventuellement déjà une branche.&lt;/p&gt;
&lt;p&gt;Ce travail-là reste souvent en plan parce qu&apos;il ne rentre nulle part proprement. Trop petit pour un sprint. Trop gros pour le faire &quot;vite fait&quot;. Avant que tu t&apos;en rendes compte, ce ticket est encore ouvert une semaine plus tard avec les trois mêmes remarques vagues en dessous.&lt;/p&gt;
&lt;h2&gt;Ce qui tourne sur le Pi&lt;/h2&gt;
&lt;p&gt;La base est petite :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Raspberry Pi 5, 4 GB RAM&lt;/li&gt;
&lt;li&gt;OpenClaw Gateway en local sur le Pi&lt;/li&gt;
&lt;li&gt;OpenAI GPT-5.5 comme modèle dans cette config&lt;/li&gt;
&lt;li&gt;Slack comme interface&lt;/li&gt;
&lt;li&gt;Tailscale pour l&apos;accès distant&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Le Pi est surtout disponible ici. C&apos;est son talent.&lt;/p&gt;
&lt;p&gt;OpenClaw relie les couches entre elles : channel, session, agent-runtime, model-provider et tools. Un message Slack arrive par la couche channel. OpenClaw en prépare un agent-turn, avec le bon contexte et les bons tools. Le runtime exécute ce turn avec le modèle choisi. Ensuite OpenClaw renvoie la réponse via Slack.&lt;/p&gt;
&lt;p&gt;De cette manière, le même agent peut lire des fichiers, lancer des shell-commands, récupérer des pages web, regarder le git-status ou préparer une PR, selon les tools que tu autorises. Le Pi n&apos;est donc pas un mini-GPU. C&apos;est la couche de contrôle locale.&lt;/p&gt;
&lt;p&gt;Tailscale garde ça pratique. Je peux atteindre le Pi quand je suis en déplacement. Ouvrir un port public pour un build-log serait un peu trop d&apos;honneur.&lt;/p&gt;
&lt;h2&gt;Slack comme atelier&lt;/h2&gt;
&lt;p&gt;Slack était le choix le plus facile parce que j&apos;y suis déjà toute la journée. Mes entreprises ont des workspaces, des channels, des threads, des fichiers et des notifications. Un dashboard en plus ne ferait surtout que ramasser de la poussière d&apos;onglet en plus.&lt;/p&gt;
&lt;p&gt;Pour moi c&apos;est ça le cœur : l&apos;agent doit être disponible là où l&apos;équipe travaille. S&apos;il trouve quelque chose à partir d&apos;un ticket, je veux la réponse de retour dans le même flux. L&apos;analyse a sa place à côté de la question, dans le même thread.&lt;/p&gt;
&lt;p&gt;OpenClaw supporte plus d&apos;entrées que Slack. Il fonctionne aussi via, entre autres, Telegram, Microsoft Teams, Google Chat, WhatsApp, Discord et iMessage. Slack est mon entrée. L&apos;idée plus large, ce sont des agents sur les canaux de communication existants, avec des tools et de la mémoire derrière.&lt;/p&gt;
&lt;h2&gt;L&apos;installation était moins palpitante qu&apos;espéré&lt;/h2&gt;
&lt;p&gt;L&apos;installation était moins dramatique que ce que j&apos;avais prévu. C&apos;est agréable pour moi et mauvais pour le genre &quot;build-log avec du feu&quot;.&lt;/p&gt;
&lt;p&gt;Le plus gros du temps est parti dans la lecture. OpenClaw a beaucoup de documentation, et tu dois trouver quelle partie correspond à ta config. Slack, Gateway, agents, runtimes, channels, tools : ce sont des couches séparées qui forment au final un seul assistant ensemble.&lt;/p&gt;
&lt;p&gt;Configurer Slack a demandé de l&apos;attention aussi. Tu décides quels utilisateurs peuvent DM le bot, dans quels channels il peut parler et s&apos;il réagit à chaque message dans les channels de groupe ou seulement sur un @mention. Ce ne sont pas des détails pour plus tard. Tu dois choisir ces règles à l&apos;avance et les partager avec ton équipe, sinon personne ne comprend quand l&apos;agent participe ou non.&lt;/p&gt;
&lt;p&gt;Après environ deux heures, ça marchait. Je tapais dans Slack, le Pi attrapait le message, OpenClaw lançait un run, GPT-5.5 réfléchissait avec moi et la réponse revenait dans le même thread.&lt;/p&gt;
&lt;p&gt;Beaucoup de plumbing pour un message texte. Sauf que ce message texte peut maintenant utiliser des tools.&lt;/p&gt;
&lt;h2&gt;Premier test : ce site&lt;/h2&gt;
&lt;p&gt;Le premier endroit où j&apos;utilise ça, c&apos;est djangodevreng.nl.&lt;/p&gt;
&lt;p&gt;Le contenu doit venir de vrai travail : ce qu&apos;on a construit, ce qui a cassé, quels choix sont restés, où un outil avait l&apos;air bien jusqu&apos;à ce qu&apos;il commence à transpirer sous la charge. L&apos;agent a le droit d&apos;aider avec la forme et l&apos;exécution.&lt;/p&gt;
&lt;p&gt;Dès que cet input brut est là, il peut faire beaucoup. Structurer un dump. Faire un premier outline. Réécrire un draft dans mon ton. Enlever le langage marketing. Vérifier si un post sonne comme s&apos;il était tombé d&apos;un carrousel LinkedIn générique.&lt;/p&gt;
&lt;p&gt;Le workflow pour ce site commence en général en bazar. Je balance dans Slack ce que je veux dire : quelques observations, une demi-idée, parfois juste du feedback sur un post existant. L&apos;agent va ensuite chercher le bon repo, lit les fichiers pertinents et prend le guide d&apos;écriture du workspace.&lt;/p&gt;
&lt;p&gt;Ensuite je lui demande une modification concrète : &quot;réécris l&apos;intro&quot;, &quot;enlève le langage marketing&quot; ou &quot;rends cette explication technique plus précise&quot;. Plus l&apos;instruction est nette, plus le diff est utilisable. Il modifie le markdown sur une branche, lance les checks et pousse la modification vers une PR.&lt;/p&gt;
&lt;p&gt;C&apos;est là que ma partie recommence. Je lis le diff, je donne du feedback dans Slack et je le laisse traiter le tour suivant. Ce n&apos;est que quand le post est juste que je merge moi-même. L&apos;agent fait le travail préparatoire. Je reste responsable de ce qui passe en live.&lt;/p&gt;
&lt;p&gt;Un agent qui publie sans que je regarde, ce n&apos;est pas un workflow. C&apos;est une machine à sous avec des droits de commit.&lt;/p&gt;
&lt;h2&gt;Pourquoi ça ressent différent du chat&lt;/h2&gt;
&lt;p&gt;Beaucoup d&apos;outils AI donnent l&apos;impression que tu dois amener ton travail vers une fenêtre de chat. Tu copies du contexte, tu colles des logs, tu expliques pour la troisième fois où se trouve le chemin du repo et tu espères que le modèle fasse comme s&apos;il était là.&lt;/p&gt;
&lt;p&gt;Cette config tourne plus près du contexte. L&apos;agent peut commencer tout seul parce qu&apos;il voit le workspace, connaît la branche, peut lire les règles du site et sait quels checks doivent être lancés.&lt;/p&gt;
&lt;p&gt;Ça n&apos;en fait pas encore un développeur autonome. Il pousse surtout le premier bout ennuyeux vers l&apos;avant.&lt;/p&gt;
&lt;p&gt;Pour moi c&apos;est ça la couche agent intéressante : lire en amont, faire une première version, montrer où ça coince. Un collègue junior avec une patience infinie, sans agenda et parfois une confiance inquiétante dans ses propres phrases.&lt;/p&gt;
&lt;p&gt;Je vais écrire un post séparé là-dessus, parce qu&apos;OpenClaw mérite en fait plus d&apos;explications que ce qui rentre dans ce build-log. Quels canaux il supporte. Quels tools tu lui accroches. Et surtout : pourquoi ça devient intéressant.&lt;/p&gt;
&lt;p&gt;On glisse lentement de l&apos;AI comme partenaire de sparring vers l&apos;AI comme couche d&apos;exécution. Ces dernières années on parlait surtout aux modèles : brainstormer, résumer, réécrire, réfléchir avec. Ça reste utile, mais la vraie différence est dans les agents qui peuvent exécuter du travail dans des systèmes existants.&lt;/p&gt;
&lt;p&gt;Les agents ne reprennent pas le travail des gens un pour un. Ce n&apos;est pas aussi simple, heureusement. Le glissement est dans les workflows : trouver des tickets, rassembler du contexte, préparer des drafts, proposer des modifications de code, lancer des checks, rendre compte. Du travail pour lequel tu demandes normalement quelqu&apos;un parce que ça prend du temps, alors que ça demande peu de jugement humain profond.&lt;/p&gt;
&lt;h2&gt;Étape suivante : tickets et MCP&lt;/h2&gt;
&lt;p&gt;L&apos;étape suivante, c&apos;est &lt;a href=&quot;https://modelcontextprotocol.io/&quot;&gt;MCP&lt;/a&gt;. Je veux accrocher des tools proprement à ce workflow, en commençant par Linear.&lt;/p&gt;
&lt;p&gt;Le scénario est simple : un ticket arrive, l&apos;agent lit le contexte pertinent du repo, cherche les fichiers probables, écrit une courte analyse et revient avec une proposition ou une liste de questions.&lt;/p&gt;
&lt;p&gt;Le merge autonome, je le saute. D&apos;abord je veux savoir où se trouve la limite entre une préparation utile et un zèle d&apos;action dangereux.&lt;/p&gt;
&lt;p&gt;Après ça viennent GitHub, le contexte du repo et peut-être une knowledge base locale. Certains contextes devraient simplement être disponibles, sans que je les recolle dans un prompt à chaque fois.&lt;/p&gt;
&lt;h2&gt;Workflow après workflow&lt;/h2&gt;
&lt;p&gt;Cette config Pi est petite. C&apos;est exactement pour ça que je l&apos;aime bien.&lt;/p&gt;
&lt;p&gt;Assez petite pour la comprendre. Assez réelle pour en apprendre quelque chose. Assez bon marché pour la laisser allumée tout le temps. Assez locale pour rester proche de mon travail, sans faire comme si le modèle tournait lui-même en local.&lt;/p&gt;
&lt;p&gt;Pour de l&apos;AI en production chez des clients, c&apos;est au mieux une couche dans l&apos;architecture. Pour mon propre workflow ça marche déjà très bien : Slack comme entrée, OpenClaw comme Gateway, OpenAI comme model-provider, GitHub comme endroit où le travail se retrouve prêt.&lt;/p&gt;
&lt;p&gt;Dans les temps qui viennent je vais bien bricoler avec ça. D&apos;abord ce site. Ensuite les tickets. Ensuite les tools MCP. Ensuite probablement un truc dont je pense encore maintenant qu&apos;il est trop spécifique pour être automatisé.&lt;/p&gt;
&lt;p&gt;C&apos;est ça la route intéressante : remplacer workflow après workflow par un agent qui fait le travail préparatoire, rassemble du contexte et prépare des propositions. Étape par étape je développe ma config OpenClaw. Juste comme un assistant pratique qui me retire un peu plus de travail des mains à chaque fois.&lt;/p&gt;
&lt;p&gt;Et si ça déraille, il est assez proche pour tirer la prise.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Ce que la quantization s&apos;est révélée être</title>
    <id>https://djangodevreng.nl/fr/blog/quantization-llms-locaux/</id>
    <link rel="alternate" type="text/html" href="https://djangodevreng.nl/fr/blog/quantization-llms-locaux/"/>
    <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/fr/a-propos/</uri></author>
    <category term="on-prem"/>
    <summary>Retour pratique sur la quantization sur la DGX Spark : ce que BF16, FP8 et NVFP4 font à la mémoire, la vitesse et la tail-latency, après trois rounds avec vLLM.</summary>
    <content type="html">&lt;p&gt;C&apos;était le premier billet que j&apos;ai mis en ligne sur ce site. Quand je l&apos;ai écrit, je venais de faire tourner deux modèles sur la DGX Spark : Gemma-4-26B-A4B-it, un modèle MoE, et un modèle dense 31B. Les deux en local, les deux via &lt;a href=&quot;https://docs.vllm.ai/&quot;&gt;vLLM&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;À ce moment-là, la quantization restait surtout une question pour moi. Je connaissais le terme, je voyais à peu près de quoi il s&apos;agissait, mais j&apos;avais trop peu de mesures à moi pour en dire quoi que ce soit de solide.&lt;/p&gt;
&lt;p&gt;Depuis, on a avancé de quelques rounds de benchmark. D&apos;abord &lt;a href=&quot;/fr/blog/gemma-4-dgx-spark-benchmarks/&quot;&gt;Gemma-4 sur la DGX Spark&lt;/a&gt;. Ensuite &lt;a href=&quot;/fr/blog/gemma-4-nvfp4-vs-bf16-dgx-spark/&quot;&gt;NVFP4 vs BF16 sur ce même modèle&lt;/a&gt;. Et après ça &lt;a href=&quot;/fr/blog/nemotron-3-dgx-spark-precisions/&quot;&gt;Nemotron-3 en BF16, FP8 et NVFP4&lt;/a&gt;. Ensemble, ils forment le guide &lt;a href=&quot;/fr/dgx-spark/&quot;&gt;faire tourner des LLMs sur la DGX Spark&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Du coup, ce billet a vraiment changé. Il parle moins de &quot;c&apos;est quoi la quantization ?&quot; et plus de ce qui se passe quand la quantization cesse d&apos;être un terme de model card pour devenir un choix d&apos;architecture.&lt;/p&gt;
&lt;h2&gt;La première question, c&apos;était juste : ça rentre ?&lt;/h2&gt;
&lt;p&gt;Avec les modèles hosted, tu commences souvent par la qualité. Lequel est le plus malin, lequel suit mieux les instructions, lequel écrit du meilleur code ?&lt;/p&gt;
&lt;p&gt;En local, tu commences plus brutalement : ça rentre ?&lt;/p&gt;
&lt;p&gt;Ça paraît presque trop simple, mais sur ta propre hardware, c&apos;est le premier mur. Un nom de modèle et une model card, c&apos;est de la paperasse ; les poids doivent vraiment tenir en memory. Après ça, tu veux encore de la place pour du contexte, traiter plusieurs requests en même temps, et idéalement voir quelque chose revenir en quelques secondes.&lt;/p&gt;
&lt;p&gt;Sur la DGX Spark, tu le sens tout de suite. Tu regardes vLLM travailler : télécharger, charger, réserver la memory, monter en température. Ce n&apos;est qu&apos;après que commence la discussion sur le throughput, la latency et l&apos;utilisabilité.&lt;/p&gt;
&lt;p&gt;C&apos;est un autre ressenti qu&apos;un appel API vers Claude ou GPT-5.5. Là, l&apos;infrastructure existe surtout comme une abstraction. Tu envoies du texte et tu récupères du texte. En local, tu vois l&apos;arrière du décor. Parfois c&apos;est sympa. Parfois ça prend surtout du temps.&lt;/p&gt;
&lt;p&gt;C&apos;est exactement là qu&apos;intervient la quantization.&lt;/p&gt;
&lt;h2&gt;Mon premier tableau était trop étroit&lt;/h2&gt;
&lt;p&gt;Ma première définition de travail était assez propre : la quantization stocke les poids du modèle de façon plus compacte. FP16 ou BF16 prend plus de place que du 8-bit ou du 4-bit. Moins de bits, c&apos;est moins de memory. Moins de memory, c&apos;est un modèle qui rentre plus vite, se charge plus vite, ou laisse de la place pour plus de contexte et plus de requests.&lt;/p&gt;
&lt;p&gt;C&apos;est correct, mais c&apos;est trop petit.&lt;/p&gt;
&lt;p&gt;Après les benchmarks, je vois ça autrement. La question &quot;est-ce que ce modèle rentre sur cette machine ?&quot; n&apos;est que le début. Vient ensuite la question de ce que tu peux faire avec cette machine une fois que le modèle rentre.&lt;/p&gt;
&lt;p&gt;Faire tourner une request, c&apos;est la démo. Faire tourner plusieurs requests, c&apos;est le workflow.&lt;/p&gt;
&lt;p&gt;C&apos;est là que se situe la différence pour moi. Un modèle local qui répond proprement à un prompt, c&apos;est sympa. Un modèle local qui encaisse plusieurs utilisateurs, agents ou tâches à la fois sans que la latency s&apos;effondre, ça devient utile.&lt;/p&gt;
&lt;p&gt;La quantization décide donc de combien de marge de manœuvre il te reste.&lt;/p&gt;
&lt;h2&gt;vLLM rend ça concret&lt;/h2&gt;
&lt;p&gt;J&apos;utilise vLLM parce qu&apos;une request à la fois, ce n&apos;est pas la situation vers laquelle je vais. Lancer un chatbot local, c&apos;est bien pour tester, mais dès que tu parles d&apos;agents, tu obtiens un autre trafic.&lt;/p&gt;
&lt;p&gt;Un agent va chercher du contexte, appelle des tools, découpe le travail, demande parfois des choses en parallèle et attend des résultats entre-temps. Pendant ce temps, tu veux qu&apos;une deuxième request n&apos;ait pas à attendre que la première soit complètement terminée.&lt;/p&gt;
&lt;p&gt;C&apos;est là que le serving devient important.&lt;/p&gt;
&lt;p&gt;vLLM est la couche qui rend ça concret : batching, scheduling, utiliser la memory plus efficacement et gérer plusieurs concurrent requests. Ça rend aussi visible que faire tourner en local, c&apos;est un système. Le modèle, la précision, la longueur de contexte, le nombre de requests simultanées et le scheduler tirent tous sur la même hardware.&lt;/p&gt;
&lt;p&gt;Ça a été ma première vraie leçon. La quantization n&apos;est pas un petit truc isolé tout en bas de la stack. Elle influence la façon dont toute la stack se comporte.&lt;/p&gt;
&lt;h2&gt;BF16 ressemblait d&apos;abord au choix sûr&lt;/h2&gt;
&lt;p&gt;Tant que tu n&apos;as pas mesuré, une précision plus haute paraît vite plus sûre. BF16 sonne sérieux. Plus de détail, moins de risque de qualité, moins de chances que le modèle se mette à se comporter bizarrement.&lt;/p&gt;
&lt;p&gt;C&apos;était aussi mon premier réflexe. Si la hardware encaisse, pourquoi descendre plus bas ?&lt;/p&gt;
&lt;p&gt;Les mesures ont rendu ça moins évident. Sur la DGX Spark, BF16 s&apos;est souvent révélé être le choix le moins pratique dans les runs plus tardifs. BF16 n&apos;est pas &quot;mauvais&quot; ; c&apos;est juste que la hardware et la workload pèsent plus lourd que le sentiment propre d&apos;une précision plus haute.&lt;/p&gt;
&lt;p&gt;Si une précision plus basse donne beaucoup plus de place pour la concurrency, le contexte ou le throughput, alors en pratique ça peut être mieux. Surtout pour des workloads où la vitesse et la simultanéité comptent plus que le dernier petit bout de qualité de modèle.&lt;/p&gt;
&lt;p&gt;C&apos;est le retournement que j&apos;ai trouvé intéressant. La précision la plus haute paraît intuitivement le choix sérieux. Sur cette machine, c&apos;était souvent surtout le plus cher.&lt;/p&gt;
&lt;h2&gt;NVFP4 a changé la Spark&lt;/h2&gt;
&lt;p&gt;Le plus grand basculement est venu avec NVFP4. Dans les billets de benchmark et l&apos;&lt;a href=&quot;/fr/arena/&quot;&gt;arena&lt;/a&gt;, tu vois que NVFP4 double presque la DGX Spark pour beaucoup de workloads. Ce n&apos;est plus une petite optimisation. Ça change ce que tu oses tenter sur la même machine.&lt;/p&gt;
&lt;p&gt;Pour l&apos;on-prem AI, c&apos;est exactement le point. Tu achètes de la hardware pour un workflow, pas pour un joli prompt. Tu veux savoir combien de vrai travail tu peux caser sur cette boîte.&lt;/p&gt;
&lt;p&gt;Si NVFP4 veut dire que tu peux faire tourner plus de requests à la fois, garder plus de marge et te cogner moins vite aux limites de mémoire, alors ce n&apos;est pas un détail dans un tableau. Là, ton architecture change.&lt;/p&gt;
&lt;p&gt;Tu peux répartir les tâches autrement. Tu peux garder plus de choses en local. Tu peux expérimenter plus vite avec des étapes d&apos;agent qui sinon partiraient direct vers un modèle hosted.&lt;/p&gt;
&lt;p&gt;C&apos;est ce qui a rendu la quantization plus pratique pour moi que je ne le pensais au départ. Ça ne parlait plus d&apos;un modèle plus petit, mais de rendre un autre workflow possible.&lt;/p&gt;
&lt;h2&gt;FP8 avait un autre genre d&apos;avantage&lt;/h2&gt;
&lt;p&gt;FP8 ne se situait pas simplement &quot;entre BF16 et NVFP4&quot;. Dans les runs Nemotron-3, c&apos;est surtout la tail-latency qui devenait intéressante. Ça attire moins l&apos;attention qu&apos;un gros bond de throughput, mais à l&apos;usage ça compte au moins autant.&lt;/p&gt;
&lt;p&gt;Les moyennes ne mentent pas forcément, mais elles te rassurent aux mauvais moments. Un workflow semble lent à cause des quelques requests qui restent coincées.&lt;/p&gt;
&lt;p&gt;C&apos;est pour ça que la tail-latency est si pratique. Si un agent-workflow se compose de plusieurs étapes, les retards s&apos;empilent. Une étape lente, c&apos;est pénible. Trois étapes lentes à la suite, on dirait que le système réfléchit à ses choix de vie.&lt;/p&gt;
&lt;p&gt;FP8 a l&apos;air utile dans ce coin-là : moins extrême que NVFP4, mais intéressant quand la prévisibilité importe plus que faire tourner un maximum de choses à la fois.&lt;/p&gt;
&lt;p&gt;C&apos;est la nuance que je n&apos;avais pas encore dans la première version. La précision n&apos;est pas une échelle où plus bas est toujours plus rapide et moins bon. C&apos;est un ensemble de choix avec des trade-offs différents.&lt;/p&gt;
&lt;h2&gt;La qualité reste la question ouverte&lt;/h2&gt;
&lt;p&gt;Les benchmarks répondent sur la memory, le throughput et la latency. Ils disent moins de choses sur le comportement.&lt;/p&gt;
&lt;p&gt;Ça reste le côté difficile de la quantization. Tu ne vois pas toujours la perte de qualité proprement dans une seule metric. Parfois une réponse devient plus plate. Parfois le code se trompe un peu plus souvent. Parfois un agent choisit le mauvais tool. Parfois tu ne remarques rien, jusqu&apos;à ce que ta tâche soit juste différente de ton set de test.&lt;/p&gt;
&lt;p&gt;Pour des tâches simples, ça peut très bien aller. Pense à la classification, au routing, aux premiers résumés, aux embeddings ou à un passage léger sur des documents internes. Il n&apos;y a pas toujours besoin du modèle le plus lourd là-dessus.&lt;/p&gt;
&lt;p&gt;Pour la génération de code et les agent-workflows, c&apos;est plus sensible. Les petites erreurs s&apos;empilent. Un raisonnement moyen, c&apos;est pénible. Un mauvais tool-call, c&apos;est un autre genre de problème.&lt;/p&gt;
&lt;p&gt;C&apos;est pour ça que je ne veux pas benchmarker les modèles quantized seulement sur la vitesse. Je veux savoir où j&apos;ose les déployer.&lt;/p&gt;
&lt;p&gt;C&apos;est une autre question. Et honnêtement, c&apos;est la seule qui compte.&lt;/p&gt;
&lt;h2&gt;Le split devient plus clair&lt;/h2&gt;
&lt;p&gt;Mon attente reste que la meilleure setup on-prem devient un mix. &quot;Tout en local&quot; ça claque, mais c&apos;est en général aussi inutilement strict.&lt;/p&gt;
&lt;p&gt;Le split logique ressemble plutôt à ça :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;embeddings en local&lt;/li&gt;
&lt;li&gt;documents sensibles en local&lt;/li&gt;
&lt;li&gt;routing et classification en local&lt;/li&gt;
&lt;li&gt;étapes d&apos;agent simples en local&lt;/li&gt;
&lt;li&gt;raisonnement lourd vers Claude ou GPT-5.5 quand c&apos;est nécessaire&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;La quantization décide de l&apos;ampleur que peut prendre cette partie locale. Plus de tâches tournent en local de façon fiable et assez rapide, moins tu dois envoyer dehors.&lt;/p&gt;
&lt;p&gt;C&apos;est important pour le travail client. Pas parce que chaque token doit absolument rester entre quatre murs, mais parce que certaines données ont bien leur place là. Et parce que la latency, les &lt;a href=&quot;/fr/cout-dgx-spark/&quot;&gt;coûts&lt;/a&gt; et le contrôle comptent tout simplement en production.&lt;/p&gt;
&lt;p&gt;Une setup on-prem n&apos;est pas une conviction. C&apos;est une répartition du travail.&lt;/p&gt;
&lt;h2&gt;Ce que je mesurerais autrement maintenant&lt;/h2&gt;
&lt;p&gt;Dans la première version de ce billet, j&apos;avais surtout une liste de questions. Combien de temps prend le téléchargement ? Combien de temps prend le chargement ? Combien de VRAM reste-t-il ? Combien de concurrent requests je peux envoyer avant que la latency devienne pénible ?&lt;/p&gt;
&lt;p&gt;Ces questions restent utiles, mais c&apos;est le début. Comment je mets exactement ces mesures en place sur la Spark, c&apos;est dans la &lt;a href=&quot;/fr/arena/methodologie/&quot;&gt;méthodologie de l&apos;arena&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Maintenant, je mettrais trois choses côte à côte par précision :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;comportement système : chargement, memory, throughput, latency et tail-latency&lt;/li&gt;
&lt;li&gt;comportement du modèle : sortie en néerlandais, questions de code, contexte plus long, tool-use&lt;/li&gt;
&lt;li&gt;adéquation au workflow : quelles tâches j&apos;ose faire tourner en local avec ça&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cette dernière, on la rate vite si on regarde seulement des tableaux de benchmark. Un modèle peut techniquement tourner et rester malgré tout peu pratique. Ou justement scorer moins joliment, mais être exactement assez bon pour du routing ou du résumé.&lt;/p&gt;
&lt;p&gt;Pour la production, ça fait la différence. Personne n&apos;achète des &quot;tokens par seconde&quot; tout seuls. Tu achètes de la place dans un workflow.&lt;/p&gt;
&lt;h2&gt;Ce que je comprends maintenant&lt;/h2&gt;
&lt;p&gt;Ma définition de travail s&apos;est déplacée.&lt;/p&gt;
&lt;p&gt;La quantization rend un modèle plus petit, mais ce n&apos;est que l&apos;entrée. Elle change combien de travail tu sors de la même hardware, quelle latency tu acceptes et quelles tâches tu oses garder en local.&lt;/p&gt;
&lt;p&gt;Sur la DGX Spark, la précision la plus haute semble rarement automatiquement le meilleur choix. NVFP4 rend la machine bien plus utilisable pour beaucoup de workloads. FP8 est intéressant quand la tail-latency commence à compter. BF16 reste utile comme point de référence, mais sur cette hardware il ressemble moins souvent au default pratique.&lt;/p&gt;
&lt;p&gt;C&apos;est exactement pour ça que je voulais faire ces mesures. Un classement universel aide peu ; de meilleurs choix d&apos;architecture, oui.&lt;/p&gt;
&lt;p&gt;La question n&apos;est pas : quel niveau de quantization gagne ?&lt;/p&gt;
&lt;p&gt;La question est : quelle tâche a le droit d&apos;aller sur quelle précision, sur quelle machine, avec combien de risque ?&lt;/p&gt;
&lt;p&gt;C&apos;est là que l&apos;on-prem AI commence à devenir intéressant pour moi : à la répartition du travail.&lt;/p&gt;
</content>
  </entry>
</feed>
