GB300 DeepSeek Flash 4.1 Testing
:30006 since 10:22 AM. It is v22 plus one environment variable. PYTORCH_CUDA_ALLOC_CONF=pinned_max_cached_size_mb:1024 stops torch from rounding large pinned host allocations up to a power of two. Each Engram table had been pinned as 128 + 4 GiB for 94.42 GiB of data. Pinned host went from 327.96 to 252.79 GiB (−75.17 GiB), MemAvailable from 124 to 196 GiB, and the pin-hot hook's host-guard margin from about 1–2 GiB to about 188 GiB. Decode is flat: RT C1 +0.1%, replay r2 −0.2%, and the confirm window's knee6 C8 +1.3%. Non-tool greedy parity is 16/16 against both controls, KV is unchanged at 2,504,891 tokens, and the autotune hash hits. v22 is stopped and kept as the rollback.
Config C is our pin-hot hook rebased onto vLLM
nightly-39983a5e, with MegaAttn, --no-swa-bounded-replay, off60 and fp8_ds_mla. It passed all 7 pre-registered promotion gates against v23. It is not promoted; the decision is pending with James. Against v23 it measured:
- TTFT at 8K: 0.645 → 0.430 s.
- Cold 64K prefill: +27%.
- Speed: RT C1 +6.1% and knee6 C8 +8.5%. Per-class decode wall-clock cleared its bar in 20 of 20 cells.
- KV: +55%, to 3,878,024 tokens at 1M.
- Fidelity: BFCL dev 92.50 vs 92.33, BFCL held-out 77.65 vs 77.65, gauntlet 12/12.
P1 is closed. Prefill CUDA graphs to 8,192 tokens cost 8.21 GiB at 8192, which kills the boot, and 3.63 GiB at 2048, which costs 43% of KV. The capture list is also part of the FlashInfer autotune hash, so the cheap boots the plan assumed were never available.
One methodology change came out of the day. An 8-prompt-per-class acceptance fixture flagged a code regression that real-transcript decode timing does not show. Per-class acceptance is now an investigation trigger, and per-class decode wall-clock is the gate. Full section below; recipe deepseek-v4.1-flash-vllm-uva-dspark.
Current reference, v23 = the 1017 checkpoint on the 0909 image + pin-hot hook v21 + off60 + util 0.97 + 1M context + 24 seats + k-schedule [[1,8,5],[9,24,1]] + exact-size pins. Residency 206.61 GiB of experts in HBM and 62.33 GiB pinned in Grace. The RT, TTFT, KV, knee and fidelity numbers are the v23 control arms of the October 18 gate card (P2f). Host memory is from the S0 and S0c cards. The September cards further down are history.
October 18, 2026 — v23 exact-size pins; nightly rebase passes gates; the acceptance-rule lesson
This round worked through a list of cards drawn from original-el8's dgx-station-gb300-research notes (Apache-2.0). Every card ran as a same-window CTL → CAND → CTL bracket on this Station, with one axis per boot and bars written into the runner before launch. One card shipped (v23), one failed (P1), and one passed its gates and is waiting on a decision (config C). Along the way we found that one of our gates was measuring the wrong thing.
v23: one environment variable, 75 GiB of Grace back
The pin-hot hook refuses to boot if host memory available at re-home time drops under a 10 GiB floor. Earlier this month that margin was about 0.3 GiB, and three boots aborted on it. The obvious fix was to make something smaller, for example NVFP4 Engram tables. The actual fix was to stop wasting what we already had.
Torch's pinned-host caching allocator rounds every allocation up to a power of two unless pinned_max_cached_size_mb caps it. Allocations above the cap are never rounded. The 0909 image ships torch 2.13.0+cu130, and neither the image nor our launcher set the variable. Each Engram table is 91.55 GiB of rows plus 2.86 GiB of scales, and they had been sitting in 128 GiB and 4 GiB pinned maps. We measured it by summing Size: over the EngineCore's /dev/zero (deleted) maps in /proc/<pid>/smaps, which matches cgroup shmem to 0.01 GiB.
| host memory (GiB) | v22 | v23 (exact-size pins) | change |
|---|---|---|---|
| pinned host total (sum of the 192 cudaHostAlloc maps) | 327.96 | 252.79 | −75.17 |
| Engram table maps | 2 × 128 | 2 × 91.55 | exact size |
| Engram scale maps | 2 × 4 | 2 × 2.86 | exact size |
| hook cold-expert pins (40 × 1 GiB w13, 40 × 512 MiB w2, plus scales) | 62.33 | 62.33 | unchanged (maps at or under the 1 GiB cap are still rounded and cached) |
| MemAvailable, steady state (confirm window) | 124.0 | 196.2 | +72.2 |
| MemAvailable after drop_caches | 131.4 / 132.7 | 205.9 | about +74 |
| hook host_avail at layer 9 (guard floor 10) | 12.10 / 11.29 | 197.68 | margin about 1–2 → about 188 |
| hook host_avail, minimum layer | 11.52 / 11.29 | 152.69 | +141 |
Card S0 (CTL1 → S0 → CTL2, 08:56–09:29 CDT) and confirm card S0c (10:10–10:21 CDT). The v22 column shows both controls where they differ.
| speed and fidelity check | v22 | v23 | result |
|---|---|---|---|
| RT C1, mean of controls | 190.0 | 190.2 | +0.1% |
| RT C4 / C16 | 327.3 / 531.6 | 325.1 / 523.5 | −0.7% / −1.5% (fail bar was −3%) |
| replay r2 | 439 | 438 | −0.2% |
| confirm window: knee6 r2 C8 / C6, RT C1 mean of 3 | 597.0 / 535.8 / 187.8 | 604.8 / 529.6 / 188.2 | +1.3% / −1.2% / +0.2%, all inside −2% |
| non-tool greedy parity (16 prompts) | v23 matches each control 16/16 | identical | |
| KV tokens | 2,504,891 | 2,504,891 | unchanged |
| autotune | hash hit 62426808, Loaded 189 configs, on both | no retune | |
One lesson from this card: a single rep of the prose knee at C8 can mislead. The first S0 window read knee C8 at −3.4% with no mechanism; the variable changes no kernel and no HBM byte. The confirm window showed why. C8 reps are bimodal on both arms, a fast mode around 620–632 tok/s and a slow mode around 556–583: v22 had 2 slow reps of 8 and v23 had 3 of 8. Fast-mode means were 628.3 vs 625.1 (−0.5%). Promoted on the pre-registered confirm bar; launch = launch_v22.sh + one -e line.
P1: prefill CUDA graphs to 8,192 tokens — closed
The idea comes from original-el8. On his lane, prefill steps above 1,024 tokens ran eager, and capturing graphs up to 8,192 tokens cut 8K TTFT. Our 8K TTFT is 0.65 s, so the card asked whether eager-prefill launch overhead was part of that. It did not get far enough to answer. vLLM's CUDA-graph memory profiling charges graph memory against KV, and graph memory grows faster than linearly with the largest capture size.
| capture list, largest size | graph memory (profile / actual) | KV memory | KV tokens | outcome |
|---|---|---|---|---|
| ≤ 128 (v22 reference) | 1.29 / 1.08 GiB | 5.26 GiB | 2,504,891 | reference; hash hit, 316 s boot |
| ≤ 2048 | 3.63 / 2.46 GiB | 2.98 GiB | 1,420,753 (−43%) | FAIL on the KV bar (−1,084,138 tokens vs ≤ 0.3M allowed); new autotune hash, about 15 min live tune, 1,380 s to bind; benches skipped by rule |
| ≤ 8192 | 8.21 GiB | −1.6 GiB | — | boot died at 425 s: No available memory for the cache blocks |
Two of the plan's assumptions were wrong. First, the capture list is part of the FlashInfer autotune hash, so every list change is a new hash and a provisional first boot of about 23 minutes, not an 8-minute hash hit. Second, at about 0.47M KV tokens per GiB on this lane, the KV pool cannot pay for graphs much above 512 tokens. P1 reopens only when bundled with an HBM source (shorter max-model-len or nvfp4_ds_mla KV), with a capture cap of 512 or 1024.
Config C on nightly-39983a5e: every gate passed, not promoted
The P2 series rebased the pin-hot hook onto vLLM nightly-39983a5e, which is 1,194 commits newer than our 0909 image. It includes vllm#58132 (decoder SWA bounded replay) and the DSpark and Engram fixes from that span. The hook bound unchanged through the same _invoke_kernel seam: rehome done layers=40, with 206.61 GiB of experts in HBM and 62.33 GiB pinned. The bracketed P2, P2b, P2c and P2e cards narrowed the build down to config C: MegaAttn target, --no-swa-bounded-replay, off60, fp8_ds_mla, and v23's allocator variable in both arms. The gate card (P2f, 19:09–20:18 CDT) ran v23 live → config C (store-loaded, Preloaded 210/210, no live tune, 335 s bind) → v23 restart.
| gate (bars fixed before launch) | v23 | config C | bar | result |
|---|---|---|---|---|
| per-class decode wall-clock, 5 classes × C1/C8, against 2 controls | control mean per cell | +0.5% to +12.6% | ≥ control mean − max(2%, 1.5 × same-config spread) | PASS 20/20 |
| GSM8K-200 | 97.0 (194/200) | 95.5 (191/200) | ≥ v23 − 1.5 pt | PASS, exactly at the bar. 3 flips, all v23-only (McNemar p = 0.25). C emits +48% completion tokens (mean 293.5 vs 198.3); two of the three flipped items are long C outputs (3,229 and 2,256 tokens). Flagged for a full 1,319-item re-check. |
| BFCL dev (simple_python + multiple, 600) | 92.33 | 92.50 | ≥ v23 − 1 pt | PASS |
| BFCL held-out (live_simple + live_multiple, 1,311) | 77.65 | 77.65 | ≥ v23 − 1 pt | PASS (1018/1311 on both) |
| tool gauntlet | 12/12 | 12/12 | 12/12 | PASS |
| RT C1 (mean of 3) | 186.4 | 197.8 | ≥ v23 − 2% | PASS (+6.1%) |
| knee6 C8 (4-rep mean) | 605.2 | 656.5 | ≥ v23 − 2% | PASS (+8.5%) |
v23 = mean of the live arm and the restart arm, except fidelity rows, where the gate control is the live arm. Validity checks were clean: rehome 40/40, no replay line, MegaAttn line present, prompt set sha1 618898aa on all three arms, serving-log errors 0.
| speed cell | v23 live | config C | v23 restart | C vs v23 |
|---|---|---|---|---|
| RT C1 median TTFT (≈ 8K prompt) | 0.643 s | 0.430 s | 0.647 s | −33% |
| cold 64K prefill (N=1) | 24,094 tok/s | 30,518 tok/s | 24,006 tok/s | +26.9% |
| knee r2 C1 / C8 / C16 | 192.2 / 618.5 / 944.4 | 200.3 / 647.8 / 1,074.8 | 192.4 / 600.6 / 944.5 | +4.2% / +6.3% / +13.8% |
| knee6 C8 reps | 615.7 / 569.2 / 622.8 / 578.2 | 645.9 / 647.3 / 649.5 / 683.4 | 622.7 / 603.8 / 625.9 / 603.7 | v23 bimodal again; C never in the slow mode |
| KV tokens at 1M | 2,504,891 | 3,878,024 | 2,504,891 | +55% |
| pinned host / MemAvailable | 252.79 / — | 252.79 / 194.6 GiB | — | same host footprint |
Two things still need James's eyes before any promotion. The first is the GSM8K cell above: it passes at exactly the pre-registered line and shows length inflation that lil-bench treats as an early damage signal, and n = 200 cannot separate 1.5 points from noise. The second is that structured-class C1 acceptance moved −2.6 pts against v23 (−3.6 against the post-window control). Under the amended rule below, that is a trigger that needs a written explanation, even though the cell still decodes faster. The candidate explanation is the same drafter-input numerics shift described below, and it is not verified. The teacher-forced flip gate ran on this image in P2 (replay on; W 3.110 against a bar of 3.85) and was not repeated for config C. The candidate container is stopped and kept. Promotion is James's decision.
Per-class decode wall-clock
The new gate instrument (perclass_decode.py) uses 40 prompts per class, 200 in total: the 8 fixture prompts plus 32 harvested from real Hermes transcripts, with prompts capped at 8K tokens. Each request is forced to exactly 256 output tokens, and each class runs at C1 (sequential) and C8 (waves of 8), r1 and r2. There are two controls: the 18:29 v23 baseline and a v23 run after the candidate window. Acceptance comes from /metrics deltas, token-weighted.
| class | cell | config C tok/s | v23 18:29 (spread) | Δ | v23 post (spread) | Δ | accept % C / v23 / v23 post |
|---|---|---|---|---|---|---|---|
| prose | C1 | 241.6 | 232.3 (0.3%) | +4.0% | 231.4 (0.4%) | +4.4% | 40.5 / 41.8 / 41.5 |
| prose | C8 | 564.8 | 532.0 (2.5%) | +6.2% | 519.3 (1.9%) | +8.8% | 43.1 / 42.2 / 41.0 |
| structured | C1 | 255.5 | 250.3 (0.8%) | +2.1% | 254.3 (1.6%) | +0.5% | 42.9 / 45.5 / 46.5 |
| structured | C8 | 573.8 | 509.4 (2.7%) | +12.6% | 522.2 (0.8%) | +9.9% | 43.2 / 42.8 / 43.5 |
| code | C1 | 312.6 | 293.4 (3.3%) | +6.6% | 296.1 (1.6%) | +5.6% | 66.5 / 66.6 / 66.9 |
| code | C8 | 673.2 | 642.1 (1.2%) | +4.8% | 642.8 (4.6%) | +4.7% | 67.7 / 67.6 / 69.3 |
| shell_ops | C1 | 331.6 | 316.9 (5.0%) | +4.6% | 317.0 (2.9%) | +4.6% | 67.1 / 68.4 / 67.9 |
| shell_ops | C8 | 739.1 | 681.4 (3.3%) | +8.5% | 657.7 (0.7%) | +12.4% | 67.2 / 66.6 / 66.5 |
| tool_json | C1 | 316.9 | 300.0 (1.6%) | +5.6% | 286.2 (0.1%) | +10.7% | 59.6 / 59.6 / 56.3 |
| tool_json | C8 | 767.1 | 684.0 (3.1%) | +12.1% | 721.8 (1.7%) | +6.3% | 59.5 / 57.4 / 59.1 |
Every cell passes against both controls. The tightest is structured C1 against the post-window control: +0.5%, with 2.4% headroom over its bar. The window drifted: the two v23 controls differ by more than their spread on tool_json C1 (−4.6%), tool_json C8 (+5.5%) and shell_ops C8 (−3.5%). C clears both controls, so the verdict does not depend on which one is used. At equal acceptance, C's engine step is about 6% faster at C1 in every class and 5–11% faster at C8.
SWA bounded replay: off for the agent lane
vllm#58132 runs layers 21–39 on each request's last 128 tokens during eager prefill. It is the reason the first P2 build had the best prefill numbers of the day, and it caused one of that build's two failures. On a warm prefix hit it recomputes the cached window, so a warm request costs as much as a cold one: median TTFT 0.208 s warm vs 0.212 s cold, where v23 drops to 0.108–0.116 s on a warm hit. On replayed agent turns, which are mostly warm prefixes, that is a throughput loss.
| P2b, same window | v23 (live / restart) | replay ON | replay OFF (config C) |
|---|---|---|---|
| TTFT 8K (RT C1 median) | 0.640 / 0.650 s | 0.280 s | 0.430 s |
| cold 64K prefill | 24,547 / 23,981 tok/s | 46,318 tok/s (about 1.9×) | 30,362 tok/s |
| forced-length warm replay r2 (24 real turns, 256 tokens each) | 509 / 482 tok/s | 463.6 (−6.4%) | 548.4 (+10.7%) |
| RT C1 (mean of 3) | 187.9 / 187.8 | 197.3 | 198.2 |
| KV tokens at 1M | 2,504,891 | 3,455,569 | 3,878,024 |
The first P2 card measured the standard replay r2 at −10.9% with replay on, and P2b reproduced it on a store-loaded restart at −10.3%. That rules out tune luck. Forcing identical work per turn still left −6.4%, which rules out extra output tokens. Turning replay off gives up some cold prefill speed (still +25% over v23 in this window) and gains KV, because the SWA cache rejoins prefix caching. Replay is parked for a cold-prefill lane, not this one.
The code-acceptance bisect, in one paragraph
The pre-registered gate had a drafter clause: DSpark acceptance within 2 pts per class on the agent fixture. Every nightly build failed it on code, by −3.7 to −4.8 pts on 8 prompts per class, and on one prompt whose target output was byte-identical across all arms, acceptance fell from 0.943 to 0.800. So we bisected everything reachable from Python:
- The #56893 MXFP8 KV-record gate, forced off: code unchanged at 73.6, at a cost of 305K KV tokens.
- The drafter's attention kernel, moved to
FLASHMLA_SPARSE_DSV41: 74.2. - The target's attention kernel together with its fused fp32→FP8 o-proj cast, whole model on SPARSE: 74.7.
- SWA replay on vs off: byte-identical per-prompt results on code.
- A desk read of the drafter's aux-capture path against DeepSeek's 1017 reference and the 0909 image: the same tensor, rounding order only.
- A desk read of the FP8
act_quantbeforewo_a: both images match the 1017 reference.
Nothing recovered code, and the remaining candidates live in compiled kernels. Then the 40-prompt real-transcript instrument above put config C's code acceptance within 0.4 pt of v23 at C1 and within 1.6 at C8, with code decoding faster at both. The engine was not the problem; the fixture was. The drafter does propose differently on a handful of short canned prompts, but eight such prompts per class cannot tell that apart from the per-class swing that harmless numeric changes produce, and the result does not reach real traffic.
What we learned about gates
The acceptance clause came from the v19 lesson in September: a drafter disagreeing with the target was how we first saw the target itself move. It was being applied with a noise floor taken from bit-identical controls. That measures determinism, not tolerance, and at 8 prompts per class the honest spread under harmless numeric changes is wider than the 2-pt bar. We sent the whole question to Opus 5.5 as a second-model review, and it put the point plainly: "verification stays lossless even when the drafts change. A drafter change on a lossless verify path can only cost speed. It can't change what the agent says." So you measure the speed. The rule is now written down as a draft amendment for James, and the P2f gate boot ran under it:
- Per-class DSpark acceptance moving more than 2 pts is an investigation trigger: a written explanation is owed before promotion, but it does not block on its own.
- The gate for drafter-side change is per-class decode wall-clock on the frozen 200-prompt set at C1 and C8, r1/r2. Every one of the 10 cells must reach control mean × (1 − max(2%, 1.5 × the control's same-config spread)), against a same-window, bracketed v23 control.
- The fidelity gates do not change: BFCL dev and held-out within 1 pt, GSM8K-200 within 1.5 pt, margin-weighted teacher-forced flips, gauntlet 12/12. Those measure the target, and the target is what the agent's answers come from.
The review also pushed back on two of our habits. One was treating 0909 as ground truth; it is the incumbent, and the checkpoint's reference code is ground truth. The other was quoting speed from prose RT when this lane mostly emits tool JSON, shell and code.
Also closed today
- P4, NVFP4 Engram tables, is superseded. v23 freed the host memory P4 was meant to free, with zero numerics change. A desk check also found that the published
aidendle94NVFP4 Engram shards are built from the df42c109 weights, not 1017, so they cannot be gated against this lane. - P5: el8's long-generation test shape (one prompt, EOS respected, tens of thousands of tokens) is now an optional cell in our window runner. The dry run on the reference produced 2,048 tokens at 219.2 tok/s decode, TTFT 1.92 s, acceptance length 3.52. The full-length cell is not in today's receipts.
Receipts. Recipe: J-M-Recipes · dgx-station-gb300/deepseek-v4.1-flash-vllm-uva-dspark. Today's bundles, being merged into that directory: results/2026-10-18-v23-pinexact-host/, results/2026-10-18-p1-prefill-graphs-FAIL/ and results/2026-10-18-p2-nightly-39983a5e-gate/. Related: the hook itself in vLLM: Pin Hot Experts, and the October 17 checkpoint swap in DeepSeek-V4.1-Flash-1017 on One GB300.
Everything below this line is dated history, newest first, through September 27. The lane moved to the 1017 checkpoint on October 17 (day-zero post) and to v23 on October 18. Older banners that say "serving" or "the reference" describe their own day.
cudaHostAlloc regions ignore THP sysfs entirely. Then a torch-profiler capture of the exact v18 configuration: a C1 prose step is 14.6 ms at 101% GPU-busy, no CPU bubble — expert GEMM 5.0 ms (34%, 56 launches at 53+28 µs on six rows, tile-quantized ~4× above the bandwidth floor), dense GEMM 4.2 (29%), ~1,300 small kernel launches 2.7 ms (18%), attention 1.2, mHC 1.0, Engram 0.04. About 400 of the small launches were the hook's own split_ids() — six torch ops per MoE layer to route expert ids to hot or cold rows. v21 does that remap in one Triton kernel, bit-exact at every batch size. Same-window bracket on fresh boots, same autotune set, v18 → v21 → v18: C1 171.8 / 182.8 / 172.5 (+6.2%), C8 656 / 687 / 657, C16 957 / 993 / 952, replay a wash, acceptance unchanged, non-tool greedy 16/16 against both controls. v18 stopped and kept as rollback. Two open items stated plainly: the held-out BFCL sibling has not been run on v21, and this was measured against v18 rather than v20 because the lane had been on v18 since the v20 container was stopped on September 21 — v20 plus the v21 hook should compose and is unmeasured. Full write-up in Pin Hot Experts; bundle results/2026-09-27-v21-fused-split · the card (now v21).
temperature and that 10 of 1,311 BFCL live schemas are invalid draft-2020-12 for Anthropic). The card carries this recipe's own number with its basis; that is the claim.
The Sept-18 "24-slot profiles cost ~6% at C8, unexplained" is closed. One pair on the v20 image, same hook, same window,
--cudagraph-capture-sizes omitted vs the 15-size list: engine default wins the 4–8 stream knee (C4 443 vs 400, C8 720 vs 657); the list wins C1 (180 vs 173), the KV pool (2.55M vs 1.86M tokens — default graphs cost 0.7M) and, decisively, the 24-seat lane: fund C24 warm agent p95 2.4 s vs 9.5 s, because with 0.7M fewer KV tokens the slot queue comes back. It was never the autotune. The list is a stated trade — ten points on the knee for a lane that stays warm at 24 seats — and it stays; a deployment that lives at 4–8 streams should drop the flag.
Same window on
:30001: the newer GLM-5.3-Flash DFlash2 draft (bf582e4e) against the pinned 7d74cdd8 on SGLang v0.5.20 — greedy 20/20 identical, acceptance 3.42 vs 3.37, C1 246 vs 248, tools 10/10 ×2. No change; pin stays. Receipts: cardG · cloud-comparison · the card.
dc36fcce (09-14: Engram prefetch, DeepSelect — which is opt-in) is bit-identical to 2671fedf on the 79,544-position teacher-forced corpus; cd10ed6f (09-15: fold mHC post into delayed pre projection) moves 12.7% of agent-tool top-1s and 9.0% of held-out, and shell acceptance drops 0.908 → 0.829. One PR, same box, same weights, T=0 logprobs. Mega-mHC on top is what then costs the tool-JSON acceptance (0.878 → 0.757). Engram prefetch and DeepSelect are exonerated.
v20 promoted at 12:30. Nightly
2671fedf + the v15 hook + off54 + fp8_ds_mla + v18's 24-seat flags, on a loaded autotune set: same window, r1 → v18 → r1, C1 180.9 / 171.8 / 180.3 (+5.3% / +4.9%; the ≥5% bar is met on the pair mean, not on boot b alone — said so on the card), C16 979 / 952 / 965, fund C24 warm agent p95 2.8 / 3.4 / 2.4 s, tools 64/64 on all three. It is an eight-day-old nightly on purpose: every later one inherits #56633. v18 is the rollback container.
The recipe now carries an agent claim card, after @getmeosu's critique that tok/s plus a hardware list is not a claim about agents: a pinned outer protocol (
harness/protocol.yaml, sha256 on the card), a dev suite that decides candidacy (BFCL simple+multiple, 600), a held-out sibling frozen this morning that decides promotion and is run once (BFCL live_simple + live_multiple, 1,311), and cost per solved task. First fill: v20 78.8% vs v18 78.9% held-out (dev 92.7 vs 93.0 on the same boots; ±1 pt is run-to-run on 600), $0.008 per 1,000 solved tasks at $0.15/kWh from 490 W × 392 s — v18's power sample was lost to a script bug and is an estimate, labelled. The two-sample W mean is the known weakness of that number. Same-grader A/B only; it does not go beside the public leaderboard. Receipts: v20-promotion bundle.
2671fedf (September 13, model support on main, no perf kernels) 1.77% → af1c0149 (September 16: #56464 DeepSelect top-k, #56962 Mega-mHC, #56512 Engram prefetch) 11.73% → 0bfc7a15 (mega-attention + NVFP4 KV, MegaMoE) 10.99% → dee37d89 (v19) 11.45%. The drift enters with the September 16 PR set; which of the three is not separated yet. The MegaMoE rung recovers part of the draft acceptance without touching the flips.
2671fedf is the v20 candidate. It keeps v18's acceptance on every class (tool-JSON 0.860, shell 0.908, prose 0.349), scores BFCL 561/600 = 93.5% (v18 93.3, same failure kinds), and reads 189 tok/s C1 on a live tune, 180 loaded (v18 172), C8 685, C16 958 — it passes the promotion rule as written, with an order of magnitude less drift than v19. Not promoted: that needs a same-window v18 pair, the fund harness at C16/C24 and a pinned autotune set, in a scheduled window. Bundle: 2026-09-21-kernel-bisect. :30006 is v18.
Correctness is a wash; acceptance fails; the cause is the image. BFCL v4
simple_python + multiple, 600 calls, T=0, our AST grader, same window: v18 560/600 = 93.3%, v19 559/600 = 93.2%, identical failure kinds (value/name/no-call; zero JSON-parse or extra-argument failures on either side). But the drafter agrees with the target less often on exactly the text an agent lane emits — tool-JSON acceptance 0.878 → 0.838, shell 0.908 → 0.829 — and that is the same fact the per-class teacher-forced split was reporting: top-1 flips on tool-shaped text 0.56% for v18's hook, 11.45% for v19. The cheapest physical suspect, vLLM #49435's fp8_ds_mla writer-scale fix (merged after our pin), was tested in the same window: a boot on nightly d05da62e at the v19 flags leaves the flips at 11.45%, unchanged to two decimals. The drift lives in the DSV4.1 kernels that landed between the 0909 image and dee37d89, not in the KV path. That boot also selected FLASHMLA_MEGA_ATTN_DSV41 — the backend upstream's own #56625 calls "broken for this checkpoint" — and live-tuned in 16 minutes, not 74.
State.
:30006 is v18 Many Seat again (172 tok/s C1, KV 2.50M, autotune hit). v19 and the d05da62e candidate are stopped-and-kept; the nightly's +6.6% is real and not adopted. The equal-budget GPQA-Diamond pair is now a pair: v18 86.9% (172/198) vs v19 87.4% at 64k, a wash. The recipe card carries a BFCL row and the per-class split; a corpus-wide fidelity average never goes on it alone again. A correction to the previous callout: the "fidelity measured" cell hid this — 95% of the corpus positions are prose, and prose was fine. Next: bisect the kernel PR set one nightly per boot (running tonight), and the GLM-5.3-Flash rebase to SGLang v0.5.20 for the DFlash/KDA checkpoint fix.
v19 is real, and it is the lane's job that says so. Two fresh boots of the loaded v19 configuration — nightly + hook +
--cpu-offload-gb 54 + fp8_ds_mla + the pinned ddf01704… autotune set — land at 183.2 and 183.1 tok/s single-stream (four runs within 0.1; +6.6% over v18's 171.6–172.0 in the same window) and produce identical tokens on 18/18 prompts, including the agent-tool prompt that jitters on every other pair on this page. On the 35-seat fund harness the candidate is at or better than v18 everywhere that matters to a seat: C24 aggregate 306–310 vs 285 tok/s (+8%), C24 warm agent turn p50 0.55 vs 0.60 s, cold 120K prefill under 16 streams p95 8.3 vs 8.4 s, tools 64/64. One miss: the second boot's C16 warm-agent p95 came in at 2.56 s against a 2.28 bar, while the first boot beat it at 1.68 — one p95 on 71 turns. The C8 knee is −5% on every loaded nightly boot, unexplained and recorded. Candidate holds; not promoted — one more C16 window for the p95, and the fixture's tool_json −6% on a tool-heavy lane is a call, not a measurement.
The flag-only row: 90.5 tok/s. For the upstream
vllm-project/recipes Station entry every flag has to be reproducible from the yaml, and the hook is a bind-mounted patch. So T3c measured what a Station owner gets from flags alone on the nightly: hook off, --cpu-offload-gb 60, --kv-cache-dtype fp8_ds_mla — 91.3 tok/s C1 on the live-tune boot, 90.5 loaded · 302 at eight streams · ~420 at sixteen · KV 3.09M tokens at 1M, 7.5-minute bind on a cache hit. fp8 KV restores draft acceptance hook-off exactly as it did hook-on (tool JSON 0.855 vs 0.810 on the nightly's nvfp4 default, shell 0.908 vs 0.829) at −4.6% on the prose knee; code acceptance went the other way (0.609 vs 0.688, one boot). And the live-vs-loaded autotune tax that runs −4…−6% with the hook is −0.9% without it — the tax lives in the hook's extra kernel shapes, which is now the discriminating observation on the FlashInfer autotuner-v2 RFC. The row itself is blocked on an upstream schema change (recipes PR #979) that lets a single-GPU Station declare a post-offload fit budget; we +1'd it with these numbers and will file on top of it.
--async-scheduling is closed at v18. Cursor's Mixture-of-Kittens post reported Grace-side CPU work catching the GPU on GB300 NVL72s, so we re-asked a question that was a wash at v12's 11 ms step. At v18's 5.8 ms step: C1 −0.4%, C8 −0.3%, C16 −0.6%, acceptance identical. The scheduler is not on this lane's critical path. Their other GB300 lesson — a dedicated many-CTA gather to saturate the link — we had already measured as E1c on September 17: the cold read runs at the C2C ceiling and staging lost to the split we ship. Two ideas stolen, two closed with receipts; that is what the post was for.
Also on the record: the runner's receipt-move glob missed the window script's output directory twice today (files intact, moved by hand, template fixed next); v19 boots at 206.61 / 62.33 GiB residency, identical to v18. Everything below this callout is the record through Round 8.
[[1,4,5],[5,12,3],[13,24,1]], [[1,8,5],[9,24,3]]) is verify-compute tax at C8 (−6%, −13%) and does nothing for the agent replay — four replay workers never leave the k=5 band, and accepted-per-step sits at 2.6–2.7 on every schedule including v18's. On-line adaptive placement is closed for this hook: the E4b counter, unfrozen, costs 3% at C1 and C8 and applied twelve swaps, all in the first drain after warm-up and none after. The nightly image binds (dee37d89, vLLM v0.29.1rc1, FlashInfer 0.6.18.post1) with every v18 flag and the hook unchanged; hook-off it is +9.5% over positional v14 and half of v18 at eight streams — the hook is still the bigger lever.
Find one: the autotune file is part of the recipe. Nightly + hook measured 184 and 196 tok/s single-stream on two boots (+7%, +14% over v18) — but only when FlashInfer autotuned live. Any boot that loaded a saved autotune cache landed on exactly v18's 172 with a −15% hole at four streams. Two live tunes of the same 191 kernel shapes disagreed on 86 of them, including the M=1 decode MoE tactic itself. Pinning the good set as the cache (two fresh boots) gives a stable 187.8 / 187.9 tok/s (+9.6%), hole gone, tokens identical to the tune that produced it. And in every pair we have — three now — the process that ran the tune is 4–6% faster than a fresh process loading that tune's own output, with identical tokens. We record the set's sha256 as a pin now; the two saved sets and the live-vs-loaded observations are filed with the FlashInfer autotuner-v2 RFC.
Find two: the nightly's draft-acceptance drop was one log line. On the agent fixture the nightly lane was losing prose −10%, tool JSON −10%, structured −8% because it accepted fewer DSpark drafts on every boot (prose 0.29 vs 0.35, tool JSON 0.81 vs 0.88). The boot log says why: the nightly silently switched the KV cache from
fp8_ds_mla to nvfp4_ds_mla (the SM100 default since #56935) — which is also why its KV pool read 4.5M tokens instead of 2.5M. A 4-bit NoPE latent moves the target logits the drafter is verified against. One flag, --kv-cache-dtype fp8_ds_mla, same-window against v18: acceptance back to 0.34 / 0.84 / 0.49 (control 0.35 / 0.88 / 0.53), KV 3.2M, and the speed grew — 193.7 tok/s on the live-tune boot (+12.6%), 183.3 on the cache-loaded boot (+6.5%), C16 1052 / 986 vs 966, replay 380–484 vs 319–434. The +7–14% is the nightly's merged DSV4.1 kernels, not the FP4 cache. The residual acceptance gap of one to four points is the nightly's own numerics: greedy parity against 0909 is 2/18 on every nightly boot.
Why v18 still holds. Tool JSON is −6% on the fixture against a −3% bar, and this lane serves agents. The servable number is 183, not 194 (you cannot serve from a live tune without paying fifteen minutes per boot). v19 candidate = nightly + hook +
--cpu-offload-gb 54 + fp8_ds_mla + the pinned ddf01704… autotune set. To promote it we want a second same-window pair on the loaded configuration, the 35-seat fund harness at C16/C24 (the job v18 was promoted for), and a decision on whether +6.5% single-stream and +2% at sixteen streams is worth −6% on tool JSON. Not this weekend.
Also new: every window receipt from tonight on carries GPU power draw, cap, SM clock and throttle reasons — the Station shares a fixed 1600 W budget between the GB300 and the RTX card under
vsloshd, and a moved cap would look exactly like the ~3%/day "reference drift" this page has been reporting (tonight: cap 1300 W, throttle 0x0, 207 W idle → 510–610 W under the knee). Three overnight-runner bugs are on the record, the worst a heredoc without -i that silently skipped the one test that mattered; and the hook's 10 GiB host guard turns out to clear by 1–2 GiB on every boot — --cpu-offload-gb 54 is the fix, with identical residency. Receipts, briefs, runners and all three autotune JSONs: the recipe bundle (PRs #32–#34). Everything below this callout is the record through v18.
--max-num-seqs 16 — a slot queue, with KV usage peaking at 16% — and the scheduler's per-request chunk cap letting two long prefills take the whole 8192-token budget. Four boots, each one axis, same-window against the incumbent on the fund harness: v16 (32 slots + long-prefill-token-threshold 2048) fixed the queue but the 2048 cap cost idle prefill −47% and prose p10 −29%, and 32-slot graphs took KV to 1.39M — failed. v17 (24 slots, cap back at 6144) took C24 warm p50 27.4 → 1.33 s, cold 120K under sixteen streams 15.8 → 8.9 s, knee C1 +7%, but default 24-slot graphs cost 2.33 + 1.78 GiB and KV fell to 1.79M; promoted for the many-seat story. v18 = v17 + --cudagraph-capture-sizes 1 2 4 6 8 12 16 18 24 32 40 48 64 96 128 (token counts: k=5 at 1–4 seqs is 6–24 tokens, k=1 at 5–24 seqs is 10–48) — graphs 1.30 + 1.10 GiB, KV 2,502,950 tokens (2.4× at 1M), knee 172 / 655 / 945 at C1/C8/C16 vs v17's 165 / 650 / 914 on an idle same-window re-check, fund harness at sixteen streams: warm p95 1.86 s, chat p95 11.5 s, cold 120K p95 9.2 s, tools 64/64; per-seat prose p10 16 tok/s. Two mistakes in my own briefs, kept on the record: a KV gate 14K tokens too tight, and a capture-size list written in sequences when the flag takes tokens (v17b: everything above twelve seats ran uncaptured, decode 10 tok/s/stream). Same-window v15 control on the same afternoon read knee C8 698 and C16 prose p10 16.5, so the 24-slot profiles cost ~6% at C8 and are a wash at sixteen streams; C1 gains +12% over v15. Receipts: pin-hot-experts/results/{v16,v17,v18}/; the recipe bundle follows. Everything below this callout is the record through v15.
do_finalize=False routed-kernel calls plus one fp32-FMA finalize — is bit-identical to v14 (greedy 8/8, acceptance rates identical) and lifted two same-window pairs to 153.3 tok/s single-stream prose (from 88.7; four runs within 0.4), ~705 at eight streams (from 305), ~955 at sixteen (from 386–417; one 667 outlier in seven reps); shell 250, code 214, structured 208; fifty-prompt greedy parity exact on every non-tool prompt, round-7 gauntlet 36/36, held-out math +41%, held-out Japanese/German a −1.5% wash. KV 4.75 GiB, 62.3 GiB pinned, 21 new autotune profiles in 12 minutes. The mechanism, the three spikes that got there in one day, the two things the design note got wrong, and the confirm run are in vLLM: Pin Hot Experts. James promoted it as v15; recipe PR #26 is merged. Everything below the two callouts is the record through v14.
dsv41-feat branch pins the 189 GiB Engram tables in Grace RAM and reads 60 GiB of routed experts over CUDA UVA (70 until September 12); the in-checkpoint DSpark drafter (mtp.*, block 5) is loaded from the same weights. Decode is content-dependent: 89 tok/s single-stream on prose (the tight, repeatable instrument; was 82), 140–160 on code, shell and tool JSON where the drafter accepts 64–91%. A short request no longer waits behind a long prefill (one scheduler flag; 18 s → 1 s). Under the real Hermes agent loop it called tools on 10 of 10 turns that needed one. September 12 update: the reference moved. A second night on the tight instrument found that decode is bound by expert bytes fetched over the Grace link, and that the cheapest way to cut them is fewer GiB of experts in Grace, not more GPU headroom. OFFGB 70 → 60 with util 0.97 put 12.7 GiB back in HBM and lifted the sweep to 89.2 tok/s at C1 (+12% same-day, +9% vs the old 82.1), +10% at C16, every category up, acceptance unchanged — see night two. The price is KV: 4.9 GiB, 2.3M tokens, 2.2 concurrent full-1M requests. To reproduce it, use the formal recipe (pinned digests, scripts, provenance, gates — v12, updated September 12, is the off60 config); this post is the story. Cold boot is 8 minutes from local NVMe (it was 92 this morning over SMB). Wired into Hermes as an experiment provider and passing real tool-use turns at 1M. Not promoted; the production lane on this Station is untouched.
September 12 (v12) snapshot cards below — history. The current v23 cards, measured October 18, 2026, are at the top.
Release “Sixty” — September 12, 2026: the campaign closes, the lane runs
--cpu-offload-gb 60 --gpu-memory-utilization 0.97, DSpark k=5. 89.6 tok/s single-stream prose (+14.7% over v11 in a same-window pair), 241 aggregate at eight, 312 at sixteen; 145–160 tok/s on shell, code and tool JSON; 10/10 Hermes tool calls; 207K-token prefill in 11.4 s; 2.2 concurrent 1M contexts. Recipe, launcher and this page agree. Named for the lever: 60 GiB of routed experts in Grace is the floor at 1M context, and every other axis was measured against it.
Two nights and a day of measurement end here, on purpose. The remaining levers were, at the time, judged either closed by hardware (expert residency — reopened and won on September 17, see the callout at the top), closed by the model (no static speculation depth fits both prose and agent text), or belong upstream (adaptive verification's ~6.5 GiB varlen graph capture; a per-expert-pointer MoE backend). The cudagraph-mode override I filed as vllm#56626 was already fixed on main by #55095; I backported it and tested it the same evening — the override is gone and the memory tax is not (evening three). The lane runs on these flags until one of those moves. What follows is the record of how it got here, newest first.
| Question | Answer | Where |
|---|---|---|
| What is the decode step made of? | MoE expert streaming: 64% of GPU time single-stream, 88% at eight streams. Attention 17% / 6%. Everything else single digits. The Grace fetch is ~9 ms of a 24 ms step. | morning three |
| Can we keep the cold experts in Grace and the hot ones in HBM? | cudaHostRegister rescues are refused. It was wrong to stop there. Keeping the cold rows in a separate pinned tensor and calling the routed kernel twice with do_finalize=False plus one FMA finalize is bit-identical and turns the ~9 ms Grace fetch into ~2 ms in-domain. v15: C1 89 → 153. Pin Hot Experts. | v15 results |
| Is k=5 the right speculation depth? | No single k is. A 6-token verify window touches 3.7× the unique experts of one token (router property, ±3% across categories). Static k=1 wins prose (+8%) and every multi-stream point (+23–33%); k=5 wins agent traffic by a third. vLLM's DSpark adaptive verification lands between with no losing category but misses the promotion bar (prose 104.1 vs 105, shell 145.3 vs 150) on the V2 runner's ~6.5 GiB graph-capture cost. I first blamed the 29 piecewise graphs that model_runner.py forced by overriding cudagraph_mode; with that override fixed (#55095, backported and tested) the piecewise graphs are gone and capture still takes 6.57 GiB — the cost is the varlen FULL decode graphs themselves. Both static points and the adaptive result are in the recipe; k=5 stays. | morning three |
| What did the first two nights buy? | Night one: five levers, nothing adopted, the autotune-hash lesson. Night two: k-sweep (k=0 89.7, k=3 87.7, k=5 79.5 same-day), util ≠ offload, and the residency change that became v12. | night one, night two |
| Upstream | Three comments on vLLM RFC #38256 (expert-offload provider design): C2C measurements, the two-path finding, the 3.7× multiplier, and the adaptive-verification result. One bug filed and closed: adaptive verification silently overrode cudagraph_mode; #55095 had fixed it on main a day earlier, a contributor pointed that out, I verified it by backport and closed the issue with a correction to my own premise (the override was real, the memory attribution was wrong). | #38256, #56626 |
| What would reopen this | A cheaper varlen FULL capture for adaptive verification (adaptive at off60 is the one config that might beat both static points — #56626 turned out not to be the path there); a cuDNN discrete-mode grouped GEMM path in vLLM's MoE backends; or a different box. Not: more offload ladder, more k values, host-memory tricks. | — |
Sections below run newest first. The September 10 head-to-head, knee, 1M-context, prefill, mixed-workload and SGLang material is intact under The first day.
Evening three, September 12: the upstream fix arrives, and it is not the fix
The override I filed as vllm#56626 had already been fixed on main a day earlier by #55095, which a contributor (he-yufeng) pointed out within hours: model_runner.py no longer forces FULL_AND_PIECEWISE; a new resolve_adaptive_cudagraph_mode() returns a user's FULL_DECODE_ONLY unchanged. I could not simply pull a newer image — DeepSeek-V4.1 support lives only on the dsv41-feat branch, 377 commits behind main, and there is no post-#55095 deepseekv41-flash tag — so I backported the PR's three files (model_runner.py, adaptive_verification.py, compilation.py) onto the 0909 image, bind-mounted over site-packages, and booted adaptive verification at the v12 flags (off60, util 0.97, k=5, V2 runner) with cudagraph_mode=FULL_DECODE_ONLY. Same-window control on the v12 reference first: 89.1 C1 / 310 C16, consistent with last night.
| Boot | Mode honored | PIECEWISE graphs | Graph capture | KV at off60 |
|---|---|---|---|---|
| Adaptive, 0909 as shipped (needed off66) | No — forced FULL_AND_PIECEWISE | 29 | 7.07 GiB | — |
| Adaptive, 0909 + #55095 backport | Yes — FULL_DECODE_ONLY | 0 | 6.57 GiB | −0.21 GiB, no boot |
| v12 reference, static k=5, no adaptive | n/a | 29 | 1.08 + 1.52 GiB | 4.89 GiB |
So the fix does what it says and my premise was half wrong. The silent override was real and is gone; the piecewise graphs it forced were about half a gigabyte of the seven. The rest is the varlen FULL decode graphs adaptive verification captures (supports_varlen=True, next_n=6: two FULL captures plus twelve DSpark captures), which the non-adaptive DSpark path does not need. Adaptive still cannot boot at off60 on this box, and the residency verdict from the morning stands. I closed #56626 as fixed with that correction rather than retitle it — the reported bug is fixed; whether a ~6 GiB varlen capture is a bug or the price of the feature is a different question, and I have one data point from a backport on a fork branch, which is not enough to file on.
Two campaign lessons, both self-inflicted: the safety check refused only dsfv-* containers, so a GLM lane on :30001 was up when the first launch went out and had to be stopped by hand; and local tag=$1 logf=…$tag killed the waiter under set -u for the second time this week, after it had already been written into the ledger. The engine had already failed by then, so no data was lost, only the lesson was re-learned.
Morning three, September 12: where the step goes, and speculation depth
With residency closed the remaining question was what the decode step is actually made of. A profiler boot (same v12 flags, torch profiler on, 89.5 tok/s so the overhead is negligible) says: MoE expert streaming is 64% of GPU time single-stream and 88% at eight streams; attention 17% and 6%; everything else single digits. Against the all-HBM two-station reference for the same model, the Grace fetch is about 9 ms of a 24 ms step.
Then a fact from the routing capture that reframes DSpark on this box. A six-token verify window does not touch six times one token's experts — it touches 22.5 unique experts per layer against 6.0 for a single token, 3.7×, and the multiplier is the same ±3% across prose, shell, code, tool JSON and structured output. It is a property of the router, not the workload. A quarter of those experts live in Grace. So speculation at k=5 streams 3.7× the bytes through the slow link, and only categories that accept enough drafts pay it back. That is exactly the T1 split: shell at 91% acceptance doubled, prose at 30% lost 11%.
We measured both ends of that. Static k=1, same window as a k=5 control: 110.8 tok/s single-stream (+23%), 422 aggregate at sixteen (+33%), prose +8% — and code, tool JSON and shell down by a third. No static depth fits the lane. Then vLLM's DSpark adaptive verification — the V2 model runner's confidence-head controller that prices each request's drafts per step and trims the batch before the forward. It works as described: prose gets trimmed to about one draft and lands within 2% of the static k=1 boot; shell keeps its drafts. It is the first configuration with no losing category. It still misses the promotion bar (prose 104 vs ≥105, shell 145 vs ≥150), for two reasons that are upstream's, not the controller's: the V2 runner's graph capture takes about seven gigabytes against one on V1, which forced six more gigabytes of experts into Grace to boot at all (that evening's backport of the upstream fix showed the piecewise graphs were only half a gigabyte of it — evening three); and the cost curves it prices from are profiled on dummy batches that never see expert streaming, so on an offloaded lane it under-prices verification. Both are fixable. Neither is fixable by us today.
v12 static k=5 stays the reference. Three lessons for the ledger: any change to the verify shape — depth, runner — is a new FlashInfer autotune hash and a 75-minute boot, no matter what was cached; the V2 runner costs residency on a memory-bound lane; and a fast-fail check that matches WARNING lines will kill a healthy boot, which one did.
Night two, September 11–12: is decode fetch-bound, and what moves it
The first night taught me to put the bar on the concurrency sweep. The second night asked one question with that instrument: is decode bound by expert bytes fetched over the Grace link, and by how much? Five boots, one axis each, every one re-measured against a same-window reference because the reference itself drifts about 3% day to day (yesterday's 82.1 came back as 79.5 on the same config). Prod on :30003 was not touched; the reference container was restored after every window.
| Boot | Axis | Offloaded | KV | knee C1 / C8 / C16 | Fixture prose / shell | Read |
|---|---|---|---|---|---|---|
| T1a | DSpark off (k=0) | 73.83 GiB | 19.96 GiB | 89.7 / 248 / 352 | 84.6 / 77.9 | Per-step floor with no verify batch. 11.1 ms/step. |
| T1b | DSpark k=3 | 73.83 | 10.64 | 87.7 / 245 / 331 | 95.9 / 129 | Prose +6.8% over k=5; the plan's confirm bar was +25%. Not adopted. |
| T1c | DSpark k=5 (reference, same-day) | 73.83 | 10.05 | 79.5 / 229 / 282 | 89.8 / 146 | Same-day control. 3% under yesterday's 82.1 — the drift band. |
| T2 | util 0.94 → 0.97 | 73.83 | 17.54 | 79.5 / 229 / 278 | 90.3 / 145 | Void. The extra 7.5 GiB went to KV. util does not move experts; only --cpu-offload-gb does. |
| T2b | OFFGB 70 → 60 + util 0.97 | 61.17 | 4.89 | 89.2 / 234 / 311 | 97.7 / 160 | Win. +12.2% C1 same-day, +10% C16, all five categories up 10–12%, acceptance unchanged at 59.6%. |
Read the k-sweep first. With the drafter off, the lane does 89.7 tok/s. catid's two-Station vLLM PP2 run — every weight in HBM, no Grace fetch — does 141.3 autoregressive at C1 on the same model family (their revision dba1be0a, ours df42c109). That is 7.1 ms per step against our 11.1: the expert-fetch tax is about 4 ms, roughly 36% of the step. Real, not dominant. The same comparison explains why DSpark is a prose loss here (k=5 79.5 vs k=0 89.7) and a 79% win in HBM (141 → 253): a k=5 verify batch touches more unique experts per layer than a single token, and over C2C every one of them costs. DSpark stays on because this is an agent lane and shell/tool JSON accept at 88–91% — 160 tok/s on shell vs 78 with it off.
Then the lever. Moving 12.7 GiB of experts from Grace to HBM bought 9.7 tok/s — about 0.76 tok/s per GiB, twice what the linear bytes model predicted. That is the finding: the lane is more fetch-sensitive than the k=0 floor test suggested, and the residency split is the knob. It also closes T3 (--kv-cache-memory) before spending it: KV sizing does not touch the expert set either. The ceiling is set by host memory, not HBM; Grace sits at 432/494 GiB with no swap after this change, and the checkpoint page cache is the only reclaimable pool left.
The boot tax is worth stating plainly: every change to kernel shapes gets a new FlashInfer autotune hash and a 74-minute tune, three times last night. Seeding the previous config into the new directory does not skip it — the tuner ignores entries whose shapes differ. Only an exact hash hit (T1c, T2: 8-minute boots) skips it. Budget it or don't change shapes.
Not adopted, and why: k=3 (prose +7%, shell −20%); util alone (moves KV, not experts); huge pages on shmem (ShmemHugePages stayed 0 through every boot — pinned host copies are not governed by that sysctl); T3 as written; any expert pruning (James's standing rule: all 384 stay). Measured at 02:30 (T5): a per-(layer, expert) selection histogram on the agent fixture via --enable-return-routed-experts — 23 requests, 3,545 decode tokens, 850K selections across 40 MoE layers. The top 73% of experts by frequency take 98.7% of decode selections (uniform would be 73%). If the coldest 24% of each layer's experts — the same byte budget v12 offloads — were chosen by usage instead of by layer position, they would see 2.7% of the traffic instead of 24% (leave-one-request-out). The catch is that the ranking is domain-specific: agent-class and prose expert ranks correlate at 0.15, and a cold set chosen on agent traffic only gets prose to 20.5%. So usage-aware placement is worth most of the remaining fetch tax for the workload it was profiled on, and roughly nothing for a foreign one; a per-token cache is not the answer either (an oracle cache at 3× top_k hits 68% with 384 experts and k=6). The design for the static version was a CUDA VMM mapping (one virtual range per layer, expert rows backed by HBM or Grace physical pages, CUDA PG §4.19) so the kernel, shapes, and autotune hash never change. Spiked at 05:40, and the elegant version is dead on this driver: every mechanical step passes — host-NUMA cuMemCreate on 595.84, mixed backing under one address range, a torch view over it, remapping a row from Grace to HBM in place with the pointer unchanged — but the GPU reads EGM-backed host memory at 91 GB/s against 350 GB/s for the cudaHostAlloc-pinned memory UVA uses today, over the same C2C link. A cold set that sees 2.7% of traffic at four times the per-byte cost is still cheaper than 24% at normal cost, but not by enough to move rows off the path that just bought +14.7%. The fallback is the shape everyone else uses — pinned Grace tensor as-is plus a small HBM hot-row tensor and a row-map inside the kernel — which brings back the autotune hash and the parity gate. Whether that is worth two or three days for a single-stream lever is the next decision, not the next task.
Then the managed-memory spike settled it (06:30). The hope was that Grace-Blackwell's unified memory would do the hot/cold split for free: give the expert tensors to cudaMallocManaged, let hardware access counters pull hot pages into HBM, leave cold ones in Grace, same pointer, same kernel. Measured: every path from the GPU into Grace memory is one of two things. Pinned or cudaHostRegister'd memory goes through ATS — the page table shared with the CPU — at 340 GB/s, the link ceiling, with no per-page placement control. Anything mapped through the GPU's own page table — the VMM rows above, or managed memory told to prefer the CPU — reads at 90 GB/s. Plain managed memory does not do hot-page migration on this driver at all; it faults the whole range into HBM on first touch, and once the working set is bigger than free HBM (which expert tensors always are, by 60 GiB) it settles at 155 GB/s — slower than what UVA does today. There is no mechanism on this box that holds cold experts in Grace at full speed while hot ones sit in HBM under one pointer.
So the residency axis is closed at off60. Not because the skew is not there — it is, 98.7% — but because the hardware offers speed or placement, not both. The one design left is a second HBM tensor for hot rows with a row-map inside the kernel, which brings back the 74-minute autotune and the parity gate for at most another +20% single-stream. Not doing it. What is banked: +14.7% from moving 12.7 GiB, a fetch tax measured at ~27% of the decode step and known to be link-bound, a routed-expert histogram nobody had published for this model, and three negative numbers that anyone building an expert-offload provider for GH200/GB200/GB300 needs before they design one. I also left a data-point comment on vLLM's expert-offload RFC, since its numbers are all PCIe consumer GPUs and the coherent-memory case wants a different provider shape.
Overnight, September 10–11: five levers, nothing adopted, one lesson
With the config settled I wrote a plan and handed the box to a second agent for the night: measure prefix-cache TTFT, then try transparent huge pages, unpinned host memory (VLLM_WEIGHT_OFFLOADING_DISABLE_PIN_MEMORY=1), --language-model-only, and the Rust frontend, each with a mechanism check and a win bar. The operator ran it exactly as written and reported two wins. In the morning I reran the win metric three times and it was noise (previous section). Re-read on the concurrency sweep:
| Experiment | Mechanism check | C1 vs 82.1 | Verdict |
|---|---|---|---|
| Prefix cache, 8.8K-token system prompt | hits 0 → 34k | — | TTFT 0.665 → 0.360 s warm. Measurement, works as advertised. |
THP enabled=always | AnonHugePages 0.5 → 2.5 GB (needed ≥50) | 78.4 | Did not engage. The 354 GB of offloaded weights sit in shmem, and shmem_enabled is never; the enabled knob was never the relevant one. Tried again on the unpinned path: same 2.5 GB, 77.3 tok/s. |
| Unpinned host memory (ATS) | host used 450 → 384 GB | 76.3 (−7%) | Real mechanism, wrong direction. 66 GB of RAM back that nothing needs, at a decode cost. Reverted. |
--language-model-only | KV 10.05 → 11.14 GiB | 76.2 | +1.1 GiB, not the 3 needed to make offload 65 fit. |
VLLM_USE_RUST_FRONTEND=1 | tool parser survived | 76.0 | No decode gain; 6.5K-token TTFT 0.37 → 0.52 s. Reverted. |
The lane went back to last night's reference at 5:56 AM and re-measured 80.1 tok/s at C1. The lesson is not about any of the flags. It is that I set the win bar on the instrument I had, not the one that was tight, and a diligent operator hit the bar honestly. Bars go on the sweep now, and the recipe says so out loud.
The first day, September 10: head-to-head, knee, 1M context, prefill
Everything in this block was measured on the September 10 v11 configuration (OFFGB=70 UTIL=0.94, 82 tok/s prose). Numbers that moved with v12 are called out where they appear; prefill, mixed-workload and harness gates were re-run on v12 on September 12 and did not move.
Head-to-head, same box, same weights, same fixture
| Fixture (131K ctx, temp 0) | SGLang OffloaderV2 | vLLM UVA |
|---|---|---|
| C1 single stream | 3.3 tok/s | 85.2 tok/s |
| C4 aggregate / per stream | 13.0 / 3.25 | 172.7 / 43.2 |
| C8 aggregate / per stream | 25.8 / 3.23 | 183.5 / 22.9 |
| 300-word paragraph (~313 tok) | 94.9 s | 3.5 s |
| Count 1–60 (120 tok) | 36.9 s | 1.5 s |
17 × 19 → 323 | 22.4 s (cold) | 0.09 s |
Tool call get_weather | parsed | parsed |
Thinking, low effort → 36 | 25 reasoning tok | 24 reasoning tok |
Concurrency sweep: where is the knee?
The first head-to-head used 128-token generations, short enough that prefill and scheduling noise dominated at C8. Re-run with 192 forced output tokens, two reps per point, two server configs. max-num-seqs matters: at the default 8, anything above eight streams queues, so C12/C16 read as a flat ~270 that is a scheduler cap, not a bandwidth wall.
| Concurrency | seqs=8 agg / per-stream | seqs=16 agg / per-stream |
|---|---|---|
| 1 | 89.3 / 89.3 | 93.2 / 93.2 |
| 2 | 123.6 / 61.8 | 134.4 / 67.2 |
| 4 | 155.6 / 38.9 | 155.7 / 38.9 |
| 8 | 269.2 / 33.7 | 255.4 / 31.9 |
| 12 | 234.8 / 19.6 (queued) | 239.3 / 19.9 |
| 16 | 270.1 / 16.9 (queued) | 336.4 / 21.0 |
Aggregate is still rising at sixteen streams, so the UVA fetch ceiling is further out than the first C8 number suggested. Two things are not noise and are on the list: C4 is bimodal (122 vs 189 on back-to-back reps in both configs), and C8 dips slightly under seqs=16. Both smell like expert-page locality on the 60 GiB UVA tier between runs rather than kernel behavior.
DSpark: the speedup depends on what you are generating
The V4.1 DSpark drafter ships inside the target checkpoint (mtp.{0,1,2}.*, 2,401 tensors in shards 44–46). vLLM loads it with one flag, --speculative-config '{"method":"dspark","num_speculative_tokens":5}', at a cost of ~4 GiB of HBM for draft weights and graphs. Offload went from 60 to 70 GiB to pay for it, and KV came out ahead (15.56 GiB vs 12.03) because the draft's memory profile is small.
The first sweep was a disappointment: on the paragraph-generator fixture, k=5 was 10–18% slower than no speculation at every concurrency. The /metrics counters explained it. Over 7,998 draft steps, acceptance by position was 59.5 / 30.4 / 13.9 / 6.1 / 2.8%, 22.5% overall, 2.13 tokens per verify step. A verify step runs six tokens through forty MoE layers and fetches six times the expert rows over UVA, so landing two of them is a loss. Meanwhile the "count to sixty" probe went from 79 to 240 tok/s on the same server: ~4.5 tokens per step accepted. Same engine, same flags, 3× apart. The fixture decides.
So I built a fixture shaped like what a Hermes agent actually emits and measured acceptance per category:
| Category (C1, temp 0) | No spec | DSpark k=5 | DSpark k=3 | k=5 acceptance |
|---|---|---|---|---|
| Shell command sequences | 93 | 144–150 | 128 | 90.8% · 4.54 tok/step |
| Code (Python, bash) | 93 | 139–146 | 130 | 63.8% · 3.19 |
Tool-call JSON (real tools=) | 93 | ~130 | ~120 | 87.8% · 4.39 |
| Structured (tables, status bullets) | 93 | 111–116 | 108 | 49.5% · 2.48 |
| Prose | 93 | 89–93 | 95 | 29.8% · 1.49 |
| Weighted acceptance | — | 59.6% | 70.1% |
k=3 accepts a higher fraction of what it drafts (positions 3 and 4 were landing 6% and 3% on prose) but drafts less, so it nets fewer tokens per step on agent text and slightly more on prose. Neither dominates; for an agent lane, k=5 is the right default because the workload is code, tools, and shell. The published 79–86% acceptance numbers from other builds are on synthetic or code-heavy text, and the 4× DGX Spark repo's per-category table (code 73.8 vs prose 24.4 tok/s) is the same phenomenon at a lower floor. Nobody's number is wrong; "C1 tok/s" without a fixture description is now an incomplete statement for this model.
Two things that broke and how: the first k=5 boot re-ran the 74-minute autotune because the new engine-config hash directory was empty for the 60 seconds it took me to copy the cache in; restart with the seeded directory bound in 20 minutes and tuned only the 42 new draft shapes (168 → 210 configs). And each DSpark boot does two full passes over the checkpoint (target, then draft), which over SMB was 35 minutes of loading. That was the argument for the next section.
Local NVMe: cold boot 92 → 8 minutes
Two 8 TB WD SN850X went into the Station's M.2 slots as RAID0 (15 TB XFS at /models). Staged the full 510,313,343,553-byte checkpoint from the SHA-pinned archive, byte-verified every file, and relaunched with the same flags:
| Phase | SMB (this afternoon) | Local NVMe |
|---|---|---|
| Target shards + pinning | 1,246 s | 133 s (shards alone: 21 s) |
| DSpark draft pass | ~600 s | 20 s |
| Graph capture | 31 s | 34 s |
| Autotune (cached) | ~1 min | ~1 min |
| Launch → bound | ~35 min | 8–12 min |
Decode did not change (count 252.7, prose 80.7 tok/s on the smoke, within noise of the SMB build). Boot is now cheap enough that flag experiments are a coffee break, not an afternoon. The staged-shard trick from this morning (Engram shards local, everything else symlinked to the NAS) is retired.
1M context
131K was a defensive choice from when we were 4 GiB short on HBM. The model's max_position_embeddings is 1,048,576 and the KV pool was already holding 3M tokens, so the only question was what the 1M profiling pass would cost:
| 131K | 1M | |
|---|---|---|
| CUDA graphs | 1.00 GiB | 1.52 GiB |
| KV pool | 15.56 GiB | 10.05 GiB |
| KV tokens | 3.07M | 4.79M (sparser layout at 1M) |
| Max concurrent at full length | 23× at 131K | 4.5× at 1M |
| C1 code / shell / prose | 139 / 144 / 89 | 146 / 150 / 93 |
| C16 aggregate (prose) | 276 | 287 |
Costs 5.5 GiB of KV headroom, changes nothing about decode speed, and the Hermes provider now advertises context_length: 1048576. Two real turns at --reasoning low and high came back clean.
Acceptance on real transcripts, not fixtures
The agent fixture above was my guess at what Hermes emits. To check it, I pulled 24 turns from today's actual session (12 that ended in a tool call, 12 text) out of the Hermes session store, replayed each with its preceding context and the real terminal / read_file / web_search tool schemas, temperature 0, thinking off, 400-token cap, and read DSpark acceptance from /metrics deltas per turn.
| Turn type | n | tok/s | Draft acceptance | Tokens/step |
|---|---|---|---|---|
| Ended in tool call (original) | 12 | 79.5 | 51.7% | 2.58 |
| Text | 12 | 98.4 | 46.8% | 2.34 |
| All | 24 | 88.1 | 49.1% | 2.46 |
Per turn the spread is 5–171 tok/s. The turns where V4.1 actually emitted a tool call ran at 138–171 tok/s with 65–86% acceptance, matching the synthetic fixture. The turns that dragged the mean down were ones where the model wrote prose about what it was going to do instead of calling the tool, and prose accepts at 30–45%. Net: on real traffic, DSpark k=5 is a wash against no speculation (88 vs 93), with a large upside on the turns that are genuinely code or JSON. It stays on. The 150 number is real but it describes a fixture, not a day.
The replay also surfaced something that is not a speed result: in this bare setup (two-line system prompt, no Hermes harness), V4.1 produced a tool call on only 4 of the 12 turns where the original model had. That was a flag, not a verdict. Resolved the next morning: under the real harness it is 10/10 (below).
Correction, September 11. The 88.1 tok/s figure in this table should not be read as a decode number. Rerunning the same 24-turn replay three times on the same idle server, same flags, temperature 0, gave 90.6 / 119.4 / 124.3 tok/s. Batched MoE plus speculative decode is not bit-deterministic, the outputs differ by hundreds of tokens between runs, and the mean swings ±17. The acceptance percentages hold up; the tok/s does not. The concurrency sweep above runs each point twice and the pair agrees within about 1% — that is the instrument. This paragraph exists because I published an overnight "win" on the replay number and the sweep contradicted it. Details in the ledger below.
Mixed workload: a short request behind a long prefill
Everything to this point was one request at a time. The failure mode that matters for a shared lane is a 480K-token prompt arriving and every other user waiting. Measured: fire a 480K cold prefill, then a one-sentence question every 5 seconds behind it.
| Short request fired at | Default scheduler | --long-prefill-token-threshold 6144 |
|---|---|---|
| +5 s into the long prefill | 18.2 s TTFT | 0.97 s |
| +10 s | 13.2 s | 1.09 s |
| +15 s | 8.2 s | 0.89 s |
| +20 s | 3.2 s | 0.92 s |
| Long request TTFT | 23.1 s | 26.8 s (+16%) |
| Solo short TTFT | 0.10 s | |
Default vLLM runs the long request's 8192-token chunks back to back and admits nothing else until it finishes; every short request landed at exactly the moment the long prefill completed. Under a 972K prompt that is 85 seconds of dead air for everyone. The fix is one flag: capping a long request's per-step chunk at 6144 leaves 2048 tokens of each step for other requests, and short requests get a first token in about a second. They still share the GPU with the long chunk, so their decode during the overlap is slow (~2.5 tok/s vs 150 solo); interleaved, not isolated. The long request pays 16%. That is the right trade for a shared lane, and it means the 1M config can be the daily lane rather than a separate one. The older --max-num-partial-prefills knobs are gone from this vLLM build.
Offload notch at 1M
With boot at 8 minutes, the 60-vs-70 question was cheap to answer. At 1M with DSpark: offload 60 loads 226.77 GiB into HBM and dies at Available KV cache memory: −2.61 GiB. Offload 70 is the floor for this configuration; the 10 GiB difference buys 12.7 GiB of KV once the draft model, its graphs, and the 1M profile are accounted for.
| Offload | 131K, no spec | 131K + DSpark | 1M + DSpark |
|---|---|---|---|
| 40 GiB | KV −6.96 | — | — |
| 60 GiB | 12.03 ✓ | — | −2.61 ✗ |
| 70 GiB | — | 15.56 ✓ | 10.05 ✓ reference |
Prefill: a 972K-token prompt in 85 seconds
Everything above is decode. Cold prefill measured on the v10 reference config with the skill's probe: a random nonce at the start of the prompt (so no prefix-cache hit), random-word filler, max_tokens=1, thinking off, rate = prompt tokens / wall time. That is effective time-to-first-token, not a kernel counter; the server's --max-num-batched-tokens 8192 chunking is included.
| Prompt tokens | TTFT | Prefill tok/s | Reps |
|---|---|---|---|
| 6,538 | 0.37 s | 17,900 | 2, identical |
| 25,920 | 1.54 s (one cold outlier 4.5 s) | 16,900 | 2 |
| 51,797 | 2.67 s (outlier 5.5 s) | 19,400 | 2 |
| 103,814 | 5.36 s | 19,300 | 2, identical |
| 207,339 | 11.3 s | 18,300 | 2, identical |
| 414,624 | 24.9 s | 16,700 | 2, identical |
| 972,435 | 85.0 s | 11,400 | 1 |
Flat at roughly 18K tok/s from 6K to 200K, easing to 16.7K at 415K and 11.4K at 972K as the sparse indexer's top-k over a longer candidate set starts to cost. Zero preemptions; HBM stayed at 243 GiB through the 972K request, so the 10 GiB KV pool is genuinely holding a full-length sequence. The two outliers at 26K and 52K were first-touch of new UVA expert pages after idle and did not recur.
For scale: the 8× B200 report on the 0731 predecessor measured 1M TTFT of 84.7 s at c=8 under DP8 and 314.6 s under TP8. One Station at c=1 landing at 85 s for 972K with experts on UVA is the same order as an eight-GPU node's best case, which says the encoder half (8B active per prompt token) is doing what the architecture promised. What this does not say: nothing here is measured with concurrent short requests alongside a long prefill, and vLLM's partial-prefill knobs were reported not to help on that class of model. The 1M lane is a one-user-at-a-time lane until that is measured.
reasoning_effort: medium (HTTP 400 … must be low, high, xhigh, max, or an integer within [1, 100]). If your agent's default effort is "medium," every request fails until you override it per-provider.The autotune cache key trap
FlashInfer keys its tuned-config cache on a hash of the engine config. Changing --max-num-seqs from 8 to 16 produced a fresh hash with an empty directory, and the second boot was about to spend another 74 minutes re-tuning kernels whose shapes had not changed (the tuner runs at 8192 prefill tokens and the graph batch sizes, neither of which depends on the sequence cap). Copying autotune_configs.json from the old hash directory into the new one was accepted: Loaded 168 configs, graph capture in 8 s, bind in 20 minutes instead of 92. Worth knowing before touching any launch flag on a Blackwell box. I tripped this three more times today (--max-num-seqs, the speculative config, and --max-model-len each change the hash); the copy has to land before the tuner reads the directory, which is roughly two minutes after the KV gate. Seed it before launch, or disable autotune for smoke boots.
Hermes on it: the tool-calling gate
Added as an experiment provider (custom[dsv41] → :30006). The first smoke was five mixed turns, all clean. The bare replay above then raised the question that matters more than any speed: will it actually call the tool, or narrate calling it? So the gate is the real agent loop, not the raw API: hermes chat --provider custom[dsv41] --reasoning low -t hermes-cli, ten prompts each of which cannot be answered honestly without a tool — count lines in a file, read a secret string, parse a port from JSON, run uname, write a file and confirm it, append a line and report the new count, compute 1234×5678 in a shell. Scored from the harness's own tool_turns= log line plus a ground-truth check of the answer or the file on disk.
| Prompts | Turns that called a tool | Correct answer / side effect | Wall per prompt |
|---|---|---|---|
| 10 | 10 | 10 | 8–14 s incl. agent bootstrap |
The 8-of-12 narration in the bare replay was a property of a two-line system prompt, not of the model. Under the real preamble and tool schemas it just calls the tool. Two things you need for this: --tool-call-parser deepseek_v41 --reasoning-parser deepseek_v41 --enable-auto-tool-choice on the server, and --reasoning low (or high) on the client — the V4.1 chat template rejects medium with a 400. Still not routed as a default; it is a lane you pick, but the reason it couldn't be is gone.
The recipe
All of this is now a formal, machine-checked recipe in J&M Recipes: dgx-station-gb300/deepseek-v4.1-flash-vllm-uva-dspark (merged as PR #6) — pinned image digest and model revision, the launch script with the knobs, every script that produced a number here, the provenance bundle, the five gates with pass dates, and a failure ledger that includes the offload bracket, the four autotune misses, and the overnight above. That is the version to reproduce from; this post is the story.
Why 26×: bytes moved per token
Both engines keep Engram on the Grace side. The difference is how the offloaded experts get to the GPU. SGLang's OffloaderV2 in cpu mode copies the entire expert tensor for each offloaded layer, every token: 15 layers × 6.72 GiB ≈ 100 GiB across the C2C link per decode step. At roughly 330 GB/s that is about 300 ms, which is exactly the 3.3 tok/s we measured. The GPU showed 100% utilization because it was stalled on the copy stream, and batching scaled perfectly because the copy was amortized. The profile said "bandwidth," not "compute."
vLLM's UVA backend maps the pinned host tensor into GPU address space and lets the MoE kernel read the rows the router selected: 6 of 384 experts per layer, about 1.6 GB per token instead of 100 GiB. We had already seen this exact trade on this exact box with full GLM-5.3 (1.4 tok/s SGLang OffloaderV2 → 33 tok/s vLLM UVA), so the prediction was cheap; the day was spent on memory, not on the idea.
The memory ladder
Ten launches to find the envelope. Every failure was a different wall.
| Run | Engine | Offload | Died at |
|---|---|---|---|
| A | SGLang | Engram host table only | HBM OOM at layer construction; the second tier is mandatory |
| b1 | SGLang | 13 expert layers | HBM OOM in the post-load FP4 shuffle, ~4 GiB short |
| b2 | SGLang | 19 layers | Kernel OOM killer: ~490 GB shmem+anon on a 494 GB Grace |
| b3 | SGLang | 15 layers | Bound. 3.3 tok/s. |
| b4 | SGLang | 13 layers + expandable segments | HBM OOM again; the shuffle scratch is real |
| v1 | vLLM | 105 GiB, matcher experts.* | HBM OOM: vLLM nests MoE weights under routed_experts.; the matcher silently hit nothing |
| v2 | vLLM | 105 GiB | An hour of CIFS thrash: pinned host tensors page-fault the checkpoint through a 4 GB page cache, one SMB read at a time. Killed. |
| v3 | vLLM | 105 GiB, Engram shards on NVMe | OOM killer at 486 GB shmem: pinned copies plus mmap'd shards double-count during fill |
| v4 | vLLM | 40 GiB | Cleared everything, then Available KV cache memory: −6.96 GiB |
| v5 | vLLM | 60 GiB | Bound. 85 tok/s. 219 GiB weights, 12 GiB KV, host 430/494 GB. |
| v6 | vLLM | 60 GiB, seqs 16 | Bound. 93 / 336 tok/s (C1 / C16). Autotune hash changed; cache copied. |
| v7 | vLLM | 70 GiB, DSpark k=5 | Bound. KV 15.56 GiB. Agent fixture 131–144, prose 89. |
| v8 | vLLM | 70 GiB, DSpark k=3 | Bound. Higher acceptance, fewer tokens per step; not better on agent text. |
| v9 | vLLM | 70 GiB, k=5, local NVMe | Bound in 12 min cold. Same decode as v7. |
| v10 | vLLM | 70 GiB, k=5, 1M ctx, NVMe | Bound in 8 min. 146–150 code/shell, 93 prose, 287 at C16. KV 4.79M tokens. Short requests starve behind long prefill. |
| v11 | vLLM | v10 + --long-prefill-token-threshold 6144 | Reference through September 11. 82 C1 / 287 C16; short requests 1 s under a 480K prefill. |
| v12 | vLLM | 60 GiB, util 0.97, k=5, 1M ctx | Reference from September 12. 89.2 C1 / 311 C16 (+12% same-window). KV 2.33M tokens, 2.2× at 1M. First boot 82 min (new autotune hash), then 8. |
| v13 / v14 | vLLM | v12 + k-schedule [[1,2,5],[3,16,1]] / [[1,4,5],[5,16,1]] | v14 reference from September 15: k=5 at 1–4 seqs, k=1 above. Picked on real transcripts, not the knee. |
| v15 | vLLM | v14 + pin-hot-experts hook (295 hot / 89 cold per layer) | Reference from September 17. 153 C1 / ~955 C16, bit-identical. KV 2.26M. |
| v16 | vLLM | v15 + seqs 32 + long-prefill-token-threshold 2048 | Bound. C24 warm p50 27 → 3.6 s, but idle prefill −47%, prose p10 −29%, KV 1.39M. Failed; two knobs pulling opposite ways. |
| v17 | vLLM | v15 + --max-num-seqs 24, k-sched [[1,4,5],[5,24,1]] | Reference for the many-seat profile, September 18 midday. C24 warm p50 1.33 s, cold 120K p95 8.9 s, knee C1 165 (+7%); default graphs 4.1 GiB → KV 1.79M. Autotune miss 16 min. |
| v17b | vLLM | v17 + --cudagraph-capture-sizes 1 2 4 8 12 16 24 | Bound; wrong. Sizes are tokens, not seqs: above twelve seats decode ran uncaptured, 10 tok/s/stream. KV 2.96M showed the lever though. |
| v18 | vLLM | v17 + --cudagraph-capture-sizes 1 2 4 6 8 12 16 18 24 32 40 48 64 96 128 | Reference from September 18, 4:10 PM CDT. Graphs 1.30 + 1.10 GiB, KV 2,502,950 tokens (2.4× at 1M). Knee 172 / 655 / 945; fund harness C16 warm p95 1.86 s, cold 120K p95 9.2 s, tools 64/64. |
| v11 | vLLM | v10 + --long-prefill-token-threshold 6144 | Bound. Short TTFT behind a 480K prefill 18 s → 1 s. Current reference. |
| v12 | vLLM | v11 at 60 GiB | KV −2.61 GiB. 70 is the floor at 1M. |
util 0.94 — and, as night two showed, 60 GiB with DSpark once util goes to 0.97, at the cost of half the KV pool. Local NVMe for the whole checkpoint is not optional for a lane you intend to restart; over CIFS the pinned copy never finishes faulting in, and DSpark reads the checkpoint twice.First-boot autotune
FlashInfer autotunes the MXFP4 MoE kernel by running every tactic on the real hardware at 8192 tokens and keeping the fastest. With experts on UVA, each profiled run also pays the C2C fetch, so a single candidate took ~39 ms and the whole pass took 74 minutes with nothing in the log but 100% GPU. A py-spy stack (choose_one → trtllm_fp4_block_scale_moe_op) is what separated "working" from "hung." The 168 tuned configs are cached under /root/.cache/vllm, so this cost is paid once per image.
History: the SGLang first boot (morning of September 10)
Why this was uncertain
The release is 552B backbone plus a 196B Engram table, shipped as a mixed FP4/FP8/BF16 checkpoint. HBM on this Station is 250.7 GiB visible. The published SGLang recipe for GB300 is tensor-parallel across four GPUs; nobody had posted a TP=1 boot on one Station. The arithmetic said it was close: move the Engram tables to host memory as DeepSeek's report intends, and the resident set still overshoots HBM by tens of GiB before any KV cache. So a second offload tier was mandatory, and SGLang's DeepSeek-V4 model file did not wire one.
What the SGLang boot took
| Step | Result |
|---|---|
Archive to Milo-Ark, SHA-pinned df42c109f1def… | 510,313,345,254 bytes, every file byte-matched against the Hub tree. NAS-direct, provenance written first. |
Pull lmsysorg/sglang:dev-dsv41 | 48.5 GB preview image. Ships SGLANG_ENABLE_DSV41_ENGRAM_HOST_TABLE and the grouped expert offloader (V2). |
| Read the model code | deepseek_v4.py builds layers without offloader_kwargs; only the V2/V3 family passes them. Wrote a 23-line patch mirroring the V2 wiring with the TRT-LLM MXFP4 parameter names (w13_weight, w2_weight, *_weight_scale_inv). Bind-mounted read-only into the container; image untouched. |
| Stage A: Engram host table only | CUDA OOM at layer construction. Confirms the second tier is required. |
| b1: offload 13 layers (group 3, 1 per group) | All 475 GiB loaded from the NAS; Engram pinned; then CUDA OOM inside the post-load FP4 weight shuffle, roughly 4 GiB short. Fit is that close. |
| b2: offload 19 layers (group 2, 1 per group) | Kernel OOM killer took the scheduler: ~190 GiB anonymous plus ~280 GiB shared on a 494 GiB Grace. Two pinned Engram shards, 19 layers of pinned expert copies, and loader staging do not co-exist. |
b3: offload 15 layers (group 8, 3 per group), mem fraction 0.85, expandable_segments | Bound. Weights in 1,614 s. 60.9 GB HBM free after weights, 37.3 GB after KV pool, 29.4 GB after decode CUDA graphs (bs 1/2/4/8). Host at 429 of 494 GB. |
SGLang smoke results
| Probe | Result |
|---|---|
| 17 × 19, integer only | 323, 2 tokens, 22.4 s wall (first request; includes JIT and cold path) |
| Count 1–60 | Correct sequence, 120 tokens in 36.9 s → 3.2 tok/s |
| 300-word paragraph | 314 tokens in 94.9 s → 3.3 tok/s, coherent, terminated cleanly |
Tool call (get_weather) | finish_reason=tool_calls, arguments {"location": "Telluride"}, parsed by the auto tool parser |
Thinking on, reasoning_effort=low | 25 reasoning tokens, correct answer 36 for 15% of 240 |
Correctness, tool-call parsing, and reasoning-mode plumbing all pass on the first boot of a day-zero preview image. Speed does not. With 15 of 40 expert layers streaming from LPDDR5X through a synchronous one-step prefetch, decode is bandwidth-bound on the Grace side. The 16B-active design keeps the bytes per token small, but the offloader path as shipped is not built for latency.
Verdict (September 12 — history)
This is the September 12 verdict, kept as history. The current state is the October 18 banner: v23 is the reference, and config C is pending James's decision.
Next (as of September 12)
- Speculation depth is the open lever, and it is per-request. Static k=1 wins prose and every multi-stream point by 23–33%; k=5 wins agent traffic by a third; adaptive verification lands between with no losing category but pays a ~6.5 GiB varlen graph-capture tax that the upstream cudagraph-mode fix does not remove (evening three). See morning three. Cheapest next boot: vLLM's batch-size schedule (k=5 at one or two streams, k=1 above) — takes the aggregate win with no risk to single-stream agent traffic.
- Residency axis: closed at off60. Skewed histogram, but the hardware gives either speed (ATS, 340 GB/s, no placement) or placement (GPU page table, 90 GB/s), not both. Details in night two; scripts and raw JSON in the recipe bundle.
- v12 was the reference, confirmed (September 12; superseded — v23 is the reference as of October 18). Same-window pair at 03:00: v11 control 78.1, v12 89.6 single-stream, +14.7%; 312 aggregate at sixteen. Prefill, starvation and the Hermes harness (10/10) re-run on v12 and did not move.
- Expert pruning is out. James's rule, September 11: no REAP.
- C4 bimodality. Still reproducible, still uncharacterized, still not actionable.
- What would reopen residency: a driver that exposes access-counter migration on GB300, or an MXFP4 MoE kernel that takes per-expert pointers (cuDNN's SM100 grouped GEMM has a discrete mode; FlashInfer's fused kernel does not).
Reproduce (vLLM, the fast path)
The authoritative, machine-checked version of everything below — image digest, model revision, launcher with the knobs, every fixture script, provenance bundle, five gates with pass dates, failure ledger — is the J&M recipe. What follows is the same command with commentary. It is the September 12 v12 command on the df42c109 checkpoint. The current v23 launch adds the 1017 checkpoint, the pin-hot hook, 24 seats, the k-schedule [[1,8,5],[9,24,1]] and -e PYTORCH_CUDA_ALLOC_CONF=pinned_max_cached_size_mb:1024, and it lives in the recipe.
docker run -d --name dsv41-vllm --gpus all --ipc host --network host \
--ulimit memlock=-1 --cap-add IPC_LOCK \
-v /models/DeepSeek-V4.1-Flash-df42c109f1defefcbfcedbe7d905718a12266e40:/model:ro \
-v /path/to/vllm-cache:/root/.cache/vllm \
vllm/vllm-openai:deepseekv41-flash-0909 \
--model /model --trust-remote-code --tensor-parallel-size 1 \
--offload-backend uva --cpu-offload-gb 60 \
--cpu-offload-params routed_experts.w13_weight routed_experts.w2_weight \
--engram-config '{"cpu_offload": true}' \
--speculative-config '{"method":"dspark","num_speculative_tokens":5,"num_speculative_tokens_per_batch_size":[[1,4,5],[5,24,1]]}' \
--max-model-len 1048576 --max-num-seqs 24 --max-num-batched-tokens 8192 \
--long-prefill-token-threshold 6144 \
--cudagraph-capture-sizes 1 2 4 6 8 12 16 18 24 32 40 48 64 96 128 \
--gpu-memory-utilization 0.97 \
--tool-call-parser deepseek_v41 --reasoning-parser deepseek_v41 --enable-auto-tool-choice \
--port 30006
Whole checkpoint on local NVMe; pinned host copies page-fault from the checkpoint after load, DSpark reads it twice, and over CIFS neither converges. The matcher is routed_experts.*, not experts.*. Expect ~8–12 min to bind with a warm autotune cache, ~80 cold. Every launch-flag change makes a new autotune hash directory: seed it with an existing autotune_configs.json before launch or you pay the 74 minutes again. Shown as the v18 launch (24 slots, k-schedule, token-sized capture list); the pin-hot-experts hook is the bind-mounted sitecustomize.py + PIN_MODE=split PIN_ROWMAP=rowmap-static-v1.json described in its own post. Drop the last three flags and num_speculative_tokens_per_batch_size and this is the v12 config (--cpu-offload-gb 60 --gpu-memory-utilization 0.97, KV 4.89 GiB, 2.2× at 1M). For more than two concurrent full-1M requests use v11: --cpu-offload-gb 70 --gpu-memory-utilization 0.94 at 82 tok/s. Do not send reasoning_effort: medium.
Reproduce (SGLang, the 3.3 tok/s history)
docker run -d --name dsv41-dryrun --gpus all --ipc host --network host \
--ulimit memlock=-1 --cap-add IPC_LOCK \
-v /path/to/DeepSeek-V4.1-Flash:/model:ro \
-v /path/to/patched/deepseek_v4.py:/sgl-workspace/sglang/python/sglang/srt/models/deepseek_v4.py:ro \
-e SGLANG_ENABLE_DSV41_ENGRAM_HOST_TABLE=1 \
-e PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True \
lmsysorg/sglang:dev-dsv41 \
python3 -m sglang.launch_server --trust-remote-code --model-path /model --tp 1 \
--mem-fraction-static 0.85 --context-length 131072 \
--chunked-prefill-size 8192 --max-running-requests 8 \
--cpu-offload-gb 0 --offload-mode cpu \
--offload-group-size 8 --offload-num-in-group 3 --offload-prefetch-step 1 \
--reasoning-parser auto --tool-call-parser auto --port 30005
The patch adds offloader_kwargs to the make_layers call in DeepseekV4Model, copied from DeepseekV2Model, whitelisting the fused-MoE weight and scale tensors. Drop the page cache before launch; the Engram pre-fault needs the RAM. Expect roughly 27 minutes to first token over 10GbE storage.
Credits
- original-el8 — the
dgx-station-gb300-researchnotes (Apache-2.0) behind the October 18 cards: prefill CUDA graphs (P1), the #58132 SWA-replay prefill numbers that put the nightly rebase on the list, and the long-generation cell (P5). - Ronan (RonanLabsAI) — the real-text RT harness behind every RT C1 number in the October rounds, and the habit of reporting C1 as a mean of three with acceptance beside it.
- Opus 5.5, consulted as a second-model reviewer on October 18. It argued that on a lossless verify path a drafter change can only cost speed, so the speed should be measured. That review produced the per-class wall-clock gate.
- vLLM upstream —
nightly-39983a5e, vllm#58132 decoder SWA bounded replay, and the DSpark and Engram fixes that the October 18 config C build rides on. - 0xSero — the 4× RTX PRO 6000 build with a 45-case published sweep and DSpark acceptance numbers; the receipt that DSpark works on the shipped weights.
- vLLM — the
dsv41-featbranch and the recipe page:EngramConfig.cpu_offloadand the UVA offloader are the whole reason the second number exists. - DeepSeek — weights, tech report, and the Engram design that makes host placement legitimate rather than a hack.
- SGLang / LMSYS — day-zero support, the Engram host-table path, and the grouped offloader.
- Tony (tonyd2wild) — the parallel 4× DGX Spark vLLM effort, which framed the memory math we checked against.