π° Tower Defense β model shootout
β also: the agentic research shootout (LangGraph Γ Atlas codebase)
One complete HTML5 game per configuration, identical 2-round prompts (full build β review/fix/balance/polish),
thinking disabled, temp 0.7 Β· top_p 0.8 Β· 26K tokens/round. NVIDIA DGX Spark GB10 (128 GB unified), 2026-08-28.
|
Model configuration |
Results |
|
| Config |
Model |
Engine & serving |
Memory |
tok/s R1βR2 |
Build time |
Game size |
Outcome |
|
P prod |
Huihui-Qwen3.8-27B-abliterated 27B dense Β· Q4_K_M GGUF (17 GB) |
llama.cpp DFlash build (PR #27342) DFlash2 speculative decoding (n-max 7) Sharp v22.3 template Β· 128K ctx |
17 GB |
46.6 β 49.0 |
~9 min |
35.8 KB |
β
works functional, plainest design |
Play |
| E2 |
Qwen3.8-Flash-Next 125B MoE Β· 6B active Β· qwen4exp Unsloth UD-Q3_K_XL (84 GB) |
llama.cpp PR #27742 (unmerged) 51B n-gram/PLE β CPU/mmap
-ot per_layer_token_embd=CPU 64K ctx Β· f16 KV |
~89 GB |
24.6 β 20.0 |
~32 min |
56.1 KB |
β
works most polished game: splash + lore, hotkeys, sound, damage text |
Play |
| A |
Qwen3.8-Flash-Next (same) Unsloth UD-Q4_K_XL (111 GB) |
llama.cpp PR #27742 n-gram/PLE β CPU/mmap 128K ctx Β· q8_0 KV
LLAMA_ATTN_ROT_DISABLE=1 |
~111 GB |
23.4 β 19.4 fix: 19.4 |
~31 + 16 min |
50.2 KB |
β οΈββ
works shipped with 1 fatal typo, then self-fixed (single-char diff) given the console error |
Play |
Same prompts, same sampling, same hardware. Flash-Next served by Atlas (Rust/CUDA engine, RadixArk NVFP4 checkpoint, QSA sparse attention + PLE n-gram table) instead of llama.cpp β first on main@4df6e609, then on a branch carrying seven engine fixes found during the first run
(assist/flashnext-longform-fixes).
| Config | Model | Engine & serving | Memory | tok/s R1βR2 | Build time | Game size | Outcome | |
ATLAS main |
Qwen3.8-Flash-Next RadixArk NVFP4 (126 GB on disk) |
Atlas 4df6e609 single-chunk prefill (20K), 48K ctx, watchdog off, no speculative 11 serve attempts to get one complete run |
~100 GB |
17.3 β 16.3 fix: 16.6, 16.4 |
~17 + 27 min +2 fix rounds (17 + 18) |
47.8 KB |
β οΈ dead β loads R1: 4 unclosed script blocks; R2: three rewrites, hit the 26K cap; loads clean after 2 fix rounds, play-through unverified |
Play |
ATLAS2 fixed engine |
Qwen3.8-Flash-Next (same) |
Atlas branch 97aa9fa4 (7 fixes) default 8K chunked prefill, 64K ctx, no env pins one attempt, zero server errors |
~100 GB |
17.3 β 16.7 |
~9 + 14 min |
38.2 KB |
β οΈ inert "BULWARK": syntax-clean, complete file β but defines everything and never boots (no newRun() call, no frame loop, no event listeners) |
Open |
ATLAS3 fixed engine + llama.cpp sampling |
Qwen3.8-Flash-Next (same) |
Atlas branch cb6b1a59 (7 fixes, merged main) default 8K chunked prefill, 64K ctx request: presence_penalty 0 Β· top_k 40 Β· min_p 0.05 (Atlas's model preset otherwise applies presence 1.5) |
~100 GB |
17.0 β 16.0 fix: 15.5 |
~19 + 20 min + 20 min fix |
51.2 KB |
β
playable after 8 fix rounds (+1 reviewer token) "Ironline": full game, RAF loop, 12 listeners; shipped with one typo, self-fixed byte-identical except that token. Then four logic bugs (intro state, path in cell units, HUD never written, 0/0 reward) each needed its own literal brief; every "extra" edit the model made on its own introduced a regression. Chain: fix2 Β· fix3 Β· fix4 Β· fix5 (plays, NaN gold) Β· fix6 Β· fix7 Β· fix8 (final; road no longer buildable β path-distance NaN from duplicate waypoints) |
Play |
ATLAS4 rollback rework Β· BF16 head + attention |
Qwen3.8-Flash-Next (same NVFP4 experts) |
Atlas branch 7db64cca (PR #23 second batch) BF16 lm_head (new default) + --attn-proj-dtype bf16 same request sampling as ATLAS3; watchdog off for the quality run |
~102 GB |
15.2 β 14.0 |
~22 + 27 min |
57.7 KB |
β οΈ one logic bug "Bastion Defense": clean syntax, boots, road, placement, combat (36 shots / 5 kills in wave 2) all work; but killed and leaked enemies are never removed from the enemy list, so a wave never registers as cleared. Zero syntax errors vs ATLAS3's typo + 4 logic bugs β n=1, suggestive not conclusive. Speed cost of the BF16 projections β 10%. With the watchdog on (same build, run "ATLAS4"): three rollback+re-steers and zero indexer desyncs (item 3 verified), but the loop detector false-positived on a coordinate array and cut a coherent file at 8.4K tokens. |
Open |
ATLAS5 A1 + A2 + A3 from the gap study |
Qwen3.8-Flash-Next (same NVFP4 experts) |
Atlas branch 0b9ada13 (PR #23 third batch) BF16 head + --attn-proj-dtype bf16 as ATLAS4 + device-side QSA selection (ATLAS_QSA_DEVICE_TOPK=1), 4M-slot PLE row cache, event-scoped PLE pin release; watchdog off; same request sampling |
~102 GB |
14.7 β 13.9 |
~26 + 29 min |
62.2 KB |
β
playable after 1 fix round "IRONGATE": briefing, road, placement rules, three tower types, waves, kills and leaks, win/lose all work on first load; zero syntax errors. One bug: the stats panel (gold/lives/wave/score) was refreshed only on shop clicks, never from the frame loop β a 2-line fix round (fix1, exactly the two calls asked for, nothing else) made it live. Speed is unchanged vs ATLAS4 (14.7 vs 15.2 on a longer output): none of A1/A2/A3 moved decode β the PLE barrier is one layer, the tensor-core MoE arm is 3Γ slower at M=1, and the QSA host round trips were not the bottleneck either. The gap is in the decode kernels themselves (MoE GEMV, GDN, hyper-connections). |
Play |
ATLAS6 profile-driven: NVFP4 GDN + x4 hyper-connection kernel |
Qwen3.8-Flash-Next (same NVFP4 experts) |
Atlas branch e7b6af2f (PR #23 fourth batch) GDN projections NVFP4 (ATLAS_QWEN4EXP_BF16_GDN=0), BF16 head, NVFP4 attention, hc_pre_finish_x4, device top-k, 4M PLE cache; 48K ctx; watchdog off; same request sampling |
~108 GB |
21.6 β 20.3 |
~16 + 17 min |
52.9 KB |
β
playable after 3 fix rounds "IRONWORKS Β· TD": fastest Atlas run yet (+47% vs ATLAS5), file closed and syntax-clean, but shipped with two defects: the reset code called a helper by the wrong name (buildStatic vs bakeStatic) so nothing rendered (fix1 echoed the file; fix2 made the one-token rename), then the update loop died on an undefined lv in the turret branch (fix3: the two-line binding asked for). Play-through: four waves cleared, kills, gold and score all correct, 20/20 lives. Quality gate for NVFP4 GDN: two defects in one sample versus one for ATLAS5; not conclusive either way. |
Play |
Raw round data β Atlas runs
| Config | Round | Tokens | Wall time | tok/s | Finish | Code emitted |
| ATLAS | R1 build | 18,027 | 17 m 19 s | 17.3 | stop | 47,611 ch (unterminated; 4/6 scripts unclosed) |
| ATLAS | R2 fix/polish | 26,624 | 27 m 18 s | 16.3 | length | 35,208 ch (best of 3 rewrites; broken) |
| ATLAS | Fix 1 (console error) | 16,687 | 16 m 48 s | 16.6 | stop | 45,479 ch β loads |
| ATLAS | Fix 2 (playtest bugs) | 17,475 | 17 m 46 s | 16.4 | stop | 47,824 ch β loads |
| ATLAS2 | R1 build | 9,612 | 9 m 16 s | 17.3 | stop | 26,675 ch β closed |
| ATLAS2 | R2 fix/polish | 13,866 | 13 m 51 s | 16.7 | stop | 38,163 ch β closed, never boots |
| ATLAS3 | R1 build | 18,954 | 18 m 34 s | 17.0 | stop | 51,198 ch β closed |
| ATLAS3 | R2 fix/polish | 18,953 | 19 m 44 s | 16.0 | stop | 51,220 ch β closed (1 fatal typo) |
| ATLAS3 | R3 self-fix | 18,980 | 20 m 22 s | 15.5 | stop | 51,222 ch β (1-token diff; intro-state bug remains) |
| ATLAS3 | fix2 (runtime brief: intro state, road) | 19,014 | 19 m 51 s | 16.0 | stop | 51,218 ch β state fix correct, but a stray } broke the script |
| ATLAS3 | fix3 (console error) | 30,334 | 35 m 55 s | 14.1 | stop | 51,218 ch β one statement changed; 11K tokens of extra output |
| ATLAS3 | fix4 (runtime brief: overlay, path units) | 19,073 | 20 m 24 s | 15.6 | stop | 51,267 ch β fixed the two lines named, ignored the other path consumers |
| ATLAS3 | fix5 (literal brief: pixels + HUD) | 19,318 | 20 m 32 s | 15.7 | stop | 51,495 ch β playable; kill reward 0/0 β NaN gold |
| ATLAS3 | fix6 (runtime brief: NaN) | 19,259 | 20 m 11 s | 15.9 | stop | 51,552 ch β NaN fixed; an unasked edit reintroduced the original typo |
| ATLAS4 (watchdog on) | R1 build | 8,401 | 9 m 13 s | 15.2 | watchdog cap | 21,201 ch β cut by 3 false-positive rollbacks on a coordinate array |
| ATLAS4 (watchdog off) | R1 build | 20,217 | 22 m 08 s | 15.2 | stop | 52,954 ch β closed |
| ATLAS4 (watchdog off) | R2 fix/polish | 22,432 | 26 m 39 s | 14.0 | stop | 57,685 ch β closed, 0 syntax errors, 1 logic bug |
| ATLAS5 | R1 build | 23,227 | 26 m 24 s | 14.7 | stop | 61,907 ch β closed |
| ATLAS5 | R2 fix/polish | 23,711 | 28 m 30 s | 13.9 | stop | 62,179 ch β closed, 0 errors, playable |
| ATLAS6 | R1 build | 20,621 | 15 m 57 s | 21.6 | stop | 52,597 ch β closed |
| ATLAS6 | R2 fix/polish | 20,697 | 16 m 59 s | 20.3 | stop | 52,854 ch β closed; calls undefined buildStatic() |
| ATLAS6 | fix1 (console error) | 20,719 | 17 m 35 s | 19.6 | stop | file echoed unchanged |
| ATLAS6 | fix2 (literal brief: rename) | 20,399 | 16 m 39 s | 20.4 | stop | exactly the one-token rename; board renders; update loop then throws on lv |
| ATLAS6 | fix3 (literal brief: bind lv) | 20,516 | 17 m 12 s | 19.9 | stop | 52,880 ch β exactly the two bindings; playable (4 waves cleared) |
| ATLAS5 | fix1 (runtime brief: stats panel) | 23,581 | 29 m 28 s | 13.3 | stop | 62,223 ch β exactly the two calls requested; panel verified live |
| ATLAS3 | fix7 (console error) | 19,046 | 20 m 10 s | 15.7 | stop | 51,554 ch β single-token diff; fully playable (3 waves cleared, 20/20 lives, HUD + log live) |
| ATLAS3 | fix8a (runtime brief: road placement) | 16,480 | 18 m 17 s | 15.0 | stop | no code block β explanation, then EOS mid-file |
| ATLAS3 | fix8 (tighter brief, rerun) | 19,012 | 22 m 31 s | 14.1 | stop | 51,632 ch β both requested fixes correct; an unasked edit broke the overlay grid again (reviewer reverted that one token to ship the final page) |
Engine defects found on main, and what the branch fixes
D-1 loop-watchdog rollback desynced the QSA indexer ("pos 2864 but 2932 ingested") β 500
D-2 speculative verify + QSA active at 2051 tokens β error laundered into finish_reason="stop"
D-3 prompts > 8192 tokens refused twice (PLE cap off by one vs the 8193 chunk; 65536-slot n-gram row cache)
D-4 continuation prefill chunks faulted CUDA_ERROR_MISALIGNED_ADDRESS; engine lingered with a dead context
D-5 ATLAS_QSA_MAX_TOKENS default 32768 aborted decode mid-stream
Branch assist/flashnext-longform-fixes (7 commits on 4df6e609):
ple: derive the n-gram row cache from the serve config; clamp ATLAS_PLE_MAX_TOKENS up (D-3)
qsa: size the indexer from --max-seq-len instead of a fixed 32768 (D-5)
scheduler: report engine aborts as finish_reason "error"; log cuBLASLt fallbacks (D-2b)
scheduler: stop the loop once a GPU fault is latched (D-4 aftermath)
rollback: rewind QSA/PLE aux state with the SSM state, or decline (D-1; restore path works, cursor 1 token behind β open)
scheduler: latch MTP off before a sequence can cross the QSA inert bound (D-2a)
mhc: align the prefill GEMM scratch sub-buffers; odd slabs faulted the GEMM (D-4 root cause)
D-4 root cause: hc_pre_gemm packed its BF16 scratch as raw byte products; inj_pre is lay*8 bytes, so an ODD
prefill chunk (1,117 tokens, or the default 8,193) put up_wt 8 bytes off a 16-byte boundary and the tensor-core
GEMM faulted. Found with CUDA_LAUNCH_BLOCKING=1 (compute-sanitizer was ~100x too slow to reach the chunk).
Verified: the failing 13,397-token prompt prefills at 4,096 and 8,192 chunking; ATLAS2 ran end to end with defaults.
Sampling was the quality lever. Atlas fills unspecified request fields from the model's [sampling.non_thinking] preset β top_k 20, min_p 0 and presence_penalty 1.5 (the vendor card's chat value) β while llama.cpp used its own defaults (top_k 40, min_p 0.05, no penalty). A flat β1.5 on every already-emitted token is poison for 20K-token code (closing tokens, repeated keywords, boilerplate all get suppressed): the dead/inert games of ATLAS and ATLAS2. With the request pinned to llama.cpp's values (ATLAS3) the model wrote a complete, wired 51 KB game and, like config A in August, self-repaired its one typo with a single-token diff. What remains is an ordinary logic bug.
Speed did not change with the fixes (16β17 tok/s single-stream, ~430 tok/s prefill): they remove failure modes, not cost. Atlas remains ~3x slower than prod and ~20β30% slower than llama.cpp on this model; its quality-of-output on the task was worse in both runs (dead or inert games vs. llama.cpp's playable ones), which is a sampling/serving-stack difference worth its own investigation.
Round 3 β long-horizon agentic run on the PR head (2026-09-09) Β· full report with transcripts β
Not a prompt benchmark: a four-hour, seven-phase, test-driven build of an idle RPG in Rust + Yew/WebAssembly driven by a tool-using harness against one Atlas session at a 98K context (PR #23 head, ATLAS6 pins). Every phase carries 20β50K tokens of context into every turn, so this measures what an agent actually pays on this engine: prefill. Orientation packet, read budget, scope fence with logged overrides and an independent verify tool after each phase; every prompt, transcript, trace and crate snapshot is in the report.
| Phase | Steps / wall | Tokens in / out | Decode tok/s median / long-gen | TTFT median / max | Harness-verified outcome |
| 1 math + skeleton | 25 / 32 min | 413K / 15.9K | 20.8 / 21.7 | 55 s / 81 s | compiles, wiring clean, 12 tests (9 pass); 1 read instead of 9β14 |
| 2 interfaces + tests (TDD) | 15 / 45 min | 495K / 17.5K | 19.1 / 21.2 | 134 s / 170 s | β
gate met: 44 tests collected, 33 red on todo!() |
| 3 battle core | 7 / 41 min | 291K / 23.2K | 18.5 / 19.9 | 136 s / 251 s | one 12,155-token write, regenerated; 6 compile errors left |
| 4 shop / events / save | 17 / 39 min | 574K / 2.6K | 19.2 / 20.8 | 138 s / 161 s | all on battle.rs via 5 logged overrides; still not compiling |
| 5 Yew UI | 15 / 20 min | 339K / 3.9K | 20.6 / 21.9 | 68 s / 102 s | same errors; loop detector masked <tool_call> once; harness bug β forced summary |
| 6 mechanics integration | 19 / 30 min | 482K / 4.3K | 19.7 / 22.0 | 77 s / 128 s | β
compiles native + wasm32 after 11 steps; 17/44 pass; no UI |
| 7 testing | 14 / 35 min | 466K / 3.0K | 19.1 / 19.6 | 122 s / 256 s | 1 real bug + 1 constant fixed, 19/44 pass; no dist |
Engine: 118 requests, 3.06M tokens in / 70.5K out, no engine error at 98K, every request finished with stop. Decode 18.5β20.8 tok/s median across the run, 19.6β22.0 on long generations. TTFT is the agentic cost: 55 s median at 20K context, 122β138 s at 35β50K, worst 256 s (β330β380 tok/s effective prefill in 8K chunks). One new finding: the chat-level loop detector judged three similar read_file calls a loop and hard-masked the tool-call token for a turn β set ATLAS_LOOP_NO_SUPPRESS=1 for agentic serving.
Model / harness: the three failure modes of the previous run (read-everything, hidden modules, balance-test attractor) are gone; compile-error gravity replaced them β phase 3's 990-line module blocked phases 4β5 until a fresh-context phase 6 fixed it in eleven steps. Honesty held in all seven summaries.
Round 4 β the game, 18 hours later (2026-09-09/10) Β· full report β Β· Play
Round 3's crate (compiling, 19/44 tests, no interface) continued with harness v4 β a compile gate with fresh-context repair rounds, capped writes with append, a git-backed revert, region-only reads β for ten hours, then eight more at the user's request: 21 measured rounds, 987 min, 576 requests, 14.9M tokens in / 117K out, one Atlas session per driver at 98K context. The model finished the core (41/44), a self-driving tick engine and a rendering wasm build; the reviewer then hand-wired the interface to the engine in about 45 minutes (labelled in the report) and the game plays.
| Round | Steps / wall | Tokens in / out | Decode tok/s | TTFT median / max | Harness-verified outcome |
| 3 continuation (stats, battle) | 23 / 58 min | 806K / 4.5K | 17.9 | 129 s / 193 s | 20/44, compiling |
| 4 shop/events/save β repair | 24+26 / 73+20 min | 1.31M / 14K | 19.0 / 19.7 | 147 s / 221 s β 39 s / 79 s | save.rs + shop.rs; broken β β
repaired, 28/44 |
| 5 Yew UI β 2 repairs | 22+23+27 / 78+25+38 min | 2.02M / 20K | 19.9β20.5 | 177 s / 268 s | 10 UI files written; 22 wasm errors (Yew 0.20 idioms) survive both repairs |
| 6 game loop β 2 repairs | 21+36+36 / 76+38+38 min | 2.35M / 16K | 18.9β20.6 | 218 s / 255 s | no loop; wasm errors 23 β 10 β 5 β 1 |
| 7 testing β repair | 25+27 / 53+24 min | 1.24M / 6.5K | 19.3 / 20.3 | 118 s / 199 s | last wasm error fixed in the repair; first browser build (start + main screen render) |
| 8 ship | 19 / 64 min | 794K / 2.1K | 17.6 | 213 s / 301 s | fourteen reads at 44β56K context, 0 bytes written |
| ext. 1β4 finish rounds | 27+29+35+31 / 58+42+59+50 min | 3.28M / 22K | 18.1β20.5 | 79β114 s / 114β194 s | events (35/44) β shop (40/44) β 41/44; then two rounds circling the three tuning tests |
| ext. 5 (aborted) + 6β8 loop rounds β 2 repairs | 22+34+18+30+29 / 58+32+18+40+45 min | 3.12M / 32K | 18.9β20.4 | 54β118 s / 70β211 s | src/game.rs tick engine (453 lines, capped writes) wired and self-driving; app.rs never touched |
| Reviewer hand-finish | ~45 min | β | β | β | timer, buttons, save/load, hooks, CSS, 2 loop fixes (XP never awarded; no rest beats) β β
playable, 42/45 tests |
Engine: decode flat at 17.6β20.6 tok/s median for 19 hours regardless of context; TTFT median 39 s at 6β16K tokens to 218 s at 46β50K, worst 301 s. All 576 requests finished on stop; no engine error. The rule the data gives: keep an agent's per-turn context under ~25K tokens on this engine β the rounds that built the game ran at 8β30K and 20β90 s per step; the phases at 45β56K ran 3β5 min per step and wrote nothing.
Harness: the compile gate fixed the build in 5 of 7 repairs; capped writes ended corrupted generations; the scope fence held with zero overrides β and was the only thing that stopped the model from returning to the three failing tuning tests, which it did in three consecutive rounds despite a written deferral. What the model sees (a failing-test list) beats what it is told.
Round 5 β INKBOARD: a 12-hour TDD whiteboard build on Atlas work/qwen38-nvidia-nvfp4 (2026-09-15) Β· full report β Β· Open the whiteboard
New task and a new Atlas line under test: a single-user whiteboard as a three-crate Rust workspace β a pure domain crate, an axum/tokio/sqlx backend on Postgres, a wasm canvas engine with a plain HTML frontend β built test-first by Qwen3.8-Flash-Next (RadixArk NVFP4, 98K context) under harness v5 (workspace verifier, compile gate with fresh-context repairs, scope fence, capped writes, region reads, crash handling). Twelve hours of inference time, restarts excluded. The reviewer formulated the epics and briefs and did not touch the code.
| Epic | Round(s) | Gate | Outcome |
| E0 design + skeleton + model contract | p1 (Γ4 on the branch), p1c | compiles, β₯20 red model tests | β
(branch crashed 4Γ; rerun on PR #23 binary) |
| E1 domain model | p2 | model tests green | β
27/27 |
| E2 backend contract | p3 | β₯12 red API tests on Postgres | β
14 tests |
| E3 backend implementation | p4 | API tests green | β οΈ 4/14 β per-board routes 404/405 |
| E4 engine contract | p5 β p6 | β₯20 red engine tests | β οΈ skipped in p5 (0 tests, gate only checked compile) β written in p6 (58) |
| E5 engine implementation | p6, p6r1, p14, p15 | engine tests green, wasm 0 | β οΈ 34/58, wasm β
β creation-drag geometry NaN, nothing draws |
| E6 renderer + bridge + frontend + trunk | p7, p7r1, p11 | dist present | β
13 exports, page renders, drags reach the engine |
| E7 integration + e2e + compose | p8 | e2e green | β not started (57 min on axum/tower-http API details) |
The branch: work/qwen38-nvidia-nvfp4 cannot serve Flash-Next as checked out (no kernel target, config parse error, missing flag; it is 287 commits behind upstream main). Merged with upstream main it resolves the model but leaves 3 kernel lookups unresolved (its DP4A M=4 arms) and destroys the CUDA context on continuation chunks of multi-chunk prefills β 4 crashes in 35 minutes (8.8K, 17.3K, 17.4K, 17.9K-token prompts; CUDA_ERROR_MISALIGNED_ADDRESS 716 in layer 0), intermittent, while the PR #23 head has served the same configuration for 50+ hours without one. The driver fell back to the PR #23 binary after the third crash; the synthetic A/B is in the report.
Totals on the PR #23 binary: 19 measured rounds, 724 min, 450 requests, 10.6M tokens in / 142K out; decode 18β20.4 tok/s median, TTFT 28 s at 12K β 135 s at 40K tokens. Harness lesson: a compile-only gate let a TDD contract phase deliver zero tests; scope-fenced single-purpose rounds (the frontend round: 31 min to a browser build) beat general finish rounds (four of them, 12β13 reads each, no change).
Round 6 β headless pi as the harness (Docker on orange, tools over SSH on spark) (2026-09-15) Β· full report β
Can the self-made langgraph harness be replaced by the pi coding agent (headless, JSON events) plus the s1lver091/pi-agent-config recipe, packaged as a container on orange with the workspace and every build tool on spark over SSH and Claude only orchestrating? A like-for-like replay of INKBOARD phase 4 (E3, backend implementation) with the same model, engine, snapshot, brief and 60-minute budget.
| Harness | Outcome | Tool calls / wall | Prompt tokens | Context at end | TTFT median | Wasted calls |
| v5 (self-made, langgraph) | 31/41, 4/14 API, compiles | 32 / 57.6 min | 801K | 36.4K tok (pruned) | 103 s | 1 |
| pi 0.85 + harness.ts (Docker) | 31/41, 4/14 API, compiles, same bug left and correctly diagnosed | 38 / 65.9 min | 942K | 37.1K tok (no pruning) | 101 s | 5 |
Verdict: feasible and equivalent β same result on the same phase; pi supplies the loop, event stream, multi-edit tool, validation, skills and extension hooks for free. It costs more time only because it does not prune context (Atlas has no prefix cache, so 88 % of the wall time was prefill). Next: a context-pruning extension, parallel-read accounting, and the compile-gate driver moved into the container. The first attempt was aborted at 16 min by a wrong parameter shape in the reviewer's edit override (pi 0.85 uses edits[]); the retry is the reported run. Full permissions on the repo as asked (no confirmation gates); production model restored afterwards.
Round 7 β a pixel world map on canvas, through pi (2026-10-05) Β· open the map β
An art task for the production model (Qwen3.8-Flash-Next NVFP4, blazux vLLM recipe) run headless through the pi harness: draw the world as a grid of squares on an HTML5 <canvas> β no images, no SVG, no fetch; the land data has to come from the model's own geography. Acceptance is a static Python check the model can call as verify: page structure, forbidden elements, data validity, area-weighted land coverage (22β38 %), inland and open-water spot checks by latitude/longitude, polar-band sanity β and, from round 3, self-crossing outlines and undeclared enclosed water. Five rounds on one workspace; the published page is the last one.
| Round | Task | Outcome | Steps / wall | verify runs |
| r1 | 360 Γ 180 grid; land as 180 latitude rows of [lonStart, lonEnd, code] spans, hand-written band by band | FAIL (7) β 178 rows, 104 overlapping spans, 44.5 % land, USA full-width to 15 Β°N; page + renderer fine | 141 / 77 min (budget exhausted) | 12 |
| r2 | Same grid; land as 39 lat/lon polygons + 9 water cut-outs, scanline-rasterized by map.js | PASS β 27.8 % land, 43/43 inland + 25/25 water checks; a recognisable world map. A human then spotted a hole of "ocean" in the Sahara | 54 / 10 min | 19 |
| r3 | Repair: verify v3 now flags self-crossing outlines and enclosed water; retrace Africa as one coastal loop | PASS β Africa's outline had folded back through the interior (even-odd fill carved 716 cells out); Africa, Iberia, Persian Gulf, Hokkaido fixed, Black Sea/Aegean declared water. Left Europe's mainland truncated (2 misses allowed then) | 57 / 13.5 min | 19 |
| r4 | 7200 Γ 3600 grid (0.05Β° cells, 25.9 M) as a zoom/pan viewport: ImageData sampling zoomed out, one fillRect square per cell with a gutter at β₯ 3 px/cell; plus the Europe repair | PASS β rasterizes 50 shapes in 6 ms, 62/62 inland points, wheel/drag/double-click work; the one FAIL it reported was the checker matching grid.fill() in a comment | 119 / 12 min | 41 |
| r5 | README only (r4 ran out of steps before documentation) | documentation, PASS | 11 / 2 min | 2 |
Lessons: the model cannot hand-author a dense data table and recovers badly from mid-file edits (r1), but it can author a few dozen polygons from geographic knowledge and the code that derives the big artefact from them (r2βr4). The checks decide what it can see: v2 rasterised the shapes the same way map.js must and printed an ASCII preview; the Sahara hole got through because no spot point sat in it, so v3 added geometry checks (self-crossing edges, flood-filled enclosed water) that name the shape and the lat/lon β r3 cleared them in 13 minutes. The 7200 Γ 3600 renderer landed in one round once the brief spelled out the two drawing regimes. Delivered as-is: pixelmap/ (the page, land.js, map.js, the r1 data that failed, every brief and round summary, verify.py, metrics). Known limit: the polygons still have vertices every ~5Β°, so zoomed in you see straight polygon edges, not coastline detail β the next program would be one round per continent with a regional preview in verify.
Performance & bug fixing β decision log (ATLAS β ATLAS6)
Every Atlas round in order: what was decided, what was changed (commits and files on the fork branch andrew-vogulkin/atlas:assist/flashnext-longform-fixes, PR Atlas-Inf/atlas#23), and what it did to speed and output. Speeds are single-stream decode on the same NVIDIA GB10; llama.cpp serves the same model there at 24β25 tok/s.
| Round | Engine head | Decision | R1 / R2 tok/s | Game |
| ATLAS | main @ 4df6e609 | serve as-is; find out why it fails | 17.3 (11 attempts) | fix2 needed to boot |
| ATLAS2 | cb6b1a59 | fix the five engine defects, run on defaults | 17.3 β 16.7 | inert |
| ATLAS3 | cb6b1a59 + request sampling | equalise sampling with llama.cpp | 17.0 β 16.0 | playable after 8 fix rounds |
| ATLAS4 | 7db64cca | BF16 head + attention for quality; rollback rework | 15.2 β 14.0 | 1 logic bug |
| ATLAS5 | 0b9ada13 | execute the gap-study items A1βA3 | 14.7 β 13.9 | playable after 1 fix round |
| ATLAS6 | e7b6af2f | profile with nsys, act on the measured top two stages | 21.6 β 20.3 | playable after 3 fix rounds |
ATLAS β 2026-09-03 Β· main @ 4df6e609
Serve the RadixArk NVFP4 checkpoint on stock Atlas2026-09-03R1 17.3 tok/s Β· 11 serve attempts
Decision
Run the same two-round TD benchmark llama.cpp had run in August, on Atlas main, and record every failure rather than tune around it.
What happened
Five distinct engine defects over eleven serve attempts: watchdog rollback desyncing the QSA indexer (HTTP 500), MTP verify with QSA active ending the response as a clean stop (silent truncation), prompts over 8,192 tokens refused twice at the PLE layer (cap off by one, then a 65,536-slot row cache), continuation prefill chunks faulting with CUDA_ERROR_MISALIGNED_ADDRESS and a zombie process holding 100 GB, and ATLAS_QSA_MAX_TOKENS defaulting to 32,768 regardless of context.
Workarounds used
ATLAS_PLE_MAX_TOKENS, ATLAS_QSA_MAX_TOKENS, ATLAS_PLE_CACHE_SLOTS env pins, single-chunk prefill (--max-prefill-tokens 20480), --content-loop-watchdog false, no speculative decoding.
Output
Game "IRONGATE" needed a second fix round to boot. Speed 17.3 tok/s single stream, ~300 tok/s prefill.
Files
no engine changes; scripts
~/driver_td2*.sh,
~/tdbench_r2.py,
~/tdfix3.py on the bench machine; report
bench report.
ATLAS2 β 2026-09-04 Β· fixed engine, default settings
Fix the five defects on a branch, then run with no env pins2026-09-04R1 17.3 β R2 16.7 tok/s
Decision
Two independent investigations (prod model and config A) produced a seven-item plan; execute all seven with the config-A executor on one branch, then re-run the benchmark on defaults.
Commits
e623e010 PLE row cache derived from max_batch_tokens Γ ngram_heads, env stays an override; cap clamped up with a warning β crates/spark-model/src/weight_loader/qwen4_exp/ple.rs, qwen4_exp.rs
561be215 QSA indexer capacity from --max-seq-len (ModelConfig.max_seq_len) β crates/atlas-core/src/config.rs, config/factory.rs, spark-server/src/main_modules/serve_load.rs, spark-model/src/layers/qsa.rs, attach.rs
16af4d33 engine errors reported as finish_reason="error" (500 for blocking clients) instead of stop; cuBLASLt failure logged with M/N/K β scheduler/types.rs, lifecycle.rs, ir/response.rs, anthropic/translate.rs, qwen3_ssm/trait_prefill_proj.rs
35028544 scheduler breaks on a latched CUDA fault instead of holding 100 GB and 500-ing β scheduler/mod.rs
f4e81291 watchdog rollback saves/restores the QSA/PLE aux state with the SSM snapshot, declines when missing β traits/model.rs, model/types.rs, impl_a1.rs, model/trait_impl/mod.rs, scheduler/rollback.rs
86bdf303 MTP latched off before a sequence crosses the QSA inert bound β scheduler/mtp_gate/qsa_latch.rs, scheduler/mod.rs, multi_seq/mod.rs
97aa9fa4 hyper-connection prefill scratch laid out on 256-byte boundaries (odd slabs faulted the GEMM) β spark-runtime/src/buffers/sizes.rs, buffers.rs, ops/hyper_connection_lowrank.rs
d32605b2 merge of upstream main; cb6b1a59 test fix
Impact
Zero server errors across the run on default chunking with no env pins. Speed unchanged (the fixes remove failure modes, not cost). The game "BULWARK" was inert (no boot loop, no listeners): the quality problem was not the engine.
ATLAS3 β 2026-09-04 Β· sampling equalised
Pin the request to llama.cpp's effective sampling2026-09-04R1 17.0 β R2 16.0 tok/s
Decision
Found in spark-server/src/api/chat/sampling_setup.rs: Atlas fills unset 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. Re-run with presence 0, top_k 40, min_p 0.05 on the same engine.
Impact
A complete, wired 51 KB game ("Ironline") with one typo; the identical engine had produced dead/inert files twice. Then eight fix rounds: the model repairs exactly what a brief names, and every unrequested edit it made introduced a regression (fix2, fix6, fix8 each re-broke one token; one round emitted EOS mid-file; one echoed the file). Playable after fix7; a road-placement bug (path distance NaN from duplicate waypoints) fixed in fix8 with one reviewer token reverted. Per-round data on the bench page.
Commit
953820a0 [behavior].use_sampling_preset_penalties_for_core (default true, Flash-Next opts out) β crates/atlas-kernels/build_parse_behavior.rs, build.rs, build_codegen.rs, src/lib.rs, spark-server/src/api/chat/sampling_setup.rs (+ tests), kernels/gb10/qwen3.8-flash-next/MODEL.toml
ATLAS4 β 2026-09-04 Β· precision and rollback
BF16 head and attention for quality; make the watchdog rollback correct2026-09-04R1 15.2 β R2 14.0 tok/s (watchdog off)
Decision
Attribute the remaining quality gap to precision: the checkpoint ships attention and lm_head in BF16 and Atlas requantised both to NVFP4 at load. Keep the head BF16 by default and add a flag for BF16 attention projections. Finish the rollback fix (the earlier item left a one-token lag).
Commits
61f1993b BF16 lm_head default for qwen4_exp β crates/atlas-core/src/config/methods.rs
c120f479 --attn-proj-dtype bf16 β atlas-core/src/config.rs, spark-server/src/cli/{serve_args,validate,flag_values}.rs, main_modules/serve_load.rs, spark-model/src/weight_loader/qwen35/load_layers/attention_arms.rs
6c5b61c3 + 7db64cca rollback rewinds by fed tokens (the two watchdog sites sit on different sides of the step's push) and drops the token sampled from the discarded context β scheduler/rollback.rs, rollback_tests.rs, decode_logits_content.rs, decode_logits_step.rs
Impact
Rollback verified on the GPU: five rollback + re-steer events, zero indexer desyncs. Two side findings: the content-loop watchdog false-positives on code (it cut a coherent file at a coordinate array), so it stays off for code; and each BF16 choice costs 5β6% decode (both β 12%). Game: 57.7 KB, zero syntax errors, one logic bug (killed enemies never removed). The speed drop from ATLAS3 is the precision trade, measured later per half: BF16/BF16 13.8 Β· BF16/NVFP4 15.0 Β· NVFP4/BF16 14.7 Β· NVFP4/NVFP4 15.6 tok/s at 3.9K context.
ATLAS5 β 2026-09-04 Β· gap study executed
Compare the two engines from source, execute the three cheapest items2026-09-04R1 14.7 β R2 13.9 tok/s
Decision
Two investigations by the prod model (Atlas-only decode anatomy, then Atlas vs llama.cpp side by side,
gap study) proposed six items. Measured first: CUDA graphs are worth β3% to llama.cpp (
GGML_CUDA_DISABLE_GRAPHS=1: 24.8/25.1 β 24.4/24.1), so capture was dropped to last. Execute A3, A2, A1 with the executor; verify each contribution.
Commits
69cb6625 A3: event-scoped PLE pin release + pinned slot staging (the PLE is one layer, so one barrier per token) β spark-runtime/src/gpu.rs, cuda_backend/gpu_impl.rs, spark-model/src/layers/ple/{aux_state,layer,verify}.rs β neutral
ad7203f5 A2: ATLAS_MOE_DECODE_ARM=grouped selector β spark-model/src/layers/moe/forward.rs, MODEL.toml β grouped tensor-core arm at M=1 is 3Γ slower (5.3 vs 15.0 tok/s); scalar stays
0b9ada13 A1: device-side QSA block selection qsa_select_topk behind ATLAS_QSA_DEVICE_TOPK=1, parity mode ATLAS_QSA_TOPK_VERIFY=1 β kernels/gb10/qwen3.8-flash-next/nvfp4/qsa_indexer.cu, spark-model/src/layers/ops/qsa.rs, layers/qsa.rs, new layers/qsa_decode_select.rs β parity clean; +3β5% only once the sparse selection is active (past 2,051 tokens)
Impact
Decode unchanged on the full benchmark: host synchronisation, PLE placement and graph capture together are worth a few percent. Game "IRONGATE" (62 KB) played out of the box except a stats panel refreshed only on shop clicks; one two-line fix round. Conclusion recorded: the gap is in the decode kernels.
ATLAS6 β 2026-09-06 Β· measured, not reasoned
Profile one decode window with nsys, act on the two largest stages2026-09-06R1 21.6 β R2 20.3 tok/s
Decision
Stop estimating. 40 s of pure decode under nsys (642 tokens, ~69 ms each): dense_gemv_bf16 73 launches = 24 ms (42%) β 72 of them the 36 GDN layers' in/out projections, which the loader keeps in BF16 (4.15 GB/token, bandwidth-bound); hyper-connection stage 3 hc_pre_finish 97 Γ 121 Β΅s = 11.7 ms (27% with its siblings); MoE β 8.3 ms at ~77% of achievable bandwidth (already near its floor, so the proposed GEMV rewrite was dropped). The prod model's own reading of the same profile misattributed the MoE term and was overruled by the numbers. Profile: reports/atlas-flashnext-decode-profile-2026-09-06.md.
Changes
- Flag
ATLAS_QWEN4EXP_BF16_GDN=0: requantise the GDN projections to NVFP4 through the loader path that already existed (its comment, written at a 2.2 tok/s era, said GDN bandwidth "is not what bounds decode") β 15.0 β 18.3 tok/s; costs ~6 GB (context 64K β 48K at 0.95 utilisation)
e7b6af2f hc_pre_finish_x4: one warp per hyper-connection stream, lanes over 32 output dims (4Γ the threads), identical FP32 accumulation order under --fmad=false β greedy output hash identical to the previous binary; 18.3 β 20.3 tok/s β kernels/gb10/qwen3.8-flash-next/nvfp4/hyper_connection.cu, spark-model/src/layers/ops/hyper_connection_lowrank.rs, hyper_connection_lowrank_gemm.rs (ATLAS_HC_FIN_X4=0 reverts)
3fff25af hc_pre_finish rank loop unrolled Γ8 β neutral (the kernel was thread-starved, not load-starved; kept as documentation)
22321152 qsa_select_topk scores staged in shared memory β neutral on tok/s, bit-exact
Measured, rejected
ATLAS_GDN_FUSED_NORM=1 (recurrence + gated norm fused): no speed change and the greedy output differs. ATLAS_D2H_SPIN_SYNC, ATLAS_PLE_FAULT_THREADS=128: neutral. FP8 / NVFP4 lm_head: +1 / +1.5 tok/s on top, at the documented argmax-flip risk; not adopted.
Impact
Full benchmark 21.6 / 20.3 tok/s (+47% over ATLAS5, ATLAS3 was 17.0 on NVFP4 head and attention). Game "IRONWORKS Β· TD" shipped with two defects (a helper called by the wrong name, an unbound level variable in the turret loop); a console-error round echoed the file, two literal-brief rounds fixed exactly what was named; then four waves cleared cleanly. Quality gate for the NVFP4 GDN requantisation: two defects in one sample versus one for ATLAS5 β not conclusive.
Left on the table
BF16 lm_head β 6 ms/token (precision decision); hc_pre_down and the other hyper-connection launches β 5 ms; conv1d launched separately from the GDN recurrence β 1 ms (the existing fused kernels are not bit-exact); ~1,000 launches/token of gaps. llama.cpp: 24β25 tok/s.
Method notes that shaped the decisions
Every engine change is proven bit-exact by a greedy 160-token generation on the previous binary and the new build (hash compare) before its speed is quoted; the one lever that failed that test was rejected. The prod model ran the investigations and the plumbing-shaped executor items (A3, A2) from anchored briefs; it produced no change for kernel items (A1, the HC kernel), which were written by the reviewer and stated as such. Game fix rounds follow the August protocol: console error first, then a runtime brief naming the exact change; regressions from unrequested edits are logged rather than hidden. Speeds at "3.9K context" are 1,200-token generations after a 3,882-token prompt so the sparse attention path is active; benchmark speeds are the two 20K-token rounds.
Prompts
Round 1 β build prompt (identical for all configs)
Build a complete, polished single-file HTML5 tower-defense game (index.html: all CSS and JS inline, canvas-based, no external assets or libraries). Requirements:
- 20x12 grid map with a fixed winding enemy path (visually distinct road) and buildable cells
- 3 tower types: Gun (fast, single-target), Cannon (slow, splash AoE), Frost (slows enemies). Each has cost, range, and 2 upgrade levels (better damage/range/fire-rate). Towers can be sold for 70% of invested cost.
- Enemies: normal, fast, tank; a boss every 5th wave. Health bars above enemies. HP/speed/count scale each wave.
- Economy: start with 100 gold and 20 lives; kill bounties; wave-completion bonus; a leaked enemy costs 1 life (boss costs 5).
- UI: top bar (gold, lives, wave, score); tower shop panel with prices; click a shop tower then click the map to place (show range preview and valid/invalid cell highlight); click a placed tower to select it and show upgrade/sell panel; Start Wave button plus auto-start toggle; game speed x1/x2 button.
- Targeting: enemies closest to the exit first. Render projectiles. Show splash radius on cannon impact and a frost visual on slowed enemies.
- Win after beating wave 20; lose at 0 lives. Proper end screens with a Restart button.
- Polish: hover states, particle effects on kills, hit flashes, clean modern dark theme.
Output the COMPLETE index.html in one ```html code block. No placeholders, no TODOs β fully working code only.
Round 2 β review / fix / balance / polish prompt
Below is your current index.html for the tower-defense game. 1) Review it for bugs: logic errors, undefined variables, broken event handlers, targeting/pathing mistakes, economy bugs, rendering issues β fix them all. 2) Ensure every item of the original spec is implemented: 3 tower types with 2 upgrade levels + sell; 4 enemy types incl. boss every 5th wave; wave scaling; gold/lives/wave/score UI; place/select/upgrade/sell interactions with range preview; start-wave + auto-start + speed toggle; closest-to-exit targeting; projectiles + splash + frost visuals; win at wave 20 / lose at 0 lives with restart; particles and polish. 3) Balance: tune costs, bounties and wave scaling so waves 1-20 are beatable but challenging. 4) Polish: particles, hit flashes, nicer end screens. If the file below is cut off mid-code, complete it properly. Output the COMPLETE final index.html in one ```html code block.
Current file:
```html
{FILE}
```
Config A only β self-fix round (given its own console error)
Your single-file HTML5 tower-defense game below fails to load. The browser console shows this error:
Uncaught SyntaxError: Unexpected token ')'
Find and fix the error. Also scan the whole file for any other syntax mistakes (unbalanced brackets/parentheses, broken statements) that would prevent it from running. Do not redesign anything β make the minimal corrections needed so the game runs. Output the COMPLETE fixed index.html in one ```html code block.
Current file:
```html
{FILE}
```
Raw round data
| Config | Round | Tokens | Wall time | tok/s | Finish | Code emitted |
| P | R1 build | 12,365 | 4 m 25 s | 46.6 | stop | 35,591 ch β |
| P | R2 fix/polish | 12,550 | 4 m 16 s | 49.0 | stop | 35,762 ch β |
| E2 | R1 build | 21,146 | 14 m 21 s | 24.6 | stop | 55,956 ch β |
| E2 | R2 fix/polish | 21,504 | 17 m 55 s | 20.0 | stop | 56,057 ch β |
| A | R1 build | 18,608 | 13 m 16 s | 23.4 | stop | 48,934 ch β |
| A | R2 fix/polish | 19,198 | 16 m 31 s | 19.4 | stop | 50,226 ch β (fatal typo) |
| A | R3 self-fix | 18,908 | 16 m 13 s | 19.4 | stop | 50,225 ch β (1-char diff) |
Prompt processing (prefill)
| Config | Short prompt (84 tok) | Long prompt (~13β21K tok, file embedded) | Source |
| P | ~180β340 tok/s | 720.6 tok/s (12,750 tok in 17.7 s) | server log of the actual R2 round |
| E2 | 123.7 tok/s | 475.0 tok/s (20,983 tok in 44.2 s) | dedicated probe, same file-embedded prompt |
| A | 118.7 tok/s | 465.7 tok/s (20,983 tok in 45.1 s) | dedicated probe, same file-embedded prompt |
Prefill = server-side prompt eval time from llama.cpp.
Short prompts are dominated by fixed overhead on every config; on long prompts prod processes ~1.5Γ faster than Flash-Next.
In practice: feeding a ~50 KB file back for review costs ~18 s of prefill on prod vs ~45 s on Flash-Next, before any generation starts.
Method & pipeline
How this bench was run
Sampling: temperature 0.7, top_p 0.8, max_tokens 26,624 per round, thinking disabled
(chat_template_kwargs: enable_thinking=false β mandatory: with thinking on, models burned
the entire budget reasoning; prod produced 52K chars of thought and zero code).
Protocol: each config got the same Round-1 build prompt, then Round 2 received its own
full file back for review/fix/balance/polish. The harness extracted the single ```html
block from each response; the last complete file became the published game.
Config A shipped with `flashColor='#ff4d9d');` β one stray parenthesis that killed the
whole game on load. Given only its file plus the console error, it regenerated all 50 KB
byte-identical except that character. Verified with node --check before republishing.
Serving: prod runs live on spark:8000; Flash-Next configs run on :8001 with prod stopped
(128 GB unified memory is one pool β CPU offload frees nothing while another model is
resident). Publishing: spark β orange ~/sites/bench β orange Caddy (handle_path /bench*)
β wg umbilical β edge Caddy (TLS) β you.
Speed note: prod got FASTER on this task than on short prompts (49 vs 38.6 tok/s) β
long uninterrupted code generation is ideal for DFlash2 speculative decoding. Flash-Next
slowed as context grew (24.6 β 19.4 tok/s).
Spec: 20Γ12 grid, 3 tower types with upgrades/sell, 4 enemy types incl. bosses every 5th wave, economy,
wave scaling, win at wave 20. Both Flash-Next runs independently branded their game βIRONHOLDβ and out-designed prod;
prod built a working game 3.5Γ faster. Verdict: prod for speed and agent duty, Flash-Next (E2) when craft beats latency.