Tested in this article: XHToken Spark-X2.6-4B at BF16/FP8 (vLLM) and Q8_0/Q4_K_M (llama.cpp) Hardware: RTX 6000 Blackwell · 96 GB VRAM, 128 GB DDR4 OS: Debian 13 Engine: vLLM / llama.cpp Benchmarks: BFCL v4, MMLU-Pro, IFEval, HumanEval+, MBPP+, Arena-Hard, (+ harness tests)
📊 Expand Model Config
  Spark-X2.6-4B-bf16:
    cmd: >
      vllm serve /models/spark-x2.6/bf16
      --served-model-name Spark-X2.6-4B-bf16
      --port ${PORT}
      --max-model-len 131072
      --max-num-seqs 1
      --gpu-memory-utilization 0.9
      --dtype bfloat16
      --enable-prefix-caching
      --async-scheduling
      --chat-template /models/spark-x2.6/bf16/chat_template.jinja
      --reasoning-parser qwen3
      --enable-auto-tool-choice
      --tool-call-parser spark25
  Spark-X2.6-4B-fp8:
    cmd: >
      vllm serve /models/spark-x2.6/fp8
      --served-model-name Spark-X2.6-4B-fp8
      --port ${PORT}
      --max-model-len 131072
      --max-num-seqs 1
      --gpu-memory-utilization 0.9
      --dtype bfloat16
      --enable-prefix-caching
      --async-scheduling
      --chat-template /models/spark-x2.6/fp8/chat_template.jinja
      --reasoning-parser qwen3
      --enable-auto-tool-choice
      --tool-call-parser spark25
  Spark-X2.6-4B-Q8:
    cmd: >
      /usr/local/bin/llama-server
      --port ${PORT}
      --model /models/spark-x2.6/Spark-X2.6-4B-Q8_0.gguf
      --ctx-size 131072
      -ngl 99
      --main-gpu 0
      --split-mode none
      --fit off
      --flash-attn on
      --temp 1.0 --top-p 0.95 --top-k 0
      --jinja
      --parallel 1
      --batch-size 4096
      --ubatch-size 2048
  Spark-X2.6-4B-Q4_K_M:
    cmd: >
      /usr/local/bin/llama-server
      --port ${PORT}
      --model /models/spark-x2.6/Spark-X2.6-4B-Q4_K_M.gguf
      --ctx-size 131072
      -ngl 99
      --main-gpu 0
      --split-mode none
      --fit off
      --flash-attn on
      --temp 1.0 --top-p 0.95 --top-k 0
      --jinja
      --parallel 1
      --batch-size 4096
      --ubatch-size 2048

Intro

Back in my Small LLM Finale entry I setled on Nanbeige4.2-3B as my go-to small model. XHToken’s Spark-X2.5-4B was close behind, so when I saw they dropped the sequel (Spark-X2.6-4B) with notably better benchmarks I had to pull it and give it a try. I ran it through the same tests as the models in that previous entry, and a little bit more. I wanted to be able to compare to the results there but also experiment a bit with different quantizations. I ended up focusing on BF16, FP8, Q8_0.gguf, and Q4_K_M.gguf.

Lets find out if 2.6 is enough better than 2.5 to consider using over Nanbeige.

Speed

Prefill / Decode Sweep

Single stream synthetic sweep for a quick speed check. 512 tokens generated, prompt size walked from 1k up to max. The two vLLM quants were served at the model’s full --max-model-len 1048576 for this one, so they run all the way out to 1m. The GGUF quants were served at --ctx-size 131072 like the config block above, so their lines stop at 131k.

📊 Expand Speed Table

Each cell is prefill tok/s · time-to-first-token · decode tok/s at that context size:

ContextBF16 (vLLM)FP8 (vLLM)Q8_0 (llama.cpp)Q4_K_M (llama.cpp)
1k27,155 · 0.04 s · 147.741,067 · 0.02 s · 214.011,038 · 0.09 s · 196.711,032 · 0.09 s · 240.6
8k37,662 · 0.19 s · 144.653,430 · 0.14 s · 207.418,346 · 0.40 s · 188.817,799 · 0.41 s · 222.4
32k31,339 · 0.91 s · 135.341,715 · 0.69 s · 188.419,018 · 1.51 s · 169.618,563 · 1.54 s · 202.5
131k18,794 · 6.07 s · 105.522,221 · 5.13 s · 134.411,863 · 9.63 s · 125.011,696 · 9.76 s · 143.9
262k12,032 · 18.96 s · 83.213,450 · 16.96 s · 100.0--
524k6,924 · 65.86 s · 58.57,397 · 61.66 s · 66.3--
1M4,036 · 215.45 s · 38.14,076 · 213.36 s · 41.3--
mean (1k-131k)28,738 · 1.80 s · 133.339,608 · 1.49 s · 186.015,066 · 2.91 s · 170.014,772 · 2.95 s · 202.3

Prompt tokens were 1,005 / 7,238 / 28,608 / 114,090 / 228,066 / 456,017 / 869,679 - the harness leaves headroom above the prompt for the 512 generated tokens. The 1M BF16 point is a single rep, the 1M FP8 point is two.

llama.cpp also reports a server-side prefill number (15.7k at 1k prompt, 21.8k / 21.7k at 8k / 32k, 12.7k at 131k). I used the client-measured column for all four so the columns are comparable - at 131k the two agree to within 7%, at 8k they don’t.

Decode order never changes. Smaller quant, faster decode on a single stream nearly context-independent.

FP8’s prefill lead evaporates as the prompt grows but 33%+ at 32k or less is HUGE for the range I’ll actually use this model for. 1 million ctx is just fun to see.

llama.cpp prefill tops out near 19k. Falls behind vLLM as always. Q4_K_M does have the small decode lead, but sacrifices TTFT for it.

Worth noting as always: vLLM took 23.5 s / 23.4 s to load and warm up versus 1.5 s for both GGUFs. If this is an always-loaded task model, no big deal, but llama.cpp makes a load-on-demand setup viable.

Concurrency

1,2,4,8 simultaneous requests. vLLM gets to shine as it always does.

📊 Expand Concurrency Tables

Table one is the per-client experience - each cell is aggregate prefill tok/s · mean TTFT (worst TTFT) · per-client decode tok/s at that client count:

ClientsBF16 (vLLM)FP8 (vLLM)Q8_0 (llama.cpp)Q4_K_M (llama.cpp)
137,449 · 0.19 (0.19) s · 147.953,629 · 0.13 (0.13) s · 214.318,376 · 0.39 (0.39) s · 197.017,887 · 0.41 (0.41) s · 225.7
239,648 · 0.28 (0.37) s · 139.656,299 · 0.20 (0.26) s · 199.320,133 · 0.66 (0.72) s · 152.219,492 · 0.68 (0.74) s · 104.9
439,649 · 0.47 (0.73) s · 138.557,340 · 0.34 (0.51) s · 198.120,375 · 1.17 (1.42) s · 101.619,607 · 1.22 (1.48) s · 79.6
839,687 · 0.87 (1.46) s · 135.157,528 · 0.62 (1.01) s · 191.620,720 · 1.94 (2.79) s · 62.820,057 · 2.00 (2.89) s · 65.1

Table two is what the box as a whole puts out, aggregate decode tok/s, and how much of the theoretical 8x survived:

Quant1 client248scaling 1 → 8per-client retained
BF16 (vLLM)147.9278.8550.81,064.5x7.20-8.6%
FP8 (vLLM)214.3398.1788.61,512.3x7.06-10.6%
Q8_0 (llama.cpp)197.0304.2406.3501.6x2.55-68.1%
Q4_K_M (llama.cpp)225.7209.7318.3520.3x2.31-71.2%

vLLM barely notices the additional clients. Looking at the trend and my GPU’s utilization it would likely continue to scale favorably even up to 16 concurrent streams.

llama.cpp falls sharply. Q4_K_M even surprisingly had fewer aggregate decode tokens at concurency 2 than at 1.

Nothing surprising here, if you’re running a single user either is viable. If you’re expecting simultaneous requests, vLLM is your pick.

Size

All four context sizes:

QuantOn disk16k ctx32k ctx64k ctx131k ctxKV growth 16k → 131k
BF16 (vLLM)7.7011.9012.5013.6015.80+3.90
FP8 (vLLM)4.208.409.0010.1012.31+3.91
Q8_0 (llama.cpp)4.107.468.479.6410.54+3.08
Q4_K_M (llama.cpp)3.304.615.236.489.10+4.49

These numbers are simply for future reference. These would be slotted into a secondary GPU as a task model, so every gig counts. I’ll be weighing these numbers against the benchmark results heavily.

Traditional Benchmarks

note these are not comparable to leaderboards - different sample size, different Arena Hard judge.

All six typical suites, all four quants. These are the rerun numbers (28 Oct) - server default temperature rather than 0.0, 16k max tokens rather than 2048, and all four quants through one invocation at batch 10. The axis starts at 30 so the differences between quants are actually visible.

📊 Expand Benchmark Tables

Scores, as percentages:

SuiteSamplesBF16 (vLLM)FP8 (vLLM)Q8_0 (llama.cpp)Q4_K_M (llama.cpp)Best
BFCL v430085.0083.0081.3381.33BF16
IFEval (prompt-strict)25074.4076.4081.6076.80Q8_0
MBPP+37878.0476.4679.3775.13Q8_0
MMLU-Pro42061.6761.9161.4360.24FP8
Arena-Hard (winrate)7558.8056.4055.0446.28BF16
HumanEval+16449.3942.0747.5645.12BF16
mean of the six67.8866.0467.7264.15BF16

Difference against the BF16 run, in points:

SuiteFP8Q8_0Q4_K_M
BFCL v4-2.00-3.67-3.67
IFEval+2.00+7.20+2.40
MBPP+-1.58+1.33-2.91
MMLU-Pro+0.24-0.24-1.43
Arena-Hard-2.40-3.76-12.52
HumanEval+-7.32-1.83-4.27
mean-1.84-0.16-3.73

IFEval’s other three variants (prompt-strict is what’s charted) - instruction-level strict 82.73 / 84.20 / 87.73 / 84.33, prompt-level loose 78.40 / 78.40 / 84.40 / 80.40, instruction-level loose 85.67 / 85.73 / 90.13 / 86.67.

BFCL v4 by subset, 50 samples each:

SubsetBF16FP8Q8_0Q4_K_M
simple_python94989498
multiple96909092
parallel90909088
irrelevance84848482
live_multiple88807274
multi_turn_base58565854

Mean wall time per request. The harness runs its own concurrency, so this mixes scheduling in with generation - same caveat as the last entry.

SuiteBF16FP8Q8_0Q4_K_M
BFCL v41.89 s3.60 s5.94 s4.98 s
HumanEval+3.45 s2.18 s4.44 s5.08 s
IFEval4.79 s2.42 s8.83 s8.46 s
MBPP+7.34 s8.77 s23.38 s20.10 s
MMLU-Pro10.89 s8.77 s27.09 s23.63 s
Arena-Hard199.61 s195.31 s266.24 s240.75 s

Instruction following went up with quantization. which is the opposite of expectation.

BFCL is still the flattest suite at a 3.7 point spread, but is directional by quant.

HumanEval+ is a pain cave for this model and the quantizations chip away at it further. We don’t have a clean directional result though.

Six-suite mean: BF16 67.88, Q8_0 67.72, FP8 66.04, Q4_K_M 64.15. Q4_K_M is the only quant that clearly had a reduction in capability (over 12 point drop in Arena-Hard!).

Pause… but some of these results are worse than Spark-X2.5-4B? I would have expected an outright beat.

Suite2.52.6
BFCL v482.3385.00
HumanEval+74.3949.39
IFEval75.2074.40
MBPP+83.0778.04
MMLU-Pro64.7661.67
Arena-Hard58.2458.80

HumanEval+ was the only to fall by a statistically significant amount. MBPP is borderline and would need a much higher sample size to differentiate. The only take here is that 2.6 is meaningfully worse than 2.5 at no-think HumanEval+. It may be the case that 2.6 changed to rely heavier on thinking than 2.5 did, or somehow the test is broken. But to the best of my knowledge nothing has changed in the application of my benchmarks in the meantime. The in-harness tests will have to settle things.

Unstructured / Personal Tests

Curated suites need something to anchor them to reality. This is the same in-harness job I ran in the Small LLM Shootout and the Swift1.5 NVFP4 comparison, so these four quants line up directly against the models in those entries.

Open-WebUI Chat - Search, Reasoning, Function Calling

The same two-turn job:

  1. Can you find a few recently released unified memory systems with 128gb+ ram and compare their prices/availability/pros and cons?
  2. Can you format that information into a single page pdf so i can share it?

Same expectations as those entries: Nvidia RTX/DGX Spark, the Mx Ultra Mac Studios, the various Ryzen AI Max 395 mini pcs, MSRP, and pros/cons covering availability, memory size, bandwidth and compute. And of course it needs to find and call the PDF creation tool sitting in the harness.

Results

BF16 (vLLM · 7.7 GiB)

Open PDF ↗

The only one of the four that used the task list - and it created, updated and closed every task cleanly, which is rare. Nine searches at the default N=5, prices all valid, though it never covered the M5 Ultra Studios. The report is formatted with no errors, and it is two pages, so the single page ask was ignored.

Only BF16 used the task list. It created the tasks, completed them, closed them and moved on - which is a legitimately rare level of sys prompt adherence. I have only seen this behavior done right a few times and only in much larger models. FP8, Q8_0 and Q4_K_M never opened one. All four still called the search tool without trouble.

Research quality did not track quant size. Q4_K_M found the expected systems and their prices better than Q8_0 did. And Q8_0’s chat answer listed different machines than its own PDF, which is a worse failure than simply being wrong once.

The one page ask is objectively checkable and only two of the four nailed it. BF16’s report is two pages while FP8 and Q8_0 are one. Notably, FP8 got there by actually looping to check and revise the layout. Very impressive.

Q4_K_M is the interesting failure of the group. It performed well BFCL but IRL it could not form one valid PDF tool call in as many attempts as I gave it. This is similar to Ninfer6000’s inability to form tool calls despite a notable victory in BFCL. Its calling into question the value of the BFCL suite.

Pelican on a Bicycle

And ending on the classic silly prompt, as always.

Generate an SVG of a pelican riding a bicycle

BF16 (vLLM · 7.7 GiB)

Open SVG ↗

Not a very impressive pelican, but it competes with the best of this size class.

SVG generated by BF16

None of these are passing as a pelican on a bicycle to anyone. At least there are wheels. I would say that BF16’s result was actually mildly better than what Spark-X2.5-4b output in the roundup. Regardless, this one is still clearly out of reach for models this size. The quantization barely matters because none of them can draw a pelican.

Conclusion

tl;dr

Overall this model seems like its got a lot going for it in a vacuum, but doesn’t dethrone Nanbeige4.2-3b as my victor amongst the small models. It also doesn’t CLEARLY outrank the previous model in my tests of Spark-X2.5-4B. I’m unsure if there is something wrong with my setup or if the model changed in such a way that made the thinking being off a bigger hindrance.