Goal
Benchmark llama.cpp CUDA on a10g-small using Unsloth GGUF quants.
Hypothesis
A mid-size GGUF quant that fully offloads to the 24GB A10G (e.g. IQ3/Q4/MXFP4_MOE) will outperform Transformers 4-bit due to lower runtime overhead and compact weights. Very large Q4/Q5 quants may leave too little VRAM for KV/cache and slow/OOM.
Method
Download one GGUF at a time, run llama-bench/llama-cli with full GPU offload (-ngl 999) and small prompt + fixed decode, sweeping quant and KV cache type where available. Compare decode tokens/sec.
Findings 2026-06-15
Setup notes
- Correct llama.cpp image:
ghcr.io/ggml-org/llama.cpp:full-cuda(the olderghcr.io/ggerganov/...tag failed to pull). - Must set
LD_LIBRARY_PATH=/app:$LD_LIBRARY_PATH; otherwise/app/llama-benchcannot findlibllama-bench-impl.so. - Mounting the full GGUF repo as a Jobs volume stayed in scheduling for several minutes; direct
hf_hub_downloadof one GGUF at a time worked (~188 MB/s for 11.5 GB IQ2_M).
Completed result: UD-IQ2_M full GPU offload
Job: https://huggingface.co/jobs/abidlabs/6a2f465b871c005b5352cd21
Runtime: llama.cpp build c2ba3e47a (9628), CUDA backend, A10G 24GB, -ngl 999 -fa 1 -p 512 -n 256 -b 1024 -ub 512 -r 3.
| GGUF quant | KV type | pp512 tok/s | tg256 tok/s |
|---|---|---|---|
| UD-IQ2_M (10.72 GiB, 2.7 bpw) | f16 | 2623.31 ± 113.20 | 118.42 ± 0.48 |
| UD-IQ2_M | q8_0 | 2748.98 ± 5.80 | 116.65 ± 0.61 |
| UD-IQ2_M | q4_0 | 2750.66 ± 13.34 | 116.17 ± 0.48 |
Initial best: 118.42 decode tok/s with F16 KV. Quantized KV did not help at this short context; it slightly reduced decode throughput.
Running next
UD-Q3_K_Mfull-offload KV sweep: https://huggingface.co/jobs/abidlabs/6a2f47b6871c005b5352cd4bUD-Q4_K_Sfull-offload attempt with fallbackngl=36: https://huggingface.co/jobs/abidlabs/6a2f47c9234ca64b60123ea4
Completed result: UD-Q3_K_M full GPU offload
Job: https://huggingface.co/jobs/abidlabs/6a2f47b6871c005b5352cd4b
Same runtime/settings as IQ2_M (-ngl 999 -fa 1 -p 512 -n 256 -b 1024 -ub 512 -r 3).
| GGUF quant | KV type | pp512 tok/s | tg256 tok/s |
|---|---|---|---|
| UD-Q3_K_M (15.45 GiB) | f16 | 2562.50 ± 200.30 | 111.64 ± 0.46 |
| UD-Q3_K_M | q8_0 | 2542.85 ± 202.53 | 110.01 ± 0.50 |
| UD-Q3_K_M | q4_0 | 2542.26 ± 205.74 | 109.40 ± 0.57 |
Q3_K_M is slower than IQ2_M for decode, and F16 KV is again best at this context.
Failed: UD-Q4_K_S via hf_hub_download
Job: https://huggingface.co/jobs/abidlabs/6a2f47c9234ca64b60123ea4
The first Q4_K_S attempt was killed with exit 137 (OOMKilled) during hf_hub_download at ~4.85/20.9 GB on a10g-small, likely CPU RAM pressure from the downloader/Xet path rather than GPU OOM. Retrying with plain curl streaming: https://huggingface.co/jobs/abidlabs/6a2f49a1234ca64b60123ecb
Running next
- IQ2_M runtime settings sweep (
fa,poll,n_batch): https://huggingface.co/jobs/abidlabs/6a2f49b5234ca64b60123ecf
Completed result: UD-Q4_K_S full GPU offload
Job: https://huggingface.co/jobs/abidlabs/6a2f49a1234ca64b60123ecb
Using plain curl avoided the CPU OOM seen with hf_hub_download; a10g-small has ~15 GiB RAM and ~233 GB disk. Q4_K_S (19.45 GiB in llama.cpp) fully offloaded successfully.
| GGUF quant | KV type | pp512 tok/s | tg256 tok/s |
|---|---|---|---|
| UD-Q4_K_S (19.45 GiB) | f16 | 2427.23 ± 321.35 | 111.64 ± 0.41 |
| UD-Q4_K_S | q8_0 | 2430.76 ± 250.49 | 110.13 ± 0.52 |
| UD-Q4_K_S | q4_0 | 2431.92 ± 274.80 | 109.37 ± 0.42 |
Q4_K_S fits but does not beat IQ2_M/Q3 for throughput; F16 KV remains best.
Completed result: IQ2_M runtime settings sweep
Job: https://huggingface.co/jobs/abidlabs/6a2f49b5234ca64b60123ecf
Swept fa ∈ {1,0}, poll ∈ {0,50,100}, n_batch ∈ {512,1024,2048} for IQ2_M + F16 KV. Best decode result was 118.80 ± 0.57 tok/s at -fa 1 --poll 0 -b 1024 -ub 512.
Observations:
- Decode is insensitive to batch/poll in this range (~118 tok/s throughout).
- Flash attention slightly improves prompt processing and very slightly helps decode (
fa=1≈ 118.6–118.8 vsfa=0≈ 118.0). - Prompt processing best seen: ~2761 pp tok/s with
fa=1,b>=1024.
Running next
- Lower-bit GGUF sweep (
UD-IQ1_M,UD-IQ2_XXS,UD-Q2_K_XL) with current best runtime flags: https://huggingface.co/jobs/abidlabs/6a2f4dc9871c005b5352cdac
Completed result: lower-bit GGUF sweep
Job: https://huggingface.co/jobs/abidlabs/6a2f4dc9871c005b5352cdac
Settings: -ngl 999 -fa 1 --poll 0 -ctk f16 -ctv f16 -p 512 -n 256 -b 1024 -ub 512 -r 3.
| GGUF quant | Size in llama.cpp | pp512 tok/s | tg256 tok/s |
|---|---|---|---|
| UD-IQ1_M | 9.35 GiB | 2775.83 ± 12.09 | 122.57 ± 0.55 |
| UD-IQ2_XXS | 10.01 GiB | 2637.17 ± 55.39 | 121.00 ± 0.84 |
| UD-Q2_K_XL | 11.44 GiB | 2564.41 ± 42.92 | 117.27 ± 0.37 |
New best throughput is UD-IQ1_M at 122.57 tok/s, about +3.2% over the prior IQ2_M best. If quality budget allows IQ1_M, this is the current fastest measured config.
Running next
- IQ1_M KV/settings sweep to confirm best flags: https://huggingface.co/jobs/abidlabs/6a2f4f36871c005b5352cdc6
Completed result: IQ1_M KV/settings sweep
Job: https://huggingface.co/jobs/abidlabs/6a2f4f36871c005b5352cdc6
Best single-sequence decode observed: 122.65 ± 0.55 tok/s with UD-IQ1_M, F16 KV, -ngl 999 -fa 1 --poll 0 -b 1024 -ub 512.
| Setting | pp512 tok/s | tg256 tok/s |
|---|---|---|
| F16 KV, fa=1, poll=0, b=1024 (r=3) | 2769.20 ± 13.18 | 122.65 ± 0.55 |
| Q8_0 KV, fa=1, poll=0, b=1024 | 2763.63 ± 16.15 | 120.70 ± 0.60 |
| Q4_0 KV, fa=1, poll=0, b=1024 | 2757.39 ± 11.44 | 119.99 ± 0.58 |
Batch/poll/flash sweep again showed decode nearly flat; F16 KV remains best. fa=1 improves prompt processing (2770 vs ~2725 pp tok/s) and gives a small decode edge over 122.6 vs ~122.1).fa=0 (
Running next
- Batched/parallel throughput with IQ1_M (
npl=1,2,4,8,16): https://huggingface.co/jobs/abidlabs/6a2f5074234ca64b60123f77
Completed result: IQ1_M batched throughput (npl sweep)
Job: https://huggingface.co/jobs/abidlabs/6a2f5074234ca64b60123f77
llama-batched-bench with UD-IQ1_M, F16 KV, -ngl 999 -fa 1 --poll 0 -c 16384 -b 2048 -ub 512 -npp 512 -ntg 256.
Parallel prompts (npl) |
S_PP tok/s | Aggregate decode S_TG tok/s | Total S tok/s |
|---|---|---|---|
| 1 | 2375.53 | 117.25 | 320.16 |
| 2 | 2812.21 | 199.23 | 523.52 |
| 4 | 2815.20 | 248.03 | 632.61 |
| 8 | 2814.63 | 284.62 | 710.23 |
| 16 | 2815.05 | 385.02 | 906.97 |
For serving throughput, batching/parallel sequences is the biggest win so far: ~385 aggregate decode tok/s at 16 parallel sequences, vs ~122 single-sequence tok/s. Running higher npl sweep next: https://huggingface.co/jobs/abidlabs/6a2f515c871c005b5352cdf0
Completed result: IQ1_M higher batched throughput (npl 16–64)
Job: https://huggingface.co/jobs/abidlabs/6a2f515c871c005b5352cdf0
llama-batched-bench with UD-IQ1_M, F16 KV, -ngl 999 -fa 1 --poll 0 -c 65536 -b 4096 -ub 512 -npp 512 -ntg 256.
Parallel prompts (npl) |
N_KV | S_PP tok/s | Aggregate decode S_TG tok/s | Total S tok/s |
|---|---|---|---|---|
| 16 | 12,288 | 2767.91 | 386.58 | 906.52 |
| 24 | 18,432 | 2759.79 | 442.54 | 1005.24 |
| 32 | 24,576 | 2752.42 | 472.81 | 1055.73 |
| 48 | 36,864 | 2742.35 | 481.33 | 1068.81 |
| 64 | 49,152 | 2727.81 | 497.44 | 1093.50 |
New best aggregate decode throughput: 497.44 tok/s at 64 parallel sequences. Gains are tapering but still positive through 64.
Completed result: high concurrency KV quantization sweep (npl 64–160)
Job: https://huggingface.co/jobs/abidlabs/6a2f52a2234ca64b60123fa9
At very high concurrency, the F16 KV cache context allocation failed for npl=64,96,128,160 with -c 196608 (CUDA OOM allocating ~10.05 GiB rs cache). Quantized KV enabled the run.
Settings: UD-IQ1_M, -ngl 999 -fa 1 --poll 0 -c 196608 -b 4096 -ub 512 -npp 512 -ntg 256.
| KV type | npl | N_KV | S_PP tok/s | Aggregate decode S_TG tok/s | Total S tok/s |
|---|---|---|---|---|---|
| q8_0 | 64 | 49,152 | 2754.12 | 490.21 | 1084.54 |
| q8_0 | 96 | 73,728 | 2725.97 | 503.53 | 1103.08 |
| q8_0 | 128 | 98,304 | 2703.54 | 507.21 | 1106.46 |
| q8_0 | 160 | 122,880 | 2688.58 | 554.90 | 1178.31 |
| q4_0 | 64 | 49,152 | 2697.42 | 486.19 | 1072.10 |
| q4_0 | 96 | 73,728 | 2692.17 | 499.80 | 1093.41 |
| q4_0 | 128 | 98,304 | 2692.14 | 503.99 | 1100.09 |
| q4_0 | 160 | 122,880 | 2688.96 | 552.04 | 1174.05 |
New best aggregate decode throughput: 554.90 tok/s with UD-IQ1_M, q8_0 KV, 160 parallel sequences. For single-sequence latency, F16 KV remains best; for high concurrency, q8/q4 KV are necessary to fit larger contexts and improve total throughput.
Failed/negative: exact-context attempts above npl=160
Jobs:
- High-npl overlarge context attempt: https://huggingface.co/jobs/abidlabs/6a2f559d234ca64b60123fdd
- Exact
c=147456attempt: https://huggingface.co/jobs/abidlabs/6a2f56d1234ca64b60124007
Findings:
-c 196608withnpl=160,192,224,256failed context creation even with q8/q4 KV due ~16.08 GiB CUDA allocation for the rs cache.-c 147456withnpl=160,192also failed: q8_0 KV failed allocating12.06 GiB CUDA buffer; q4_0 then failed compute-buffer allocation (864 MiB) after context setup.- Practical high-concurrency ceiling on
a10g-smallfor this model/runtime appears near the previous successfulnpl=160configuration with-crounded to 204800 and q8/q4 KV, yielding 554.90 aggregate decode tok/s. Larger max contexts can fail even if the measuredN_KVlooks only moderately larger.
Failed/abandoned: MTP GGUF speculative CLI smoke
Job: https://huggingface.co/jobs/abidlabs/6a2f55b5234ca64b60123fef
Downloaded unsloth/Qwen3.6-35B-A3B-MTP-GGUF UD-IQ1_M (11 GiB) and attempted llama-cli with --spec-type none then draft-mtp. The first CLI run produced no progress for several minutes after starting, so the job was canceled to avoid wasting GPU time. Need a more controlled MTP-specific smoke test before treating MTP as promising.
Running next
- Test
MXFP4_MOEGGUF single-sequence throughput; it is larger (~21.7 GB file) but may offer a quality/throughput tradeoff if it fits: https://huggingface.co/jobs/abidlabs/6a2f5d11871c005b5352ceb8
Negative: npl=192 does not fit even with smaller batch/ubatch
Job: https://huggingface.co/jobs/abidlabs/6a2f5dcc871c005b5352cec8
Tried UD-IQ1_M, q4_0 KV, -c 147456, npl=192, npp=512, ntg=256, sweeping b ∈ {1024,2048,3072} and ub ∈ {256,512}. All failed during context creation with CUDA OOM for compute pp buffers (~844–864 MiB) after allocating model/KV. This reinforces npl=160 as the practical high-concurrency ceiling under current flags on A10G 24GB.
Completed result: exact context for npl=160 recovers F16 KV and improves aggregate throughput
Job: https://huggingface.co/jobs/abidlabs/6a2f5ef6234ca64b601240e1
Previous high-concurrency runs used a larger max context (-c 196608, rounded to n_kv_max=204800), which made F16 KV fail and required q8/q4 KV. Setting context exactly to the required 160 * (512 + 256) = 122880 allowed F16 KV to fit and improved throughput.
Settings: UD-IQ1_M, -ngl 999 -fa 1 --poll 0 -c 122880 -b 4096 -ub 512 -npp 512 -ntg 256 -npl 160.
| KV type | N_KV | S_PP tok/s | Aggregate decode S_TG tok/s | Total S tok/s |
|---|---|---|---|---|
| f16 | 122,880 | 2765.15 | 563.77 | 1201.41 |
| q8_0 | 122,880 | 2701.97 | 554.94 | 1180.09 |
| q4_0 | 122,880 | 2693.56 | 552.24 | 1174.95 |
New best aggregate decode throughput: 563.77 tok/s. Important optimization: for batched serving, keep ctx-size as tight as possible to the served batch (npl * (prompt + generation budget)), otherwise extra KV/rs-cache allocation can force quantized KV or OOM and reduce throughput.
Completed result: MXFP4_MOE single-sequence throughput
Job: https://huggingface.co/jobs/abidlabs/6a2f5d11871c005b5352ceb8
MXFP4_MOE is a large GGUF (~20.21 GiB in llama.cpp / 21 GiB file) and does fit with full GPU offload on A10G 24GB, but it is slower than smaller Unsloth dynamic quants.
Settings: -ngl 999 -fa 1 --poll 0 -p 512 -n 256 -b 1024 -ub 512 -r 3.
| GGUF quant | KV type | pp512 tok/s | tg256 tok/s |
|---|---|---|---|
| MXFP4_MOE (20.21 GiB) | f16 | 2446.48 ± 277.99 | 108.59 ± 0.48 |
| MXFP4_MOE | q8_0 | 2451.65 ± 246.96 | 107.16 ± 0.43 |
| MXFP4_MOE | q4_0 | 2440.56 ± 239.45 | 106.47 ± 0.32 |
Conclusion: MXFP4_MOE may be useful for quality, but not for maximizing tok/s on a10g-small; it is slower than IQ1/IQ2/Q3/Q4_K_S in these decode benchmarks.
Negative/inconclusive: shared-prefix -pps batched-bench
Job: https://huggingface.co/jobs/abidlabs/6a2f621e234ca64b60124126
Tried llama-batched-bench -pps (shared prompt across parallel sequences) with UD-IQ1_M, F16 KV, npl ∈ {64,160,256,384} and tight-ish contexts. Results were not useful:
npl=64andnpl=160exited with rc=0 but printed no benchmark rows.npl=256OOMed during rs-cache allocation (~16.08 GiB).npl=384is invalid becausen_seq_max must be <= 256.
No throughput improvement recorded from -pps; treat as unsupported/inconclusive in this benchmark path.
Running next
- Test
--no-hostfor single-sequence andnpl=160exact-context batched serving: https://huggingface.co/jobs/abidlabs/6a2f633a871c005b5352cf26
Completed result: --no-host check
Jobs:
- Partial/failed first attempt due bad
llama-batched-bench --no-host 0syntax: https://huggingface.co/jobs/abidlabs/6a2f633a871c005b5352cf26 - Corrected run: https://huggingface.co/jobs/abidlabs/6a2f6472871c005b5352cf38
Single-sequence (UD-IQ1_M, F16 KV, -ngl 999 -fa 1 --poll 0 -b 1024 -ub 512):
--no-host 1: 122.70 ± 0.49 tok/s decode, 2771.54 ± 15.67 pp tok/s.- This is only a tiny/noisy improvement over the prior best 122.65 tok/s.
Batched exact-context npl=160:
| no-host | S_PP tok/s | Aggregate decode S_TG tok/s | Total S tok/s |
|---|---|---|---|
| off | 2763.15 | 563.91 | 1201.38 |
| on | 2720.25 | 562.33 | 1193.54 |
Conclusion: --no-host is not a meaningful win here; leave it off for batched serving. For single-sequence, it is within noise and can be tried, but the main recommendation remains IQ1_M + F16 KV + full offload + flash attention.
Completed result: CPU threads / priority check
Job: https://huggingface.co/jobs/abidlabs/6a2f6611871c005b5352cf56
Single-sequence UD-IQ1_M, F16 KV, -ngl 999 -fa 1 --poll 0 -b 1024 -ub 512.
| Threads | Priority | pp512 tok/s | tg256 tok/s | Notes |
|---|---|---|---|---|
| 2 | 0 | 2778.46 ± 12.62 | 122.59 ± 0.51 | baseline-like |
| 4 | 0 | 2776.59 ± 12.23 | 122.54 ± 0.52 | no improvement |
| 2/4 | 2/3 | — | — | failed: permission denied setting priority |
Conclusion: CPU thread count 2 vs 4 is noise-level; elevated priority is not permitted in Jobs.
Running next batch 2026-06-15
Launched follow-up attempts to find incremental improvements beyond current bests (single 122.70 tok/s; batched aggregate 563.91 tok/s):
- KV matrix (
ctk/ctvmixed F16/BF16/Q8/Q4) for single andnpl=160batched: https://huggingface.co/jobs/abidlabs/6a2fb04b871c005b5352d3c0 - Batched
npl=160batch-size/ubatch sweep: https://huggingface.co/jobs/abidlabs/6a2fb060871c005b5352d3c2 - GPU power/clock lock attempt plus single/batched benchmark: https://huggingface.co/jobs/abidlabs/6a2fb078234ca64b60124903
Completed/partial result: KV cache type matrix
Job: https://huggingface.co/jobs/abidlabs/6a2fb04b871c005b5352d3c0 (canceled after enough signal; mixed-KV batch cases were extremely slow)
Single-sequence UD-IQ1_M, -ngl 999 -fa 1 --poll 0 -b 1024 -ub 512:
| K type | V type | pp512 tok/s | tg256 tok/s |
|---|---|---|---|
| f16 | f16 | 2777.99 ± 9.72 | 122.85 ± 0.50 |
| bf16 | bf16 | 2779.77 ± 13.61 | 122.21 ± 0.55 |
| q8_0 | q8_0 | 2768.94 ± 14.19 | 121.17 ± 0.50 |
| q4_0 | q4_0 | 2760.96 ± 11.48 | 120.32 ± 0.53 |
| q8_0 | f16 | 521.12 ± 4.78 | 102.19 ± 0.31 |
| f16 | q8_0 | 374.27 ± 5.50 | 98.54 ± 0.44 |
| q4_0 | f16 | 500.21 ± 2.12 | 100.79 ± 1.53 |
| f16 | q4_0 | 345.15 ± 3.96 | 97.80 ± 0.74 |
Mixed K/V quantization is a major regression for this model/runtime. Symmetric F16/F16 is best; BF16 is close but slightly slower for decode.
Batched npl=160, exact context -c 122880, -b 4096 -ub 512:
- F16/F16: 565.22 aggregate decode tok/s, 2780.32 pp tok/s, 1205.51 total tok/s (new best, though within noise of prior 563.91).
- q8_0/f16: 147.23 aggregate decode tok/s (bad).
- f16/q8_0: 111.68 aggregate decode tok/s (bad).
Conclusion: keep K and V the same type; for both latency and batched throughput, F16/F16 remains the best if it fits.
Completed result: batch / ubatch sweep for npl=160
Job: https://huggingface.co/jobs/abidlabs/6a2fb060871c005b5352d3c2
UD-IQ1_M, F16 KV, exact context -c 122880, -npp 512 -ntg 256 -npl 160.
| batch | ubatch | S_PP tok/s | Aggregate S_TG tok/s | Total S tok/s | Notes |
|---|---|---|---|---|---|
| 1024 | 128 | 1465.64 | 522.51 | 915.07 | slow |
| 1024 | 256 | 2079.91 | 564.47 | 1097.63 | ok decode, slower pp |
| 1024 | 512 | 2775.88 | 563.82 | 1202.83 | best total in sweep |
| 2048 | 128 | 1429.35 | 521.70 | 904.69 | slow |
| 2048 | 256 | 2054.20 | 563.56 | 1091.69 | ok decode |
| 2048 | 512 | 2770.20 | 563.99 | 1202.38 | near-best |
| 4096 | 128 | 1365.40 | 522.26 | 887.70 | slow |
| 4096 | 256 | 1978.53 | 563.35 | 1076.83 | ok decode |
| 4096 | 512 | 2727.69 | 563.98 | 1196.97 | near-best |
| 8192 | 128 | 1161.80 | 522.01 | 824.82 | slow |
| 8192 | 256 | 1831.49 | 563.36 | 1046.37 | ok decode |
| 8192 | 512 | 2517.02 | 563.81 | 1168.12 | lower pp |
| any | 1024 | — | — | — | OOM compute buffers |
Conclusion: ubatch=512 is important for prompt throughput; ubatch=128 hurts both pp and decode. batch between 1024 and 4096 has similar decode; b=1024, ub=512 had best total tok/s in this sweep. Previous b=4096,ub=512 remains fine if serving code prefers a larger logical batch.
Completed result: GPU clock/power lock attempt
Job: https://huggingface.co/jobs/abidlabs/6a2fb078234ca64b60124903
nvidia-smi reports A10G already at 300W power limit and max app clocks 1710/6251 MHz. Attempts to set power limit or lock clocks failed with insufficient permissions. Benchmark results were baseline-like: single 122.81 tok/s; batched 565.17 aggregate decode tok/s.
Running next
- Boundary search above
npl=160with exact contexts (npl=168,176,184) and F16/Q4 KV, using smaller logical batch to reduce compute buffer pressure: https://huggingface.co/jobs/abidlabs/6a2fb81a871c005b5352d3f7
Completed result: boundary search above npl=160
Job: https://huggingface.co/jobs/abidlabs/6a2fb81a871c005b5352d3f7
Goal was to see if concurrency above npl=160 could beat the exact-context F16-KV best. Used exact c = npl * (512 + 256), smaller logical batch -b 1024 -ub 512, and tried F16 plus q4_0 KV.
| npl | ctx | KV | S_PP tok/s | Aggregate S_TG tok/s | Total S tok/s | Result |
|---|---|---|---|---|---|---|
| 168 | 129,024 | f16 | — | — | — | OOM compute pp buffers |
| 168 | 129,024 | q4_0 | 2809.15 | 550.99 | 1187.23 | fits, slower than npl160 F16 |
| 176 | 135,168 | f16 | — | — | — | OOM rs cache |
| 176 | 135,168 | q4_0 | 2802.04 | 554.97 | 1192.54 | fits, slower |
| 184 | 141,312 | f16 | — | — | — | OOM rs cache |
| 184 | 141,312 | q4_0 | 2792.13 | 557.80 | 1195.66 | fits, slower |
Conclusion: although q4_0 KV allows higher npl, it still underperforms npl=160 with F16 KV (565.22 tok/s). Current best remains npl=160, exact ctx, F16 KV.