solid.cpp

GLM-5.3-Flash, on a card it isn't supposed to fit on

UD-IQ1_S · 93 GB, measured 2026-08-30

GPURTX 3060 12 GB
CPUi5-12400F
RAM30 GB
total memory42 GB
Placement
experts on CPU45 / 45 layers
context · slots2,048 tok · 1
Brain / expert cache
positioned10,575 experts
hot / warm / cold313 / 340 / 12,307
hit rate38.7%
VRAM used3.70 / 3.97 GB
substitute rate28.9%
Throughput
prompt processing1.39 tok/s
generation1.18 tok/s

Why this is the number, not a complaint: Unsloth's own guidance puts the practical minimum for this model around 102 GB of combined memory; this runs on 42 GB. We couldn't find a single published benchmark of GLM-5.3-Flash below its stated minimum footprint — this is slow because the hardware is 60 GB short, not because the placement is wrong.

solid.cpp logo

solid.cpp

llama.cpp, hardened: real bugs found, root-caused, and measured before they ship — with a live view into whether the fix is actually working.
MIT licensed based on llama.cpp CUDA · Metal · Vulkan · CPU
Everything on, one command

Run it the way it was measured

Every feature on this page, enabled together. This is the exact command the numbers below were taken from — a 26B MoE model at 64k context on a single 12GB RTX 3060.

GGML_CUDA_MOE_CACHE_TRAIN_PREDICTOR=1 \
GGML_CUDA_MOE_CACHE_SUBSTITUTE=1 GGML_CUDA_MOE_CACHE_SUBSTITUTE_MIN_RANK=4 \
llama-server \
  -m gemma-4-26B-A4B-it-UD-Q4_K_M.gguf \
  -md mtp-gemma-4-26B-A4B-it-Q8_0.gguf \
  --spec-type draft-mtp -bs --jinja \
  --moe-cache auto \
  -c 65536 \
  -ctk q8_0 -ctv q8_0 \
  --expert-atlas-file expert-atlas-evolve.json \
  --host 0.0.0.0 --port 8080
FlagWhat it turns on
-c 65536The context you want. Expert placement is derived from this — ask for more and the fork moves experts to CPU RAM to fit it, rather than silently shrinking your context. No -ncmoe needed: it resolved to 21 CPU layers on its own.
--moe-cache autoGPU-resident cache for the CPU-offloaded experts, LFRU eviction. 79% hit rate warm. Also enables router-lookahead prefetch, which predicts the next layer's experts and fetches them during the current layer's compute.
GGML_CUDA_MOE_CACHE_TRAIN_PREDICTOR=1Live-activation predictor: a small linear layer per model, trained online on the real hidden state each routing decision came from, feeding the same free-slot-only warming path the atlas uses. Self-tuning (no rate constant to guess) and persists its learned weights across restarts. Measured separately, on Ornith-1.5-35B: +0.12pp hit rate, 3/4 rounds — see the proof table below, not part of this command's own gemma-4 numbers.
GGML_CUDA_MOE_CACHE_SUBSTITUTE=1 _MIN_RANK=4On a miss, serve a resident stand-in from the router's own other top-k picks instead of paying CPU fallback — gated to only the router's least-confident picks (rank≥4) so its most consequential choices always get exact compute. Measured separately, on Ornith-1.5-35B: +8.11% tok/s, 4/4 rounds, hit rate unchanged (it can't move that metric by construction) — see the proof table below.
-ctk q8_0 -ctv q8_08-bit KV cache — halves it (2,180 → 1,158 MiB) and the freed gigabyte automatically becomes two more expert layers on the GPU. Verified lossless here: identical outputs, full needle recall at 24.7k tokens. Keep K and V the same type or you fall off the fused Flash Attention path.
--spec-type draft-mtp -mdMulti-token prediction with the draft model. ~0.6 draft acceptance here.
-bsBackend sampling.
--expert-atlas-fileFeeds the Brain view expert positions instead of an arbitrary layout. Use the discovered/evolving atlas from scripts/moe-atlas-evolve.py (co-activation-derived, persists and settles further with more traffic) rather than the static 9-topic-probe atlas from llama-expert-atlas — same file format, either works, but the discovered one is what the Brain view's layout was actually designed around. Cells serving as a live substitution stand-in flash yellow in the same view.

Measured on that command (predictor/substitute env vars not included in this specific run): ~64 tok/s generation warm (61.8–66.3 across runs), 4 concurrent slots. The first request after startup runs nearer 35 tok/s and climbs as the expert cache fills — the steady-state number is the honest one to plan around, and the cold one is the honest one to expect. The predictor and substitute numbers above are real but come from a separate hardware/model pairing (Ornith-1.5-35B) - not yet re-measured on this exact gemma-4 command.

Two optional extras, deliberately not in the line above. --cache-disk DIR persists the prompt cache so a restart costs 271ms instead of 5.8s — but it writes prompt content to disk (owner-only, model-fingerprinted), so it is opt-in on purpose. And GGML_CUDA_VMM_KV=1 reserves KV lazily, committing only what a conversation actually reaches.

Why this exists

Capable inference on hardware people actually own

Running a genuinely large model used to mean a rack of enterprise GPUs, or accepting whatever a hosted API gives you. The core unlock here — a GPU-resident cache for CPU-offloaded MoE experts — means a 26B-parameter MoE model that can't fit in a 12GB card doesn't just technically run: it runs fast, on a single consumer GPU. That's the whole point: put capability that used to require a data center within reach of hardware someone actually has on their desk.

But a fork that just claims speedups is exactly as trustworthy as any other unverified benchmark. So the second half of the project is making sure every claim can be checked: numbers are measured before/after on real hardware and reported honestly even when they're a loss (see the FR-Spec row below), and the cache itself is watchable live — not "trust me, it's caching," but a real-time view of which experts are hot right now. Prove it, then ship it, then let you go verify it yourself. Not a fork chasing every upstream feature — one willing to slow down and prove each claim before it ships.

Scope, honestly: the expert-cache fits the model's parameters into limited VRAM - it doesn't and can't stretch VRAM to hold arbitrary context length, which is a separate resource that scales with sequence length regardless of how the weights are cached. Pushed this to a real boundary on our own 12GB card: requesting 1M context used to hard-crash the whole process rather than fail gracefully - found and fixed that (--fit now searches by doubling from a safe floor and lands on a real, measured context size instead of guessing or crashing). A physical ceiling still exists - ask for 1M on a 12GB card and you will not get 1M - but it sits much higher than we first reported, and finding out why was the more useful result.

The first version of that fix asked the wrong question. It picked a placement, then shrank context until the model fit around it - so a 12GB card landed on 8192 tokens/slot, and we wrote that down as the hardware ceiling. It wasn't. The expert placement had been fixed first (by a command-line flag nobody revisited), and context was quietly paying for it. Inverting the order - decide placement from the context actually asked for, pushing more expert weights to CPU RAM until the request fits - gives 65,536 tokens across 4 concurrent slots on that same card and model, 8x the context and 4x the concurrency, at higher throughput than before. Nothing about the hardware changed; the program simply stopped trading away the one thing the user explicitly asked for. The lesson we'd pass on: when two automatic systems each optimise locally, check what the pair does to the number the user actually cares about.

Measured

What this actually buys, on one 12GB card

Every number below is from the same machine - an RTX 3060 with 12GB, a 26B MoE model, a SATA SSD - measured before and after, and reported whether or not it flattered us.

16×
usable context
4,096 → 65,536 tokens
concurrent slots
1 → 4
21×
faster restarts
5,775ms → 271ms prefill
1 GiB
VRAM freed by q8_0 KV
spent on 2 more expert layers
6/6
byte-identical outputs
quantized vs f16 KV
3/3
needle recall at 24.7k tokens
10% / 50% / 90% depth

Context that follows the request, not the flag. Placement used to be chosen first and context shrunk to fit around it; now the requested context is fixed and expert placement gives way to it. Same hardware, 16× the context and 4× the concurrency.

Restarts stop costing a re-read. The prompt cache now persists to disk keyed by content hash, so a 3,600-token document that cost 5.8s to process is restored in 0.27s after a full restart. Files are owner-only and model-fingerprinted - they contain your prompts, and the docs say so plainly.

KV cache quantization that pays for itself twice. Symmetric q8_0 halves the conversation cache (2,180 → 1,158 MiB), and because placement is derived from measured memory rather than a fixed flag, the freed gigabyte automatically becomes two more layers of experts on the GPU - no code, no tuning. Verified lossless here: 6/6 byte-identical outputs at temperature 0, and full needle recall at 10/50/90% depth of a 24,700-token prompt.

And the ones that didn't work, because a fork that only publishes its wins is the thing this project exists not to be: pinning hot experts in RAM measured worse than doing nothing and ships disabled; page-cache readahead gained 5% on the first request and cost 8% thereafter, so it ships disabled too; and a claimed “+21% to +50%” speedup was retracted outright once measured properly - it was a single noisy sample, and the real effect is about 5%. The benchmark harness that caught it is in the repository.

What this is

A fork that measures its own claims

solid.cpp tracks llama.cpp upstream directly and layers on a curated set of performance and correctness fixes — triaged from real, reported issues, root-caused in code, and validated with actual measurements before anything is kept. Every change here is either a measured win or a documented, honest "no": a fix that was tried, found not to be the cause, and kept anyway if it was independently valid, or reverted if it wasn't.

The starting focus was MoE expert-cache placement and speculative decoding, but the scope covers the whole inference hot path — kernels, batching, KV-cache, sampling, quantization — and, increasingly, whether you can actually see what any of it is doing while it runs.

Proven, not promised

Big MoE models don't just fit here — they run fast

Every number below is a real before/after on the same hardware (RTX 3060, 12GB VRAM — about as constrained as it gets for a 26B-parameter MoE model), same prompt, same config except the one thing being measured. Chat-templated and content-verified, not a synthetic benchmark.

+65%
Biggest single fix (frozen expert-cache bug, found live)
+41%
Buffer fed by prediction (vs historical heat)
+36.0%
Expert-prefetch, prefill (via thecodacus)
+31.3%
MoE expert-cache vs naive CPU offload
+28.1%
MTP speculative decoding

The throughput pipeline

Every row below is a real tok/s (or pp2048 tok/s) before/after on this hardware, ordered smallest gain to largest so the table reads as a rising case, not a grab-bag — each row is still its own independent A/B, not a single chained run, since these were measured at different points in the session's real history and some are pp2048 (prompt processing) rather than tg (generation).

WhatBeforeAfterChange
Real placement optimum (vs a conservative -ncmoe guess)46.995 tok/s51.71 tok/s+10.0%solid
Native full-tensor prefetch double-buffer (GGML_SCHED_PREFETCH_EXPERTS — found shipping disabled, now safe to opt into)951.77 tok/s1072.96 tok/s+12.7%solid
--moe-calibrate empirical search (vs safe-floor placement)41.68 tok/s50.70 tok/s+21.6%solid
MTP speculative decoding50.70 tok/s64.92 tok/s+28.1%solid
MoE expert-cache (vs --moe-cache off)39.37 tok/s51.71 tok/s+31.3%solid
Expert-prefetch, prefill (overlapped weight uploads, ncmoe=20, pp2048 — via thecodacus)1403 tok/s1908 tok/s+36.0%solid
Buffer fed by prediction (vs fed by historical heat, 5 GiB cap)3.74 tok/s5.26 tok/s+41%solid
Frozen expert-cache bug (budget latch, found live)37.2 tok/s61.3 tok/s+65%solid

Everything else: capacity, correctness, and honest losses

Not every real result is a tok/s number — some are context/VRAM capacity, hit-rate improvements too small to move tok/s yet, live-found bugs, or losses reported because a fork that only publishes wins is the thing this project exists not to be.

WhatBeforeAfterChange
Speculative eviction, single guess (refuted across 3 different memory-pressure regimes)3 regimes-35% / -16% / -15%refuted 3×refuted
Cross-depth-agreement eviction (a closer depth must confirm a farther one before evicting; 200-tok run)76.5% hits77.3% hitsfewer evictions, more hitssolid
NVMe migration (model reads, SATA → PCIe NVMe)0.50 GB/s2.9 GB/s5.8×solid
Expert-cache size knee (sweep 1/2/4/6/8 GiB, production shape)7.4–8 GiB4 GiB, same tok/s3.5 GiB freedsolid
Low cache-size placement collapse (2,048 MiB request starves the fit search)4 slots1 slotcollapsedfound live
Router-lookahead ranking fix (missing sigmoid + load-balancing bias)raw logitssigmoid+biasfixedsolid
Brain/atlas UI (upstream #22937 defaulted the build to a prebuilt HF bundle)stock squareatlasrestoredsolid
TurboQuant V cache (WHT+Lloyd-Max vs q4_0, real V, matched 4.5 bpw — no outliers to remove on this axis)0.0475 err0.0500 errworsenot built
Fit margin dropped to 0 (the margin also covers post-probe allocation, not just placement uncertainty)65536 ctx4096 ctxcollapsedreverted
FR-Spec draft-vocab trim (measured, not assumed free)75.93 tok/s56.2 tok/s-26%solid
Context + concurrency from placement fix (-ncmoe 15 -c 65536, same command, same root fix)4,096 tok / 1 slot65,536 tok / 4 slots16× / 4×solid
Restart re-prefill (disk prompt cache, 3,623-token prompt)5,775 ms271 ms21×solid
KV cache size (q8_0, 65,536 ctx × 4 slots)2,180 MiB1,158 MiB-47%solid
Host-resident hot-expert holding (2 variants tried, both worse — dedicated buffer -56%, mlock pinning -12%)2 variants-56% / -12%both worsesolid
Usage-history pre-warm (claimed +21–50%, retracted after a 5-round harness; re-measured)96 D2D hits121 D2D hits+26% hits, tok/s unchangedsolid
Router-lookahead prefetch (next layer's experts predicted, filled early — confirmed still firing live, 232 calls/short run)88.3% hit rate89.4% hit rate+1.1ppsolid
LFRU hit/miss split for prefill (device-to-device for resident experts — design from FreeToken; the buffer it rides on costs 5%, see row below)0 D2D, always PCIe65 D2D hits, warmmechanism works, see belowsuperseded
moe-cache prefill double-buffer, measured at last (GGML_CUDA_MOE_PREFILL_BUFFER — distinct from the +12.7% flag above; Gemma-4 only, pp2048)953.00 tok/s905.35 tok/s-5.0%refuted
Silent corruption under copy failure (a failed prefill copy still published its slot — fixed, verified with deterministic fault injection)122 prefill hits, garbage out19 hits, correct out, loud disablefixed + validatedsolid
Low draft-acceptance warning (Ornith's MTP head drafts at 0.19 acceptance, runs ~2× slower — now diagnosed, not silent)silent 2× slowdownwarns once, names the causediagnosablesolid
Stale LFRU hit-mask corruption (a shared hit bitmask vs. per-tensor LFRU residency — bisected, root-caused, fixed)corrupts reliably, 2nd of 2 sequential promptsverified clean across repeated varied promptsfixed, root causesolid
Sentinel expert-id (−1) handling (an assert, an off-by-one, and an OOB read, all from one unhandled sentinel id)assert / OOB risksentinel skipped correctlyfixedsolid
Recurrent-state dtype, Gated DeltaNet/KDA (new -ctr/-cts flags — needed a new F16 ggml_cuda_op_scale kernel too)62.81 MiB (f32)31.41 MiB (f16)-50%solid
Explicit pread() for cold expert fetch (vs. mmap demand-paging — fill mechanics were never the bottleneck here)750.4ms729.3mswithin run-to-run noiseno gain here
Topic-affinity atlas cache warming, iterated to a real win (3 designs tried in sequence — thin/wide eviction, then free-slot-only admission)3 designs-0.31 / -6.55 / -0.35ppunwound in ordersuperseded below
Live-activation MoE predictor (two graph-discovered alternatives lost decisively to the hand-picked atlas on link-prediction AUC)0.71 vs 0.57/0.49 AUC40-45% top-1 (~3% chance)real signal, not noisesolid
Predictor-driven warming, self-tuned and persisted (free-slot budget floor + live evidence ratio, both self-tuning; weights persist across restarts)off (no warming)+0.12pp hit rate, 3/4 roundsfirst real win, post-atlassolid
Substitution, rank-gated draft/fallback (exact fallback above a router-rank floor, cheap resident stand-in below it)55.12 tok/s59.59 tok/s+8.11%, 4/4 roundssolid

Several of those rows are losses, and they are deliberate. Speculative eviction failed three separate times before cross-depth agreement fixed it. FR-Spec was expected to be a free win and wasn't. The host buffer and mlock pinning both tried to hold hot experts in RAM and measured worse than doing nothing. The prefill double-buffer that carries the device-to-device path turns out to cost 5% — measured only after it had been shipped (off by default) and cited for a year on the strength of a different flag's benchmark. That last one is the uncomfortable kind: not a failed experiment but a claim that was never actually tested, and the correction cancelled a planned piece of work rather than starting one. That's the actual point of solid.cpp: every number here is something that was measured and would be reported honestly either way. Full raw numbers, per-row methodology, and the complete working log — including dead ends — live in docs/moe-cache-colibri-notes.md and docs/plan.md.

Live, not just logged

The Brain view: watch the cache, don't just trust it

A cache you can't see into is a black box you have to take on faith. The Brain view polls the running server and renders, live, which experts are actually resident in GPU memory right now — cold (never cached), warm (probation), or hot (protected) — with real-time hit flashes as tokens route through them.

hot / protected warm / probation cold / not cached

Merged into the same view: the Atlas — each expert positioned by measured topic affinity (code, math, law, medicine, and five other categories, probed with greedy decode and a replication gate so one lucky prompt can't fake a specialist), colour still driven by live cache state. Position from measurement, colour from reality, one canvas. Cells flash yellow when serving as a live substitution stand-in — a resident expert doing someone else's work rather than the router's own missed pick paying CPU fallback.

What building this actually caught: live-testing the Brain view against real traffic surfaced a genuine, previously-undiscovered engine bug — under backend sampling (-bs), the entire moe-cache session was silently destroyed and rebuilt empty on every request, because its lifetime was tied to a scheduler object that gets torn down and recreated whenever a new sampler is attached. The cache was working for exactly one request, then quietly doing nothing, with no error or warning. Root-caused with temporary per-call logging (not guessed at), fixed by detaching the cache session from the old scheduler and re-attaching it to the replacement instead of recreating it from scratch. This is exactly why the "proven, not promised" numbers above matter — a feature that silently stops working is worse than one that was never built, because you don't find out until you go looking.

Where experts live, measured not assumed

The Atlas — a good map, and proof the system can't tell

Every expert gets a position from measured topic affinity: probed offline across nine categories with greedy decode and a replication gate, so one lucky prompt can't promote an expert to specialist. The map is genuinely predictive. The finding that matters is that being predictive doesn't reach the running system at all.

As a map, it holds up. Tested on link prediction — “do these two experts fire together?” — against embeddings factored directly out of the observed co-activation graph, on 31.6× more data than the atlas ever saw:

MethodAUC
Popularity control (expert frequency alone)0.5437
Incremental / online-learned atlas (AdaGrad, 178M steps)0.4823 — chance
Discovered co-activation embedding (64 dims)0.5761
Probe atlas (9 categories)0.7116

The probe atlas predicts co-firing better than an embedding built from co-firing itself, having never seen that traffic — a result from a test designed to be fair to the alternative. Two successive attempts to replace the nine hand-chosen categories with something label-free both lost.

Then the same two maps were run through the live server, and were indistinguishable. Probe atlas (AUC 0.7116): 58.45 tok/s, 71.20% hit. Incremental atlas (AUC 0.4823, i.e. chance): 58.43 tok/s, 71.28% hit. A map scoring at chance performs exactly as well in the running system as one scoring 0.7116, on the original rate-limited, admission-gated design that supplied roughly 2% of fills. The map's advantage is real as a statement about maps and never reaches the mechanism consuming it. Any claim that one atlas beats another now has to say which of the two things it means.

That 2% figure is from the original design; a later rebuild (free-slot-only admission, a wider candidate pool, burst-mode firing on a detected topic shift) touches roughly 22% of decisions instead, and fixed two designs measured actively harmful (−0.31pp, −6.55pp) along the way. Current honest state: safe, not harmful, but still short of a consistent win — post-switch pooled hit-rate delta of −0.35pp overall, with individual topics mixed rather than uniform (one, law, is a real +0.40pp win, 3 of 4 rounds). Ships off by default either way.

Why the ceiling is that low is the deeper answer: atlas warming gains +0.31pp hit rate in simulation, while a topic oracle with perfect foreknowledge of the segment's experts gains +0.30pp. The atlas is already at the ceiling. Every topic touches ~6,000 of 7,680 experts and the top 1,000 cover only 52–61% of selections — there is no compact topic hot set to preload, so no prefetcher of this shape can win. One result explaining an entire family of refuted experiments is worth more than the feature would have been.

So the atlas ships as what it demonstrably is: the spatial layer of the Brain view — position from measurement, colour from live cache state — with its warming path rate-limited, admission-gated and off by default. Related reversals were reported the same way: combining atlas admission with eviction-weighting went net negative (−0.95pp, 1 of 4 rounds) against admission alone (+2.75pp, 4 of 4); and hit rate itself turned out to be purchasable — 79.25% is available for one environment variable and a slower server, which is exactly why throughput, not hit rate, is the objective here.

Below the expert: individual neurons

Sparse activation: not every neuron in a hot expert earns its VRAM

Caching decides which experts live in GPU memory. But an expert is not atomic — inside one, some neurons carry almost all the signal and others contribute essentially nothing. This fork measures that directly, per expert, and serves the ones that matter at reduced width.

The measurement came first, and it changed the design twice. Concentration is scored by value, not rank: “top 25% of neurons” picks a fixed cutoff regardless of what that expert's distribution actually looks like, so instead each neuron is compared against a threshold relative to its own expert's maximum, and the resulting count falls out as an observation rather than an input. Dead-neuron fraction is anchored on the median, not the mean — these distributions are skewed enough (max/mean ratios of 120–270× measured) that the mean is not a trustworthy reference.

What was measuredResult
Neurons clearing 25% of their expert's own maxhold ~81% of that expert's total activation mass
Per-expert width needed for 90% coveragebimodal — median ~83%, but a real minority need as little as 6–7%
Shared index set across a layer's expertsretains only 55.9% of mass — proof the selection must be genuinely per-expert, not one set reused
Per-layer miss rate vs. concentrationdecoupled (12.1–16.0%, flat) — today's cost is uniform precisely because nothing yet exploits concentration

An early read of the per-layer medians said layer 0 was uniquely concentrated (30.9% dead neurons vs ~0% everywhere else) and the mechanism was nearly scoped to that one layer. Checking at the individual-expert level instead found 17 more outlier experts scattered across layers 1, 2, 3, 4, 37 and 39, some on large sample counts. The layer median had been hiding a real per-expert effect that exists everywhere, just unevenly. The shipped mechanism is layer-agnostic because of that check, not despite it.

Converted experts are served from a second, narrower cache pool: gate/up only (down_exps's row axis is an output dimension, where dropping rows would delete real outputs rather than approximate sparsity), the chosen rows gathered once at fill time, and a small CUDA kernel scattering the computed values back into their correct positions in an otherwise-zeroed row. The CPU path never learns any of this exists. Measured 50% VRAM saved on every converted expert, and the reduced pool evicts under the same heat-driven LFRU policy as the primary one, so it keeps tracking whichever experts are currently hot instead of freezing at whoever converted first.

What building this actually caught — three times, and only by measuring: every one of these looked correct on inspection. (1) Taking the cache's core lock on every hit cost 16% throughput (35.56 → 29.89 tok/s) — the exact regression shape an earlier dedicated mutex already existed to prevent, reintroduced. (2) A full session decided 20,201 conversions and placed zero of them: greedy pool admission consumed the entire budget first, so the reduced pool never had room to exist — pure overhead, no benefit, completely silent. (3) Reduction was being applied to down_exps, which the design explicitly excludes; the restriction had been written down but never coded. The fix for (2) then failed twice more on its own, each time visible only in the pool table: reserving a flat slice starved a whole primary pool out of existence and dropped hit rate 31.3% → 23.1%, costing far more than it saved.

Where the throughput actually was

The safety margin that was quietly costing 30%

Chasing why the neuron work only bought 8%, the utilization numbers said something else was wrong: CPU at ~5.4 of 12 cores, GPU at 41%, and ~3 GB of a 12 GB card unused. Neither resource saturated means the bottleneck is serialization, not capacity.

Placement reserves 3 × --fit-target of VRAM before deciding anything, and that reservation silently raises a requested -ncmoe until the context fits. With the 1024 MiB default that is 3 GiB held back — and it invalidated an entire sweep before being noticed: requesting 22, 16 and 10 CPU layers all produced identical memory use and near-identical throughput, because all three were being raised to 27. The log line saying so had been there the whole time.

--fit-targetEffective CPU layerstok/sGPU memory used
1024 (default)2747.328951 MiB
6402450.3710307 MiB
4482351.6110741 MiB
3202252.8511237 MiB

+11.7% from the margin alone (~+30% against the starting configuration), every candidate passing its correctness check, on Ornith-1.5-35B. The margin cannot simply be deleted — it covers allocation that happens after the fit probe can see it, and removing it reproduced a hard context collapse — so the honest answer is to measure it per machine rather than ship a constant.

And the tightened margin is not universally good, which is the entire argument for calibrating it. On gemma-4-26B the result inverts: the default margin scored 71.86 tok/s while every tightened value scored ~50. Opposite of Ornith. Any single constant would have been wrong for one of the two models no matter which was chosen. --moe-calibrate now searches this dimension and records it alongside placement, threads and cache size — and the first real run of that new search promptly exposed a bug in itself, comparing candidates against a baseline measured under a different cache size and nearly shipping a 29% regression as a “calibrated optimum”. Caught by reading the numbers it printed, fixed, re-run.

What's different from upstream

Every fix, one card each

MoE expert-cache

GPU-resident hot-expert cache for CPU-offloaded MoE layers. LFRU eviction (capped SLRU + heat tiebreak), live auto-placement, --moe-calibrate for empirical, concurrency-aware placement search instead of a fixed guess.

Live Brain + Atlas view

Real-time per-expert tier/heat heatmap merged with measured topic-affinity positioning, served straight off the running server — see above.

Expert-prefetch + host-pinning

Overlaps an offloaded expert's weight upload with the previous expert's compute, plus lifetime host-page pinning for -ncmoe's CPU→GPU copies. +36.0% prefill — ported from thecodacus.

LFRU-aware prefill hit/miss split

Prefetch now checks per-expert residency against the cache decode already fills: a resident expert copies device-to-device instead of re-fetching from host memory. Design from FreeToken, re-derived against our own cache — zero regression on a cold cache.

Live NVMe-prefetch hint for cold experts (GGML_CUDA_MOE_CACHE_LIVE_PREFETCH, default on)

The live-activation predictor already ranked candidates and the cache already tracked per-expert CPU cold/warm state — admission just never acted on it. Now hints the kernel to start reading a cold candidate's host pages the moment it's queued, not when the fill worker actually needs them. A/B'd under real memory pressure (5 rounds each, 15 GiB cap): 29.1 → 39.8 tok/s mean, and it specifically kills the worst-case stalls (17 tok/s low end gone) rather than just nudging the average — unlike the older one-shot READAHEAD flag (blind, history-based, a measured net loss on warm throughput), this only fires for a candidate already independently wanted.

Cost-aware (GreedyDual-Size) eviction tiebreak

Eviction now weighs heat by an NVMe-vs-RAM cost tier, so a hotter-but-cheap slot can lose to a colder-but-expensive one. A correctness fix for real memory pressure, not a throughput win — noise-neutral under normal load.

Cross-depth-agreement speculative eviction (SPEC_EVICT_MODE=agree, default)

Only lets a closer, more accurate depth's prediction confirm a farther depth's guess before evicting — fixes the regression the old any policy had at depth≥3. Default now, inert unless GGML_CUDA_MOE_LOOKAHEAD_DEPTH>=2 is set.

Probabilistic draft acceptance

--spec-prob-accept accepts a draft token whenever the target considers it at least as likely as the draft did, not only on exact match. Opt-in, verified byte-identical when off.

FR-Spec-style vocab trimming

MTP draft-vocab trimming with its real cost measured directly — a genuine trade of throughput for VRAM, not a free win, plus a cache-correctness bug found and fixed along the way.

p_min footgun warning

A diagnostic that fires when a drafter's confidence early-stop is left disabled while draft width has been raised — mirrors the existing concurrency-cliff warning pattern.

Permanent debug instrumentation

LLAMA_DEBUG_VERIFY=1 gates zero-cost-when-unset traces that were directly responsible for finding more than one of the fixes on this page.

Context-size fitting doesn't hard-crash

An explicit -c request too large for VRAM used to abort the whole process - upstream's --fit only auto-adjusts unset arguments. Now searches by doubling from a safe floor and lands on the real, measured context that fits instead - see above for how this one was actually found.

Stale LFRU hit-mask corruption, root-caused

A garbled, repeated-character output bug, git-bisected and root-caused: the device-to-device hit bitmask is legitimately shared across gate_exps/up_exps/down_exps, but LFRU residency is tracked per tensor — a bit cleared for one left the others neither device- nor host-copied, reading garbage as weights. Fixed by separating the shared routing bitmask from a fresh per-tensor copy mask.

Recurrent-state dtype: -ctr/-cts

Gated DeltaNet/KDA's recurrent state was hardcoded F32 with no override, unlike K/V cache's -ctk/-ctv. New flags mirror that pattern; f16 halves the footprint (62.81 → 31.41 MiB) with output verified coherent — needed a new F16 ggml_cuda_op_scale kernel underneath it too.

Live-activation predictor for expert-cache warming

A small linear layer trained online on the real hidden state each routing decision came from, feeding the same free-slot-only admission path the topic atlas uses. Self-tuning throughout, weights persisted across restarts. 40–45% top-1 accuracy from a cold start (~3% chance); +0.12pp hit rate, 3/4 rounds — the first mechanism here to beat the no-warming baseline on repeat measurement.

Description

Inherited from upstream, unchanged

The core goal of llama.cpp — minimal-setup, state-of-the-art LLM/VLM inference on a wide range of hardware, locally and in the cloud — carries through untouched:

Built on top of ggml. See upstream's own manifesto and API references for lib llama and llama-server REST.

Quick start

Running in two commands

cmake -B build -DGGML_CUDA=ON   # or your backend of choice
cmake --build build --config Release -j
# Download and run a model directly from Hugging Face
llama cli -hf ggml-org/Qwen3.5-0.8B-GGUF

# Launch an OpenAI-compatible API server
llama serve -hf ggml-org/Qwen3.5-0.8B-GGUF

# A MoE model too big for VRAM - the expert-cache does the work
llama serve -hf <big-moe-model-gguf> -ngl 99 -ncmoe 15 --moe-cache auto

Docker: .devops/*.Dockerfile builds from this source tree, so it includes every fix on this page - the pre-built ghcr.io/ggml-org/llama.cpp images in the Docker docs are upstream-only and won't have them. Full guide either way in docs/build.md.

Supported backends

Runs where llama.cpp runs

BackendTarget devices
BLASAll
BLISAll
CANNAscend NPU
CUDANvidia GPU
HIPAMD GPU
Hexagon (in progress)Snapdragon
IBM zDNNIBM Z & LinuxONE
MUSAMoore Threads GPU
MetalApple Silicon
OpenCLAdreno GPU
OpenVINO (in progress)Intel CPUs, GPUs, and NPUs
RPCAll
SYCLIntel GPU
VirtGPUVirtGPU APIR
VulkanGPU
WebGPUAll
ZenDNNAMD CPU

Per-backend build flags in docs/build.md.

The mmap-prefetch-safety fix behind the GLM-5.3-Flash result above (skips eagerly populating the whole model file into RAM when it won't fit) reads /proc/meminfo directly, so it's Linux-only. It doesn't crash or fail to build elsewhere - Windows/macOS fall back to ggml's own device-memory query, which reports installed capacity rather than what's actually free, so the fix is less accurate (not absent) at catching the same OOM there. Verified on Linux; not yet verified on other platforms.

Documentation

Everything, one click away

Relationship to upstream

Same history, extra commits

This repo's git history is llama.cpp's history, plus the commits on top. Upstream changes are pulled in directly rather than re-implemented; nothing here is intended to diverge from upstream's own architecture or conventions more than a given fix requires. See upstream's README and manifesto for the base project this builds on.

Contributing

Open to real fixes

License & acknowledgements

MIT, same as upstream

Real sources for real ported work, not vendored libraries but genuine prior art this fork builds directly on:

Prior art that shaped decisions here without code being taken — including several that told us not to build something:

Third-party libraries, inherited unchanged from upstream: