Skip to content
Jared Frost
Go back

TRT-LLM vs vLLM vs llama.cpp on Blackwell — sparkinfer v0.3.7 owns decode on the RTX 5090

I develop and maintain sparkinfer — a Blackwell-native inference runtime that is ~30% faster than llama.cpp on decode while keeping the same LLM quality (~64.4% vs ~65.9% overall on a 196-item benchmark; within ~1.5 points). On an RTX 5090 with Qwen3-30B-A3B at Q4_K_M, that’s ~485 tok/s vs ~366 tok/s. That’s the headline.

sparkinfer: ~30% faster decode than llama.cpp, same LLM quality — on a 2.5 MB native binary.

Below that, I also ran TRT-LLM, vLLM, and llama.cpp head-to-head on my RTX PRO 6000 Blackwell — same model (Qwen3-30B-A3B), same 28K-token input — to show where the datacenter engines win (prefill) and where llama.cpp wins on my workstation card (decode vs TRT-LLM). Different GPU, different phase, different answer.

Table of contents

Open Table of contents

The setup

A methodology note up front, because honesty matters more than a clean story: my prompt builder capped at ~28K tokens, so the “32K” and “128K” tests used the same input content (the 128K run only differed in having YaRN active). And TRT-LLM ran on its PyTorch (_torch) backend, not the compiled TRT engine path — the engine path would likely add 10–25%. So read these as a real but specific snapshot, not a final verdict.

The head-to-head (32K context, 28,002 → 512 tokens)

EngineQuantTTFTTotal TPSDecode TPSVRAM
TRT-LLM 1.2.1BF16779 ms133.0166.893.5 GB
vLLM 0.23.0BF16909 ms125.6161.691.0 GB
llama.cppBF167,196 ms50.7176.4~60 GB
llama.cppQ8_03,477 ms96.2276.7~34 GB
llama.cppQ4_K_M3,576 ms101.8351.6~21 GB

Two different engines hold the two crowns. That’s the whole story, and it isn’t an accident.

The split: prefill is compute, decode is bandwidth

TTFT (prefill) — TRT-LLM wins, 4.5–9.2× over llama.cpp. Processing a 28K-token prompt is compute-bound: you run the whole context through the model in big parallel matmuls. TRT-LLM’s fused MoE GEMM and graph-optimized prefill deliver 779 ms; vLLM is close at 909 ms; llama.cpp lands at 3.5–7.2 s. If your product shows the user a first word fast (interactive chat), this is the axis that matters and the datacenter engines own it.

Decode — llama.cpp Q4_K_M wins, 2× over TRT-LLM. Autoregressive decode at batch 1 is the opposite regime: it’s memory-bandwidth-bound. Every single token, you stream the active weights out of VRAM to produce one output token; the tensor cores mostly idle. So the metric that decides decode speed isn’t FLOPs — it’s bytes moved per token. 4-bit weights are 4× smaller than BF16, so Q4_K_M moves ~4× less data per step and hits 351.6 tok/s, more than double TRT-LLM’s 166.8. The roofline literally flips the winner between the two phases.

That’s the lesson I keep coming back to: “which engine is fastest?” is the wrong question. “Fastest for which phase, at which batch size?” is the right one. Prefill-heavy (RAG, long prompts, short answers) and decode-heavy (long-form generation) workloads want different engines on the same hardware.

The counterintuitive bit: BF16 prefill is slower than Q8/Q4

Look at llama.cpp’s prompt-processing speeds: BF16 does 3,894 tok/s, but Q8_0 does 8,061 and Q4_K_M does 7,838. Full precision is slower at prefill — which surprises people, because prefill is “compute-bound.” The reason is that MoE prefill is still gated by getting the expert weights to the tensor cores, and BF16 experts are 2× the bytes of Q8_0. Even in the compute-bound phase, the bandwidth to feed the cores is the bottleneck, so the more tightly packed weights keep the cores better fed. Quantization helps on both sides of the prefill/decode divide here.

sparkinfer v0.3.7: decode king on the RTX 5090

sparkinfer v0.3.7 on the RTX 5090 — the numbers behind the ~30% claim. The PRO 6000 table earlier is a separate snapshot: TRT-LLM owns prefill there, llama.cpp Q4_K_M owns decode vs the datacenter engines.

Where TRT-LLM and vLLM retrofit sm_120, sparkinfer targets Blackwell natively — ~15 s compile, decode-only today, experts kept quantized-resident and dequantized on read. On v0.3.7 (Jul 7) it ships two models and leads llama.cpp on both — same RTX 5090, same Q4_K_M GGUF, warm and interleaved vs llama-bench:

Modelsparkinferllama.cpp
Qwen3-30B-A3B~485 tok/s~366 tok/s+32%
Qwen3.6-35B-A3B~387 tok/s~275 tok/s+38%

That’s not a marginal win — it’s +32% on the same model this post benchmarks, and +38% on Qwen3.6-35B-A3B, a Gated-DeltaNet hybrid llama.cpp hasn’t really optimized yet. sparkinfer shipped Qwen3.6 cold and still pulled ahead: 23 → 387 tok/s in 48 hours, driven by a shared-expert coalesced-GEMV kernel (7.2× on that path), int8 dp4a MMVQ for Q5_K expert down-projection, split-K bf16 dense projection GEMV, and Q8 shared-expert MMVQ with MoE pipelining. (The split-K-for-occupancy lever is the same one I used for decode attention in genie-ai-runtime.)

Runtime footprint

Speed isn’t the only axis where a from-scratch engine can differ. Runtime footprint, excluding model weights and Python launcher scripts:

RuntimeMeasured artifactSizesparkinfer is
sparkinfernative runtime binary2.5 MBbaseline
llama.cppCUDA runtime executable + shared libs80 MB33× smaller
vLLMruntime package605 MB243× smaller
SGLangruntime + native kernel packages1.9 GB743× smaller
TensorRT-LLMruntime package3.6 GB1,430× smaller

A 2.5 MB native binary vs 3.6 GB for TensorRT-LLM is a different product shape entirely — closer to shipping a single executable on edge hardware than deploying a Python framework stack.

Quality check

Decode speed means nothing if the model stops being the model. LLM quality check, 25% benchmark tier, 196 items:

RuntimeBFCLGSM8KHumanEvalIFEvalMMLU-ProOverall
sparkinfer GGUF73.33%84.85%80.00%77.08%44.00%64.37%
llama.cpp GGUF72.00%90.91%80.00%64.58%48.00%65.90%
vLLM AWQ76.00%84.85%80.00%77.08%48.00%66.92%

Honest read: sparkinfer trails llama.cpp by 1.5 points overall and vLLM AWQ by 2.6 points — not identical, but in the same band. The wins on IFEval and BFCL trade against GSM8K and MMLU-Pro. For a decode-only engine still adding models, that’s the quality bar I’d want before claiming a speed crown.

Two honest caveats. First, these are RTX 5090 numbers — I haven’t re-run v0.3.7 on my RTX PRO 6000, so the headline table above and this section are different cards. Second, sparkinfer is still decode-only — no prefill/TTFT yet — so TRT-LLM remains the chat-latency pick on the PRO 6000. But for sustained decode on consumer Blackwell, v0.3.7 is the king. The eval pipeline backs the claim: dual-model scoring, same-box baselines against origin/main, and every run committed as raw log + result JSON with a per-run URL — a better bar than most “we’re faster” screenshots.

Memory is the other axis

Decode speed isn’t the only thing quantization buys. At BF16, both datacenter engines sit at ~91–93.5 GB of the 95 GB ceiling — Qwen3-30B-A3B at BF16 with a 32K KV cache is essentially the practical floor for this card; an 80 GB A100/H100 couldn’t hold it without quantizing or shortening context. Q4_K_M runs the same model in ~21 GB — meaning the GPU that barely fits one BF16 instance fits four Q4_K_M instances. Quantization is a density lever as much as a speed one.

128K context, cold starts, and the rest

A couple of practical findings that don’t fit the headline tables but decide real deployments:

What I’d actually pick

If you’re doing…PickWhy
Latency-critical chat, ≤40KTRT-LLM779 ms TTFT, best prefill
Long-form generation (PRO 6000)llama.cpp Q4_K_M351 tok/s decode in my snapshot; 2× over TRT-LLM
Long-form generation (RTX 5090)sparkinfer v0.3.7~485 tok/s, +32% vs llama.cpp — decode king on consumer Blackwell
128K context, one flagvLLMonly API-level YaRN
Limited VRAM / many instancesllama.cpp Q4_K_M21 GB → 4 instances in 95 GB
Rapid prototypingvLLMpip install vllm, done
A Blackwell-native decode enginesparkinfer v0.3.72.5 MB binary; +32% decode on 5090; quality within ~2 pts of llama.cpp

The takeaway

The roofline decides, not the brand name on the engine. Prefill is compute-bound, decode is memory-bandwidth-bound, and a 4-bit quant trades a sliver of quality for ~4× less decode bandwidth and 4× the density — which is why a hobbyist-friendly engine beats two datacenter engines on the one metric (decode) that dominates long-form generation, while losing the one (TTFT) that dominates chat.

None of this is knowable from a spec sheet or a vendor benchmark. It came from running three engines in the same regime on my PRO 6000 — same GPU, same model, same 28K prompt — and reading the numbers honestly, including the parts that don’t flatter a tidy narrative. On consumer Blackwell, sparkinfer v0.3.7 is the decode king I haven’t re-benchmarked on my card yet. That’s the whole point of this blog: measure your actual workload on your actual hardware, because the answer flips depending on which half of the forward pass you’re standing in — and which GPU you’re standing on.


Share this post:

Previous Post
Distilling frontier reasoning into an edge model — Unsloth + W&B in Colab, Axolotl on Blackwell
Next Post
The confidential API: sending a prompt to a cloud GPU the provider can't read