Why run one myself. The model sits on a box in my office, so my code stays there. It works offline, and nothing about the setup changes unless I change it.
What it's for. 32 GB of graphics memory runs a strong 27B model. I still reach for hosted models when the problem is genuinely hard; the box in my office handles the fast, private, repetitive passes.
What comes with it. Running a model yourself means you get to customize and control a lot of things during inference. It also means you manage your own security and configure your own safeguards.
What I did. Took every major model I could fit in VRAM and varied the configurations, with vLLM and llama.cpp as the inference servers, until the results reached statistical significance.
Gaps are paired by item vs a control run, with 95% CIs. ★ = CI excludes zero. Dotted terms define themselves on hover.
The map
Everything runs on one RTX 5090. Below is what's on the bench, then what each setting turned out to cost. Every row links to the tab with the working.
| Setup — what's on the bench | ||
|---|---|---|
| Models | 23 registered checkpoints and each one's permutation surface | Current leader: a dense 27B with the MTP head — pending three validation gates. |
| Engines | llama.cpp vs vLLM — does the stack change the answers? | llama.cpp serves everything today; parity rows keep vLLM honest. |
| Hardware | The one box everything runs on | RTX 5090 32 GB · Ryzen 9900X · 64 GB DDR5 · all Micro Center. |
| Evals | The eight tasks every cell is scored on | Frozen at run start; no LLM judges; item counts set each task's resolution. |
| Configurations | The factor grid the experiments walk | 276 cells; one knob varied at a time, everything else pinned. |
| Results — what was learned | ||
| Weight precision | Can I recover 5–10 GB without losing accuracy? | Yes within the GGUF family. NVFP4 is the exception: real instruction-following deficits, being checked against run-to-run noise. |
| KV cache | Can I shrink the cache for free? | No detectable loss in 56 clean comparisons; q8_0 is the current choice. Long-context retrieval still unchecked. |
| Speculation | How much free speed does drafting buy? | Quality preserved; the speed measurement (depth 0/2/4) is running now. |
| Dense vs MoE | Which frontier point do I want to serve? | Currently the dense 27B — quality costs me more than latency. New MoEs queued that could change this. |
| Load | Does quality survive 8 concurrent requests? | Untested — the last gate before cutover; queued next. |
| Watermark | Can output carry a provable signature without a quality tax? | Detection pipeline validated on CPU; the quality-cost question runs on GPU after the cutover decision. |
Reading the tabs: each has a toy model you can play with (simulated), the run's actual measurements, and my call. Dotted terms define themselves on hover.
Which model?
23 measured — one leaderEvery checkpoint benchmarked on this machine: decode speed (log scale) vs mean quality (unweighted mean over shared tasks). The dense 27Bs lead quality, the 30B-class MoEs lead speed.
The full registry, and which permutations each checkpoint supports: an MTP draft head is a property of the checkpoint; a vLLM twin exists only where the same weights are published as an HF checkpoint; KV columns show which cache quantizations have measured cells so far. Click a column header to sort.
The current leader is qwen38-27b with the MTP head — the newest dense 27B, which arrived mid-campaign and beat the incumbent it was measured against. What keeps this honest: the field moves monthly. Four candidates are queued for evaluation after this run's ledger freezes — Nemotron 3.5 Lightning, Ornith 1.5 35B-A3B, GLM-5.3-Flash, and Nex-N2.5-mini 35B, and anything new that fits in 32 GB gets the same paired treatment against the frozen baseline rather than a fresh start.
Evals
Eight tasks, chosen to cover what a coding-assistant server actually does — and frozen at run start, because a suite that shifts mid-campaign can't support paired comparisons. Item counts matter: they set how small a gap each task can resolve.
| task | items | what it measures | resolution |
|---|---|---|---|
| gsm8k_cot_zeroshot | 1,319 | grade-school math word problems, step-by-step reasoning | fine (~±1 pt) |
| bigcodebench_full | 1,140 | realistic library-using coding tasks, executed and unit-tested | fine |
| cruxeval_output | 800 | predicting what a piece of code prints — code reading | fine |
| ifeval | 541 | following precise, checkable instructions ("exactly 3 bullets…") | medium |
| ifbench | 300 | harder instruction-following | medium (~±3–5 pts) |
| humaneval_instruct | 164 | classic small Python functions, executed | coarse |
| bigcodebench_hard | 148 | the hardest bigcodebench slice | coarse |
| internal_workload | 5 | private smoke check of the server's real prompts | go/no-go only |
Scoring is automatic end-to-end: code tasks run the generated code against tests; instruction tasks check constraints programmatically; math extracts the final answer. No LLM judges anywhere — a deliberate choice, since a judge model would itself be a confound when comparing engines and quantizations.
Coverage is deliberately skewed toward code and instruction-following, because that's the server's job. What it cannot see: long-context retrieval (queued with the 131k pass), conversational quality, and anything a 5-item smoke check merely gestures at. A capped general-knowledge pass (HLE) is queued in the fixup stage as a breadth check, not a decision input.
Configurations
Every experiment tab is a slice of one crossed design: each cell is a (model × engine × settings) combination run against the full quality suite under identical conditions. These are the factors and the levels being crossed:
| factor | levels | experiment tab |
|---|---|---|
| model checkpoint | 23 registered (see Models) | Models · Dense vs MoE |
| weight precision | Q4_XL · Q5 · Q6 · Q8 · F16 · NVFP4 | Weight precision |
| KV cache type | f16 · q8_0 · q4_0 | KV cache |
| draft depth (MTP) | 0 · 2 · 4 | Speculation |
| engine | llama.cpp · vLLM (where mapped) | Engines |
| concurrency (np) | 1 · 8 (quality); 1·2·4·8 (speed) | Load |
| watermark bias δ | 0 · 2 · 4 (γ=0.25, h=4) | Watermark |
| held constant | temp 0 · seed 1234 · 32k context · same prompts | every cell |
Each factor's range: the grey track is what the software allows, solid dots are levels with measured cells, hollow dots are levels queued but not yet run. Hover a dot.
Not every combination runs — the full cross would be thousands of cells. The design measures each factor where it can matter and drops cells whose outcome is provably implied by others (e.g. depth × quantized-KV interactions, since verification makes depth quality-inert). Replicate cells re-run identical configurations to measure the noise floor that every comparison is judged against.
Because the question is never "is model A good" — it's "does this knob change the answer while everything else is pinned." Paired cells that differ in exactly one factor are what let a 0.4-point gap be attributed to the cache instead of to luck.
Weight precision (Q4 → Q8, NVFP4)
no detected in-family loss — one exceptionThe question: can I recover 5–10 GB for context without losing accuracy? Quantization stores weights in 4–8 bits instead of 16. There are reasons it can work (rounding errors tend to average out in large sums) — but where it fails is not predictable from theory, which is why every cell below is measured rather than assumed.
Same family, only bit-width changes: the line stays flat while VRAM moves 5–10 GB. no detectable in-family difference between Q4_XL and Q8 on these tests. The f16 controls agree.
The exception: NVFP4 shows three starred deficits — two instruction-following (IFEval −4.1, IFBench −5.3 points) and one math reasoning (GSM8K −1.4). Large enough to investigate before the cutover; the replicate tier grades them against the run-to-run noise floor.
I use the smallest XL quant and spend the recovered VRAM on context. What surprised me: NVFP4's deficit concentrates in instruction-following, not in the math or code where I expected a 4-bit float format to hurt. If replicates show the IFEval gap is above run-to-run noise, choosing NVFP4 means knowingly paying ~4 IFEval points for its speed — that is the decision, and I haven't made it yet.
KV-cache precision (f16 → q8_0 → q4_0)
no detectable lossThe question: can I shrink the cache — the other big VRAM consumer — for free? The KV cache holds every previous token's attention state; at 32k context it rivals the weights in size. It should tolerate quantization better than weights do, because attention renormalizes scores — but "should" is exactly the thing under test, and long-range retrieval is where I'd expect it to fail first.
Every clean gap estimate as one dot — the distribution behind the verdict. Hover a dot for its model, task, CI and n.
The payoff: q8_0 saves ~1–3.8 GB, q4_0 up to ~5.9 GB of measured peak VRAM.
KV quantization looks worth using on this machine. Across 56 unconfounded comparisons, none showed a statistically significant quality difference, and every CI excludes a loss worse than 4.1 points (2.1 on the n≥800 tasks). That still leaves room for losses the tests could not detect. The memory savings are useful, so q8_0 is my current choice. Long-context retrieval is the next check.
Speculative decoding (MTP head)
quality preserved — speed being measuredThe question: how much free speed does drafting buy on my workload? A small head guesses the next few tokens; the big model verifies them in one pass and keeps only tokens it would have sampled anyway, so the output distribution is preserved. Speed is the open question — it depends on acceptance rate and drafting overhead, and that measurement is running now.
MTP-on vs MTP-off at identical weights: every delta inside ±0.01 — consistent with distribution-preserving verification (theory does not bound finite-sample deltas, so this is evidence, not proof).
Speed at depth {0,2,4} is measuring right now (tier 4b, in progress).
Quality results are reassuring. I'll keep it enabled only where the depth-ladder speed numbers justify it — the ladder is on the GPU now, and depth 0 (off) is a live possibility.
Dense vs mixture-of-experts
real trade — speed vs depthThe question: which point on the speed–quality frontier do I actually want to serve? Dense models run every token through all parameters; MoEs activate ~10%, so they read far less memory per token — and memory bandwidth is the wall on one GPU. That mechanism predicts the speed difference well. It does not predict the quality difference; that's what the measurements are for.
All 23 models: decode speed (log) vs mean quality — the unweighted mean of each model's benchmark scores over its shared task set (internal workload check excluded). Two clusters among these tested checkpoints — the dense models land slow-and-higher, the MoEs fast-and-slightly-lower. This supports picking a model from this set; it does not isolate architecture as the cause (training data, scale and tuning all differ).
Why I currently prefer the dense 27B: on my workload the quality gap costs me more than the latency gap does, and the other knobs (MTP, NVFP4, quantized KV) claw back most of the speed. The strongest MoE alternatives in this set are qwen3-coder-30b (fastest, weakest on code-execution prediction) and qwen36-35b (closest on quality, ~4× faster). An MoE that closed the bigcodebench gap would change my answer — which is why Nemotron and Ornith are queued for October.
Concurrency & load (np=1 → np=8)
pending — queued nextEvery quality number so far was taken one request at a time. Under batching, kernel shapes and cache sharing change the arithmetic, so the same prompt can legitimately produce different tokens. Throughput at np∈{1,2,4,8} is already measured (see the explorer); quality under 8-way load never has been — that measurement runs right after the replicates.
I won't cut over on single-request quality alone — the production server runs loaded, and the new config stacks three changes at once. If quality at np=8 holds within the replicate noise floor, the provisional config goes final.
Watermarking (green-list bias δ)
pilot done — GPU study queuedThe question: can the server's output carry a provable signature without a quality tax? Hash the last h tokens → split the vocab into a γ "green" fraction → add bias δ to green logits. Text drifts green in a way only the key-holder can test. Detection strength grows ~√length; δ trades detectability against a possible quality tax.
Detection increased with watermark strength and with text length, and every generation is visible: the control cloud sits at z≈0 (0/30 detections), δ=2 straddles the threshold (16/30 at 300 tokens), δ=4 clears it for 29/30 — and the spread within each dose is as informative as the means. (One pilot, one small model — staging evidence, not the study.)
Pipeline validated for zero GPU time. Next: quality cost on the production model (study W1: dose-response), whether quantized KV blunts detection (W2), and whether the mark survives paraphrase and splicing (W5) — after the cutover decision.
Inference engines
two stacks, measured head-to-head where possibleThe engine is a knob like any other — the same weights can serve faster or slower, and in principle differently, depending on who runs them. The trap in comparing engines is comparing files instead: llama.cpp serves GGUF, vLLM serves HF checkpoints, and two files of "the same model" usually aren't. This run's design: for a handful of parity rows, the GGUF was converted losslessly from the exact HF checkpoint vLLM serves, so an f16 gap is purely the stack; the 27B Q8-vs-FP8 row adds one controlled variable (8-bit format) on top.
| llama.cpp v0.2.0 | vLLM 0.27.1 | |
|---|---|---|
| model format | GGUF (all 23 rows) | HF checkpoints (4 mapped twins) |
| quantized weights | K-quants, NVFP4 | FP8, NVFP4 (blocked on this GPU) |
| quantized KV cache | q8_0 / q4_0, any model | FP8, where the checkpoint pins it |
| speculative decoding | MTP head (3 checkpoints) | not exercised in this run |
| concurrency model | slot-split cache, np≤8 tested | continuous batching, paged attention |
| tool-calling API | — | queued (characterization tail) |
| Blackwell (sm120) status | clean | partial — see below |
Three findings, all discovered by trying rather than reading release notes. One: vLLM 0.27.1 routes hybrid-architecture prefill through a JIT compiler (FlashInfer) that cannot build for this GPU's compute capability with the CUDA toolkit the machine has — which makes the NVFP4 twin unservable on vLLM here, full stop. Two: a checkpoint that pins FP8 KV in its own config silently selects an incompatible attention backend unless the launcher detects it and overrides — a class of failure where the config is honored and the server still dies. Three: at 32 GB, the f16 parity rows are the only place both stacks can hold identical weights; everywhere else the honest comparison is "stack + format", not "stack".
The parity quality gaps themselves (f16 rows, Q8-vs-FP8 row) live in the run ledger and land in the decision brief with the fixup pass — not summarized here until they're checked.
llama.cpp is the serving stack today: it runs every checkpoint, quantizes any cache, and carries the MTP head that the leading model depends on. vLLM stays in the campaign for what it's genuinely better at — continuous batching and a tool-calling API — and because the parity rows keep both stacks honest. The open item that could flip this: a CUDA toolkit upgrade unblocks vLLM's Blackwell path, and its batching may matter more once the server is measured under real concurrent load.
My local AI setup
I run an AI coding assistant on a desktop computer in my office. Here's the hardware, what each part does, and the limits I've run into.
A Gigabyte RTX 5090 runs the AI model. Its memory decides which models fit entirely on the graphics card.
Separate from graphics memory. Two 32 GB DDR5 sticks that load the models and run the software and tests around them.
Holds the downloaded model files — about 40 of them, 10–30 GB each — and every saved test result.
This is an air-cooled build — a three-fan card and a large air cooler on the processor, in a mesh case. Under sustained testing the graphics card reaches 89–91 °C and slows itself down to stay there. The speed numbers on this page are what this machine does under those conditions, which is why they're measured here rather than quoted from reviews.
The main constraint is graphics memory: 32 GB is the budget, and each setting on this page changes how much of it the model needs. System memory turned out to matter more than I expected — one piece of model-serving software peaked at 56.6 GB of it mid-run and the process was stopped after running out. That's one measurement on one configuration, not a minimum spec.
Full hardware specifications
| Part | Component | What it does here |
|---|---|---|
| Graphics card | Gigabyte RTX 5090 Gaming OC, 32 GB GDDR7SKU 805390 | Runs the model. Its 32 GB is the limit everything else is tuned against. |
| Processor | AMD Ryzen 9 9900X, 12 coresSKU 721209 | Ran the whole watermark pilot on idle cores while the graphics card was busy — 7 CPU-hours, no GPU time. |
| System memory | 64 GB DDR5-6400 CL34 (2×32) | Loads models and runs the test harness. Peak observed: 56.6 GB. |
| Storage | SanDisk WD_BLACK SN850X 4 TB NVMeSKU 753095 | Saved model files and run results. At 10–30 GB per model, 2 TB would already be tight. |
| Motherboard | MSI MPG X870E Carbon WiFiSKU 755892 | Connects the graphics card and the drive at full speed. |
| Power supply | 1200 W, 80+ Platinum, ATX 3.x | Headroom for a card that pulls hard in bursts. |
| CPU cooler | Noctua NH-D15 chromax.blackSKU 958470 | Air, not liquid. Keeps the processor quiet during CPU-only runs. |
| Case | Lian Li LANCOOL 217 InfinitySKU 919928 | Mesh front. The airflow the card is working against. |
Everything from Micro Center, bought together in June 2026. Where the specific model matters, the link goes to Micro Center's search for that SKU; memory and power supply are listed by spec, since any part meeting it would do.
Technical notes. The 5090 is a Blackwell card, which is why NVFP4 is available as a weight format at all — see Precision. It's also a factory-overclocked model, so its speed numbers run a little above a reference card's; that's one more reason every speed on this page is measured on this box. The board gives the card PCIe 5.0 x16 and the drive a direct NVMe link. The CPU-only watermark pilot is under Watermark.
How I test
Paired by item. Two configurations are only ever compared on the exact same questions; the difference is computed per question and summarized, which cancels question difficulty.
Intervals, not points. A gap is reported with a 95% bootstrap confidence interval over items. A ★ means the interval excludes zero. The registry's per-model quality intervals are a looser approximation (binomial per task, averaged) used only to refuse false rankings.
Ties are ties. Models whose intervals overlap the leader's are marked ≈ tie rather than ordered.
No LLM judges. Code is executed against tests, instructions are checked programmatically, math answers are extracted. Nothing is graded by another model.
One writer. Every number on this page comes out of one generated data file; nothing is hand-edited. Dates on each Record strip say when and under what environment each number was measured — a measurement is only ever quoted with its era.
Known limits. One machine, one run, greedy decoding at temperature 0, a suite skewed toward code and instruction-following. The run-to-run noise floor is being measured now (replicate tier) and will retroactively grade every borderline ★.