Home lab · spark (NVIDIA DGX Spark GB10, 121.7 GB unified) · 2026-09-03/04 · report by Claude, reviewed run log
Atlas main at 4df6e609 (the commit that merged our PR #17) serving RadixArk/Qwen3.8-Flash-Next-NVFP4, put through the same two-round tower-defense build benchmark as prod and the llama.cpp Flash-Next configs. Eleven serve attempts, one benchmark result, five engine defects, and a fix loop that was stopped on request.
Performance. Atlas decodes this model at 16–17 tok/s regardless of context length, with prefill around 430 tok/s (a 13.4K-token prompt took 31 s). That is a third of prod's speed on the same task and below both llama.cpp Flash-Next configs measured on 2026-08-28 (20–25 tok/s). The recipe's own claim (16.5 tok/s, 747 tok/s prefill) is consistent with what we saw, so this is the engine's current ceiling, not a misconfiguration.
Output quality. The model produced a full-featured game design ("IRONGATE", 47.6 KB) in round 1 but shipped it dead: four of six <script> blocks were unterminated. Round 2 thrashed, rewriting the file three times inside one response until it hit the 26K-token cap. Two targeted fix rounds later the game loads with zero console errors, towers place and a wave starts; whether it plays through to wave 20 was the check in progress when the run was stopped.
Engine. The dominant cost was not the model but Atlas: every long-form request path (rewinds, speculative verify, prompts over 8K tokens, generations past 32K positions) hit a separate failure. Section 5 lists them with the verbatim error lines and the workaround that got past each.
| Config | Model | Engine | Serving |
|---|---|---|---|
| ATLAS (this run) | RadixArk/Qwen3.8-Flash-Next-NVFP4, 125B MoE / 6B active + 51 GB FP8 n-gram table, 126 GB on disk | Atlas main 4df6e609, release build, kernel target qwen3.8-flash-next | local :8001, prod stopped, single stream |
| P (prod, reference) | Huihui-Qwen3.8-27B-abliterated, Q4_K_M GGUF, 17 GB | llama.cpp DFlash build (PR #27342), DFlash2 speculative decoding, Sharp template | :8000, live |
| E2 / A (2026-08-28) | Qwen3.8-Flash-Next UD-Q3_K_XL / UD-Q4_K_XL (Unsloth GGUF) | llama.cpp PR #27742, n-gram table on CPU via mmap | prior TD shootout rows, not re-run |
chat_template_kwargs.enable_thinking=false, which Atlas honours).```html block; an unterminated block is kept as a truncated file. A self-fix round feeds the file back with the browser's console error (config A got one such round in August; Atlas got two, on request "no budget limit").ATLAS_QSA_MAX_TOKENS=65536 ATLAS_PLE_CACHE_SLOTS=524288 ATLAS_PLE_MAX_TOKENS=20500 \
spark serve --model-from-path ~/models/radixark-fn-nvfp4 --model-name qwen \
--kernel-target qwen3.8-flash-next --bind 127.0.0.1 --port 8001 \
--max-seq-len 49152 --max-num-seqs 1 --max-batch-size 1 \
--gpu-memory-utilization 0.95 --fast-load-prefetch-shards \
--max-prefill-tokens 20480 --content-loop-watchdog false --request-timeout 3600
Every value above that is not a default exists because a default failed (section 3). No --speculative: the recipe's documented C=1 win (--num-drafts 1, +18%) breaks once the sparse-attention indexer activates at position 2051.
| Memory at load | GB | Note |
|---|---|---|
| Pre-KV footprint (weights, PLE scratch, arena, MTP head, vision) | 99.7 | of a 115.6 GB budget at 0.95 utilization |
| Buffer arena for a 20,481-token prefill chunk | 7.9 | 1.9 GB at 4,096-token chunks; this is the price of single-chunk prefill |
| PLE scratch delta, 32,768 vs 8,192 tokens | +3.5 | why attempt 1 had no room left for KV at 0.90 |
| KV cache (bf16, required by QSA) | 11.7 | 32,048 blocks × 16 tokens; far more than 49K context needs |
| System memory available while serving | 0–5 | touched 0 GB during a 17K-token round; survived, but there is no margin |
Each row is one server launch. Times are spark local. Every attempt after the first changes exactly the parameter the previous failure named.
| # | Time | Change from previous | What happened | Verdict |
|---|---|---|---|---|
| 1 | 10:49 | PLE scratch 32,768 · util 0.90 · speculative (1 draft) · 64K ctx | Model loaded, then refused: 103.8 GB pre-KV + 6.5 GB reserve exceeded the 109.5 GB budget. No KV cache possible. | no start |
| 2 | 10:53 | PLE 8,192 · util 0.95 | Round 1 died at 2,492 content tokens: the loop watchdog saw a period-2 repeat, rolled back 68 tokens, and the QSA indexer reported "pos 2864 but 2932 ingested". HTTP 500. | defect 1 |
| 3 | 10:58 | --content-loop-watchdog false | Round 1 cut at exactly 300 s with finish_reason=timeout (5,641 tokens). Server-side default deadline. | default |
| 4 | 11:08 | --request-timeout 3600 | Round 1 ended after 1,679 tokens at position 2,051 with a clean stop; the log shows the MTP verify path erroring on QSA selection. Round 2 got a truncated file and produced 80 tokens. | defect 2 |
| 5 | 11:13 | drop --speculative (recipe default) | Round 1 complete: 18,027 tokens, 17.3 tok/s, 17.3 min, 47.6 KB. Round 2's 18,234-token prompt was refused: PLE cap 8,192 < chunk 8,193. | R1 defect 3a |
| 6 | 11:33 | PLE 8,200; round 2 only, reusing the round-1 file | Same prompt refused again: "NgramRowCache: every one of 65536 slots is pinned by the batch" (8,193 tokens × 16 heads). | defect 3b |
| 7 | 11:38 | ATLAS_PLE_CACHE_SLOTS=262144 | First 8,193-token chunk prefilled; the continuation chunk faulted at layer 0 with CUDA_ERROR_MISALIGNED_ADDRESS. Engine exited. | defect 4 |
| 8 | 11:44 | --max-prefill-tokens 4095 (4,096-wide chunks) | Prefill succeeded through all chunks. Decode ran 14.5K tokens, then "QSA: pos 32768 >= ATLAS_QSA_MAX_TOKENS". HTTP 500, output discarded. | defect 5 |
| 9 | 12:02 | ATLAS_QSA_MAX_TOKENS=65536 | Round 2 complete: 24,174 tokens, 16.1 tok/s, 25 min, 62.8 KB. Fence unterminated, so the harness did not persist it (harness gap, fixed for the next run). | R2 lost |
| 10 | 12:32 | harness persists raw output | Round 2 re-run: 26,624 tokens (hit the cap), 16.3 tok/s, 27 min. The response held three full rewrites; the longest closed one (35.2 KB) has a syntactically incomplete main script. | broken file |
| 11 | 13:04–14:20 | persistent server; fix rounds fed the real console error / playtest bugs | Fix-1 prompt (13.4K tokens) faulted twice on continuation chunks even at 4,096 width. Moved to single-chunk prefill (20,480-token chunks, 48K ctx). Fix 1: 45.5 KB, loads clean. Fix 2: 47.8 KB, loads clean, towers place, wave starts. Stopped by request during the playtest. | loads unverified |
| Config | Round | Tokens | Wall | tok/s | Finish | Code | Outcome |
|---|---|---|---|---|---|---|---|
| ATLAS Flash-Next NVFP4 on Atlas main | R1 build | 18,027 | 17 m 19 s | 17.3 | stop | 47,611 ch | unterminated; 4 of 6 script blocks unclosed, dead on load |
| R2 fix/polish (att. 9) | 24,174 | 24 m 57 s | 16.1 | stop | 62,826 ch | unterminated; not persisted | |
| R2 fix/polish (att. 10) | 26,624 | 27 m 18 s | 16.3 | length | 35,208 ch | 3 rewrites in one response; best closed block still broken | |
| Fix 1 (console error) | 16,687 | 16 m 48 s | 16.6 | stop | 45,479 ch | loads, 0 console errors; towers never fire, wave never ends, toast spam | |
| Fix 2 (playtest bugs) | 17,475 | 17 m 46 s | 16.4 | stop | 47,824 ch | loads, 0 console errors; towers place (gold 100→10), wave 1 starts; play-through unverified | |
| P (prod, Aug 28) | R1 | 12,365 | 4 m 25 s | 46.6 | stop | 35,591 ch | works first time; plainest design |
| R2 | 12,550 | 4 m 16 s | 49.0 | stop | 35,762 ch | ||
| E2 (FN Q3, llama.cpp) | R1 | 21,146 | 14 m 21 s | 24.6 | stop | 55,956 ch | works; most polished game of the set |
| R2 | 21,504 | 17 m 55 s | 20.0 | stop | 56,057 ch | ||
| A (FN Q4, llama.cpp) | R1 | 18,608 | 13 m 16 s | 23.4 | stop | 48,934 ch | shipped with one fatal typo; self-fixed in one round |
| R2 | 19,198 | 16 m 31 s | 19.4 | stop | 50,226 ch | ||
| R3 self-fix | 18,908 | 16 m 13 s | 19.4 | stop | 50,225 ch |
Prefill: the single-chunk 13,397-token fix-1 prompt showed TTFT 31.3 s, about 430 tok/s. That is in line with the llama.cpp Flash-Next configs (466–475 tok/s on 21K-token prompts) and well below prod (720 tok/s).
Speculative decoding could not be measured: with --speculative --num-drafts 1 the MTP-heavy regime (98% of steps) ran at 23.5 tok/s until it hit defect 2 at position 2,051, and a run where the auto gate parked in serial mode gave 18.8 tok/s. The +18% the recipe documents is real but currently unusable past 2K tokens.
All logs are kept on spark under ~/agentbench/atlas-td2-attempt*.log and the fix-loop logs; the consolidated write-up is ~/claude/atlas-flashnext-findings-2026-09-03.md.
decode_batch error: QSA: decode at pos 2864 but 2932 tokens ingested — the indexer cache lost sync (prefix-cache skip or a rewound sequence) → HTTP 500--content-loop-watchdog falsestop--speculative --num-drafts 1; generation reaches 372 + 1,679 = 2,051 tokens, exactly where the QSA indexer switches on.decode_verify_graphed: QSA selection active for row 1 on the batched ms path; the dispatch gate should have routed this batch per-seq, followed by Done: 1679 tokens (stop)--speculative.chunk_size = max_prefill_tokens + 1 = 8193.ATLAS_PLE_MAX_TOKENS=8192: the load log itself warns it "is below the 8193 tokens a single forward can present", and the request 500s. With 8,200: Prefill chunk layer 1 failed: NgramRowCache: every one of 65536 slots is pinned by the batch in flight (8,193 tokens × 16 heads ≈ 131K rows).ATLAS_PLE_MAX_TOKENS ≥ chunk width, ATLAS_PLE_CACHE_SLOTS ≥ chunk × 16 (we used 524,288, ~168 MB pinned).phase_continue_prefills::run_standard: Prefill chunk layer 0 failed: … CUDA_ERROR_MISALIGNED_ADDRESS (716), launch grid=[1024,ceil(width/16),1] block=[16,16,1]; then "the CUDA context is destroyed"./tokenize, returns 500s, and holds ~100 GB until killed with SIGKILL.--max-prefill-tokens 20480 (costs ~8 GB of arena).ATLAS_QSA_MAX_TOKENS default 32,768 aborts decode mid-stream--max-seq-len 65536.decode_batch error: QSA: pos 32768 >= ATLAS_QSA_MAX_TOKENS → HTTP 500; 14.5K tokens of output discarded.--max-seq-len, and surfaced as an error instead of finish_reason=length.ATLAS_QSA_MAX_TOKENS=65536--request-timeout is 300 s (any long generation returns finish_reason=timeout), and the recipe-documented --speculative --num-drafts 1 path is the one D-2 breaks.A persistent server stayed up between rounds. Each round fed the model its own file plus what the browser actually reported; the prompt was even-padded via /tokenize while the chunk-parity hypothesis was being tested. Output budget was raised to 28K–40K tokens.
| Round | Input given | Prompt tok | Result | Browser verdict |
|---|---|---|---|---|
| Fix 1 | "Uncaught SyntaxError: Unexpected end of input" | 13,398 | 45.5 KB, one script block, syntax-clean, complete </html> | Loads, map renders, 0 console errors. Playtest: towers never fire (score 0), wave never completes, "LANE CLEAR" toast re-added every frame. |
| Fix 2 | Three playtest bugs, described in detail | 16,982 | 47.8 KB, syntax-clean; model's summary claims all three fixed (shared coordinate mapping, projectile/bounty path, latched wave end) | Loads, 0 errors, two towers placed (gold 100→10), wave 1 starts with 6 hostiles. Play-through check was in progress when stopped. |
The model's fix-1 preamble diagnosed its own round-2 file accurately: "unclosed IIFE in the main script … no game loop, no rendering, no event handlers exist despite all UI elements being present". Given a precise symptom it repairs well; left to review its own 60 KB file it rewrites from scratch and runs out of budget. That matches the August finding for config A.
Playtesting note for anyone repeating this: a hidden Browser pane reports document.hidden = true and Chrome suspends requestAnimationFrame, so the game loop appears frozen. Test copies with a virtual-clock shim (test2.html, test3.html under /bench/atlas/) were used to drive the loop deterministically; they should be deleted before anything is published.
@atlas/qwen3.8-flash-next-nvfp4 (registry Avarok-Cybersecurity/atlas-recipes) cannot reproduce this run: it pins the Inferact checkpoint (BF16 PLE table; its text says the FP8 layout is refused, which is no longer true on main), image avarok/atlas-gb10:5be3fc1 from before PR #16, max_model_len 2048, utilization 0.85, and none of the PLE/QSA environment pins or the request timeout. Its full serve line is spark serve Inferact/… --gpu-memory-utilization 0.85 --max-seq-len 2048 --kv-cache-dtype bf16 --max-batch-size 1. The recipe format supports the needed knobs (siblings pin ssm_cache_slots, dflash, etc.), so an update is mechanical once a configuration is agreed.
All seven plan items were implemented on assist/flashnext-longform-fixes (fork branch, seven commits on 4df6e609, pushed; no upstream PR yet). Executor: Flash-Next Q4 on llama.cpp for the edits with exact anchors; three items were finished by hand where the executor stalled in search loops. Every commit passed fmt, clippy (deny-all) and the full spark-server test targets.
| Item | Commit | Verified how |
|---|---|---|
| 1 PLE caps derived from the serve config | e623e010 | unit test; ATLAS2 loaded with the derived 262,144-slot cache, no env pin |
2 QSA capacity from --max-seq-len | 561be215 | load log "indexer capacity 65536 (max_seq_len=65536)"; ATLAS2 decoded past 32K positions of context without the old cap |
3 engine errors → finish_reason=error; cuBLASLt fallback logged | 16af4d33 | unit test; the fallback log was the instrument that showed the GEMM operands were aligned and the fault came from an earlier kernel |
| 4 scheduler stops on a latched GPU fault | 35028544 | live: the reproduction run exited with "Exiting after a fatal GPU fault" instead of lingering |
| 5 rollback restores QSA/PLE aux state | f4e81291 | unit test; live: the rollback now restores and continues, but the restored cursor is one token behind the resume position ("pos 6050 but 6049 ingested"). Restore path works; the boundary accounting is being pinned with an instrumented run. Bench ran with the watchdog off. |
| 6 MTP latched off before the QSA bound | 86bdf303 | unit table test; live log "mtp declined: qsa_active seq_len=2050" |
| 7 aligned mHC prefill scratch (the misaligned-address fault) | 97aa9fa4 | the 13,397-token prompt prefills at 4,096 and default 8,192 chunking; ATLAS2's 10K-token round-2 prompt went through an 8,193-wide chunk |
D-4 root cause. compute-sanitizer was ~100× too slow to reach the failing chunk, so the reproduction used CUDA_LAUNCH_BLOCKING=1, which attributed the fault to dense_gemm_bf16_pipelined launched as grid=[80,9]: the up_pre = low × up_wt^T GEMM inside hc_pre_gemm. Its scratch was packed as raw byte products and inj_pre is lay × 8 bytes, so an odd slab (1,117 tokens, or the default 8,193) placed up_wt 8 bytes off a 16-byte boundary. Even slabs (1,074 / 1,862 / 4,092) never faulted, which is why it looked random. The cuBLASLt "status 14" seen earlier was the sticky error from that kernel surfacing at the next API call, not a GEMM operand problem.
| Round | Tokens | Wall | tok/s | Finish | Code | Outcome |
|---|---|---|---|---|---|---|
| R1 build | 9,612 | 9 m 16 s | 17.3 | stop | 26,675 ch | closed block |
| R2 fix/polish | 13,866 | 13 m 51 s | 16.7 | stop | 38,163 ch | closed block; zero server errors; the 10K-token prompt prefilled through the default 8,193-wide chunk |
The engine side is now clean: one attempt, no workarounds, no 500s. The output is not: "BULWARK — Grid Defense Protocol" is a syntactically complete 38 KB file that defines the whole game and never boots — no newRun() call, no requestAnimationFrame loop, no event listeners. It loads with zero console errors and does nothing. Decode speed is unchanged by the fixes (16–17 tok/s single-stream; ~430 tok/s prefill): they remove failure modes, not cost.
assist/flashnext-longform-fixes (seven commits, CI-style checks green locally; the benchmark gate would need a re-measure).Artifacts on spark: ~/tdbench/ATLAS/ (v1.trunc, v2, fix1, fix2, raw responses, rounds.jsonl), ~/driver_td2*.sh, ~/tdbench_r2.py, ~/tdfix3.py, ~/agentbench/atlas-td2-*.log. Prod (qwen38-dflash) was restored after every attempt and is running.
Sampling was the quality lever. Atlas fills unspecified request fields from the model card's [sampling.non_thinking] preset, including presence_penalty = 1.5. Over a 20K-token file that suppresses every token already emitted: ATLAS and ATLAS2 shipped dead/inert games. Pinning the request to llama.cpp's effective values (presence 0, top_k 40, min_p 0.05) on the same fixed engine gave ATLAS3: a complete, wired 51 KB game ("Ironline") with one typo.
| Round | Tokens | tok/s | Outcome |
|---|---|---|---|
| R1 / R2 | 18,954 / 18,953 | 17.0 / 16.0 | 51.2 KB, one fatal typo (c.lineTo) |
| fix1 (console error) | 18,980 | 15.5 | byte-identical except the one token; intro state never leaves idle |
| fix2 (runtime brief) | 19,014 | 16.0 | state fix correct, stray } added |
| fix3 (console error) | 30,334 | 14.1 | one statement changed, 11K tokens of extra output |
| fix4 (runtime brief) | 19,073 | 15.6 | fixed the two lines named, ignored the other path consumers |
| fix5 (literal brief) | 19,318 | 15.7 | playable; kill reward 0/0 → NaN gold |
| fix6 (runtime brief) | 19,259 | 15.9 | NaN fixed; an unasked edit reintroduced the original typo |
| fix7 (console error) | 19,046 | 15.7 | single-token diff; fully playable (3 waves, 20/20 lives) |
Pattern: the model repairs exactly what the brief names, and both times it edited something unasked it introduced a regression. Config A on llama.cpp needed one round in August; with one sample per configuration that is suggestive, not proof.
[behavior].use_sampling_preset_penalties_for_core (default true; Flash-Next sets false). Tool requests keep the tools preset.qwen4_exp; --attn-proj-dtype bf16 keeps Q/K/V/O at checkpoint precision (ModelOpt's ignore list does the same).rewind_buffers now pops every fed token from the boundary on, and the phantom token sampled from the discarded context is dropped instead of appended. On the GB10: two fuzzy and three content-loop rollbacks with re-steer, zero indexer desyncs.The investigation (config A as investigator, 129 tool calls, report in ~/claude/reports/): CUDA graphs are vetoed model-wide because a QSA layer (host top-k) and a PLE layer (host hash) both veto; every token is ~1,500–2,000 eager launches with 12 D2H syncs and 36 PLE stream barriers. The two zero-code levers, ATLAS_D2H_SPIN_SYNC=1 and ATLAS_PLE_FAULT_THREADS=128, measured neutral (15.4 tok/s vs 15.5). The real levers are code: device-side QSA top-k to lift the veto, an event instead of the PLE barrier, one launch per hyper-connection site.
Same request sampling as ATLAS3, watchdog off: R1 20,217 tokens at 15.2 tok/s, R2 22,432 at 14.0, a 57.7 KB game ("Bastion Defense") with zero syntax errors and one logic bug (killed and leaked enemies are never removed from the enemy list, so a wave never clears). Placement, road, combat all work. Against ATLAS3's typo plus four logic bugs that is better, at a ~10% speed cost; one sample each. With the watchdog on, the same build rolled back cleanly three times but the loop detector false-positived on a coordinate array and cut a coherent file at 8.4K tokens: keep it off for code.
Pages: bench round 2 · ATLAS3 final game · ATLAS4 game · PR #23.