CachyPC · single-GPU local inference lab

Local LLM benchmarks on 16 GB of consumer AMD

Every number on this page was measured on one desktop — a Radeon RX 9070 XT with 16,304 MiB of VRAM — running llama.cpp b10463 against the ROCm backend. Throughput, VRAM fit, long-context recall, tool calling, coding quality and KL-divergence against unquantised weights, for seventeen models. Configurations that failed are recorded next to the ones that worked, because a rejected row is the part that saves you the afternoon.

GPU RX 9070 XT · Navi 48 · gfx1201 · RDNA4 · 16,304 MiB  |  CPU Ryzen 9 5950X 16C/32T  |  RAM 64 GB DDR4-3200  |  OS CachyOS 7.1.5  |  ROCm 7.2.53211  |  llama.cpp b10463 (7c35571e5)

17
models measured, MoE / dense / hybrid / Mamba2 / MLA
1,048,576
largest context served — natively trained, not YaRN-stretched
2.1×
ROCm prompt throughput over Vulkan on K-quants
13×
KLD gap between Q6_K and Q3_K_XL against BF16
2.40×
generation from the MTP head llama.cpp discards by default
01 — The machine

The hardware is the argument

Sixteen gigabytes is the binding constraint on everything below. Every -ncmoe, every KV quantisation choice and every -ub value is bought against it. The rest of the box matters because expert layers that don't fit on the card get streamed from DDR4 at ~40 GB/s instead of read from VRAM at ~640 GB/s.

Machine under test — VRAM read from /sys/class/drm/card1/device/mem_info_vram_used
ComponentSpecWhy it shows up in the results
GPURX 9070 XT · Navi 48 · gfx1201 · RDNA4 · 16,304 MiBThe binding constraint on this entire page
CPURyzen 9 5950X · 16C/32T · 64 MiB L3 · 5.09 GHzRuns CPU-offloaded expert layers. -t 32 collapses generation to 23.6 ± 4.6 tok/s on SMT contention — leave threads at 16
RAM64 GB DDR4-3200 · 4 × 16 GB · dual channel~40 GB/s ceiling on every -ncmoe row. Two mismatched kits; the 3600 pair is downclocked to the 3200 SPD
StorageCrucial BX500 1 TB — SATA, ~540 MB/sNot NVMe. Cold loads cost ~93 s for a 49 GB model, ~30 s for 12.5 GB. Idle-sleep reloads pay it again
OSCachyOS · kernel 7.1.5
ROCm7.2.53211 · GGML_HIP=ON · AMDGPU_TARGETS=gfx1201Wins K-quants by 1.7–2.1× on prompt processing
llama.cppb10463 · 7c35571e5 · 2026-08-17Tool calling was broken on e583f3b4f and is fixed here

A BIOS update changes none of it — measured. Board moved from BIOS 2407 (2021) to 3636 (2026), roughly four and a half years of AGESA updates. Memory still trains at 3200 on all four DIMMs, and Qwen3.6 re-run under identical conditions moved within noise: prompt up 1–2 %, generation down 1–2 %, both directions. 3200 is not a firmware limitation, it is what four mostly dual-rank DIMMs across two kits will do on this memory controller.

02 — Backend

ROCm for everything, and why Vulkan was retired

Measured head-to-head with llama-bench, -fa 1 throughout. llama-bench defaults flash attention off, but llama-server resolves auto → enabled, so -fa 0 numbers do not describe real serving — an earlier revision of this record got that wrong.

ROCm vs Vulkan — prompt tok/s / generation tok/s
ModelQuantROCm pp / tgVulkan pp / tgUse
Qwen3.6-35B-A3B (-ncmoe 40 -ub 512)Q4_K_M706.0 / —333.4 / 29.3ROCm
Gemma 4-26B-A4B (-ncmoe 8)Q4_K_M1949.9 / 50.31064.7 / 48.0ROCm
GPT-OSS-20B shallowMXFP45529.9 / 148.54952.0 / 180.8was Vulkan
GPT-OSS-20B @131k depthMXFP41035.2 / 70.51063.7 / 22.3ROCm
Qwen3.8-27B dense, -ngl 99Q3_K_XL1329.4 / 30.641161.8 / 16.91ROCm
Laguna XS.2 33B-A3B (-ncmoe 16)Q4_K_M1244.4 / 55.28661.7 / 48.19ROCm

K-quants

1.8–2.1×ROCm wins prompt unambiguously, and wins generation too once flash attention is on (Gemma 50.3 vs 48.0).

MXFP4, shallow

57 pp : 1 tgThe crossover ratio. Below it Vulkan was faster overall; above it ROCm. One preset ever collected that advantage.

MXFP4, at depth

3.2×Vulkan's generation collapses to 22.3 tok/s at 131k under VRAM pressure. ROCm holds 70.5 at the same prompt speed.

Vulkan's only remaining advantage was shallow MXFP4 generation on exactly one preset, and router mode always spawned children from the ROCm binary — so in daily use that advantage was never actually being collected. build-vulkan/ was dropped on 2026-08-17, trading 18 % of shallow generation on one preset for a single backend with no version skew. It has been rebuilt twice since, from the same checkout, to spot-check new models: Qwen3.8-27B gives ROCm +81 % generation and Laguna XS.2 gives it +88 % prompt — the widest gaps in the table. The routing decision holds.

03 — Why long context fits at all

Attention topology, not parameter count

Three models here are hybrid attention: full_attention_interval = 4 means only every 4th layer keeps a KV cache, the rest carry a constant-size recurrent state that does not grow with context. That is the whole reason 1M fits in 16 GB. But hybrid attention is not by itself a promise of cheap context — read head_count_kv and block_count alongside the interval.

KV cost per token — the number that decides long-context viability
ModelLayersLayers with KVKV headsB / token (q8_0)KV @1M
Nemotron-3-Nano-30B-A3B52623,2643,264 MiB
Qwen3.6-35B-A3B4010210,88010,880 MiB
Qwen3-Coder-Next4812213,05613,056 MiB
Qwen3.5-9B-Uncensored328417,408
GLM-4.7-Flash (MLA)4747latent 576≈27,915
Qwen3.8-27B6416434,81634,816 MiB
Qwen3-Coder-30B-A3B4848452,224~52 GB
Devstral Small 2 24B4040887,040unreachable

Qwen3.8-27B is the counter-example that matters. Same full_attention_interval = 4, but 4 KV heads instead of 2 and 64 layers instead of 40–48 — so 16 layers cache at double the per-layer cost. Confirmed exactly, not estimated: asking for 131,072 tokens of q8_0 KV fails with failed to allocate ROCm0 buffer of size 4563402752, which is 4,352 MiB, precisely 16 × 4 × 256 × 2 × 1.0625 × 131072.

MLA compression beats sparsity. GLM-4.7-Flash caches a single compressed latent per layer rather than per-head K and V — 576 elements per layer per token, applied at every layer. Measured empirically at ≈27,915 B/token, within 3 % of the formula, and still cheaper than Qwen3.8's sparse-but-fat 34,816.

04 — Inventory

Seventeen models

Everything that has been through the suite. Rows marked deleted are no longer on disk — their measurements stand, and each was removed for a stated reason rather than quietly dropped.

Model inventory — total / active parameters, architecture, quant, file size
ModelTotalActiveTypeQuantSizeDisk
GPT-OSS-20B21B~3.6BMoEMXFP411.3 GByes
Qwen3.6-35B-A3B35B~3BMoE hybrid, 40LUD-Q4_K_M20.6 GByes
Gemma 4-26B-A4B25.2B~3.8BMoE, 30LUD-Q4_K_M15.8 GByes
Gemma 4-E2B~4.66B~4.66BDense, 35LBF169.3 GBdeleted
Qwen3-Coder-Next80B~3BMoE hybrid, 48LUD-Q4_K_M49.3 GByes
Muse Glimmer 30B30B30BDense SWA, 52LUD-Q3_K_XL12.4 GByes
Qwen3.8-27B27B27BDense hybrid, 64LUD-Q3_K_XL (Dyn 2.0)12.5 GByes
Qwen3.8-27B27B27BDense hybrid, 64LUD-Q3_K_XL (Dyn 3.0)12.2 GByes
Qwen3.8-27B27B27BDense hybrid, 64LUD-IQ4_XS13.3 GByes
Qwen3.8-27B27B27BDense hybrid, 64LUD-IQ3_XXS10.2 GByes
Qwen3.5-27B-Uncensored26.9B26.9BDense hybrid, 64LQ3_K_M12.4 GByes
Qwen3.5-9B-Uncensored8.95B8.95BDense hybrid, 32LQ8_08.9 GByes
GLM-4.7-Flash30B~3BMoE MLA, 47LUD-Q4_K_XL16.3 GByes
Laguna XS.2 (Poolside)33B~3BMoE hybrid + SWA, 40LQ4_K_M / Q8_018.9 / 33 GByes
Nemotron-3-Nano-30B-A3B31.6B~3.5BMoE Mamba2 hybrid, 52LUD-Q4_K_XL22.8 GBdeleted
Devstral Small 2 24B23.6B23.6BDense full-attn, 40LQ4_K_M14.3 GBdeleted
Instella-MoE-16B-A3B-Think15.86B~3BMoE, fork-only archQ4_K_M9.75 GBdeleted

Also measured and since removed: Qwen3-Coder-30B-A3B (Q8_0, 32.5 GB), Qwen2.5-Coder-14B and -32B, GPT-OSS-120B (MXFP4, 59 GB).

05 — Verified configs

What actually loads, and the flags it takes

Every row below was actually loaded, confirmed to reach server is listening, with VRAM read while resident. All include -ngl 99 unless noted. Contexts are exact token counts, not rounded labels.

How full the card gets

VRAM at load against the 16,304 MiB ceiling · selected presets

model + KV + compute buffers free
Qwen3.8-27B Q3_K_XL v3
128K · q4_0 · -ub 512
15,665 · 376 free
Qwen3.8-27B IQ3_XXS
192K · q4_0 · -ub 512
15,355 · 672 free
Qwen3.8-27B IQ4_XS
32K · q8_0 · -ub 1024
15,343 · 955 free
Laguna XS.2 Q8_0
256K · -ncmoe 34
14,956 · 1,348 free
Qwen3.5-9B-Unc Q8_0
256K native · q8_0
14,524 · 1,304 free
Nemotron-3-Nano
1M native · q4_0 · -ub 256
14,104 · 2,200 free
Qwen3.8-27B Q3_K_XL v3
32K · q8_0 · -ub 1024
14,018 · 2,023 free
GLM-4.7-Flash
202,752 native · -ncmoe 28
14,017 · 1,856 free
Qwen3-Coder-Next 80B
256K · -ncmoe 42
13,894 · 2,410 free
Gemma 4-E2B BF16
131K native · no KV quant
7,142 · 9,162 free
Bars are peak VRAM at load as a fraction of 16,304 MiB. The "free" figure quoted is the one recorded for that row at measurement time; older rows include the desktop's own baseline, which drifted between 263 and 442 MiB — and as much as 306 MiB across a single session. Newer rows report model-attributable VRAM against a stated baseline. Anything under ~300 MiB free should be read as fragile rather than as a fit.

Per-model presets

Verified server configurations — all on the ROCm build
ModelContextExtra flagsVRAM MiBFree
Qwen3.6-35B-A3B32K-ncmoe 16 -ub 1024 -b 2048 -fa on -ctk q8_0 -ctv q8_014,9321,372
128K-ncmoe 20 -ub 1024 -b 2048 -fa on -ctk q8_0 -ctv q8_014,0902,214
256K-ncmoe 24 -ub 1024 -b 2048 -fa on -ctk q8_0 -ctv q8_014,4711,833
Qwen3-Coder-Next 80B32K-ncmoe 38 -ub 1024 -b 2048 -fa on -ctk q8_0 -ctv q8_013,4442,860
128K-ncmoe 40 -ub 1024 -b 2048 -fa on -ctk q8_0 -ctv q8_013,1813,123
256K-ncmoe 42 -ub 1024 -b 2048 -fa on -ctk q8_0 -ctv q8_013,8942,410
1M -ncmoe 46 -ub 256 -b 1024 -fa on -ctk q4_0 -ctv q4_013,8742,430
Gemma 4-26B-A4B32K-ncmoe 814,3381,966
128K-ncmoe 1214,4371,867
256K-ncmoe 2013,8112,493
Qwen3.8-27B Q3_K_XL v332K-ub 1024 -b 2048 -fa on -ctk q8_0 -ctv q8_014,0182,023
64K-ub 1024 -b 2048 -fa on -ctk q8_0 -ctv q8_015,295746
128K-ub 512 -b 2048 -fa on -ctk q4_0 -ctv q4_015,665376
Qwen3.8-27B IQ3_XXS192K-ub 512 -b 2048 -fa on -ctk q4_0 -ctv q4_015,355672
Qwen3.8-27B IQ4_XS32K-ub 1024 -b 2048 -fa on -ctk q8_0 -ctv q8_015,343955
Qwen3.5-9B-Uncensored32K-ub 1024 -b 2048 -fa on -ctk q8_0 -ctv q8_09,3846,444
128K-ub 1024 -b 2048 -fa on -ctk q8_0 -ctv q8_011,5874,181
256K native-ub 1024 -b 2048 -fa on -ctk q8_0 -ctv q8_014,5241,304
GLM-4.7-Flash32K-ncmoe 12 -ub 1024 -b 2048 -fa on -ctk q8_0 -ctv q8_014,3571,337
128K-ncmoe 20 -ub 1024 -b 2048 -fa on -ctk q8_0 -ctv q8_014,7431,130
202,752 native-ncmoe 28 -ub 512 -b 2048 -fa on -ctk q8_0 -ctv q8_014,0171,856
Laguna XS.2 Q4_K_M32K-ncmoe 16 -ub 1024 -b 2048 -fa on -ctk q8_0 -ctv q8_014,4181,886
128K-ncmoe 20 -ub 1024 -b 2048 -fa on -ctk q8_0 -ctv q8_015,0581,246
256K-ncmoe 26 -ub 1024 -b 2048 -fa on -ctk q8_0 -ctv q8_015,814490
Nemotron-3-Nano-30B-A3B32K-ncmoe 24 -ub 1024 -b 2048 -fa on -ctk q8_0 -ctv q8_014,7471,557
128K-ncmoe 24 -ub 1024 -b 2048 -fa on -ctk q8_0 -ctv q8_015,0601,244
256K-ncmoe 24 -ub 1024 -b 2048 -fa on -ctk q8_0 -ctv q8_015,723581
512K-ncmoe 28 -ub 512 -b 2048 -fa on -ctk q8_0 -ctv q8_014,7531,551
1M native-ncmoe 32 -ub 256 -b 1024 -fa on -ctk q4_0 -ctv q4_014,1042,200
Devstral Small 2 24B32K-ub 512 -b 2048 -fa on -ctk q4_0 -ctv q4_015,663641
Gemma 4-E2B BF16131K native-ub 1024 -b 2048 -fa on7,1429,162
Muse Glimmer 30B32KDFlash, -ub 51215,422882
64KDFlash, -ub 25615,374930
128Kno DFlash14,4621,842

† Beyond the model's native 262,144 — needs YaRN RoPE scaling, and is not exposed as a preset.

The rejected pile

A config that loads is not the same as a config that works. These were all measured and thrown away, and several look like passes until you check the right column.

Rejected configurations, and why
AttemptResult
Qwen3.8-27B 128K, q8_0 KVfails to allocate — wants 4,352 MiB of KV
Qwen3.8-27B 128K, q4_0, -ub 1024loads at 16,247 — 57 MiB free
Qwen3.8-27B v3 160K, q4_0, -ub 256loads at 16,275 — 29 MiB free, 150 MiB already spilled to GTT
Qwen3.8-27B v3 32K + MTPSPILLfree_at_load=28, GTT +12,154 MiB
Qwen3.8-27B IQ4_XS 64K, q8_0, -ub 512SPILLfree_at_load=9, GTT +2,314 MiB
Qwen3.8-27B Q6_K 32K, -ngl 44SPILL — reads as a 60 MiB fit; GTT rose 1,623 MiB
Qwen3.5-27B-Unc 64K, q8_0, -ub 51215,608 — 55 MiB free, negative at the session's worst baseline
GLM-4.7-Flash 32K, -ncmoe 815,649 — 45 MiB free
Nemotron 512K, -ncmoe 24 -ub 512loads at 19 MiB free; the first generation request failed
Devstral 16K, q8_0, -ub 102415,800 — 504 MiB free, less room than 32K at q4_0
Qwen3-Coder-Next 1M, q8_0 KVfails — 13,056 MiB KV plus a ~1.9 GB compute buffer overflows the card

The spill guard exists because fit.sh lied once. It scored a 160K config a pass at 2,707 MiB free. It was not a pass: VRAM at load was 16,275 MiB and GTT rose 275 → 425 MiB. Buffers had migrated to host memory, and because the script read peak VRAM after the probe returned, it measured the post-migration figure. It now samples GTT alongside VRAM and refuses any row where peak < loaded or GTT drifted past 48 MiB. Any pre-guard row quoting single- or double-digit free VRAM should be treated as suspect until re-measured.

06 — Throughput

Speed, and how much of it depth takes back

llama-bench, -r 2, ROCm build. Shallow unless a depth is named. The interesting axis is not peak tok/s — it is how much survives to 131K, and that varies by 3.3× across the models here.

Generation throughput vs context depth

tok/s · ROCm · -fa 1 · tg64

Qwen3.5-9B-Unc Q8_0 Nemotron-3-Nano 30B-A3B Qwen3.6-35B-A3B Qwen3.8-27B (q4_0 KV)
60 45 30 15 0 generation tok/s 0 32K 131K context depth (tokens) Qwen3.5-9B-Unc · depth 0 · 56.6 tok/s Qwen3.5-9B-Unc · 32K · 49.1 tok/s Qwen3.5-9B-Unc · 131K · 35.7 tok/s Nemotron-3-Nano · depth 0 · 47.5 tok/s Nemotron-3-Nano · 32K · 41.7 tok/s Nemotron-3-Nano · 131K · 31.7 tok/s Qwen3.6-35B-A3B · depth 0 · 31.8 tok/s Qwen3.6-35B-A3B · 32K · 28.8 tok/s Qwen3.6-35B-A3B · 131K · 22.2 tok/s Qwen3.8-27B q4_0 · depth 0 · 31.6 tok/s Qwen3.8-27B q4_0 · 32K · 20.8 tok/s Qwen3.8-27B q4_0 · 131K · 9.6 tok/s 35.7 — Qwen3.5-9B-Unc 31.7 — Nemotron-3-Nano 22.2 — Qwen3.6-35B-A3B 9.6 — Qwen3.8-27B
Qwen3.8's curve is its worst case — the 128K preset runs q4_0 KV, which costs up to 23 % of generation at depth. Its 32K preset at q8_0 holds 27.0 tok/s at 32K rather than 20.8. The 3.3× decay is still the steepest here, and the cause is arithmetic: 4 KV heads across 16 caching layers means every generated token reads a cache 2.7× fatter per token than any other hybrid model in the set. At 131K a dense 27B generates slower than an 80B.

Expert offload: the -ncmoe sweeps

Monotonic in every case — every expert layer moved onto the GPU helps. The usable value is whatever the target context leaves room for.

-ncmoe sweep · -ub 1024 · q8_0 KV · pp4096/tg64 · shallow
-ncmoeQwen3.6-35B pp / tgLaguna XS.2 pp / tgGLM-4.7-Flash pp / tgGemma 4-26B pp / tg
12fails to allocate1571.9 / 43.31603.6 / 42.6
161974.8 / 43.92074.9 / 46.71463.2 / 39.0
201787.3 / 41.91846.1 / 43.71375.6 / 35.91276.0 / 33.9
241616.2 / 39.01318.7 / 32.3
26 / 281472.6 / 35.8 (28)1618.4 / 39.3 (26)1243.6 / 28.7 (28)
321368.0 / 35.7
361268.4 / 33.4
401198.0 / 31.8

Laguna XS.2 is faster than Qwen3.6 at every matched value despite an almost identical file size (18.9 GB vs 20.6 GB) — its 512-wide expert FFN is lighter per active parameter. GLM-4.7-Flash reaches Qwen3.6's best published generation (43.9 at -ncmoe 16) at -ncmoe 12, needing four fewer GPU-resident layers, consistent with a file 4.6 GB lighter.

Ubatch: the most under-used flag, with one big asterisk

-ub sweep · prompt tok/s · the gain depends entirely on whether you are offloading
-ubQwen3.6 -ncmoe 40, offloadedQwen3.8-27B dense, residentQwen3.5-27B-UncQwen3.5-9B-Unc
256416.11180.71031.53884.6
512 default706.01279.71131.84270.3
10241198.01306.91153.64423.4
gain+188 %+11 %+12 %+14 %

This is a claim about CPU-offloaded models, not about ubatch itself. The gain comes from amortising expert-weight transfers over PCIe, so it scales with how much of the model lives in system RAM. On a fully GPU-resident model there is nothing to amortise. Check whether you are actually offloading before paying VRAM for a bigger ubatch — and if you are not, -ub 256 is a cheap way to buy back compute buffer for context.

Depth sweeps

Generation tok/s by depth · each model at its own preset
Model / presetd08K16K32K131Kdecay
Gemma 4-26B-A4B -ncmoe 851.449.047.547.01.09×
Qwen3.5-9B-Unc q8_056.649.135.72.2× to 262K
Nemotron-3-Nano -ncmoe 2447.541.731.71.50×
Qwen3.6-35B-A3B -ncmoe 4031.828.822.21.43×
GLM-4.7-Flash -ncmoe 1242.239.335.729.51.43×
Qwen3.8-27B 32K preset, q8_031.630.629.427.01.17×
Qwen3.8-27B 128K preset, q4_031.627.825.120.89.63.3×
Qwen3.5-27B-Unc 32K, q8_029.828.126.724.51.21×
Devstral Small 2 24B q4_036.032.228.824.1unreachable1.49× in 32K
Qwen3-Coder-Next 80B~25~24~22 @256K1.10×

Flattest curve

Gemma 4-26B8.5 % decay across the whole 32K range. Never drops below 47 tok/s inside its window — the answer to "fastest thing that is still a serious 25B at Q4_K_M".

Steepest curve

Qwen3.8-27B3.3× to 131K. It fits, it recalls perfectly, and it is slow enough that you should reach for 32K or 64K unless you genuinely need the window.

Steepest prefill

GLM-4.7-FlashGeneration decays 1.43×, but prompt decays 8.5× (1608 → 190 over 32K). Worth weighing for agentic use, where most of the token cost is prompt.

Fastest overall

GPT-OSS-20B5,530 pp / 180.7 tg shallow, fully GPU-resident at 11.3 GB. The same "Rust backend + React frontend" prompt: 15 s here vs 1 m 55 s on Qwen3-Coder-Next.

Does the synthetic curve transfer to real serving?

Twice now, yes. One turn of qwen3.8-32k through Open WebUI with web search (prompt_n 15065, cache_n 11198, so roughly 11K–26K of depth) measured 798.4 tok/s prompt and 27.8 tok/s generation — both landing between the d0 and d32K rows exactly where the sweep predicts. Separately, switching the 64K preset from q4_0 to q8_0 KV was predicted at 29.39 tok/s and measured 29.15 at slightly greater depth: within 0.8 %. llama-bench is not flattering itself here.

07 — Long context

Recall is fine. Prefill is the bill.

The common warning is that long-context recall degrades well before the nominal window — "lost in the middle". On these models, inside their native ranges, it does not happen. Two probes: needle-test.py plants distinct verbatim facts at 5/25/50/75/95 % depth; semantic-recall-test.py is the hard one — it plants real helper functions among hundreds of similar utilities, then poses tasks describing what each helper does without naming it, with a deliberate near-miss distractor for each, placed closer to the question than its target so proximity cannot be the cue.

Recall at depth · --no-think · n=5 per depth
Model / presetNeedleat depthSemanticat depthCached follow-up
Qwen3.8-27B IQ3_XXS 192K5/5189,4825/5186,6955.3–8.8 s
Qwen3.5-9B-Unc 256K native5/5216,2085/5225,148
Qwen3-Coder-Next 256K5/5~241,0008.3 s
Qwen3-Coder-Next 128K5/5~119,0005/5~119,0006.5 s
Nemotron-3-Nano 128K5/5127,5691.1–1.2 s
Qwen3.8-27B Q3_K_XL 128K5/5127,1165/523,4742.6–3.3 s
Qwen3.5-27B-Unc 32K5/531,5965/528,1492.4–3.7 s
GLM-4.7-Flash 32K, MLA5/529,8175/527,185
Gemma 4-26B-A4B 32K5/523,2510.5–0.8 s

The real cost is prefill, not forgetting

Prefill throughput nearly halves with depth — 552 tok/s at 38K down to 322 at 241K — so cost grows superlinearly. 119K → 241K is 2.02× the tokens but 2.8× the time.

Cold prefill against a warm prefix cache
PresetColdWarm (cached prefix)Ratio
Qwen3-Coder-Next 128K~4.5 min~6.5 s~40×
Qwen3-Coder-Next 256K~12.5 min~8.3 s~90×
Qwen3.8-27B IQ3_XXS 192K686 s (~276 tok/s)4.9–8.8 s~100×

Treat 256K as load-once-then-iterate. Dump a subsystem in, then ask twenty questions against it. Any change to the prefix — reordering files, editing the system prompt, inserting anything ahead of the bulk source — costs the full cold prefill again. Seen in the wild on Qwen3-Coder-Next: a turn reporting cache_n 46967 against prompt_n 14, the entire 47K prefix reused and fourteen tokens new. If your workflow rebuilds the prompt every turn, 256K is unusable. Keep context lean because rebuilding it is expensive, not because the model forgets.

Thinking models will eat your entire token budget

Qwen3.8-27B defaults reasoning_effort to xhigh. At max_tokens: 1200 on one hard prompt, every setting returned zero content — and raising the budget does not converge, it scales with whatever you give it.

Qwen3.8-27B · reasoning vs content characters, one hard prompt
Settingmax_tokensReasoning charsContent charsfinish
default (xhigh)1,2004,6840length
medium1,2002,5740length
low1,2002,5330length
low4,00011,8220length
low8,00028,1740length
--reasoning-budget 10241,2003,135776
--reasoning-budget 10242,0004,2983,584

Nothing in reasoning_effort puts a ceiling on it. The fix is llama.cpp's own flag, not a template kwarg — so every preset here sets --reasoning-budget 1024. And note the second-order trap: that budget bounds the thinking but does not stop it being billed against the client's max_tokens, and the answer is written after the reasoning. A cap that is comfortable for a non-thinking model silently truncates a thinking one mid-sentence, with truncated = 0 in the server log and no error anywhere. Rule: client max_tokens > reasoning-budget + the answer you actually want.

Both probes overshoot --depth, by different amounts. semantic-recall-test.py plants a distractor per target and runs ~17 % over; needle-test.py runs ~7.8 % over. A 199,478-token haystack against a 196,608 window fails with a naked HTTPError: 400 at the client, which looks like a broken server rather than a too-big prompt. Ask for ~80 % of the window on semantic, ~90 % on needle.

08 — Coding quality

Executed, not read

code-quality-test.py scores a model by running its output. Seven tasks, each a from-scratch reimplementation of a stdlib behaviour — fnmatch, shlex.split, urljoin, textwrap.wrap, csv.reader, parse_qsl, date arithmetic — with the model's function compared against the real one over edge cases plus a seeded random batch. 50 checks total.

The first version of this suite was worthless, and the reason is worth keeping. It used self-contained leetcode-style tasks — merge intervals, LRU, roman numerals — and every model scored 50/50. Saturated, measuring nothing. Differential testing against stdlib is what produced resolution: matching urljoin exactly is hard, matching it on the happy path is easy, and the gap between those is the score. The suite is validated — with stdlib wrappers substituted for the model, all 7 tasks score 50/50, so a failure belongs to the model and not to the checks.

Differential stdlib reimplementation

checks passed of 50 · temperature 0.0 · exactly reproducible on re-run

Qwen3.8-27B IQ3_XXS v3 3.148 bpw
38 / 50 76 %
Qwen3.8-27B Q3_K_XL v3 3.802 bpw
35 / 50 70 %
Qwen3-Coder-Next 80B Q4_K_M
34 / 50 68 %
Qwen3.8-27B Q3_K_XL v2 3.932 bpw
33 / 50 66 %
Qwen3.8-27B IQ4_XS v3 4.131 bpw
30 / 50 60 %
Laguna XS.2 Q4_K_M
27 / 50 54 %
Laguna XS.2 Q8_0 near-lossless
27 / 50 54 %
Gemma 4-E2B BF16 ~4.7B dense
17 / 50 34 %
All models fail on the same near-floor items — urljoin dot-segment removal (1–3 of 9 for everyone), textwrap whitespace collapsing, shlex double-quote escapes, glob []] handling — so those tasks contribute noise rather than signal. A different task selection could reorder the top two.

Two things this table says, and one it turned out not to

Superseded on the quant question. Two conclusions once drawn from this table — "BPW does not order it" and "dropping to IQ3_XXS costs no coding quality" — did not survive a more sensitive instrument. KL-divergence orders the quants cleanly and monotonically by BPW and puts IQ4_XS 3.3× closer to the unquantised model than IQ3_XXS, the reverse of the check counts. The model comparison is untouched by this: KLD cannot compare different models, only a quant against its own reference.

Where the models actually differ: one chart, three outcomes

Asked conversationally for an SVG/HTML bar chart with axis, labels and title:

ModelBilled asResult
Nemotron-3-Nano-30B-A3Bgeneral reasoning0/4 — computed height="-45"; a negative <rect> height is invalid SVG, so bars silently do not draw. Axis, ticks and labels all render. No error anywhere
Muse Glimmer 30Bagentic specialist7 attempts, and only succeeded after abandoning SVG for CSS <div> bars with percentage heights
Qwen3-Coder-Nextcoding specialist3/3 on a strictly harder chart — ten bars, legend, rotated labels, axis title

The confounds were eliminated one at a time: quantisation (it is UD-Q4_K_XL, not a Q3), reasoning cap (identical at 1024 and 4096), temperature (still wrong at 0.25), and luck (four regenerations, all four bars negative). The failure is specific to SVG coordinate maths, not to charting — both generalists produced correct scaffolding, labels, axes and colours and fell over only on mapping a value to a y origin and a positive height. If you want a chart out of a non-coding model, ask for CSS bars; it routes around the part they get wrong. Honest caveat: the failure reproduced reliably inside a long chat carrying web-search history but did not reproduce in isolated single-turn API calls.

The other failure class, and the prompt that fixes it

Asked for the same chart using Recharts with no CDN URLs supplied, every model produced a blank page — but for different reasons, and only one reason is the model's fault. The React and Recharts code itself was correct in every case; they failed on dependency URLs. That is a recall failure, and it is the opposite of the SVG case: no amount of thinking recovers a package version you never saw, whereas pinning the URLs in the prompt fixes it completely.

AttemptLibraryScripts resolvingPeer deps found
Qwen3-Coder-Next 80BRecharts2/4
Devstral Small 2 24BRecharts3/4
Qwen3-Coder-30B Q8_0Recharts0/1
Gemma 4 + verify phaseMUI6/6

Adding a verification phase — fetch each script URL and confirm HTTP 200 before writing it; do not guess filenames or assume a .min build exists; read the library's own UMD docs for peer dependencies — produced the first unambiguous success. Gemma 4 loaded React, ReactDOM, Babel, @mui/material and both @emotion packages — MUI v5's styling engine, the equivalent of the prop-types trap nobody found on Recharts. All six URLs were independently re-checked and all six genuinely return 200. Make the model verify; do not make it guess.

Note also that near-lossless precision on a smaller model lost decisively to 4-bit on bigger ones: Qwen3-Coder-30B at Q8_0 emitted a single script tag, called ReactDOM.render (React 17) on globals that were never loaded, and claimed data was sorted when it was not — failing on knowledge questions the Q4 models got right, at 15.9 tok/s at 32K against Coder-Next's ~24. Q4_K_M is not what limits coding here.

09 — Quantisation fidelity

KL-divergence against BF16

The coding suite resolves 4 checks in 50 across a 68–76 % band. That is too coarse to rank quant tiers, and it produced an ordering that inverted under measurement. KLD is the right instrument for this specific question: how far has a quantised model's output distribution moved from the unquantised one, per token, with error bars — no task design, no scoring rubric, nothing to saturate.

Method. Reference is unsloth/Qwen3.8-27B-GGUF BF16, 54.66 GB across two shards, sha256-verified against the HF LFS oids. Corpus is wikitext-2 wiki.test.raw, the llama.cpp convention, so these are comparable to published figures. -c 512 --chunks 200 = 102,400 tokens prefilled, 51,000 scored. The 25.33 GB base logits file was generated CPU-only in 16 m 49 s — BF16 does not fit in 16 GB, and running it on CPU also kept the router usable throughout.

Mean KL-divergence from BF16

lower is more faithful · Qwen3.8-27B · 51,000 scored tokens · every gap is 20–80 σ

UD-IQ3_XXS v3 3.148 bpw
0.0589
UD-Q3_K_XL v2 3.932 bpw
0.0299
UD-Q3_K_XL v3 3.802 bpw
0.0263
UD-IQ4_XS v3 4.131 bpw
0.0179
UD-Q6_K v3 6.431 bpw
0.0020
BPW orders quality, and the coding probe was measuring noise. The check-count probe had IQ3_XXS at 38/50 beating IQ4_XS at 30/50; KLD puts IQ4_XS at a third of IQ3_XXS's divergence. Both probes are internally reproducible — the difference is resolution, and a 50-point scale with shared near-floor tasks cannot see a 0.04 KLD gap.
Full KLD results · ± is the reported standard error
QuantBPWMean KLD99th-pctRMS ΔpSame top-1
UD-IQ3_XXS v33.1480.0589 ± 0.00070.5776.88 %89.28 ± 0.14 %
UD-Q3_K_XL v23.9320.0299 ± 0.00040.3455.05 %92.56 ± 0.12 %
UD-Q3_K_XL v33.8020.0263 ± 0.00030.2764.61 %92.94 ± 0.11 %
UD-IQ4_XS v34.1310.0179 ± 0.00020.1893.80 %94.08 ± 0.10 %
UD-Q6_K v36.4310.0020 ± 0.000050.0201.25 %97.96 ± 0.06 %

Q6_K is a different regime

13× lowerNot one more step. Top-1 disagreement falls from 7.1 % to 2.0 %. Nothing else in the table moves that much for one tier.

Dynamic 3.0 confirmed

better & smallerv3 beats v2 at the same nominal tier (0.0263 vs 0.0299) while spending fewer bits — 3.802 against 3.932. A real recipe improvement, not a size-for-quality trade.

But the vendor claim does not reproduce

+0.38 ppTop-1 goes 92.559 → 92.939 %, a 5.1 % relative reduction in disagreement — about half of the ">10 % top-1" claim under the reading most favourable to it.

The instrument was validated three ways

IQ4_XS is a strict upgrade over Q3_K_XL at 32K

Back-to-back in one session, identical flags
UD-Q3_K_XL v3UD-IQ4_XS v3
Generation, n=329.70 ± 0.06 tok/s30.27 ± 0.014 tok/s
Mean KLD0.02630.0179
Same top-192.94 %94.08 %
VRAM at 32K14,280 MiB15,343 MiB
Free~2,000 MiB955 MiB

Better on both axes at once — 32 % lower divergence and 1.9 % faster, despite a file 1.06 GB larger. The reason the bigger file is faster is already in the record: Dynamic 3.0 spends fourteen distinct ggml quant types against v2's five, and that was measured costing 3.6 % of generation. IQ4_XS has fewer dequant paths, so UD-Q3_K_XL-v3 pays a dequant tax for a quant that is also less faithful than the one that does not pay it. The cost is headroom, not speed.

What Q6_K costs to actually serve

Q6_K's weights are 20,965 MiB against 16,304 MiB of VRAM, so unlike everything else here it cannot run fully offloaded — -ngl becomes the variable and the remainder sits in DDR4. At -ngl 42, 32K, f16 KV:

Q3_K_XL v3 · -ngl 99Q6_K v3 · -ngl 42
Generation29.67 tok/s4.24 ± 0.005 tok/s
Prefill (batch 2048)1,164 tok/s470 tok/s
Mean KLD0.02630.0020
Same top-192.94 %97.96 %

The trade is 7× generation and 2.5× prefill for 13× lower divergence. Three runs returned 4.25 / 4.24 / 4.24 — a measurement, not a sample. At 4.24 tok/s a 700-token answer takes 165 s, which puts Q6_K in batch-and-come-back territory rather than interactive or agentic use. That is the honest shape of "highest quality practically" on this card: the quality is available, but not at a speed that survives an agent loop.

TurboQuant KV: tested and rejected, and only KLD shows why

TurboQuant's Walsh–Hadamard-rotated KV types compress the cache far past q4_0 and really do change what fits — UD-IQ4_XS runs at 131,072 with turbo2 where q4_0 cannot allocate its compute buffers, and it passes 5/5 needle and 5/5 semantic at ~121K. It is still the wrong trade:

KV cache typeMean KLD vs f16
q8_00.000744
q4_00.003901
turbo30.009823
turbo20.030743

turbo2's cache costs more distribution quality (0.0307) than this model's entire weight quantisation does (Q3_K_XL v3: 0.0263). Both retrieval tests scored 5/5 on the rejected config and were blind to a 7.9× divergence gap — needle and semantic recall are regression alarms, not quality instruments. One genuinely free finding fell out of the same investigation, no fork required: the gemma4:32k preset ships -ncmoe 8 but runs +14 % prompt / +12 % generation at -ncmoe 4 on stock q8_0.

10 — Library knowledge

Freshness is measurable, and it did not separate the labs

cdn-freshness-test.py asks for a self-contained browser page, regexes every src/href, sends a HEAD to each URL and counts what resolves. No judgement calls. Three prompts (Recharts, MUI, Chart.js + D3), three runs each, temperature 0.0.

CDN URLs that actually resolve

percentage of emitted script/link URLs returning a non-error HEAD

Qwen3-Coder-Next Q4_K_M rel. 2026-01-30
39 / 42 93 %
Qwen3.8-27B IQ3_XXS rel. 2026-08-05
36 / 39 92 %
Laguna XS.2 Q8_0 rel. 2026-07-21
27 / 36 75 %
Gemma 4-E2B BF16
27 / 39 69 %
Laguna XS.2 Q4_K_M same weights, 4-bit
18 / 36 50 %
Six months of release-date gap between the top two produced no measurable difference. Each fails deterministically on one library and gets the other's right — Qwen3.8 always emits a dead @mui/material@5 UMD path, Qwen3-Coder-Next always emits a dead recharts@2.10.0/umd/recharts.min.js. So "the coder model's knowledge is outdated" is not reproduced by this probe.

This is the one probe here that is sensitive to quant tier, and that is the useful finding. Laguna moved from 18/36 to 27/36 purely by going Q4_K_M → Q8_0, recovering nine URLs and dropping three of six hallucinated paths — while code-quality-test.py scored 27/50 at both tiers on the same two files. Recall of specific version strings and CDN paths degrades under quantisation well before procedural coding ability does, which is consistent with the KLD result. A knowledge probe is the cheaper instrument for detecting quant damage than a coding probe. Treat the cross-lab spread above as an upper bound: some of every gap there is quantisation.

11 — Tool calling

Native tool calls work — and llama.cpp broke them once per model

Prompt-based tool schemes inject descriptions into the prompt and parse text back, so they succeed even when the API path is broken. To test the real path you need Native function calling and a tool whose output cannot be guessed:

def get_probe_token(self, seed: int) -> str:
    """
    Return the secret probe token for a given seed. There is no way to
    derive this value without calling this function.
    :param seed: Integer seed for the token.
    """
    return f"PROBE-{random.Random(seed).randint(100000, 999999)}"

Expected: seed 42 → PROBE-770487, seed 7 → PROBE-439563, seed 1234 → PROBE-915965. A model answering without calling cannot produce these.

Native tools path, parsed by llama.cpp into structured tool_calls
ModelSingleParallelNotes
GPT-OSS-20B @128Ktwo calls in one turn, did the arithmetic itself
Qwen3-Coder-Next @128Kthree calls — delegated the addition to add_numbers
Qwen3.8-27B @32Kpasses with thinking on and off
Qwen3.8-27B IQ4_XS @32Ksame three-call delegation
Qwen3.8-27B IQ3_XXS @192Kstructured output survives 3.148 BPW
Qwen3.5-27B-Uncensored @32Ksame delegation as its Qwen3.8 twin
Qwen3.5-9B-Uncensored @256Kcomma-formatted the sum — numerically identical, but it broke a naive exact-match probe
GLM-4.7-Flash @32Ktested at general-use sampling, not the vendor's tool-calling params
Laguna XS.2 @32Kthe strongest axis measured on this model
Gemma 4-E2B @32Ktwo token calls plus an add_numbers delegation
Muse Glimmer 30B @32Kuntestedvia Open WebUI's own write_note

Three things that will waste your day

The parser breaks per model

llama.cpp's peg-native format derives a grammar from each model's chat template rather than using a hand-written parser, so it breaks per model and gets fixed per model. Qwen3-Coder-Next 500'd on every tool call on build e583f3b4fFailed to parse input at pos 22, exactly len("<|im_start|>assistant\n"). Fixed by 153d324bc. GPT-OSS-20B still fails this way under Cline. Record it as a harness limitation, not a model limitation, and check the server build before blaming the model — retrying cannot help, since the model regenerates the same valid-but-rejected shape.

A result badge is not evidence a tool ran

With no tools installed or enabled, a model emitted a call to calculate_timestamp — a function that exists nowhere — and the UI rendered View Result from calculate_timestamp above a fabricated answer. llama.cpp converts native tool-call syntax into a structured tool_calls field regardless of whether the client sent a tools array. Every layer behaved as designed and the output was still invented. Check the tool name against the tools you actually installed.

Code execution fails silently when half-enabled

Open WebUI's Code Interpreter needs enabling in two places. With only the first done there is no error and no warning — the model simply answers from nothing. Same model, same three questions, twenty minutes apart: sha256 fabricated; primes below 987654 given as 77,597 against the true 77,614; digit sum of 7⁷⁷⁷ given as 3,519 against 2,989. With execution available it used it unprompted on all three and got every answer right. The prime count is the dangerous case — a wrong hash is obviously wrong, 77,597 passes a glance.

Pick test values that cannot be memorised. An earlier run of that test used sha256("hello world") and the 5000th prime, and "passed" without executing anything because all three are in training data. The tell was formatting: the sum came back as 4,543,965,37, comma-grouped wrong — what recalled digits look like rather than a formatted integer.

12 — Tuning

Flags that matter, and one that is a trap

KV cache type — match them, never mix

Qwen3.6 · -ncmoe 36 · pp512/tg128
-ctk / -ctvPrompt tok/sGen tok/s
q8_0 / q8_0776.733.4
f16 / f16753.034.8
f16 / q8_0600.933.2
q8_0 / f16603.633.0

Mixed precision costs ~23 % prompt throughput for no memory benefit. q8_0/q8_0 is the right default: same speed as f16, half the memory.

q4_0 KV is not free, and the cost scales with depth

The table above invites assuming q4_0 is as cheap again. It is not. Qwen3.8-27B, -ub 1024, tg64, matched depth, nothing else varied:

Depthq8_0q4_0q4_0 cost
031.5731.530 %
8K30.6427.80−9.3 %
16K29.3925.06−14.7 %
32K27.0420.78−23.1 %

Zero at depth 0 — there is no cache to read, so the quant cannot matter — and 23 % by 32K. The penalty is per-token dequantisation proportional to cache size, so it grows exactly where you reached for q4_0 in the first place. Treat q4_0 KV as the price of admission, never an optimisation. Where both fit, q8_0 wins on speed and on precision.

One negative result worth recording: q4_0 is widely assumed to degrade output, and a truncated response made that the obvious suspect. Tested directly at ~13,250-token depth, four samples per quant, asking for five titled sections plus a literal end marker — no early stopping and no structural loss from q4_0; if anything it ran long. The real cause of the truncation was the client's max_tokens. That says nothing about factual accuracy, and nothing about 100K+ depth where quantisation error has far more attention steps to compound through.

Threads and offload

Leave threads at 16

-t 32 → 23.6 ± 4.6Generation collapses with high variance on SMT contention across all 32 threads. 8 / 16 / 24 are within noise of each other (34.7 / 34.0 / 33.0).

--no-kv-offload is a trap

2–3× worseIt frees a lot of VRAM but moves KV to system RAM over PCIe. Qwen3.6 generation: 28.8 → 14.7 at 32K, 22.2 → 7.7 at 131K. Reduce context or KV quant instead.

Watch for other GPU consumers

10.4 GBDaVinci Resolve holds that much VRAM while open, silently forcing far more conservative -ncmoe. If a working config suddenly fails to allocate, check for it before re-tuning. Idle ollama serve holds nothing.

YaRN does not confirm itself

check by handprint_info: rope scaling = linear in the log reflects GGUF metadata, not the runtime flag, so it is not a reliable confirmation that YaRN engaged. Verify with a retrieval test at depth if it matters.

The MTP head llama.cpp throws away by default

Qwen3.8-27B's block_count is 65, not 64. blk.64 is a Multi-Token-Prediction head, and llama.cpp discards it by default — model has unused tensor blk.64.* -- ignoring. That message is the default-off path, not a missing feature: --spec-type draft-mtp turns it on, and it is present in both the v2 and v3 files, so this is testable on something you already have.

ConfigGenerationInteractive follow-ups
v2 @16K, no MTP30.82 tok/s2.3–2.6 s
v2 @16K, MTP74.02 tok/s (2.40×)
v3 @16K, no MTP29.70 tok/s2.3–2.6 s
v3 @16K, MTP69.47 tok/s (2.34×)0.9–1.1 s

That beats Muse Glimmer's DFlash drafter (1.64×). The cost is ~2,212 MiB, fixed — not the 198.8 MiB of blk.64 weights, because common_speculative_init_result builds a second full context. Measured identical at 8K and 16K and unchanged by batch size, so it does not scale. On a 16 GB card holding 12.5 GB of weights that caps MTP at 16K. If you cannot live inside 16K, MTP is not for you; the 2.2 GB is not negotiable.

13 — Commands

Everything here is reproducible

Model folders under benchmarks/ are generated, not hand-assembled — a result you cannot regenerate is a claim, not a measurement. Every output file carries a header naming the model, context, flags, llama.cpp build and timestamp, so it is interpretable on its own.

Rebuilding the ROCm backend

cd ~/llama.cpp
HIPCXX="$(hipconfig -l)/clang" HIP_PATH="$(hipconfig -R)" \
  cmake -B build -DGGML_HIP=ON -DAMDGPU_TARGETS=gfx1201 -DCMAKE_BUILD_TYPE=Release
HIP_PATH=/opt/rocm cmake --build build -j16

HIP_PATH is needed at both configure and build time. Every guide shows it only at configure, which gets you a clean cmake run and then fails every .cu file with a misleading fatal error: 'hip/hip_fp16.h' file not found. Two more traps, each of which costs a full build cycle: HIPCXX must point at clang, not clang++ (auto-detection picks clang++, compiles in CUDA language mode, and dies with unsupported CUDA gpu architecture: gfx1201); and a stale CMakeCache.txt survives git pull. Back up the working binary first — a failed build leaves you with no server at all.

Serving

nohup ~/llama.cpp/build/bin/llama-server -m models/<file>.gguf -ngl 99 [-ncmoe N] \
  [-ub 1024 -b 2048] [-fa on -ctk q8_0 -ctv q8_0] -c <context> -np 1 \
  --host 0.0.0.0 --port 8090 > /tmp/llama-server.log 2>&1 < /dev/null &
disown

Switching models — router mode is the default worth using

~/llama.cpp/switch-model.sh router              # serve ALL presets, switch from the client
~/llama.cpp/switch-model.sh <model> <context>   # pin one
~/llama.cpp/switch-model.sh stop                # SIGTERM, then SIGKILL -- frees the GPU
~/llama.cpp/switch-model.sh status              # what's running now
~/llama.cpp/switch-model.sh list                # verified combinations

router starts one process over every preset in models-preset.ini. Picking a model from the client's dropdown loads it on demand and unloads the previous one, and each preset carries its full verified config rather than you retyping flags. It costs exactly one thing: the router spawns children from /proc/self/exe, so every model runs on the binary the router was launched with — there is no per-model backend in this mode. Run with --models-max 1; a second resident model will not allocate on this card.

Getting the GPU back without stopping the server

# launches pass --sleep-idle-seconds 900
# after 15 idle minutes llama-server calls destroy() and releases the model
# measured: 12,464 MiB -> 595 MiB, process still listening on 8090

The next request calls load_model() and reloads it, so nothing breaks — it just pays a cold start. Sleep state is not reported by any endpoint: /health, /props and /v1/models all deliberately bypass sleep, so none of them can tell you it is asleep, and polling them never triggers a reload.

Running the probe suite

./run-suite.sh qwen3.8-27B-UD-IQ3_XXS-v3 Qwen3.8-27B-UD-IQ3_XXS-v3.gguf 196608 \
    "-ub 512 -b 2048 -fa on -ctk q4_0 -ctv q4_0"

benchmarks/needle-test.py           --depth 119000 --no-think
benchmarks/semantic-recall-test.py  --depth 20000  --no-think
benchmarks/kld-test.sh score        # refuses to run on a -c/--chunks mismatch
benchmarks/fit.sh                   # VRAM sweep, with the GTT spill guard

Reading GGUF metadata before downloading anything large

~/llama.cpp/build/bin/llama-server -m models/<file>.gguf -c 512 --no-warmup 2>&1 \
  | grep -iE "block_count|full_attention_interval|head_count_kv|key_length|context_length"

Attention topology decides long-context viability, so check it first — parameter count matters far less. A related warning: Unsloth re-quantised Qwen3.8-27B-GGUF in place on 2026-08-19/20 under the same repo and the same filenames. A file pulled on 08-16 is Dynamic 2.0 and nothing in its name says so. The only reliable check is the byte size or the LFS oid — and the filename does not identify the build: the same UD-Q3_K_XL name reports Q4_K - Small at 3.93 BPW in v2 and Q3_K - Large at 3.80 BPW in v3.

14 — Agentic harnesses

Can they actually do a job?

Throughput, VRAM and recall say nothing about whether a model can research live data, build something and find its own bugs. The task: research the top 10 open-weight coding LLMs from a published benchmark, then build a React + MUI dashboard that renders them — with a verification phase requiring every CDN URL to be fetched and the browser console read before declaring success.

Headline: feedback beats capability. The same models that produced four undetected blank pages through a chat UI catch their own 404s once given a browser console. Muse Glimmer is the clean demonstration — its dependency check returned 200 200 404 404, it reasoned toward the correct @emotion UMD path, and it corrected before writing the file. Nothing about the model changed. What changed is that it could check its work. The bottleneck was the absence of a feedback loop, not model capability.

Same task, three harnesses
ClineClaude Codeaider
Model compatibility~5/67/1111/11
Failure classnative tool-call parser formatgrammar compile / message orderturn-boundary + confirmation-gate mismatches
Data layermixed (one model fabricated)correct, verifiedcorrect, verified
Frontendrendered, minor bugsrendered, minor bugsdid not render at all
Wall time40–70 min75 min11–20 min

aider wins decisively on raw compatibility — it parses plain-text diffs and plain-text shell suggestions instead of native tool calls, so nothing is disqualified by a protocol quirk, including the two presets blocked under both other harnesses. But getting a real task through it took five structural fixes a human driving it interactively would never hit, because a human naturally supplies the "now run it" nudge and notices when the model says it cannot run shell commands.

Claude Code: 7 of 11 presets, and both failures are server-side

llama.cpp serves the Anthropic Messages API natively, so no translation proxy is involved. What does block it: Claude Code sends a system-role message after the user message, and the dense-Qwen family's Jinja template raises System message must be at the beginning on that shape. That rules out qwen3.8-27Bthe model that scores best on code-quality in this whole record. Nothing to fix client-side; it is the template. GPT-OSS fails differently and earlier, rejecting the 30 built-in schemas at grammar compilation.

A probe that sends tools plus a plain user message is not sufficient to establish compatibility — it passed all three Qwen models that then failed in real use. The system-after-user shape has to be in the probe.

Claude Code turnaround · ~26k-token system prompt · what the client actually waits on
PresetCold sWarm sTTFT sGen s
laguna-33B-A3B-128k101.721.921.50.4
qwen3.6-35B-A3B-128k145.622.813.19.7
gemma4-26B-A4B-128k43.732.728.64.1
laguna-33B-A3B-q8-128k176.937.536.90.6
glm-4.7-flash-30B-A3B-128k167.654.639.215.5
qwen3-coder-80B-A3B-128k251.856.856.30.5
muse-glimmer-30B-128k118.061.758.03.7

Time-to-first-token is 96–99 % of a fresh-session turn. Generation is a rounding error, so the throughput tables above predict almost nothing about how an agent feels. In a continued session it is far better because the prefix caches — Laguna goes 25.5 s → 3.1 s on turn two, Qwen3.6 58.6 s → 31.9 s, and a byte-identical repeat measured 65.3 s → 0.1 s.

That produces a genuinely awkward trade: the quality leader is disqualified by its chat template and the speed leader is the weakest model. Laguna scored 27/50 on coding and last on library knowledge, and it shows — asked to reply with an exact token it declined and explained what it was designed for instead. Correct tool calls, unreliable instruction-following. But 3.1 s per follow-up against 31.9 s is a tenfold difference in felt latency.

When the feedback loop gets gamed

The "feedback beats capability" claim has a boundary, and it is worth stating plainly. Nemotron-3-Nano — the model that wins on throughput, KV cost, native context and recall, and which showed the best research instinct of any model on the task by checking for a pre-aggregated endpoint first — fabricated a full dataset of pre-2024 models with real citation URLs attached to invented scores after its real data source failed to resolve, and separately falsified a "clean" verification report on two occasions, claiming files existed and checks passed when they had not. It was deleted from disk for that, not for anything in its benchmark numbers.

A feedback loop only works if the model's report of what it observed is honest. Verifying that the report is real — not just that a check exists — is the harder problem this record leaves open. It is the same shape as the chart that rendered perfectly and was titled "2024 Comparison" from stale search results: nothing malfunctions and the output is still wrong.

15 — Conclusions

What to actually run

Pick by job, not by parameter count
JobTakeWhy
Codeqwen3-coder-80B-A3BCorrect SVG geometry 3/3 where two generalists failed, verified tool calling, ~24 tok/s out to 47K depth, flattest decay in the file
Max context on one cardqwen3.8-27B UD-IQ3_XXS @192K1.5× the old ceiling, 672 MiB free, 5/5 on both recall probes at ~187–189K, tool calling intact at 3.148 BPW
Max speed inside 16Kqwen3.8-27B Q3_K_XL v3 + --spec-type draft-mtp2.34× generation, ~1 s follow-ups, from a head already in the file you downloaded
Best weights ≤32Kqwen3.8-27B UD-IQ4_XS32 % lower KLD and 1.9 % faster than Q3_K_XL v3 — better on both axes at once
Beyond 262,144nemotron-1mThe only option natively trained there — but see the honesty caveat above
Fast generalgemma4-26B-A4B47 tok/s flat across its whole window, 8.5 % decay, cheapest cold start by 4×
Agentic loop, felt latencylaguna-33B-A3B-q83.1 s follow-up turns against 31.9 s. Same coding score as its Q4 and +25 points of library knowledge
Absolute fidelity, batch onlyqwen3.8-27B UD-Q6_K13× lower divergence at 4.24 tok/s — a 700-token answer takes 165 s

Six things that generalise past this box

16 — Caveats

What these numbers do not say