The NPU in your Strix Halo is finally doing real work: 13.6 vs 18.7 min on the same bug fix

The NPU in your Strix Halo is finally doing real work: 13.6 vs 18.7 min on the same bug fix

tldr: the gap in the title is what the NPU tools buy on a real bug fix. And local now covers about 95% of my cloud calls, the hardest few percent still goes to GLM 5.3 or Opus. Receipts in the repo.

Qwen3.8 Flash-Next, a 125B MoE, runs my pi coding agent on a 70W tablet, and the NPU finally earns its power draw. Wired halogen's endpoints expecting a party trick, kept what paid.

v1.1 since launch: opt-in voice on the CPU, search moved to semble. The tools that stayed on the NPU:

  • Decisions. Yes/no branching stops eating turns of the big model. A 0.8b answers in 120ms, 78% accurate.
  • Dup scan. Catches copied and renamed files git never shows you. 45 pairs across 4 repos in 8.4s.
  • Screening. Injection attempts get flagged before the agent acts. 0.7s, zero false alarms, fails open. 42% recall: a smoke detector, not a safe.
  • Search. The agent lands on the right file first try, on any install. The NPU pipeline beat ripgrep 15/20 to 9/20; semble replaced it at 17/18 to 13/18 on the ground-truth A/B.
  • Compaction. Big sessions summarize on a tiny sidecar, off the critical path. My 194k session: 50s, 97% cache hit.

The eight-run check was about taste and depth, not throughput: I pasted a real timeshift error to seven agents, the 125B at medium and max against 320B and 753B tiers at max. Eight runs, all healthy.

  • Flash-Next, 2m55s: journalctl trace to a racing notify-send, pacman.log check, upstream PR found.
  • glm-5.3-flashx, 2m41s: the most precise answer, spotting the snapshot mount unmounted under the script's last line. No PR.
  • GLM 5.3 753B at max, 8m30s: the deepest forensics, source-level down to function names and the one-second race window. No PR.
  • Flash-Next at max, 8m31s: level with the 753B, and still the only model with the PR.

I expected cloud to be faster. It never pulled away: 14 seconds, then one, GLM at max both times. The PR was not a tie.

All eight side by side. In opencode: flash called it in 1m8s with the wrong mechanism, the full 753B correct in 6m15s, PR missed. And when I had GLM 5.3 flashx rate both results, it picked the qwen answer too.

Reality: the GPU still does the thinking, ~7% iGPU cost only when they overlap. Decode: 64 tok/s best case, 45-50 sustained at 70W. Cap the box at 50W, give up a couple of tok/s, stay under 75C and quiet. First token in ~0.03s, 43x faster than the cloud call beside it, 7x with a second agent.

The official halogen launch is a 24-flag docker command. Mine is one, and uninstall undoes it. Fully local, 262k context. No Strix Halo? setup.sh --no-halogen runs pi against your own providers.

Anyone else putting their NPU to real use? I found nothing.

u/stereohype — 8 days ago
▲ 2 r/StrixHalo+1 crossposts

Qwen3.8-Flash-Next on Strix Halo: 40 t/s sustained decode, 353 t/s prefill, agent builds in 5 min

125B MoE (A6B) running faster than a 27B dense on the same iGPU, and producing better agent builds. Full receipts below, everything reproducible.

The numbers (all through an agent game-build workload, not llama-bench)

metric value context
decode, sustained 40 t/s (aggregate over a full 17k-token agent session) 24-31 on ladder runs at higher effort (more reasoning = lower acceptance) — peak 48.7
prefill 353 t/s aggregate / 415 peak best measured anywhere in my logs
game build, low effort 5 minutes, 17/19, zero human edits fastest verified build cell I've recorded
game build, medium effort 12-16 min, 17/19, fully clean playtest
KV-cache reuse in-session 99.4% hit rate (R201k) the agent-turn economics

For scale: the same workload on Qwen3.8-27B runs 17.5-18.5 t/s decode. This is a 125B MoE doubling a 27B's decode on the same silicon.

The stack (each line is load-bearing)

  1. Engine: Nathan's strix-halo-vulkan releases (validated v0.6.11 through v0.7.3) — built from source, not the portable payload. The payload's portable build leaves 6-24% on the table (measured: JSON-class decode 28.3 → 40.3 t/s on identical flags).
  2. Driver: your system Mesa stable. It beat the bundled devel snapshot on agent-relevant classes (deep decode +9.5%, emission +13%).
  3. Model: Unsloth UD-IQ4_XS (87.25GB, 3 shards — pin your revision, they re-upload under the same names).
  4. Drafter: the native MTP head as a Q8_0 sidecar (~4GB), fixed n4. Don't use adaptive draft sizing here — it loses on MTP's chained drafts (measured both directions; it wins on DFlash2 block drafts, different mechanism).
  5. KV: q8_0 both, context 131072. Needs ~91GB weights+sidecar+KV — fits 128GB Strix Halo. Check your own mem_info_vram_total + mem_info_gtt_total: both heaps count, the Vulkan allocator uses both.
  6. The flags that matter more than any of the above: --reasoning-effort medium --reasoning-budget 2048. Without them, this model burns its entire output budget on reasoning and emits nothing — 8.5k chars of thinking, zero content, finish=length. With them: clean, terse, fast. This is not optional.

The part benchmarks can't see

I validate agent stacks by having a coding agent build a small canvas game from a fixed contract, then scoring the code, soaking the running game headlessly, and playtesting it. Two things this workload caught that raw throughput never would:

  • The template is per-model infrastructure. Effort kwargs that work on one model's template are silently ignored here. The reasoning-budget flags above are the fix — they're server-native, not template-dependent.
  • Every model has blind spots, and they're usually spec gaps. Flash-Next failed the same game subsystem (a floating obstacle pad) three builds running — hit registration, stickiness, bounce direction. One explicit contract sentence with a trace-before-you-finish check cured it: next build fully clean, in 12 minutes. The same cure fixed a different model's serve-rule bug earlier. 2-for-2.

And the closing control experiment: Flash-Next vs a 27B at low effort, same environment, same contract — identical scores, identical failure sets, identical line counts (1,412 each).

Flash-Next: 5 minutes, 40 t/s, no edits. The 27B: 39 minutes, 4.3× the reasoning, five of my edits — and it spontaneously wrote and ran its own smoke-test script mid-build. On explicit contracts, model size buys speed, not quality. Pick by token budget.

Reproduce

The workload harness is public: neon-ladder — contract, scorer, runtime gate, runner, one-comment recipe. One cell is ~25 minutes. Post your one-liner (quant / drafter / effort / wall / static / soak / playtest) and the variance rules are documented so your numbers and mine can argue productively.

Full methodology, the 27B side of the story, and twelve playtest-found failure classes that no static check ever caught: my earlier posts in this sub.

The short version of everything I learned: the contract is the tool, the playtest is the grade, and a model that benchmarks beautifully can still be unable to build a working game — which is why you measure the thing you actually do.

u/stereohype — 1 month ago
▲ 16 r/StrixHalo+1 crossposts

Running a local coding agent on Strix Halo with pi + llama.cpp: 27B and Flash-Next, the setup guide

This is the harness companion to my Qwen3.8-27B benchmark post. That post made the model fast; this one makes it useful: pi (the coding agent) against a local llama-server, tool calling, thinking control — and response times that don't hurt.

This is a setup guide, not a benchmark post. Every trap, config, and extension here is what I actually run daily. The benchmark side (the game-build harness, scorer, runtime gates, playtest protocol) lives in neon-ladder — this post links to it where relevant but doesn't duplicate it.

Everything below was verified live on my Flow Z13 (Ryzen AI Max+ 395, 8060S, 128GB): a 10-module game built in one session, 1,587 lines of working code, all from server logs and session files — not estimates.

Note: writing is AI-assisted; every number and config here comes from my own runs.

TL;DR

  • Working recipe: pi + llama-server (Nathan's strix-halo Vulkan fork) + the Sharp chat template, 256k ctx (the model's training cap), maxTokens 32768, thinking wired via compat.chatTemplateKwargs. Verified end to end with request dumps and session logs.
  • pi's defaults will silently sabotage a reasoning model: maxTokens 16384 can be eaten entirely by thinking, and thinking flags don't reach llama.cpp's template without compat-level wiring.
  • Session economics are great: ~94% KV cache hit rate, stable across session types (a 16-turn game build and a 44-turn tool-heavy refactor both landed at 94.1-94.2%); only the first turn pays full prefill.
  • Effort control works after wiring: off produced literally zero thinking tokens, and the level you pick changes code quality, not just speed (details in the build test).
  • At pi's default temperature 0.8, planning-heavy prompts occasionally sample an instant-EOS first turn (one token, done). Retrying the identical prompt inherits the failure from cache; retry with a perturbed prompt or run temperature 0.
  • Don't chase deep context; compact before it gets expensive. Auto-compaction set to fire around 95k keeps every turn in the fast band (decode 26+ t/s, prefill ~200 t/s) while sessions past ~140k pay 17-19 t/s decode and ~140-175 t/s prefill. One settings line does it.

The server side (brief)

Two server profiles, same machine (one resident at a time):

27B (daily driver): UD-Q4_K_XL (v3) + DFlash2 Q4_K_M drafter n4, f16 KV, drafter KV q8_0, -c 262144, power pinned with my z13ctl+ profile.

Flash-Next (speed lane): UD-IQ4_XS + native MTP Q8_0 sidecar, fixed n4, q8_0 KV, -c 131072, and --reasoning-effort medium --reasoning-budget 2048 — those flags are mandatory (without them, Flash-Next burns its entire output budget on reasoning and emits nothing). Full configs in the neon-ladder repo.

Ubatch 4096 for normal work; for deliberate deep fills use 2048 (probed clean through 139k) or 1024 (proven at 145k). The model's training cap is 262144, and the full config runs healthy there at ~55GB RAM. Three harness-relevant facts worth knowing:

  • -ub 4096 has a hard ceiling: past ~140k tokens of fill it hits a deterministic Vulkan device-lost (twice, at nearly the same depth). -ub 2048 passed the same style of probe at 138.8k and -ub 1024 completed a real 144k session; the ceiling moves with ubatch, so smaller ubatch buys depth.
  • Allocating big context costs nothing until filled: decode at 8k depth was identical with -c 65536 and -c 98304. Allocate the max.
  • Deep sessions work but get slow linearly: a 144k-token agent session (resumed after a crash) decoded at 17-19 t/s throughout, with draft acceptance 0.62-0.92 the whole way. That's why the compaction setting below matters more than any ubatch choice.

Installing and wiring pi

pi is a terminal coding agent with unusually good local-model support. Install it, then point it at llama-server via ~/.pi/agent/models.json (not settings.json, that file ignores provider blocks):

{
  "providers": {
    "llamacpp": {
      "baseUrl": "http://127.0.0.1:8080/v1",
      "api": "openai-completions",
      "apiKey": "dummy",
      "models": [
        {
          "id": "qwen3.8-27b",
          "reasoning": true,
          "contextWindow": 262144,
          "maxTokens": 32768,
          "compat": {
            "thinkingFormat": "chat-template",
            "chatTemplateKwargs": {
              "reasoning_effort": {"$var": "thinking.effort"},
              "enable_thinking": {"$var": "thinking.enabled"}
            }
          },
          "thinkingLevelMap": {
            "minimal": null, "low": "low", "medium": "medium",
            "high": "high", "xhigh": null, "max": null
          }
        }
      ]
    }
  }
}

Then pi --provider llamacpp/qwen3.8-27b, or set defaultProvider/defaultModel in settings.json.

Every field in that entry is load-bearing, and several of them exist because of a trap:

Trap 1: the silent cloud fallback

If pi can't resolve your provider config, it does not error. It uses whatever else is configured: your run can look successful while the session log shows a nonzero dollar cost and your server has processed zero requests, because pi has been talking to a cloud provider the whole time.

Always verify a local run server-side. Watch curl localhost:8080/metrics while the agent works: if prompt_tokens_total isn't climbing, you're not local.

Trap 2: maxTokens 16384 is a thinking bomb

pi's default maxTokens is 16384. For a reasoning model on a planning-heavy prompt, that's not an output budget, it's a thinking budget: a "build a game" prompt can spend all 16,384 tokens on reasoning and hit the length cap with zero code emitted, with stopReason: length in the session log and a model that looks "stuck."

Set maxTokens explicitly. 32768 covers everything in a normal tool-using session, including a turn that writes two files back-to-back. A length-capped turn is also not fatal: the next turn continues without corruption.

Trap 3: thinking flags don't reach the template by default

This is the subtle one, and the failure is silent.

llama-server's chat template (the Sharp template from the benchmark post) accepts chat_template_kwargs: enable_thinking and reasoning_effort. pi has flags for thinking levels (--thinking off/low/..., shift+tab to cycle). But between the two sits a mapping layer:

  • The mapping config (thinkingFormat, chatTemplateKwargs) must live under compat on the model entry. At the top level of the model object it is silently ignored.
  • With the wiring correct, --thinking low sends {reasoning_effort: "low", enable_thinking: true} and --thinking off sends {enable_thinking: false}.
  • Without it, pi's flags go nowhere and the template defaults to thinking on, medium effort. The model thinks when you told it not to, and everything is slower.

Verify your own wiring before trusting it: point baseUrl at a logging proxy for one run and read the request body. It's ten minutes and it converts "I think it works" into "it works."

The thinkingLevelMap entry hides levels the template doesn't distinguish. The Sharp template has four real states (off, low, medium, high); pi cycles seven by default, three of which are aliases. The map collapses the cycle to the four that exist.

Trap 4: the instant-EOS prompt basin

At pi's default temperature 0.8, a planning-heavy tool prompt occasionally samples a degenerate first turn: the model emits a thinking tag, immediately stops, and the run ends with an empty response and a one-token generation in the server log.

On my game-build prompt this hits roughly one request in three to five. It is sampling behavior, not a server or client bug: replaying the identical request body at temperature 0 never fired it in six runs.

The compounding part is the retry. Resending the same prompt hits the KV cache, inherits the degenerate turn from history, and fails again, which makes the failure look deterministic and hardware-flavored. Retry with a slightly perturbed prompt (any unique marker appended) and it rolls fresh.

For reproducible benches I set "samplingParams": {"temperature": 0.0, "top_p": 0.95, "min_p": 0.05} on the model entry; for everyday sampling, perturbed retries are the fix.

What thinking control buys you

Same planning-heavy prompt, session-verified thinking token counts:

pi level thinking emitted result
off 0 chars task completed, 5 tool calls
low 14k chars task completed, cleaner code
(default, unwired) 16,384 tokens, all thinking length cap, zero code

For quick edits use off, for generation-heavy work low or medium, for debugging and architecture high. shift+tab cycles levels live in a session.

One honest note on the Sharp template: it tames runaway reasoning on normal turns (that's in the benchmark post), but it does not bound reasoning on genuinely planning-heavy prompts. The bound comes from your effort setting plus the maxTokens headroom. Template + harness flags together are the complete answer.

Effort level also buys code coordination, not just volume. Two verified game builds, same prompt: the low-effort build passed every static check yet played worse in three measurable ways (ball not glued to the paddle before launch, ball speed tied to the monitor's refresh rate instead of a fixed timestep, flatter difficulty curve).

The medium-effort build got all three right. Syntax is free; the seams between modules are what thinking pays for.

More effort past medium, though, buys breadth instead of correctness.

A high-effort run of the same prompt produced 1,995 lines with three extra self-directed modules (audio, UI, paddle) and 93 tool calls, yet scored 13/15 against medium's perfect 15/15, dropped the same localStorage persistence the low-effort builds drop, shipped a latched input flag that left the keyboard dead at runtime, and took over twice the wall time.

The sweet spot for build-shaped tasks on this model is medium: perfect score, 16 tool calls, about 25 minutes.

That medium result is robust, not a lucky roll: two more independent medium builds (different ubatch, one with five auxiliary-model extensions loaded) scored 14-15/15 in 20-23 minutes each.

A fourth medium build added a per-module test suite to the same prompt: 51 tests written alongside the code, all green on arrival, 14/15 on the same checks, 36 minutes.

That's the tier I spec for real work now: for roughly 15 extra minutes the agent ships its own regression suite with the feature.

The multi-file build test (this became neon-ladder)

To validate the whole stack I had it build "Neon Overdrive", an arcade Breakout game, as a 10-file project: 8 JS modules, CSS, index.html, strict no-placeholder rules, syntax checks required. The full prompt is below so you can run the identical test on your own stack.

Result: 15 turns, 16 tool calls (12 writes, 3 bash checks, 1 read), 1,587 lines, all syntax checks pass, all seven feature requirements present in the code, ~25 minutes wall time.

One turn hit the 32k cap mid-double-file-write and the next turn picked up cleanly. And the game actually plays: paddle reflection angles, armored bricks shifting red to orange to yellow, volatile-chain explosions, tri-ball chaos, the upgrade shop between levels.

There's a built-in bonus to this benchmark: while your agent grinds through someone's 3,000-line refactor, you get a neon Breakout to play. Post your build quality and wall time in the comments; it will be interesting to see how other engines and models handle the identical prompt.

The prompt (paste as-is; it assumes a js/ and css/ dir will be created by the agent):

Build "Neon Overdrive", an arcade Breakout game, as a multi-file project you create with tools, file by file. NO external dependencies or CDNs; HTML5 canvas + CSS3 + raw JS only.

Required file structure (use the write tool once per file, complete code every time, zero placeholders):
1. index.html - loads css/styles.css and all js/ files via script tags in dependency order
2. css/styles.css - neon/cyberpunk UI, overlays for menu/pause/shop/game-over
3. js/config.js - constants: canvas size, brick grid, speeds, powerup drop rate (15%), colors
4. js/particles.js - particle engine: spawn(x,y,color), gravity + fade update, dead-particle cleanup
5. js/bricks.js - 5-row grid from an array matrix; standard (1 hit, neon blue), armored (3 hits, red->orange->yellow as damaged), volatile (1 hit, neon green, explodes destroying direct array neighbors)
6. js/balls.js - ball entities in an active balls array; paddle reflection angle from strike position vs paddle center; no game over until the LAST ball is lost; dead-ball cleanup
7. js/powerups.js - falling capsule entities; catching Tri-Ball injects two new balls into the array
8. js/states.js - rigid state machine: menu -> gameplay -> paused -> level clear / game over
9. js/shop.js - between-levels upgrade shop: spend credits on paddle speed or paddle width (persistent)
10. js/main.js - game loop, collision wiring, score/credits, keyboard input, level generation (procedurally harder)

Workflow, in order:
A. Write all 10 files (write tool, one call each).
B. Run: node --check js/config.js js/particles.js js/bricks.js js/balls.js js/powerups.js js/states.js js/shop.js js/main.js
C. If any check fails, fix with the edit tool and re-run until all pass.
D. Read index.html to verify every script tag path matches a real file.
E. Report per-file line counts, then reply COMPLETE.

The prompt, scorer, and a retry wrapper that handles the Trap 4 basin are packaged in neon-ladder.

Scoring it is easy: all 10 files present, node --check passes clean, the seven mechanics are actually implemented (grep for the reflection math, the armored color shifts, the neighbor explosion), and the game runs when you open index.html.

Then play it for two minutes: the ball rides the paddle before launch, speed is framerate-independent, and upgrades survive a page refresh.

Reference numbers for this box: 1,587 lines, 16 tool calls, ~25 minutes at medium effort, zero placeholders.

Session economics over those 16 turns: 94.1% of prompt tokens served from KV cache (pi resends the full conversation every turn; llama-server absorbs it), ~237 t/s on the uncached remainder, decode in the low-to-mid 20s t/s with tool traffic mixed in, acceptance around 64-68%.

That's the whole reason local agentic coding works at all on this hardware: the harness's chat-pattern traffic is almost entirely cache hits, and the GPU only pays for new tokens.

Ling-3.0-tiny as the compaction service

Long sessions eventually need compaction, and there's no law saying the model that summarizes the session has to be the model doing the work.

Ling-3.0-tiny (8B total, 1.3B active, 4.8GB in Q4_K_M) is built for exactly this slot: prefill is its superpower, thinking can be disabled per request, and its hybrid attention keeps KV costs near zero.

The compaction test used a real session transcript: the full game-build session (16 turns of tool calls and results) plus all workspace files, 25,890 tokens in, asked for a structured handoff document (file inventory, verification status, bugs, next steps, constraints).

Result: a 799-token handoff in 19 seconds, and the quality holds up.

Every file and line count matched ground truth (all 10 files, 1,587 total), verification status was correct, and it refused to invent bugs that didn't exist; the constraints section surfaced exactly the architecture details a continuation session needs, from the CONFIG object and the rigid state machine to the last-ball rule and localStorage persistence.

One duplicated bullet was the only flaw, and the same job on the 27B would run roughly 5x slower.

One wiring rule, same family as Trap 3: call it through the chat endpoint (/v1/chat/completions) with chat_template_kwargs: {enable_thinking: false}. On the raw completion endpoint with a bare prompt the model degenerates into echoing workspace state in a repetition loop.

Through the chat endpoint with the template it is clean, fast, correct, and the rule is the same as pi's: the template is not optional.

Honest caveat: both tests sit at 26k and 49k input, not near the 256k ceiling; tiny's window is 256k, so there's room, but treat very deep compaction quality as untested until a session grows that large.

Set the auto-compaction threshold to ~95k and stop thinking about deep context. pi compacts when contextTokens > contextWindow - reserveTokens; the default fires only near the window's end, deep in the slow band. One line in ~/.pi/agent/settings.json moves it:

{ "compaction": { "enabled": true, "reserveTokens": 167144 } }

With contextWindow 262144 that compacts at ~95k: sessions cycle between roughly 95k and 25k (summary plus a 20k verbatim tail), every turn stays in the fast band, and the deep-context tax (device-lost ceilings, mid-teen decode, 140-175 t/s prefill) becomes somebody else's problem.

Compaction fires between agent runs, not mid-run, and each pass costs seconds on tiny.

Validated live: a 142k session crossed the threshold, compacted, and its continuation answered correctly about files read an hour earlier.

It also graduated to daily use: a 44-turn, 49k-token agentic session (TypeScript monorepo work, 44 tool calls) on the 27B, compacted with the extension live. Tiny summarized 8.1k tokens into a 2,282-token handoff in ~19 seconds: prefill at 2,904 t/s, generation at 137 t/s, server-side timings.

The same call on the 27B would have taken roughly two minutes, so ~6x end to end. The summary got every checkable fact right (file list, test counts, verification status) and the session continued cleanly after compaction, which is the real acceptance test.

The auxiliary model playbook

Compaction is just the highest-value slot for a second small model. The same pattern extends across the agent loop, and pi's extension events cover all of it. The full suite, with validation status:

job pi hook status
Compaction summaries session_before_compact validated in daily use (26k test + live 49k session, ~6x faster)
Branch summaries on /tree navigation session_before_tree wired and e2e-tested (correct 4-section handoff on a real abandoned branch)
Commit messages from working-tree diff /commit command wired and e2e-tested (proper subject+body from a real diff)
Tool-result triage (compress big outputs before they enter history) tool_result wired, one e2e test passed (51KB -> 5.6KB stored); stays dormant on clean runs and needs the real-workload quality drill before daily use
Repo map / file digest before the main model explores before_agent_start + /repomap auto-fires once per session in git repos (map injected as context, also written to .pi/repomap.md); smoke-tested

The triage row deserves its caution label: every huge bash dump costs the main model context for the rest of the session, and compressing it with tiny first keeps sessions small enough that compaction fires later or never.

But triage changes what the main model sees, and if tiny drops the one error line that mattered, the 27B makes worse decisions and you won't know why.

Before relying on it, feed it real outputs from your own sessions and verify nothing load-bearing was dropped.

One honest A/B from the game-build workload, all five extensions loaded: zero tiny calls, identical wall time and score, because clean test suites and one-line write confirmations never cross the 6KB triage threshold.

Dormant extensions cost nothing; they earn their keep on fat tool outputs (failing test runs, build logs, repo-wide greps) and long sessions, which is exactly the traffic my daily driving produces.

The division of labor in one line: the 27B reads and writes the code, tiny reads and summarizes everything else.

All of these knobs (compaction threshold, maxTokens, temperature, triage size) are three files deep by default, so I keep a /tune extension next to the suite: /tune prints the live values, /tune compactAt 95 or /tune temperature 0 writes through to the right file with bounds checking, and /tune reset restores the documented defaults.

Readers running this stack on other boxes should adjust compactAt to their own fast-band edge rather than trust mine.

Response time cheatsheet

Biggest levers first, all measured:

  1. Thinking level dominates. Reasoning streams at decode speed before you see a word.
  2. Session warmth: first turn pays full prefill (~10s), subsequent turns are cache hits and start generating in under a second. Don't restart the server between questions; use -c continuation.
  3. Lean context: extensions/skills/AGENTS.md all add to the first-turn bill.
  4. Already optimal from the benchmark post: DFlash2 n4, ubatch 4096, f16 KV. Don't shrink -c for speed; allocation is free until used.

Attacking the prefill bill (the APU's real tax)

Dense-27B prefill (~250-300 t/s) is the slowest number in this stack, and a coding agent's traffic is mostly prefill. Everything above already helps (cache hits, tiny offloading), but three more angles are worth knowing:

Keep the cache alive across turns. The 94% hit rate is the single biggest prefill saver, and its enemy is cache invalidation. Two habits preserve it: don't edit early messages mid-session (everything after the edit re-prefills), and let pi's cacheRetention default do its job. The compaction extension already sets cacheRetention: "none" for one-off summaries, which avoids polluting the main prefix.

Shrink what gets re-sent. pi resends the full conversation every turn; that's the protocol. The levers are content levers: tool-result triage from the playbook above (smaller history, smaller resend), and keeping generated outputs from ballooning (thinking low on generation-heavy turns does this too).

Route around the 27B when the job is prefill-shaped. Compaction, branch summaries, commit messages and repo maps are all "read a lot, write a little" jobs, which is exactly the profile where a 1.3B-active MoE crushes a dense 27B. Anything in your workflow that looks like "summarize/index/triage" should default to the aux model; reserve the 27B's prefill for context it genuinely needs to see.

What doesn't work: quantizing the main model below Q5 to speed prefill (prefill is compute-bound, the quant barely moves it, and decode pays the quality), and shrinking -c (allocation is free until filled, as measured above).

Which model when

The control experiment settled it: at matched effort and environment, Flash-Next and the 27B produced identical scores, identical failure sets, and identical line counts on the same contract. Flash-Next got there in 7.8× less wall time with 4.3× less reasoning. The 27B compensated by writing and running its own smoke test mid-build.

Pick the 27B when:

  • You have under ~91GB of GPU memory
  • You want 256k context
  • The job is quality-critical and you want the model that spontaneously verifies its own work

Pick Flash-Next when:

  • You have 91GB+ available and speed is the product
  • Your workload is emission-heavy (tool calls, scaffolding) — Flash-Next hits 40 t/s there
  • Build time matters more than build depth

The simplest rule: if you have the memory, Flash-Next for speed, the 27B for depth. If you don't, the 27B at Q4-v3 is never wrong.

What changed since posting

The Sharp template moved to v22.4.0 (reasoning-effort aliases, inline control tags, thinking-off fast-mode fixes). I A/B'd it on the game bench: score and speed in-family with every number above; the harness repo ships it as default with the earlier version vendored for exact reproduction.

The game-build bench grew runtime gates. A 120-second headless gameplay soak is now the default (frame advancement, reload detection, synthetic play throughout), added after a real freeze past the 60-second mark that shorter gates structurally cannot see. An uncaught page error is fatal; caught per-frame errors warn. The static scorer learned two velocity-multiplication bug patterns that shipped in builds passing every static check.

Static score anti-correlates with playability. The two highest-scoring builds of the richer-contract era were the two broken games. The final grade is and remains the human playtest; the gates are necessary, not sufficient.

Effort plumbing got real. --reasoning-budget was flat 8192 across all thinking levels in every number above; it's now mapped per level (the per-request thinking_budget_tokens field wires it cleanly), and the server accepts a top-level reasoning_effort natively ("none" is a validated zero-reasoning switch). One Sharp caveat: changing reasoning_effort mid-session re-renders the system block and invalidates the whole KV prefix on v22.3.2/v22.4.0 — the inline control tags are the safe per-turn mechanism.

Tool-choice behavior, from the session traces: models edit surgically for small fixes and rewrite whole files for cross-file structure — anchor strings past the context window are the reason. The contract now says so explicitly.

Manual beats headless on speed, loses on reliability: interactive runs (clean context, no extensions) hit 92% GPU utilization and halved walls, but shipped 1-of-3 playable vs the headless runner's 6-for-6. Deliberation is where the self-correction lives.

The bench crossed model families and engines — Flash-Next (125B MoE, native MTP) built the contract first-try at 17/19 on a stack that didn't exist when any of this was written. And a control cell closed the size question: Flash-Next vs 27B at low effort, identical environment — identical scores, identical failure sets, identical line counts. Flash-Next in 7.8× less wall time with 4.3× less reasoning; the 27B wrote and ran its own smoke test mid-build. On explicit contracts, model size buys speed, not quality.

Twelve gameplay-failure classes now, every one found by a human playtest, zero by static score — the last was a ball that vanishes mid-game, shipped in the release-gate build that scored 17/19 and passed its soak. The gates are necessary; the clicking is the grade.

Everything is reproducible from neon-ladder — contract, scorer, runtime gate, runner, one-comment recipe.

Sources

Happy to answer setup questions. The playbook rows still marked as needing quality testing are exactly that: promising, wired, but not yet proven on real workloads. Treat them as experiments and validate on your own sessions before making them load-bearing.


reddit.com
u/stereohype — 1 month ago
▲ 39 r/StrixHalo+1 crossposts

Qwen3.8-27B (Q5_K_XL) on Strix Halo at 31 t/s decode: DFlash2 + Vulkan, the optimal setup

Dense 27B, meet DFlash2. On my Flow Z13 (Ryzen AI Max+ 395, Radeon 8060S, 128GB), Qwen3.8-27B hits 31 t/s burst at 80W and averages 26+ t/s across a full 90k-token generation, with every token paying for all 27B parameters. No MoE shortcuts this time.

Three things got it there: Inco AI's DFlash2 drafter, n-max 4, and a chat template that stops the model from drowning in its own reasoning. The last one surprised me most. The stock template's runaway thinking didn't just waste tokens, it collapsed draft acceptance at depth; fix the style and the speed decay disappears.

Last week's DeepSeek V4 Flash guide (https://www.reddit.com/r/LocalLLaMA/comments/1vlmh0b) covered the MoE side of this hardware. This post is the dense half.

Note: the writing is AI-assisted editing; the research, debugging, and every number are from my own runs on this machine.

Edit (Aug 20): the recommended config was revised after the burst table below was measured. Three findings landed on top of the initial numbers: n-max 4 beats 7 by 25% on long generations, the Sharp chat template fixes a runaway-reasoning failure mode and lifts sustained decode another 26%, and q8_0 drafter KV is a free +1.6% with bit-identical output. The launch command reflects all three; sustained expectations are now 26-29 t/s from 1k to 32k output tokens.

Edit (Aug 21): two more pieces landed. The 128k config is real now: 96k and 128k ctx with -ub 4096 are rock solid on this fork (a reported ub>1024 crash past 64k does not reproduce here), and a full 90k-token generation averaged 26.4 t/s with acceptance still climbing at the finish (0.77, mean accepted draft length 4.1). I also validated the whole stack inside a coding harness: pi + this server ran an 18-step tool-using task (read/write/edit/bash) plus a continuation turn with zero format errors and ~64% draft acceptance through tool traffic. The 90k numbers below replace the earlier 64k entry, which had stopped early at ~51k tokens.

The engine reports n_ctx_train 262144 and caps anything higher; the full spec config loads and runs healthy at -c 262144 (~55GB RAM), and since allocation costs nothing until filled there is no reason to set less if you have the memory.

Caveat: these are early benchmarks. The sustained matrix (both quants to 8k, Q5 to 90k), the Unsloth v2/v3 A/B, full-corpus PPL, and harness tool-use validation have landed; more workloads are still in progress.

TL;DR

  • Daily driver: UD-Q5_K_XL + incoai DFlash2 Q4_K_M drafter (n-max 4) + Nathan's v0.6.5, f16 KV, drafter KV q8_0, ubatch 4096, Sharp chat template. ~30 t/s burst, 25-29 t/s sustained out to 90k tokens, 256k context available (the model's training cap).
  • DFlash2 over the built-in MTP is +40% burst (30.2 vs 21.6 t/s at 70W) and +60% sustained (25.9 vs 16.3 t/s at 8k tokens). Lossless: output is bit-identical to unspec decode at temp 0, verified.
  • Q5_K_XL decodes faster than Q4_K_XL with DFlash2 on short generations (31.4 vs 28.5 t/s at 80W burst; Q5's 55.9% vs 44.9% acceptance pays for the extra bandwidth). On long generations the ranking flips: at 8k tokens Q4 + Sharp beats Q5 + Sharp (28.8 vs 27.5 t/s). I run Q5 for the quality at ~1-2 t/s sustained cost; all-long-form-code workflows should take Q4 + Sharp.
  • Pin your Unsloth revision: every number here is the pre-Aug-19 v2 file. Unsloth re-uploaded all UD quants as Dynamic v3 under the same filenames. v3 is better quality (PPL 13.84 vs 14.51) and faster on burst, but collapses at depth: 19.7 vs 27.5 t/s at 8k under the recommended config. The v2 download link is in Sources.
  • n-max 4, not 7. At depth, deep drafts get rejected and waste verify bandwidth: n4 is +25% over n7 on long generations, costs ~1.5% on short ones. Nathan's v0.6.5 independently landed on the same number, and my re-sweep under Sharp (where higher acceptance could have shifted the optimum) still says 4.
  • The Sharp template is the hidden lever. The stock template burned entire 4k and 8k budgets on reasoning with zero output on my agentic test prompt. With Sharp: the model answers, and sustained decode runs 25-29 t/s from 1k to 90k tokens, peaking at 32k (full-curve table below).
  • Skip the FP4 builds: a full PPL tier worse than Q4_K_XL, slower than this stack anyway, and riding a one-off engine (gotcha #3). Tested --spec-draft-p-min 0.7 too: -3% here, an MTP-era trick that doesn't transfer to block drafting.

Quick start

  1. Grab Nathan's v0.6.5: I benchmarked the identical payload when it was a dev tag (dev-20260819-0b0f35d); v0.6.5 is the same code, now gfx1151-validated. (v0.6.6/v0.6.7 landed after this bench; they fix DeepSeek V4 quantised-KV and Ling 3.0 tool parsing, and the Qwen path is unchanged, so these numbers carry over.)
  2. Download the target: Qwen3.8-27B-UD-Q5_K_XL (20.6GB). Pin revision 408fcc1807ab (pre-Aug-19 v2, the file I benched): plain main now serves Dynamic v3, which trades sustained speed for quality (details in the note below).
  3. Download the drafter: incoai/Qwen3.8-27B-DFlash2-GGUF, Q4_K_M (1.1GB). Caveat: the upstream DFlash2 PR is still in review, so if the drafter format changes before merge you may need to re-download.
  4. Download the template: chat_template.jinja from the Sharp repo, save as sharp.jinja.
  5. Launch:
llama-server \
  -m Qwen3.8-27B-UD-Q5_K_XL.gguf \
  -md Qwen3.8-27B-DFlash2-Q4_K_M.gguf \
  -ngl all -ngld all -fa on \
  -ctk f16 -ctv f16 -ctkd q8_0 -ctvd q8_0 \
  -c 262144 -np 1 \
  -b 4096 -ub 4096 \
  -t 16 -tb 32 \
  --spec-type draft-dflash --spec-draft-n-max 4 \
  --chat-template-file sharp.jinja \
  --jinja --host 127.0.0.1 --port 8080 --metrics

The -ctkd/-ctvd q8_0 flags quantize only the drafter's KV cache: +1.6% t/s, and I verified the output stays bit-identical. No boot params needed; the model is small enough that GTT defaults just work.

The bench matrix below was measured at -c 65536; every number carries over to bigger allocations unchanged, because a larger -c only reserves memory (decode at 8k depth measured identical at 65536, 98304 and 131072). The 256k cap in the command costs nothing until your sessions grow into it.

Results

Config-comparison table: 4-task streaming bench (python, math, JSON, long-prompt; short 100-200 token generations, warm runs), stock template, same engine build throughout.

Spec method n-max @70W @80W Acceptance
plain (no spec) - ~14 t/s - -
MTP n5/p0.50 5 21.6 t/s - 66.4%
DFlash2, Q4_K_XL 7 27.3 t/s 28.5 t/s 44.9%
DFlash2, Q5_K_XL 7 30.2 t/s 31.4 t/s 55.9%

Read those as burst rates. The number that matters for real work is sustained generation, measured with a fixed agentic coding prompt at n4 with drafter-KV q8_0, 70W:

quant + template 2k tokens 8k tokens 32k tokens 90k tokens
Q5_K_XL, stock 20.6 t/s 19.8-20.2 t/s - -
Q5_K_XL, Sharp 25.9 t/s 27.5 t/s 29.0 t/s 26.4 t/s avg
Q4_K_XL, stock 23.4 t/s 20.4 t/s - -
Q4_K_XL, Sharp 26.6 t/s 28.8 t/s not tested -

Two lessons in that table. Burst belongs to Q5 (its distribution sits closer to the drafter's training, so acceptance climbs and pays for the extra weight bytes); at 8k the ranking flips to Q4, because acceptance converges between the quants and Q4's ~2.3GB fewer weight bytes per pass wins. Q5 stays my daily driver for the PPL and burst, Q4 + Sharp is the pick for long-form code, and the 90k figure comes from a single full-context run, so treat it as indicative.

Second, and bigger: with the Sharp template, decode gains speed with depth instead of decaying.

The "spec decode collapses at long context" story turned out to be half a template problem. Stock-template output drifts into sprawling reasoning that the drafter can't predict, so acceptance falls from ~56% to ~24% and t/s follows.

Sharp's terse style keeps acceptance climbing as code accumulates: 0.57 at 8k, 0.72 at 32k, 0.77 at 90k. The depth test: a 90k-token generation, context filled to ~90k, averaged 26.4 t/s end to end (peak stretch 30) with mean accepted draft length 4.1 at the finish. Decode hits slow patches in the high teens past ~60k, but the cumulative rate never collapses; what's left at depth is attention cost in patches, not draft mismatch.

Acceptance percentages are not comparable across methods (DFlash2 drafts blocks in parallel, MTP sequentially), so judge by t/s. Per-task burst spans 21 to 40 t/s depending on workload; that spread is content, not noise.

Prefill: what to expect at depth

Shallow (3k prompt): ~298-302 t/s at ubatch 4096, both spec methods identical. Ubatch 4096 is the knee: 8192 measured flat, 2048 leaves ~5% on the table.

Deep context, f16 KV, llama-bench:

Model pp32768 pp65536
Q4_K_XL 265.1 t/s 222.1 t/s
Q5_K_XL 251.5 t/s 212.2 t/s

Why the deep numbers hold up: Nathan's fork carries a flash-attention prefill series (dequant-once scratch, KV contiguize, coopmat1 tuning), and the gains grow with depth. I ported that series onto the ROCmFPX fork to A/B it properly: +59% at 32k with f16 KV (165 to 263 t/s). At 64k the unpatched engine didn't just slow down, it threw vk::DeviceLostError on every model I tried, while the ported one ran 199-219 t/s.

The port is here with full numbers, and the findings went upstream.

Why not FP4? The reddit hype, checked

The ROCmFP4 builds were everywhere last week with 30+ t/s headlines, so I ran the whole stack. What holds up: the FP4 engine's Vulkan path genuinely is fast, and it beat the MTP-era configs (23 vs 21.6 t/s). What doesn't: it's slower than Q5+DFlash2, it measures a full PPL tier worse than Q4_K_XL, and the engine is a dead end.

Same-engine, same-method short-corpus perplexity (90KB wikitext-2 slice, c8192, n512, 2 chunks, f16 KV):

Quant Short PPL
Q5_K_XL 53.85
Q4_K_XL 57.26
ROCmFP4_FAST 59.06

The engine is also a one-off: the pinned build the benchmarks ran against was force-pushed out of the repo's history, and the prebuilt crashes at 64k f16 KV. Fun demo, not a daily driver.

Dynamic v3 note (updated Aug 20): Unsloth re-uploaded every UD quant with Dynamic v3 under the same filenames on Aug 19, so all numbers in this post are the v2 files. I A/B'd both Q5s under the recommended Sharp config, same session, 70W. v3 wins two of three: PPL 13.84 vs 14.51 (wikitext-2 full corpus, ~4x the error bars) and burst 30.6 vs 29.2 t/s at 73.7% acceptance.

Sustained is the one that keeps me on v2: at 8k tokens v3 collapses to 19.7 t/s vs 27.5 for v2, with acceptance decaying to 0.40 at depth. For agentic coding that's the whole game; for short generations and maximum quality, take v3 knowingly.

Reproducing my numbers means pinning the revision. The v2 files live at commit 408fcc1807ab (Aug 14, last before the re-upload): Q5 direct is huggingface.co/unsloth/Qwen3.8-27B-GGUF/resolve/408fcc1807ab/Qwen3.8-27B-UD-Q5_K_XL.gguf, or hf download unsloth/Qwen3.8-27B-GGUF Qwen3.8-27B-UD-Q5_K_XL.gguf --revision 408fcc1807ab. My Q4_K_XL numbers are v2-era too; current main serves a different Q4.

Gotchas (learned the hard way)

1. The stock template can burn your entire budget on thinking. On my agentic test prompt it produced zero content tokens at both 4k and 8k max_tokens: 8192 reasoning tokens, no answer, six minutes of GPU time. That failure mode, not hardware, is the biggest speed killer this model ships with.

The Sharp template fixes it and lifted sustained decode 26% on top.

2. n-max is a depth knob. 4/7/8 are within ~1.5% on short generations, but at 8k tokens n4 beats n7 by 25% (19.8 vs 15.8 t/s). Deep drafts get rejected as acceptance decays, so the long tail just costs verify bandwidth. Nathan's v0.6.5 release notes independently measured the same crossover (width 4: 21.1 t/s vs width 7: 16.3 at 32k), and re-sweeping under Sharp confirmed 4: n5 is par on burst and -2% at 8k.

3. DSpark is broken for this model in llama.cpp right now. The community drafter gives 15-16% acceptance and no speedup, and it's not the drafter's fault: I verified the GGUF metadata and bisected the failure into llama.cpp's DSpark draft generation itself. Details in ggml-org/llama.cpp#25618; use MTP or DFlash2 until it lands.

4. p-min does not transfer. A commenter reported --spec-draft-p-min 0.7 lifting DS4+MTP acceptance dramatically; on DFlash2 here it cost 3%. Block drafting changes the economics, pruning just shrinks the block.

5. First bench run is always cold. First DFlash2 pass read 24 t/s; warm runs 27+. The drafter warms up across requests, so run the workload twice before believing a number.

6. Dense vs MoE is the real choice. Qwen3-Coder-30B-A3B (3B active) prefills at ~1900 t/s on this box vs ~300 here; that's arithmetic, not tuning, so huge-prompt short-answer workloads should use the A3B. For agentic coding where answer quality is the product, this stack is the strongest thing I've run locally.

Sources

Happy to answer questions. More Strix Halo benchmarks and guides at r/LocalAiCore.

u/stereohype — 2 months ago
▲ 108 r/StrixHalo+1 crossposts

DeepSeek V4 Flash 0731 at 27+ t/s decode on Strix Halo — Vulkan + DSpark full guide

I've spent a week benchmarking DSv4 Flash 0731 on a Flow Z13 (Ryzen AI MAX+ 395, Radeon 8060S / gfx1151, 128GB LPDDR5X). This is what actually works on this hardware and what doesn't.

Note: the writing is AI-assisted editing; the research, debugging, and every number are from my own runs on this machine.

Edit (Aug 13): since this went up, Nathan shipped the v0.6.3 beta with sparse-attention prefill, and I ran it on my Flow Z13 — the deeper your context, the bigger the win (+39% at 32k, +78% at 64k vs dense attention), and decode stays untouched. Long-context prefill no longer tanks like it used to — full numbers in the prefill section.

TL;DR

  • You can run a ~300B MoE locally on a from-$2,920 AMD APU mini PC or laptop — roughly half the price of a DGX Spark — at 27+ t/s decode and ~285 t/s prefill. DSpark is the decode lever, Nathan's Vulkan fork is the prefill lever.
  • Both levers come from the same place: MoE kernel tuning. On ROCm, with identical hardware and model, prefill drops ~30% and DSpark goes from +46% to +0%. Supported upgrades don't fix kernel tuning.
  • Two config picks matter: q8_0 KV (doubles context to 131k, small quality tail, measured in gotcha #1) and the Q2K-Q8 drafter (frees ~4GB VRAM, no speed cost, gotcha #2). Everything else is table stakes.

Quick start

  1. Download the model + drafter from unsloth/DeepSeek-V4-Flash-0731-GGUF: the 4-file UD-IQ3_XXS split (~98GB) and a DSpark drafter, Q2K-Q8 (~7GB) or bf16 (~11GB).
  2. Patch the Q2K-Q8 drafter's header with the bf16 drafter's tokenizer if you use it (gotcha #2). Skip this step with bf16.
  3. Add the boot params amd_iommu=off amdgpu.gttsize=126976 ttm.pages_limit=32505856 ttm.page_pool_size=32505856 to your kernel cmdline (how depends on GRUB vs systemd-boot). The model won't load without these.
  4. Install Nathan's build — v0.6.1 for these exact numbers, or v0.6.3-beta1 (adds sparse-attention prefill, see the prefill section): Nathanw1014/strix-halo-llamacpp. Bundled RADV, no ROCm.
  5. Run the command block below with your model and drafter paths.
  6. Connect a client to http://127.0.0.1:8080 (Open WebUI, LM Studio, or any OpenAI-compatible frontend).

Setup

How I measured. All numbers are from one machine, and every comparison is same-session so thermals and clocks match. I used a streaming client bench with the same prompt, 3 trials per config, and I report ranges, not just bests, wherever the run-to-run spread is real (gotcha #7). Isolated prefill/decode numbers come from llama-bench, and DGX Spark numbers are cited from external sources at the bottom.

Hardware and config.

  • Target: Unsloth UD-IQ3_XXS, ~98GB, 4-file split
  • Draft: DSpark drafter, Q2K-Q8 (~7GB, header-patched, gotcha #2) or bf16 (~11GB)
  • KV: q8_0
  • Backend: Vulkan via Nathan's v0.6.1 build with bundled RADV, no ROCm. It's a pinned release you install and run as-is, all flags are in this script, nothing to tune day to day. The v0.6.3 beta adds sparse-attention prefill (see the prefill section); these numbers are the validated v0.6.1 line.
  • OS: CachyOS, kernel 7.1.6, Mesa 26.1.6
llama-server \
  -m DeepSeek-V4-Flash-0731-UD-IQ3_XXS-00001-of-00004.gguf \
  -md DSV4-Flash-DSpark-draft-bf16.gguf \
  -ngl all -ngld all \
  -fa on \
  -ctk q8_0 -ctv q8_0 \
  -c 131072 -np 1 \
  -b 2048 -ub 2048 \
  --spec-type draft-dspark \
  --spec-draft-n-max 64 \
  --jinja --host 127.0.0.1 --port 8080

Boot params: amd_iommu=off amdgpu.gttsize=126976 ttm.pages_limit=32505856 ttm.page_pool_size=32505856 (without these the model won't load).

Power: z13ctl+ profile, CPU boost off with min freq pinned. Decode is bandwidth-bound, so the downclock costs nothing and keeps the tablet cool.

VRAM: 4GB BIOS carve-out, GTT covers the rest. Committed footprint is ~105GB (98GB model + 7GB Q8 draft), leaving ~23GB for KV and compute. The bf16 draft pushes that to ~109GB / ~19GB.

Results

Best client-side observation on 4096-token generation. Server-side timing for similar runs consistently shows 23-24 t/s. See gotcha #7 for the variance.

Metric Value
Decode (best client-side, 4096 tok) 27.13 t/s
Decode (server-side typical) 23-24 t/s
Decode (peak 3s window) 35.27 t/s
Plain decode (no spec, same engine) 18.55 t/s (llama-bench)
Plain prefill (no spec, same engine) 284.98 t/s (llama-bench, ub2048)
Max prefill (f16 KV, 65k ctx, no DSpark) 293.33 t/s (llama-bench, ub2048)
DSpark acceptance 0.7209 best, mean accepted len 4.02
Context 131,072 (q8_0 KV)
GPU util / CPU util ~92% / ~1%

131k context runs on q8_0 KV, which doubles context but costs a little quality. Full measurement of that tradeoff is in gotcha #1.

Cross-platform comparison

Both are unified-memory APUs with similar LPDDR5X bandwidth (~256 vs ~273 GB/s). The Strix Vulkan rows are Nathan's fork, not upstream llama.cpp. Its hand-tuned MoE kernels (the GGML_VK_MMID_* flags) are most of why Vulkan leads here, not the API itself (gotcha #3).

The ROCm row is stock mainline llama.cpp.

Platform Engine Backend Spec Decode Prefill @2K
Strix Halo Nathan fork v0.6.1 Vulkan none 18.55 t/s 284.98 t/s
Strix Halo llama.cpp (mainline) ROCm 7.14 none 13.35 t/s 191 t/s
Strix Halo ds4 (upstream) ROCm none 12.5 t/s 122 t/s
DGX Spark ds4 (upstream) CUDA none 14.2 t/s 392 t/s
DGX Spark Entrpi/ds4 fork CUDA none 20.0 t/s ~960 t/s
Strix Halo Nathan fork v0.6.1 Vulkan DSpark 27.13 t/s ~285 t/s
DGX Spark Entrpi/ds4 fork v0.5.6 CUDA DSpark 27.3 t/s ~960 t/s

Notes: ds4 is ROCm/HIP-only, no Vulkan backend. Its 122 t/s figure predates ROCm 7.14 and used the Q2 quant. Prefill is unaffected by speculation, and decode is bandwidth-bound, so context depth barely moves it.

Strix numbers use the header-patched Q8 drafter where noted (gotcha #2).

Decode is a dead heat. Plain decode clusters in the 12-20 t/s range everywhere, DSpark brings both platforms to ~27 t/s. The same-engine boost is 1.46x on Strix (27.13 / 18.55) vs 1.37x on DGX Spark (27.3 / 20.0). Speculation is the only lever on bandwidth-bound decode, and it behaves the same on both platforms.

Prefill is where platforms diverge. No engine runs optimally on both platforms, so the honest comparison is the best of each: llama.cpp Vulkan at 284.98 t/s on Strix vs the D2R CUDA fork at 960 t/s on DGX, a 3.37x gap. The fork's D2R pass is a clean 2.45x over its own CUDA baseline on identical hardware, which suggests most of the gap is closeable with better shaders.

I measured the Strix side with GPU perf counters (amdgpu gpu_metrics via amdgpu_top, 1 Hz during llama-bench pp2048, same v0.6.1 build and q8_0/ub2048 config; 274–285 t/s across sessions). During prefill the shader array is ~94% busy while DRAM traffic is only ~74 GB/s — about 30% of the ~256 GB/s bus. Strix prefill is shader-bound, not bandwidth-bound.

Effective compute works out to ~4.6 TFLOPS, under 10% of gfx1151's FP16 MMA peak. At ub2048 each routed expert sees exactly 48 tokens (2048 × 6 active experts ÷ 256 total = 48), so the M=48 tiles can't fill the tensor cores. The ~26 GB/s of writes is the dequant-to-fp16 staging pass — real traffic, but the bus absorbs it, so fusing it away wouldn't move prefill much.

What Strix would actually score with D2R-quality shaders remains a projection, so it stays parked in the footnote.

Since writing this, Nathan shipped the v0.6.3 sparse-prefill line (alpha1 → beta1, prefill numbers unchanged), and I validated it on this 128GB box: the raw/selected split is +26% over the single coopmat kernel, and vs dense attention prefill is +39% at 32k depth and +78% at 64k, because the split flattens the depth penalty (−8% vs −28% from 32k to 64k). That confirms the shader-bound diagnosis from the attention side too — a full 3x was on the table in the FA stage at depth, now realized. It's attention-stage work, so the MoE-GEMM lever (D2R-style shaders) in the footnote is still unclaimed, and decode is untouched.

Footnote (prefill math): MoE prefill doesn't scale with raw FP16 TFLOPS. The compute ratio is 4.2x but the measured gap is 3.37x, and expert GEMMs are tiny (M=48 at ub2048: 2048 tokens × 6 active experts ÷ 256 total), so tensor cores stay underutilized — on Strix now measured directly (94% shader busy at ~30% of DRAM peak, ~4.6 effective TFLOPS). If Vulkan's MoE shaders matched D2R quality, Strix would project to ~698 t/s (284.98 × 2.45), leaving a residual gap of ~1.4x. The 698 t/s figure remains a projection, not a measurement.

Gotchas (learned the hard way)

1. q8_0 KV: faster + more context, but it costs quality. The speed crossover first, measured same engine and prompt shape on single runs per config:

KV type Context 1024 tok 4096 tok
f16 65k 21.99 t/s 19.32 t/s
q8_0 131k 20.70 t/s 22.74 t/s

At short context q8_0 is ~6% slower because per-element dequant overhead dominates a small cache. At long context it's +18% faster because it halves bytes-per-step, and it doubles context (131k vs 65k). This is a crossover observation, not a precision delta, so treat the percentages as directional.

The real cost is quality. I ran llama-perplexity --kl-divergence (bf16 baseline vs q8_0) on ~50k tokens of prose with the same model and Nathan's build:

Metric q8_0 vs bf16 (this work, Moby Dick) erazortt's DS4F (wikitext)
Same top-p 97.84% (2.16% flip) 87.19% (13% flip)
KLD mean 0.0126 0.1459
KLD 99.9% 0.81 4.535
RMS Δp 5.12% 11.88%

Mean PPL barely moves (1.0015x), but the tail is real: the 99.9th-percentile KLD is 64x the mean, with ±40% probability swings on rare tokens. The average stays clean while a thin slice of tokens goes somewhere else. My numbers look ~10x better than erazortt's, but that's corpus: Moby Dick (PPL 1.54) is far more predictable than wikitext (PPL ~5.8).

On harder text, expect degradation closer to his figures, so treat mine as a lower bound. Root cause: DS4F's MLA is built around fp8 KV natively, which llama.cpp doesn't expose, so it's f16 or q8_0 (worse dynamic range than the model expects). That's a tooling gap, not a choice.

For precision or agentic work, stay on f16 KV at 65k context. Methodology thread: https://www.reddit.com/r/LocalLLaMA/comments/1vduxth

2. The draft doesn't need to be bf16. Use the Q2K-Q8 drafter and keep ~4GB VRAM. The ~7GB quantized drafter crashed at first with invalid token = -1, but that was a broken conversion, not a precision problem. The file had its tokenizer metadata stripped and declared tokenizer.ggml.model = no_vocab, under which llama.cpp nulls every special-token id (including the draft mask token) to -1. Patching the header with the bf16 drafter's tokenizer fixes it.

Same-session, same q8_0 KV config, 4096-token runs: decode 25.3-27.8 t/s (best 27.78, mean ~26.3 vs bf16 ~24) and draft acceptance 0.657-0.740 (mean 0.687 vs bf16 0.606). Both differences sit inside the run-to-run noise band from gotcha #7, so count them as "no speed or acceptance cost", not wins. The drafter is a few billion params in a bandwidth-bound loop, so Q8 quantization costs nothing measurable.

The 4GB of freed VRAM is the real take.

3. Vulkan beats ROCm on gfx1151, but "supported ≠ tuned". Fair pushback in the comments that I'd judged ROCm on the ds4 engine and a pre-7.14 build. So I benched mainline llama.cpp + ROCm 7.14 properly: gfx1151 went officially supported on 2026-07-16 and loads natively now, no HSA_OVERRIDE or TheRock hacks. Same model, same session, q8_0 KV, -fa on, -ub 2048, 80W sustained:

metric Vulkan v0.6.1 ROCm 7.14 (mainline) ROCm
prefill pp2048 284.98 t/s 191.28 t/s 0.67x
prefill pp8192 265.26 t/s 163.47 t/s 0.62x
plain decode 18.55 t/s 13.35 t/s 0.72x
DSpark decode 27.13 t/s @ 0.7209 13.35 t/s @ 0.613 0.49x
DSpark speedup vs plain +46% +0% n/a
GPU load ~99% steady ~53-96% (bursty)
CPU spillover ~1% ~15%
temp 81-84°C 82-90°C (spiky)

ROCm 7.14 fixed support. It did not fix kernel tuning for DSv4's MoE: about 30% behind on prefill, 28% on decode, and the gap widens with context. From pp2048 to pp8192, ROCm drops 14.5% while the fork drops 6.9%, so the "ROCm wins long context" story doesn't hold on this APU.

DSpark acceptance is a dead heat (means 0.606 vs 0.613), so the drafter produces equally good tokens on both, but on ROCm the draft runs on the same slow MoE path and its cost cancels the ~4 accepted tokens per pass. Net: +0% on ROCm, +46% on Vulkan.

The mechanism is concrete. ROCm spills ~15% of work to the CPU, which on a unified-memory APU steals bandwidth from the GPU it's trying to help. That's the bursty load and the temperature spikes.

The fork's MoE kernels keep everything on-GPU and back to back. So this is stock-mainline-ROCm's generic gfx1151 kernels vs a hand-tuned MoE Vulkan path, not "ROCm vs Vulkan" as APIs. On a dense model, or any MoE where those flags don't fire, ROCm can absolutely win.

The broader ask: the 8060S has the bandwidth to be a real local-inference answer to the DGX Spark, and decode is already a dead heat. What's missing isn't hardware or a support checkbox, it's kernel tuning. Right now one community member's Vulkan fork beats AMD's official ROCm backend on AMD's flagship APU by ~30%, and turns DSpark from a +46% win into 0%.

If AMD wants Strix Halo taken seriously against CUDA, the MoE kernels in ROCm need the same attention the Vulkan path got from one person. Supported ≠ tuned.

4. Ubatch 2048 is the VRAM ceiling with DSpark. At 109GB committed (98 + 11GB bf16 draft), ubatch 4096 OOMs. The Q2K-Q8 draft (gotcha #2) moves that ceiling to ~105GB / ~11GB free, but since ubatch >2048 isn't a prefill win on this MoE, there's no reason to chase it. Measured same-session on the ngram config (f16 KV): pp2048 at ub2048 = 293.33 t/s and at ub8192 = 292.77 t/s, identical within noise.

Prefill saturates at ub2048 here. The ngram config does edge DSpark on prefill (~293 vs ~285), but that ~3% comes from f16 KV (which halves context to 65k), not from ub8192 or dropping the draft. It's a context-vs-prefill tradeoff, not a free speed win.

Decode drops to 18.12 t/s without DSpark, and ngram's cache from a 2.3K-token prompt is too sparse for general prose (3 speculation attempts over 4096 tokens). Ngram is a capacity lever, not a speed lever.

5. v0.6 regression: upgrade to v0.6.1. Nathan's v0.6 has a stride bug in TENSOR_ALLOW_RESHAPE that miscomputes strides for block-quantized tensors. Vulkan silently falls back 43 attention layers to CPU, showing 75% GPU and 50% CPU while running at half speed. v0.6.1 fixes it. Bug report: https://github.com/Nathanw1014/strix-halo-llamacpp/issues/2

6. GPU util drops to ~92% on v0.6.1 (99% on the 0.4 build, same config). The new MoE shaders (ROWLISTS, SMALLN, BM64, M128, F16B, FA_WAVE32) finish each kernel faster, so fixed dispatch overhead becomes a bigger fraction. Lower utilization, higher throughput (27.13 vs 20.88 t/s). Don't let the number fool you.

7. DSpark decode varies run-to-run on the same prompt. Best client-side observation: 27.13 t/s at 0.7209 acceptance. The same session's three trials spanned 20.96–27.13 t/s and 0.507–0.721 acceptance, the drafter warming up across requests — variance tracks GPU thermal/clock state and swings with output content. If you benchmark anything here, run the same prompt 5+ times and report the range, not a single number.

Sources

More Strix Halo benchmarks and guides at r/LocalAiCore if you're interested in this hardware.

u/Dazzling_Equipment_9 — 2 months ago
▲ 42 r/linuxhardware+2 crossposts

Released: z13-tablet-kit for Linux on the 2025 Flow Z13

Following the Z13CTL+ and Z13GUI+ releases, I have released the third part of the stack: z13-tablet-kit.

https://github.com/aic0d3r/z13-tablet-kit

It adds posture-aware tablet support for the AMD Strix Halo Flow Z13 (GZ302) on KDE Plasma 6 / Wayland.

The kit automatically distinguishes between three modes:

- Desktop: dock or external display connected

- Laptop: keyboard folio attached

- Tablet: neither attached

It then adapts the system for each posture:

- Enables touchscreen and auto-rotate in tablet mode

- Uses 175% display scaling in tablet mode and 150% otherwise

- Enables the on-screen keyboard only in tablet mode

- Prevents the on-screen keyboard from appearing over the physical keyboard

- Restarts the keyboard after posture changes to prevent clipped layouts

- Provides two-finger scrolling in terminals, browsers, document viewers and other applications

- Optionally maps a stationary two-finger hold to a real right-click for context menus

- Creates the missing tablet-mode switch needed by Plasma’s “rotate only in tablet mode” option

- Disables the unreliable folio touchpad by default, with a guarded opt-in toggle

It uses Plasma’s native rotation support and does not require kernel patches or a root daemon. Only two udev rules require root access; the posture and gesture services run in the active user session.

The full stack consists of:

z13-tablet-kit

Posture detection, tablet mode and touch input

https://github.com/aic0d3r/z13-tablet-kit

Z13CTL+

Display coordination, persistent settings and hardware control

https://github.com/aic0d3r/z13ctl-plus

Z13GUI+

Optional touchscreen overlay with live posture status and tablet controls

https://github.com/aic0d3r/z13gui-plus

The kit currently targets Arch/CachyOS with KDE Plasma 6 on Wayland. z13ctl-plus is required, while z13gui-plus is optional.

Installation instructions and technical details are in the README. Testing, bug reports and contributions are welcome, especially from other GZ302 owners.

u/stereohype — 2 months ago

Linux controls and telemetry for the Strix Halo Flow Z13

I have released Z13CTL+ and Z13GUI+, an expanded Linux control and telemetry stack for the Strix Halo-based 2025 ROG Flow Z13.

Before anything else, special thanks to dahui, who created the original z13ctl and z13gui projects. These releases build on that foundation and would not exist without the work already done there.

My goal was to add more automation, tuning and telemetry while preserving compatibility with the original commands, services, paths and API.

Z13CTL+
https://github.com/aic0d3r/z13ctl-plus

Z13GUI+
https://github.com/aic0d3r/z13gui-plus

Z13CTL+ is the daemon and CLI. It adds:

  • Automatic switching between plugged-in and battery configurations
  • Independent TDP, fan-curve and undervolt overrides
  • AMD P-State controls for minimum CPU frequency, EPP and boost
  • CPU, GPU, NPU, memory, VRAM, temperature, fan, battery and power telemetry
  • KDE display posture, rotation, scale and refresh-rate coordination
  • Recovery for lighting and firmware fan behavior
  • CLI and API access for scripting and automation

Z13GUI+ is a GTK4 interface designed around the Z13 touchscreen. It includes:

  • A live hardware and power overview
  • Separate AC and battery profiles
  • CPU policy controls
  • 60 Hz and 180 Hz refresh-rate switching
  • Battery information
  • Fan presets and tuning controls
  • Clearer feedback when changing tuning values

The tools are currently focused on the 2025 Flow Z13, but the telemetry side may also be interesting to other Strix Halo users, especially the CPU, GPU, NPU, unified-memory, VRAM, power, clock and temperature monitoring.

Packages are available through the AUR, along with DEB, RPM and portable release archives.

I would be interested in hearing what telemetry and power-management controls are exposed on other Strix Halo systems under Linux, and whether parts of this could eventually be generalized beyond the Z13.

Testing, bug reports and contributions are welcome.

u/stereohype — 3 months ago
▲ 24 r/FlowZ13

New Linux tools for the 2025 Flow Z13

I have been using the 2025 Flow Z13 as my main Linux machine and wanted better control over power behavior, tuning, display settings and telemetry without maintaining a collection of separate scripts.

Before getting into the new features, special thanks to dahui, who created the original z13ctl and z13gui projects. Z13CTL+ and Z13GUI+ are built on that foundation and would not exist without the work already done there.

My goal was to expand the projects while preserving compatibility with existing commands, services, paths, integrations and API usage.

I have now released both projects:

Z13CTL+
https://github.com/aic0d3r/z13ctl-plus

Z13GUI+
https://github.com/aic0d3r/z13gui-plus

Z13CTL+ is the daemon and CLI. It adds:

  • Automatic switching between plugged-in and battery configurations
  • Independent TDP, fan-curve and undervolt overrides
  • AMD P-State controls for minimum CPU frequency, EPP and boost
  • CPU, GPU, NPU, memory, VRAM, fan, battery and power telemetry
  • KDE display posture, rotation, scale and refresh-rate coordination
  • Recovery for lighting and firmware fan behavior
  • CLI and API access for automation and custom integrations

Z13GUI+ is a GTK4 interface designed around the Z13 touchscreen. It includes:

  • A live hardware and power overview
  • Separate AC and battery profiles
  • CPU policy controls
  • 60 Hz and 180 Hz refresh-rate switching
  • Battery information
  • Fan presets and tuning controls
  • Clearer feedback and safer handling of tuning values

Both projects preserve compatibility with the original z13ctl and z13gui interfaces, so existing workflows using the original command names, daemon paths and API should continue to work.

Packages are available through the AUR, along with DEB, RPM and portable release archives.

The projects are currently focused on the 2025 Flow Z13. Testing from other owners would be very useful, especially across different Linux distributions, desktop environments and BIOS versions.

https://preview.redd.it/5esna8zrl1gh1.jpg?width=2560&format=pjpg&auto=webp&s=4fc621e0d3e34edb5f74aa6010268b1d3922b282

Bug reports, feedback and contributions are welcome.

reddit.com
u/stereohype — 3 months ago