A DGX Spark update made everything 3.4% faster
The DGX dashboard offered a system update described as “a driver update to improve handling of out-of-memory conditions and display stability”. I had benchmarked all three models on that machine earlier the same day, so for once I had a baseline before an update rather than after wishing I did.
Everything got faster. Twelve measurements, twelve improvements, mean +3.4%.
I do not think that is the whole story, and the reason why is more interesting than the number.
It was not a driver update
The dashboard’s description undersold it considerably. What was actually pending:
| From | To | |
|---|---|---|
| Kernel | 6.17.0-1018-nvidia | 6.17.0-1029-nvidia |
| NVIDIA driver | 580.159.03 | 580.173.02 |
| CUDA toolkit config | 13.2.75 | 13.3.29 |
| Embedded Controller firmware | 0x03000302 | 0x03000508 |
| UEFI device firmware | 0x0200980f | 0x02009b0b |
| Everything else | 336 packages |
glibc, systemd, and a kernel bump carrying linux-modules-nvidia-580-open
with it. Calling this a driver update is like calling a house move a change of
address.
That matters for what any measurement can conclude. Even a perfectly clean before/after cannot attribute a change to the driver, because the driver is one of six things that moved.
The numbers
Same harness, same four workloads, same 32k context, same machine, same day. Throughput is end-to-end: total tokens generated over total wall time.
| Model | Scenario | Before | After | Change |
|---|---|---|---|---|
| Qwen3.6-35B-A3B | chat_short |
50.2 | 52.1 | +3.8% |
chat_long_context |
37.2 | 39.3 | +5.5% | |
code_generation |
50.4 | 52.8 | +4.7% | |
summarization |
23.0 | 23.3 | +1.5% | |
| Qwen3.6-27B | chat_short |
17.4 | 17.6 | +1.6% |
chat_long_context |
13.6 | 13.8 | +2.1% | |
code_generation |
16.6 | 17.5 | +5.3% | |
summarization |
8.9 | 9.2 | +4.0% | |
| gpt-oss-120b | chat_short |
37.2 | 38.4 | +3.1% |
chat_long_context |
30.7 | 31.6 | +2.9% | |
code_generation |
37.0 | 38.6 | +4.3% | |
summarization |
19.5 | 19.9 | +2.3% |
Range +1.5% to +5.5%, mean +3.4%.
Twelve out of twelve moved in the same direction. If the direction were random you would see that about once in four thousand attempts, so something real happened. Within-run standard deviation on these measurements is under 0.1 tok/s, which rules out ordinary jitter — but says nothing about variation between sessions, which turns out to be the crux.
Cold starts did not move at all: 27B 540s against 505–555 before, gpt-oss 495s against 465–570, the 35B 465s against 435–460.
The problem
The machine had been up for eleven weeks. The before-runs were taken on that machine. The after-runs were taken on a freshly booted one.
So there are two explanations and this data cannot separate them:
- The update made inference 3.4% faster.
- Rebooting after eleven weeks made inference 3.4% faster.
The second is not a stretch. Eleven weeks of allocation churn on a machine with one unified memory pool is exactly the setting where fragmentation, accumulated kernel state and page-table pressure could plausibly cost a few percent. A fresh boot would recover it, and the update would get the credit.
I cannot rerun the before-case: the old kernel and driver are gone, and even if I rolled them back the machine would have been freshly rebooted, which is the variable I am trying to isolate.
Total run time
| Arm | What ran | Machine time |
|---|---|---|
| Qwen3.6-35B-A3B | 4 scenarios · 5 repetitions each | 4 min |
| Qwen3.6-35B-A3B | 4 scenarios · 5 repetitions each | 4 min |
| Qwen3.6-27B | 4 scenarios · 5 repetitions each | 11 min |
| Qwen3.6-27B | 4 scenarios · 5 repetitions each | 11 min |
| gpt-oss-120b | 4 scenarios · 5 repetitions each | 3 min |
| gpt-oss-120b | 4 scenarios · 5 repetitions each | 3 min |
| Total | 35 min |
About thirty-five minutes across both arms.
Twelve paired measurements for half an hour is why this post can say “twelve out of twelve moved in the same direction” rather than testing one model and calling it. The throughput harness is cheap enough that a single-run comparison has no excuse.
What that cheapness could not buy is the control. Both arms differ by a software update and a reboot, and re-running either one more times does not separate them — which is what the next section is about.
The experiment that would settle it
Re-measure in a few weeks, without rebooting.
If throughput drifts back toward the old numbers as uptime accumulates, the gain was the reboot. If it holds at the new level, the gain was the update.
That is a real prediction with a real way to be wrong, and I will publish it either way. It is also the sort of thing almost nobody tests, because it requires baselines from before an update you did not know was coming and the patience to wait weeks for the follow-up.
A hypothesis that did not survive
When the first model came back I thought I could see a mechanism. The 35B
gained 3.8–5.5% on the decode-heavy scenarios and only 1.5% on
summarization, which is prefill-dominated. That looked like a decode-path
improvement.
The 27B does the opposite: summarization +4.0%, chat_short +1.6%.
gpt-oss is mixed. Across all three there is no pattern by scenario type.
So the gain is broad and I have no mechanism for it. Writing that down is better than the alternative, which is retrofitting an explanation onto four numbers that happened to line up.
What else the update did, which is nothing
Worth recording for anyone weighing whether to take the downtime:
- Both systemd-supervised models returned automatically. The third container came back on its Docker restart policy.
- The lifecycle daemon restarted and cold-started an on-demand model correctly on the new kernel — the failure mode I was most worried about, since a kernel bump carries the NVIDIA modules with it.
- Embeddings, chat completions and the broker all answered on the first try.
- Nothing needed manual intervention.
Eleven weeks of uptime is long enough that a reboot is its own risk: anything started by hand months ago and never made persistent does not come back. Worth auditing that before clicking update rather than discovering it after.
A note on the peer that ruined a run
The first sweep after the update failed on two of three models. Not a bug — a scheduled job on another project started at 21:00 UTC, mid-benchmark. The harness refuses to measure while another workload is on the GPU, so it quarantined the 27B result and declined to start the third model at all.
Two measurements lost, two wrong numbers prevented. That is the trade, and it is the right one: a benchmark taken against contention is not a slow measurement, it is a wrong one.
The irony is that I had built a usage dashboard that morning showing exactly when that job runs — 21:00 UTC, every day, confirmed across four weeks — and then started a sweep at 20:46 without checking it.
Setup
- NVIDIA GB10, 121 GB unified memory
- Before: kernel 6.17.0-1018-nvidia, driver 580.159.03, 11 weeks uptime
- After: kernel 6.17.0-1029-nvidia, driver 580.173.02, fresh boot
- vLLM 0.19.0,
nvcr.io/nvidia/vllm:26.04-py3, 32k context,--gpu-memory-utilization 0.65 - Five measured repetitions per scenario after a discarded warmup, greedy decoding, unique prompt prefix per repetition, medians reported
- Both Qwen models run MTP speculative decoding; gpt-oss cannot, as its checkpoint ships no draft heads
Source data: 96f13c64aa06303da530d411b43438259dc6fb8aca51197399ee050fe7f2cfc944049d1e