UPDATE: Qwen3.8-Flash-Next on 2x3090 + DDR4 (Part 2): 25-29 -> 37-41 t/s decode (UD-Q4_K_XL + expert cache + MTP), plus a branch you can build
Heat trend
Collecting trend data
The percentage is based on available heat signal, not comment count or independent people.
This is a follow-up to my post from yesterday (17 -> 25-29 t/s with the expert cache PR). Same box: 2x RTX 3090 on PCIe 3.0, dual Xeon E5-2696 v4, 188 GB DDR4-2133 LRDIMM, llama.cpp, full 261k context, f16 KV, all 48 expert layers in host RAM, everything else on the GPUs. Since then I switched quants, stacked MTP on top of the cache, fixed the load time, found a bug in the cache PR and found out my RAM was thermal throttling (Now i gotta buy an additional case fan lol). Numbers are all from the same 4,000-token python coding prompt with thinking on unless stated otherwise.
Where it's at now
Starting numbers (UD-Q6_K_XL, 4+4 resident layers) First post (Q6 + cache, 135 slots) Now (UD-Q4_K_XL + cache 188 slots + n-gram draft) Now (Q4 + cache 150 slots + MTP) decode, coding prompt with thinking 17 25-29 32-35 decode, code emission, thinking off - 24 37 decode at 131k depth 12 17 18-20 prefill, 26k prompt (ub 512) ~350 at ub 2048 138 180-195 load to ready ~13 min 8.5 min 2 min host RAM for the experts 104 GB pinned + 51 GB PLE same 73 GB pinned + 28 GB PLE cache hit rate - 84-85% 90-92% Hit rate is the cache's own counter, decode is llama-server's eval time.
What changed, in order of payoff
- UD-Q4_K_XL instead of Q6_K_XL. Hit rate doesn't depend on the quant, only on slot count (Q4 at 135 slots: 84.7%, Q6 at 135: 84-85%). But what Q4 buys me is precious vram space, roughly 1.44x slots per GB of VRAM. So 188 slots actually fit where 135 did and achieved a hit rate 90-92%, increased decode from 27 to 32-35, prefill by +35% (fewer bytes per ubatch). The quality cost per unsloth's table is: KLD 0.047 vs 0.027, top-1 agreement 92.3% vs 94.1%; proper eval still to do. Host RAM drops to ~105 GB, so 128 GB is enough for this setup.
- MTP on top of the cache (mainline PR #28243, the unsloth MTP head). Yesterday I kind of concluded that "MTP does not pay" but looking back, that was the old fork with the cache off during verify. On the mainline, with the cache taking verify batches (see 4), MTP drafts every step at 50-58% acceptance on reasoning text and 94% on code emission. Decode went from 32-35 -> 37-41 t/s on the thinking prompt (single runs spread about 8% on this prompt at temp 0.7) and from 37 -> 49 on code emission. The draft head sits on the second GPU and costs about 4.5 GB, which is why the slots dropped from 188 to 150 on the table if you're wondering. Still a clear win at short context.
- Load 8.5 min -> 2 min. The loader was pulling 100 GB through page faults at 236 MB/s (MADV_RANDOM under --numa distribute). Reading host-destination tensors straight from the file fixed it, that is in my PR #28223.
- A bug in the cache PR at n_tokens > 1. #27861 maps every uncached expert to one dummy slot, and the batched CUDA mul_mat_id kernels assume distinct ids per token: out-of-bounds writes. Only the mmvq path is safe, which quantized experts use up to 8 tokens, so the branch gates the cache at 8 tokens and keeps MTP's verify batch at 4. Repro and details in my #27861 comment: Link to comment
- My RAM was thermal throttling. This is more of a me issue but putting it out there for those who may have a similar box to mine. I experienced a slowdown after a few minutes of decode and the issue was the memory controller throttling once the hottest LRDIMM hit 78 C (perf stat -e unc_m_power_critical_throttle_cycles shows it, so don't worry if you have no BMC). A fan on the DIMM banks does keep it at 44-57 C, zero throttling, 16k-token runs from 10-15 to 24.6 t/s average. I would check this before touching software if your DDR4 Xeon box slows down under sustained load. This does not affect the numbers in the table and in my last post.
Did nothing or hurt here: q8_0 KV (-18% at 131k depth), mirror-NUMA #27986, QSA gather #28213, --load-mode none, chained drafts, the ik_llama GEMV port, more than 2 cache uploads per step, thread/poll/prio flags. --lazy-mode on-direct (#28136) gives +7-12% only on the first long prompt after a restart.
To replicate
Branch with everything: https://github.com/Inovello/llama.cpp/tree/flashnext-2x3090 . It is master (b96806d) + PR #27861 (expert cache) + PR #28223 (pinned host experts under mmap + the load fix) + PR #28243 (MTP) + the mul_mat_id fix and the 8-token cache gate from my #27861 comment. Squashed into one commit, I added the credit in the commit message. It is a replication branch.
git clone -b flashnext-2x3090 https://github.com/Inovello/llama.cpp && cd llama.cpp cmake -B build -DGGML_CUDA=ON && cmake --build build -j -t llama-server LLAMA_ATTN_ROT_DISABLE=1 numactl --interleave=all build/bin/llama-server \ -m Qwen3.8-Flash-Next-UD-Q4_K_XL-00001-of-00004.gguf \ -md mtp-Qwen3.8-Flash-Next-shared-Q8_0.gguf --spec-type draft-mtp -devd CUDA1 --spec-draft-n-max 3 \ -ngl 99 -c 261888 --parallel 1 -fa on \ -ot "ffn_(gate|up|down)_exps\.weight=CUDA_Host,per_layer_token_embd\.weight=CPU" \ -lzm off --numa distribute -t 16 -tb 44 -b 4096 -ub 512 -ctk f16 -ctv f16 \ --moe-expert-cache 150 -lv 4
- The MTP head is MTP/mtp-Qwen3.8-Flash-Next-shared-Q8_0.gguf (2.79 GB) from the unsloth/Qwen3.8-Flash-Next-GGUF repo on HF. To clarify, the shared file has no embeddings of its own, the PR borrows them from the target, so it only loads as -md of the main model.
- Slot sizing on Q4: ~75 MB per slot per GPU at full 261k context and ub 512. Without MTP I fit 188 slots (~1 GB free per GPU); with the draft head on CUDA1, 150. Watch nvidia-smi after a long prompt, the CUDA pool grows ~350 MB during a 131k prefill.
- -lv 4 prints the cache hit rate every 512 steps (moe-cache: ... hit-rate=) and the draft acceptance per request.
- For sessions that you believe would reach high ctx usage, swap the three MTP flags for --spec-type ngram-map-k --spec-ngram-map-k-size-m 7 and raise the cache to 188.
- Both PRs are drafts. #28243 has open review comments and #27861 has the bug above. The branch above is what actually runs here today, and it isn't something I would call finished.
- For the single GPU brothers out there, same idea, just put -devd on your single GPU or skip MTP and take the slots.
Next thing I'll be doing is a proper comparison against Qwen3.8-27B at Q8 (speed and quality), since that is what most people here actually want to know. Happy to answer questions on any of it.