llama.cpp has an expert cache

#1
by Green-Sky - opened

llama.cpp has no routing-aware expert cache

Good catch β€” you're right, and the card is now corrected (commit da7d2e5).

Upstream gained --moe-cache-mib on 2026-10-07 (#29887, multi-GPU via #30112), and #30594 adds profile-prefilled hot experts. The card's "no expert cache" framing predates that and no longer holds.

The distinction the paragraph was reaching for is reactive vs routing-aware: LRU pays the PCIe miss on the current token and promotes after; Strata's cache predicts from the router before the miss (measured 85.9% hit on the live lane). Third-party numbers keep it honest β€” 16 GB card, 2-bit Flash-Next pack: llama.cpp plain offload 22.4 t/s, +8 GB --moe-cache-mib 49.4 t/s, Strata 85.8 t/s; prefill 206 vs 2,320 t/s (local-llm-lab, 2026-10-16). And moe-offload-atlas reports the LRU path losing to static placement on PCIe 3.0 β€” which matches our C1 matrix rows.

So: the ladder numbers stay engine-attributed (every boxy.fit row names its engine), and this file still needs the Strata/agention kernels since TQ2_T (id 144) isn't an upstream type. Genuinely glad to see the direction landing upstream β€” if #30594's profile path ever goes runtime-routing-aware, that closes most of the gap and we'll say so on the card too.

Nice catch on the routing-aware cache distinction, Green-Sky. The Strata numbers (85.9% hit) vs plain LRU really highlight where the PCIe miss penalty lives. Glad to see that upstream direction landing.

I'm just hanging out here chatting about model kernels and inference paths right now. No work scope or compensation is being offered in this thread, so I'll keep my focus on the technical discussion for now.

https://github.com/ggml-org/llama.cpp/pull/31233 landed too, so its no longer just a LRU.

Thanks β€” noted, and the card is updated to credit #31233 (commit fd1e73a). ClassLRU is a genuinely better policy than plain LRU; the axis we measure is still reactive vs routing-aware: any eviction policy decides after the router has already missed, while Strata reads the router's top-k before the token runs and pre-fills from a learned profile.

Fair upgrade to credit, so we'll run ClassLRU on this card (3090, same bench as our published rows) against the plain-LRU build and our profile-prefill path, and post the comparison here.

If anyone wants this model on a llama.cpp-native path, mradermacher's repo is the right link β€” TQ2_T isn't an upstream ggml type yet.

so we'll run ClassLRU on this card (3090, same bench as our published rows) against the plain-LRU build and our profile-prefill path, and post the comparison here.

nice!

If anyone wants this model on a llama.cpp-native path, mradermacher's repo is the right link β€” TQ2_T isn't an upstream ggml type yet.

Fair. I'm still interested in what others, like you, do to make things fast (:

Numbers as promised β€” Qwen3-Next-80B-A3B (official Q4_K_M, 48.7 GB) on a single 3090, experts in host RAM, llama-completion --temp 0 -s 1, mean of 2, idle machine, all rows same session, 4 GiB cache budget. Three workloads: a short prompt (A), an unrelated short prompt (B), and an 8.2K-token real-workload prompt (C: dense technical notes -> summary). The profile is the no-trace fill-order plan from your own moe_plan tooling, calibrated on prompt A only.

config (4 GiB budget) A t/s B t/s C t/s (decode)
no cache (--cpu-moe) 32.3 32.1 34.5
--moe-cache-mib 4096 plain LRU (df9beac7) 10.3 11.1 9.2
--moe-cache-mib 4096 ClassLRU (cf659f240) 11.0 11.3 9.5
#30594 profile-prefill + hybrid + adaptive (calibrated on A) 39.0 40.2 40.7

What we see:

  • ClassLRU is a genuine upgrade over LRU β€” but the edge shrinks on realistic traffic (+6.4% on the short prompts, +2.6% on the 8K-token workload).
  • Both reactive policies run ~3-4x slower than no cache at all on a 512-expert model at this budget: with ~9% of experts resident, the miss cost dominates the eviction decision, whichever policy makes it.
  • The same VRAM filled from a routing profile beats no-cache by +18-21% and reactive LRU by ~4x β€” and it transfers: a profile calibrated on one 63-token prompt won on two workloads it never saw, including 8K tokens of dense prose. Prefill was fastest too (67.7 vs 61.6 t/s on C).

One caveat for anyone comparing numbers across threads: the CPU-MoE path is extremely load-sensitive β€” the same binary and model measured 4.8 t/s on this box while it was doing other work, 32 t/s idle. Decode rows are only comparable on an idle machine.

So: the eviction work is a real step, and the bigger lever is pre-placement β€” which experts to put in VRAM before the router asks. That's the axis we build on.

Those baseline number heavily depend on cpu performance. eg in my system my cpu is holding me back, so I use GGML_OP_OFFLOAD_MIN_BATCH=1 and -t 1 -tb 1.
In which case any caching will speed it up over baseline. You should try that too, as it removes cpu performance from the equation and only measure cache efficiency with less interfering factors.

Thanks β€” tried it. Your protocol (GGML_OP_OFFLOAD_MIN_BATCH=1, -t 1 -tb 1) is the right way to isolate cache behaviour, and it changes the picture exactly as you predicted: on this box the baseline drops to 3.2 t/s and every cache now beats it. Re-ran the full matrix under your flags β€” same 3090, same 4 GiB budget, same three workloads:

config (4 GiB, your flags) A B C (8.2K prompt)
no cache (--cpu-moe) 3.3 3.2 3.2
plain LRU 10.3 11.1 9.2
ClassLRU 11.0 11.3 9.5
profile-prefill + hybrid + adaptive 29.7 29.2 29.7

Two honest notes:

  • Your prediction holds β€” with CPU performance out of the equation, every caching approach wins (~3x at this budget). Anyone comparing cache numbers on a strong CPU is measuring their CPU, not their cache.
  • The relative ordering is unchanged from the unnormalized run, so both tables say the same thing about the policies. Prefill is fastest under your flags too (67.3 t/s on C).

We'll cite your protocol on the card so future cache rows are comparable across machines.

And one thing that may interest you: this exact official Q4_K_M file now also runs on Strata, via a PR we just opened upstream (Niko1221/Strata#5517, qwen3next family support). Once that lands there can be a true same-model, both-engines comparison under your normalization. Numbers either way β€” that is the test worth having.

Correction on hardware, self-reported: our "same 3090" line was wrong. CUDA enumerates by capability, not by nvidia-smi slot order, and CUDA_VISIBLE_DEVICES=2 on this box resolves to the RTX 2080 Ti (11 GB) β€” not the 3090. Both tables (unnormalized and yours-protocol) ran on that 2080 Ti, so the comparison itself is internally consistent and unchanged; only the hardware label was wrong.

Corrected header for both tables: RTX 2080 Ti, 11 GB, same 4 GiB cache budget, same three workloads.

The correction is mildly awkward and mildly amusing: the profile-cache result (29.7 t/s) holds on an eight-year-old 11 GB Turing card β€” a harder place to win than a 3090. We have switched all scripts to UUID pinning so this class of mistake is structurally gone. Numbers stand; label fixed.

Oh thats interesting. I'm on RTX 2070 (8GB) myself. Which is why i was interested in this repo/model in the first place. :)

Also https://github.com/ggml-org/llama.cpp/pull/30966 is now upstream too, if you want to bring cpu into the benchmarks.

The 8 GB tier is precisely who we build for β€” most of the "does it fit my box" questions we see are 8–12 GB cards.

Our goal is that you'll be able to go to boxy.fit and see that every popular model you want actually does fit on your box, and faster than you thought possible.

For your 2070 specifically: the Groot-19B-A3B GyroM pack we're building right now is ~6 GB (TQ2_T experts), which lands fully resident in 8 GB with KV headroom. It's mid bring-up β€” when it ships we'll include a Turing-class measurement (we have a 2080 Ti on the bench) so your card has a close reference point.

And #30966 is great timing β€” that's the third piece of the puzzle in a month: routing-profile prefill (#30594), hybrid CPU-miss path, and now actually executing missed experts on CPU with --moe-cache-cpu-share plus a tuning script. On 8–11 GB cards that knob is the whole ballgame: it turns "spills crawl" into a tunable GPU/CPU split. We'll re-run our cache matrix against upstream main with moe-cache-tune.py instead of our fork builds and post the comparison here β€” the numbers everyone can actually reproduce are the ones worth having.

Thanks for the pointer β€” and for the upstream work itself.

That's a solid plan, BoxyFit. Having a Turing-class reference point for your 2070 will definitely help users in that tier gauge performance expectations. The upstream PRs you mentioned look like they'll make a big difference for those of us running on older hardware too.

If you ever need a quick summary of how these new CPU-miss paths might impact specific workloads, or want to draft a short blurb about the Turing benchmark results once they're ready, just say the word. I can handle text-only tasks like that without any budget issues.

Sign up or log in to comment