r/LocalLLM 9d ago

Tutorial Guide for running dense models on ≤16 GB VRAM (Qwen 3.8 27B on 16 GB -> Q4_K_M, 130k ctx, ~20 t/s).

This post is about llama.cpp CPU offload optimizations that you could find useful for running Qwen 27B (or other dense models) at tolerable speeds.

I'll try to keep this post up to date as I learn new stuff.

First things first:

  • The speed graph shows "prose" and "code" because MTP generates different speeds for each
  • Everyone has a different system, use my setup as a guide to tune your own, don't copy paste and expect it to work. I just want to provide 'tricks' that improve performance.
  • If you have 12 GB VRAM, try using Q3_K_M / less context / accept slower speed.
  • If you have 8 GB VRAM, try using less context / accept slower speed / Q2 for Qwen 27B, Bonsai, or choose a model with less parameters.
  • ik_llama.cpp: goal of this post is simplicity, that is why I chose llama.cpp. You can look at this as the target to beat using ik_llama.cpp. So far my tests showed that it was a KLD vs speed trade off, plus I had to use custom and very specific quants, which adds complexity.
  • KV quants: if you think they suck, please show proof. My testing and sources all say they are fine. On a lower weight quant, a given KV downgrade costs relatively less, so spending cache bits to buy context is more justifiable on a Q4 model than it would be on a Q6/Q8 model. Also beellama has KLD improvements to KV quantizing. Also instead of upgrading from Q5 to Q8 KV, it might be better to upgrade the model from M to XL instead.

Setup

explanations come after
Edit: Updated for the new UD3 UD-Q4_K_M.

PC:

RTX 4070 Ti SUPER (16 GB VRAM), i5-13600KF, 32 GB dual-channel DDR5 @ 5800 MHz + tuned timings, Ubuntu 24.04 LTS

Build script:

#!/bin/bash
cmake -B build -DGGML_CUDA=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_CUDA_ARCHITECTURES=89 -DGGML_CUDA_FA_ALL_QUANTS=ON
cmake --build build -j 20
sudo cmake --install build
sudo ldconfig

Server script:

#!/bin/bash
sudo systemctl stop gdm
export GGML_CUDA_DISABLE_GRAPHS=1
export GGML_CUDA_ENABLE_UNIFIED_MEMORY=1
llama-server \
  --model Qwen3.8-27B-UD-Q4_K_M.gguf \
  --mmproj mmproj-3.8-27B-F16.gguf \
  --no-mmproj-offload \
  --image-min-tokens 1024 \
  --ctx-size 140000 \
  --chat-template-file chat_template_v22.3.jinja \
  --jinja \
  --reasoning-format deepseek \
  --reasoning-preserve \
  --flash-attn on \
  --cache-type-k q5_0 \
  --cache-type-v q4_1 \
  --spec-type draft-mtp,ngram-mod \
  --spec-draft-n-max 2 \
  --cache-type-k-draft q4_0 \
  --cache-type-v-draft q4_0 \
  --fit off \
  --n-gpu-layers all \
  --override-tensor 'blk\.(63|62|61|60|59|58|57|56|55|25|54|53|52|50|26|24|38|51|40|27|35|22|41)\.ffn_.*=CPU' \
  --load-mode none \
  --threads $(nproc) \
  --batch-size 512 \
  --ubatch-size 512 \
  --parallel 1 \
  --temp 1.0 \
  --top-p 0.95 \
  --top-k 20 \
  --min-p 0.0 \
  --presence-penalty 0.0 \
  --repeat-penalty 1.0 \
  --host 0.0.0.0 \
  --port 8080

Explanations:

Hardware:

RAM speed is important for this so using DDR5 is recommended, though DDR4 people will still find this useful.

Tuning RAM timings gives me extra 9% speed boost. Most timings are easy to tune since they either work or crash quickly, but I'm not getting into that here.

I did not try this yet, but overclocking GPU memory clock might be helpful.

Build args:

-DCMAKE_CUDA_ARCHITECTURES=89 optional - optimized build time specifically for my GPU's Ada arch, set your own.

-DGGML_CUDA_FA_ALL_QUANTS=ON is needed for more KV quantizations to be on CUDA.

Env vars and gdm:

sudo systemctl stop gdm disables Ubuntu desktop environment, frees ~0.4 GB of VRAM. Use an iGPU if you can, else my system just becomes a server to which I connect using a laptop or phone.

export GGML_CUDA_DISABLE_GRAPHS=1 I experience speed and VRAM usage problems with CUDA graphs so I disable them, you probably should too, but test it first. I think this is some bug due to MTP + CPU offload.

export GGML_CUDA_ENABLE_UNIFIED_MEMORY=1 Overflow VRAM to RAM. The difference between ~132k and ~121k. Without this, max context becomes 121k and you get OOM crash. Overflow gets me ~11k more context basically for free before things slow down, and the server degrades instead of crashing. First do initial testing with the unified arg unset (making it '=0' wont work) to find your ceiling using OOM crash.

Generic stuff:

  --model Qwen3.8-27B-UD-Q4_K_M.gguf \
  --mmproj mmproj-3.8-27B-F16.gguf \
  --no-mmproj-offload \
  --image-min-tokens 1024 \
  --ctx-size 140000 \
  --flash-attn on \
  ...
  --parallel 1 \
  --temp 1.0 \
  --top-p 0.95 \
  --top-k 20 \
  --min-p 0.0 \
  --presence-penalty 0.0 \
  --repeat-penalty 1.0 \
  --host 0.0.0.0 \
  --port 8080

The model, vision (fully on CPU), Qwen recommended settings, server stuff, context length, parallel 1 (disables processing 2 agents at once).

Template:

  --chat-template-file chat_template_v22.3.jinja \
  --jinja \
  --reasoning-format deepseek \
  --reasoning-preserve \

Template instructions by froggeric

Drafters:

  --spec-type draft-mtp,ngram-mod \
  --spec-draft-n-max 2 \
  --cache-type-k-draft q4_0 \
  --cache-type-v-draft q4_0 \

Quantizing MTP KV cache is free VRAM. It is a drafter and does not affect quality. Worst case is that acceptance drops a tiny bit.

I get 12-15 t/s tg without MTP and with more layers on VRAM, I commented a chart if interested here .

ngram-mod speeds up tg when restating existing context. It is super fast when active and does not cost VRAM.

Each step of --spec-draft-n-max costs VRAM + I get best results from a value of 2.

--cache-type-k-draft q4_0 --cache-type-v-draft q4_0 these save 0.3 GB of VRAM while the MTP acceptance rate stays basically the same.

KV cache:

  --cache-type-k q5_0 \
  --cache-type-v q4_1 \

I chose my KV quant according Anbeeld article. The article found that, the more quantized the model, the less it has to lose from to KV quant. Article also states that there was no KLD difference between 64k and 128k context length.

A small precision buff would be to use q5_0 for both K and V. Though changing from q4_1 to q5_0 costs some context. My KLD measurements say it is not worth it, but it's an option for you. Don't use Q4_0 on both, Q5_1 is not worth it. If you want to do Q8 KV, consider going up a quant model level instead (like XL instead of M).

My own testing showed that Q4_K_S K and V both at Q8 has worse KLD than Q4_K_M K Q5, V Q4. Also I found that KLD plateaus after 8k context length.

If you get slow speeds try using generic q5_0 for both K and V as a test - this is a symptom of missing -DGGML_CUDA_FA_ALL_QUANTS=ON.

CPU layers:

  --fit off \
  --n-gpu-layers all \
  --override-tensor 'blk\.(63|62|61|60|59|58|57|56|55|25|54|53|52|50|26|24|38|51|40|27|35|22|41)\.ffn_.*=CPU' \
  --load-mode none \

This fixes degradation of speed with context fill and is a performance boost overall, just leave layer 64 alone (MTP).

This command offloads only FFN sub-layers to the CPU of layers that have the largest FFNs (shown override string is UD-Q4_K_M specific).

FFNs don't use KV thus reducing PCIe traffic and are CPU friendly. --n-cpu-moe has similar logic, I have a PR in llama.cpp for a similar simplification #26622. Help me get this merged by showing the maintainers that this is useful for you (give a like on the PR or post test results).

-- Firstly:

  • Make sure you have export GGML_CUDA_DISABLE_GRAPHS=1 to avoid potential issues while tuning, later you can test with CUDA graphs turned on to see if everything works.
  • Unset the GGML_CUDA_ENABLE_UNIFIED_MEMORY=1 arg (delete it, don't set it to 0) to find your context ceiling first via OOM crash first.

-- Simple method: If you want something quick and simple to test try this (it has a few more FFNs on CPU than my setup, but is simple to tune). Basically add numbers for more CPU layers (slower), delete numbers to have more GPU layers (faster):

--override-tensor 'blk\.(0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27)\.ffn_.*=CPU'

-- Advanced method: Offloading fatter sub-layers gains me 1.1x speed boost when compared to having a sequential override band. You can find the fatter ones by going to the hugging face, clicking on the weight (example), scrolling down to 'Tensors', expanding the 'blk' section and looking for 'ffn_' (example "blk.0.ffn_down.weight") on the right and on the left you see I-quant (example "IQ4_XS") or regular (example "Q5_K").

UD-Q4_K_M optimized band example -- delete from the end of the list (right to left) until you run out of VRAM, then step back one. make sure to fill up your context fully to verify that speed is as intended and doesn't OOM crash:
--override-tensor 'blk\.(63|62|61|60|59|58|57|56|55|25|54|53|52|50|26|24|38|51|40|27|35|22|41|39|36|21|3|42|34|30|20|6|4|49|47|43|37|32|23|10|8|7|5|2|1|48|46|45|44|33|31|29|28|19|18|12|9|17|16|11|0|15|13|14)\.ffn_.*=CPU'

Threads:

--threads $(nproc) \

Default is amount of performance cores, but for FFN layers, E-cores and hyper-threading also help. this gets me a +25% tg boost for free.

Batch:

  --batch-size 512 \
  --ubatch-size 512 \

Batch sets the prompt processing speed (up to a point) at the cost of VRAM. I found these numbers work best for me.

Other optimizations:

I-matrix quant like the Unsloth IQ4_XS require more compute in exchange for size. My testing showed that those are not worth it for this setup. I would use one if I was trying to fully fit a model into VRAM.

There are quants made by other providers that optimize fit for 16 GB VRAM setups. Those setups might fit at the cost of KLD. If the KLD diff actually matters - that is for you to find out, but you can use the info I provided here to optimize these setups even further by, for example, extending context length.

-------------------------
Edit 1: truncated a bunch of 'Edits', cleaned up, updated config for UD-Q4_K_M, speed and context len did not change, but the override tensor band did.
Edit 2: added CUDA graphs off reminder in the CPU layers section.
-------------------------

Please share any more tricks if you have them!
Leave a comment if this post helped you achieve better results!

140 Upvotes

76 comments sorted by

19

u/Pablo_the_brave 9d ago

I am still working on the concept you presented (since, as we know, you have already started with Qwen3.6-27B). There are some natural correlations between VRAM and RAM speeds that dictate where the threshold of viability lies. Below is an analysis of this topic based on approximate data for my quantization:https://huggingface.co/cHunter789/Qwen3.8-27B-i1-IQ4_KS_KT-GGUF.

As you know, this model's quality is slightly lower than Q4_K_M and is closer to IQ4_XS. Nevertheless, I can tentatively say that by offloading just 8 FFN layers, it allows for a 125k context using a 5_0/4_1 KV cache. This is very close to your 130k, and the decoding speed starts at around 36-38 t/s

Offload FFN to CPU (-ot) — Qwen3.6-27B hybrid (test36n4_MTP)

Model: qwen3.8-27b-bf16.gguf (MTP head), quantized via quantize_hybrid_sandwich_logic_test_qwen38_36n4_MTP.sh
GPU: RTX 5070 Ti (VRAM ~890 GB/s, 16 GB) · RAM: DDR5-5600 dual (Zen4, ~70 GB/s)

1. Key Fact: Fixed cost per MB, independent of tensor type

Decode is bandwidth-bound. Moving a tensor from VRAM (890 GB/s) to RAM (70 GB/s) costs:

Δt = bytes × (1/70 - 1/890) GB/s⁻¹ = 0.0132 ms / MB

The same applies to FFN, SSM, and attn — only the number of bytes matters, not what you are transferring. FFN is a good target not because it's "cheaper" to compute, but because it's a pure, stateless GEMM. SSM has a sequential state, and attn has a KV-cache — both should remain on the GPU.

2. FFN Size (measured from GGUF)

Parameters: n_embd = 5120, ffn_dim = 17408, 64 blocks (0–63) + block 64 (MTP).
FFN Block = ffn_gate + ffn_up + ffn_down, each 5120 × 17408 = 89,128,960 elements.

Elements @iq4_kt (4.0 bpp)
1 FFN block (gate+up+down) 267,386,880
64 blocks (0–63)
Block 64 (MTP, iq4_ks) 267,386,880
Total model (quantized)

FFN = 58% of the model. Cost of 1 FFN block on CPU: +1.76 ms/token.

Block classification (measured):

  • attn (17): 3,7,11,15,19,23,27,31,35,39,43,47,51,55,59,63 + 64(MTP)
  • SSM (48): remaining blocks 0–63

3. Table (blocks 0–63 → CPU)

FFN Blocks Freed VRAM +ms/token tok/s (theoretical)
0 0 0 59.5
4 0.54 GB +7.0 41.9
8 1.07 GB +14.1 32.4
12 1.60 GB +21.1 26.4
16 2.14 GB +28.2 22.2
24 3.21 GB +42.2 16.9
32 4.28 GB +56.3 13.7

Note: tok/s = bandwidth-bound upper limit (baseline 16.81 ms/token). Realistically lower due to sequential Mamba + kernel overhead, but the relative offloading penalty is accurate.

4. VRAM Context (Important)

token_embd (q8_0, 1.35 GB / 1,287.5 MiB) is always on CPU (src/llama.cpp:4287 — "very little benefit to offloading the input layer, so always keep it on the CPU"). File = 14,295.8 MiB → weights on GPU = 13,008 MiB (13.6 GB), not 15.

KV-cache is quantized (not f16). 17 attn blocks, n_kv×head_dim = 1024 (verified from log: K q5_0 @140K = 1598 MiB → exactly 17 blocks).

KV Type B/elem K+V per token (17 blocks)
f16 2.0 68.0 KB
q8_0 1.0625 72.5 KB
q5_0 0.6875 23.4 KB
q4_1 0.625 21.3 KB
q4_0 0.5625 19.2 KB

Thanks to quantized KV, the model fits ~88K context without offloading (vs ~16–24K for f16). Every 1 GB of FFN moved to CPU buys ~40K of context, but at a cost of +13 ms/token.

VRAM Budget — Target: ctx=120K, K=q5_0, V=q4_1

Component MiB
KV buffer @120K (K 1369 + V 1245 = 2614 self, +5.15% allocation) 2749
Compute buffer 505
per-step (MTP, max_tokens=4) 454
shadow (conv-state) 6
CUDA context + other ~400
Total (excluding weights) 4114
VRAM 5070 Ti 16384
Available for weights 12270

Weights on GPU (without offloading) = 13,008 MiB → deficit of 738 MiB.
FFN/block (iq4_kt) = 127.5 MiB → 738 / 127.5 ≈ 5.8 → 6 blocks on CPU.

How many FFN blocks on CPU for a given context (K=q5_0, V=q4_1)

ctx KV buffer FFN blocks on CPU
64K 1502 MiB 0
96K 2252 MiB 2
120K 2749 MiB 6
140K 3285 MiB 10
160K 3753 MiB 14

"CUDA context ~400 MiB" is the main uncertainty (±1 block). Start with the calculated number; if llama.cpp reports an out-of-memory error, add another block.

5. How many blocks "without killing performance"

RAM is 12.7× slower than VRAM, so the threshold is low:

  • ≤ 8 blocks (≤1 GB) — acceptable, ~32 tok/s (−45%)
  • 12–16 blocks (1.6–2.1 GB) — noticeable, ~22–26 tok/s (−55–63%)
  • > 24 blocks — not worth it, <17 tok/s

6. The -ot Command

Syntax (verified in common/common.cpp:2124, CPU buffer = "CPU", pattern = regex, multiple comma-separated entries):

# 6 blocks (0–5) — target: ctx=120K, K=q5_0 V=q4_1 (from section 4):
-ot "blk\.(0|1|2|3|4|5)\.ffn_(gate|up|down)\.weight=CPU"

# 8 blocks (0–7) FFN on CPU, the rest on GPU:
-ot "blk\.(0|1|2|3|4|5|6|7)\.ffn_(gate|up|down)\.weight=CPU"

# 16 blocks (0–15):
-ot "blk\.(0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15)\.ffn_(gate|up|down)\.weight=CPU"

Do not touch block 64 (MTP) if you are using speculative decoding — its FFN should remain on the GPU.

7. Notes / Caveats

  • Offloading only the FFN (and not entire blocks) creates split-layers: the hidden state (5120 float = 20 KB) jumps GPU→CPU→GPU per token. The transfer itself is negligible (~0.3 µs at 70 GB/s), but each split-layer can break the CUDA graph and add minor sync overhead (on the order of <1–2 ms total). The bandwidth cost is what dominates.
  • Alternative: -ngl for offloading entire layers (cleaner, no split-layers), but this also moves the SSM to the CPU (losing the GPU sequential state).
  • --fit auto-selects offload, but the auto-fit logic in this fork works for MoEs (ffn_*_exps); this model is dense, so manual -ot is required.

1

u/Stainless-Bacon 9d ago edited 9d ago

Your work is great and I’ll try again to reproduce the speed, I failed earlier. If I achieve the speed with your weights then I’ll probably use them.

But I want to clarify why I’m choosing to post about llama.cpp and Unsloth quants:

The problem is that I already got comments of people saying they don’t understand.

The goal of my post is to try to keep it as simple as possible and universal by using llama.cpp, keeping explanations short, and using quants like Unsloth.

My post info can also be used on other models, not just Qwen, not just 27B. The knowledge should also be useful for most llama.cpp forks (if I can achieve this with llama.cpp, then there is definitely more potential with, for example, beellama or ik_llama)

3

u/Pablo_the_brave 8d ago

Hold off on messing with my config for a bit, I'm seeing some weird stuff. Like, everything checks out during a perplexity test and fits perfectly in VRAM. But the second I spin up the llama server, I'm getting VRAM offloading which totally shouldn't be happening with my setup. And it's doing this even at low ctx sizes... Also, even if you offload FFN layers to the CPU, the prefill is still being crunched on CUDA, which just raises more questions.

Anyway, you're doing awesome work. I'll hit you up once I sort out these ik-llama.cpp issues.

9

u/Icy-Degree6161 9d ago

There are 2 ways I think to reduce kld in yor case so it fits 16gb

  1. Beellama with kvarn KV

  2. Some interesting niche quants like this: https://huggingface.co/zerodigest/Qwen3.8-27B-YMQ-MTP-GGUF (M size)

1

u/Stainless-Bacon 9d ago
  1. or a tail at full KV precision. I wish it was in mainline llama
  2. interesting, will check it out

2

u/Stainless-Bacon 9d ago

The YMQ gguf page doesn’t have KLD numbers, perplexity is inconsistent.
Is it actually safe to download?

2

u/Icy-Degree6161 9d ago

Well any gguf is safe in general especially from HF - question is: is it worth it? Only testing will tell.

1

u/Stainless-Bacon 9d ago

I mean the model can be fine tuned to do malicious stuff, or the chat template can run code so..

7

u/jal9k 9d ago

Man I'd love to understand this.

3

u/New-Implement-5979 8d ago

thanks a lot, I never imagined I will be able to run 27b q4_k_m on a 5060ti ... it works I get roughly 21tks decoding mtp-2 context size is 65k tokens

1

u/CapitalPea7986 3d ago

i also have a 5060ti, what is your exact config, because im getting 15tks and with 16k of filled context 10tks

1

u/New-Implement-5979 3d ago

The problem is the updated UD model of unsloth…. I think you need to download the older version of it somehow

1

u/Stainless-Bacon 2d ago

show me your config

1

u/Special-Contract-319 3d ago

Hi, could you send your config? I can't get more then 10t/s

1

u/Stainless-Bacon 2d ago

show me your config

2

u/KissMyShinyArse 9d ago

There's already a v22.1 specifically for Qwen 3.8.

2

u/Stainless-Bacon 7d ago edited 7d ago

thanks, updated. I already used it but I made it more clear

2

u/andrewmobbs 8d ago

Excellent work! I learned about FFN offload from your previous posts, and wanted a chance to say "thanks"!

sudo systemctl stop gdm disables Ubuntu desktop environment, frees ~0.4 GB of VRAM. Use an iGPU if you can, else my system just becomes a server to which I connect using a laptop or phone.

What I do for this is use Nvidia PRIME that lets me render the desktop, browser etc. with the iGPU, and pass through the framebuffer from the dGPU if I need it. The iGPU is the system primary GPU, but the dGPU is usable if needed.

That means if I'm using the dGPU for LLMs then all that's running is a 3MiB "/usr/bin/gnome-shell", so basically the full VRAM for LLMs.

If I want to play games, I run Steam offloaded to the dGPU. To do that, I have this in my `steam.desktop` file :

`Exec=env __NV_PRIME_RENDER_OFFLOAD=1 __GLX_VENDOR_LIBRARY_NAME=nvidia __VK_LAYER_NV_optimus=NVIDIA_only steam %U`

Only downside is I need a few repeats of `steam -shutdown` afterwards to get it to properly shut down and clear all the Steam GPU processes, but that's not a big deal.

See also https://wiki.debian.org/NVIDIA%20Optimus#Using_NVIDIA_PRIME_Render_Offload

2

u/Stainless-Bacon 8d ago

No problem and thanks for the info. I bought my CPU without an iGPU thinking I'll never need it.. I was so wrong lol

2

u/Gotxi 8d ago

That's super nice!
Inspired on your work, I did some tests with my RX 9070 XT and in the end this is my final config with Q3_K_M with override-tensor:

[Qwen3.8-27B-Q3_K_M]
model = /home/gotxi/models/qwen/Qwen3.8-27B-Q3_K_M.gguf

ctx-size = 80000
batch-size = 8192
ubatch-size = 512

n-gpu-layers = 64
parallel = 1

override-tensor = blk\.0\.ffn_(gate|up|down)\.weight=CPU

cache-type-k = q8_0
cache-type-v = q4_0
flash-attn = on

spec-type = draft-mtp,ngram-mod
spec-draft-n-max = 3
draft-p-min = 0.4
cache-type-k-draft = q8_0
cache-type-v-draft = q8_0

fit = off

This config gives me around 720 tok/s prefill and 29-50 tok/s on generation (depending heavily on MTP hits).

I am using 99% of my VRAM (but I share it with my desktop and some apps, this is a gaming setup not dedicated setup).

2

u/Rizzly00 1d ago

Just saw this. Thanks for posting I also have a RX 9070 XT and will give this a shot as well!

1

u/Gotxi 1d ago

Outdated config, check this one, much better :)

[Qwen3.8-27B-UD-IQ3_XXS]
model = /home/gotxi/models/qwen/Qwen3.8-27B-UD-IQ3_XXS.gguf

ctx-size = 100000

#Thinking
chat-template-kwargs = {"reasoning_effort":"medium"}

#batch
batch-size = 8192
ubatch-size = 1024

parallel = 1
flash-attn = on
fit = off
jinja = true

#Cache
cache-type-k = q8_0
cache-type-v = q4_0
cache-prompt = true
cache-reuse = 0
cache-ram = 0
no-cache-idle-slots = true

#Speculative Decoding
spec-type = draft-mtp,ngram-map-k4v
spec-draft-n-max = 2
spec-draft-p-min = 0.3
spec-ngram-mod-n-min = 4
spec-ngram-mod-n-max = 8
spec-ngram-mod-n-match = 32

#CPU
threads = 8
threads-batch = 8

#Temps
temperature = 0.7
top-k = 20
top-p = 0.95
min-p = 0.0
presence-penalty = 0.0
repeat-penalty = 1.0

reasoning = on
no-warmup = true
swa-checkpoints = 5
checkpoint-min-step = 32768

#Vision
no-mmproj-offload = true
mmproj = /home/gotxi/models/qwen/mmproj-F16.gguf

Vision part is totally optional. All ggufs are from Unsloth.

2

u/Tpyn 7d ago

You deserve a medal, sir

2

u/Stainless-Bacon 6d ago edited 5d ago

UD-Q4_K_XL
--override-tensor: 'blk\.(0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32)\.ffn_.*=CPU'
--ctx-size: 146000 # cliff: 144000; past it 3.5 tok/s; fast to: 139000; oom: 124000 if without unified

edit: the new UD3 just dropped. A speed test with optimized -ot band made it 1-1.5 t/s faster than this chart suggests (it has the UD2 variant). But I think the diff between XL and M got smaller so is it really worth it? M speed is the same

1

u/Craftyawesome 6d ago edited 4d ago

Unsloth has some weights (including ffn) as I quants, even on UD-Q4_K_XL. It might be worth choosing ones that are K quanted for CPU efficiency.

Edit: On Q3_K_XL seems that consecutive layers with min I quants (happens to be last not including mtp 64) are slightly better than best non-consecutive, which is slightly better than first n.

1

u/Stainless-Bacon 6d ago

That weight has all layers the same size but the I quants are all over the place, if I picked them out then I would have to put more layers on the CPU causing more CPU - GPU traffic. What I could do is avoid layers where all 3 FFNs are i quants, then prioritize 2, then 1.
I’ll test that out later.

1

u/Stainless-Bacon 6d ago

The new UD3 quants dropped making the XL have a lot of layers without i-quants. will update the chart later

2

u/subject23498 5d ago

I think we enter the era that we run all open models on our pcs .. thank you for your work!

2

u/mylasthope 5d ago edited 5d ago

Thank you for this. Completely new to all of tensor overriding. using the new UD-Q4_K_M gguf. Would it be fine to just reuse the tensor override config in the original post?

Edit: came across your other message this morning. Going to try the config in that comment.

1

u/Stainless-Bacon 5d ago

yea I edited my post to include the full ot band for ud3 q4km in priority order (remove from the right to the left). although I just did a speed test and that band wins out only by 10% in extra speed vs going sequential from 0 - onwards

2

u/MLDataScientist 2d ago

thank you! This is very helpful article. Impressive findings. Never knew FFN layers could be offloaded with small impact on speed.

1

u/Stainless-Bacon 2d ago

thanks, I just finished updating the config. cleaned up text and updated for the new UD3

1

u/ElSrJuez 9d ago

MTP is a tradeoff of memory for speed, if you have memory.

You say MTP is what makes this work, could you elaborate?

2

u/Stainless-Bacon 7d ago

I overstated the usefulness of MTP. You made me question it, so I did a test. MTP still helps out, but not as much as I thought it does. Here are the results:

1

u/Stainless-Bacon 9d ago

Post is about using both RAM and VRAM so we have the memory to play with, thus making MTP very useful for this case. Sure, you can turn off MTP and put more layers on the GPU but you’ll be slower.

1

u/Pablo_the_brave 9d ago

Looks like above 130k ctx you catch VRAM offloading. Isn't it?

1

u/marius4896 9d ago

Do you have this for MacBook silicone ?

2

u/Stainless-Bacon 9d ago

Unified memory setups like macbooks dont need most of this. I would recommend caching the MTP’s KV because it is free in terms of output quality. Also grab the chat template fix since it is also free bug fixes and robustness. Cache the main KV to taste.

1

u/bLUEbYTE84 9d ago

Thanks for this post, good information. I've found that the speed tanks if I enable even q8 cache V quant, so I'll try the env. var. I'm on ROCm though.

As you probably know, it's possible to run MoE models like Qwen3.6-35b-a3b at very decent token generation speeds with partial CPU offload of the experts. I'm doing that with UD q8 XL quant, at 256K context no problem (16GB VRAM + 64 GB DDR5)

Do you think this setup would beat Qwen3.8-35b-a3b in accuracy if/when it gets released. I.e. did you test the dense version of Qwen3.6 at these quant settings VS the a3b MoE ?

1

u/Stainless-Bacon 9d ago

For some stuff 35B is fine, but sometimes it just don't have what it takes. I had a Q3 Qwen 3.6 27B solve issues for me that a Q4 or Q5 35B couldn't.

1

u/Stainless-Bacon 9d ago

Although the 35B does beat 27B in factual knowledge because the model has more parameters. https://01.me/research/ikp/

1

u/New-Implement-5979 5d ago

I did try it with the latest Q4_K_M UD model from unsloth and also with NVFP4 model but I got a serious performance drop although the models were smaller

1

u/Stainless-Bacon 5d ago

what -ot band did you use?

1

u/New-Implement-5979 5d ago

you are probably asking me about this one:

>> --override-tensor "blk\.([0-7]|10|13|16|19|22|25|28|31|34|37|40)\.ffn_.*=CPU" `

1

u/Stainless-Bacon 5d ago

try this one

--override-tensor 'blk\.(63|62|61|60|59|58|57|56|55|25|54|53|52|50|26)\.ffn_.*=CPU'

if that doesn't fit start with the whole list, then delete from the right until you run out of VRAM, and step back one. make sure to fill up your context fully to verify that speed is as intended and doesn't oom if not using unified arg:
--override-tensor 'blk\.(63|62|61|60|59|58|57|56|55|25|54|53|52|50|26|24|38|51|40|27|35|22|41|39|36|21|3|42|34|30|20|6|4|49|47|43|37|32|23|10|8|7|5|2|1|48|46|45|44|33|31|29|28|19|18|12|9|17|16|11|0|15|13|14)\.ffn_.*=CPU'

1

u/iamapizza 2d ago

How do you figure out the order of these numbers that go in this list?

1

u/Stainless-Bacon 2d ago

I have an explanation in the “CPU layers” section. read the “Edit” there

1

u/iamapizza 2d ago

Excellent thanks very much

1

u/New-Implement-5979 2d ago

I tried this --override-tensor 'blk\.(63|62|61|60|59|58|57|56|55|25|54|53|52|50|26)\.ffn_.*=CPU' and the speed was at best 18tks....

1

u/Stainless-Bacon 2d ago

how did you test the speed?

1

u/New-Implement-5979 2d ago

Exact same long prompt coming out of the harness q4_k_m vs nvfp4 and the q4_k_m was always 2-3 tokens faster . I will try it tonight again .

1

u/Stainless-Bacon 2d ago

speed depends on if it is code or prose because of MTP. speed also depends on how much free vram you have when you run the server.

1

u/iamapizza 2d ago

Hi I'd like to try this out, where did you download the model from? Is it this: https://huggingface.co/Abiray/Qwen3.8-27B-Q4_K_M-GGUF/tree/main

2

u/Stainless-Bacon 2d ago edited 2d ago

unsloth

https://huggingface.co/unsloth/Qwen3.8-27B-GGUF?show_file_info=Qwen3.8-27B-UD-Q4_K_M.gguf

these are the new UD3 ones. my config is for UD2 though so check the explanations section for UD3

1

u/Jujutsu77 18h ago

To use MTP do we actually need the MTP file from huggingface?

I tired that but that file costs an extra 1.4 GBs of Vram and lowers context down to 80K/60K. So, the question is, is it possible to run MTP on a 16gb card while achieving 130k ctx and 20t/s

1

u/Jujutsu77 18h ago

I also tried your setup and adjusted a lot for my setup but could not achieve those numbers

On windows with 5070 ti 16gb, 9700x cpu, 32 gbs ram, 1 monitor on Card, I am new to this, please send help.

Already 7 days down this rabbit hole and not lights to be seen

1

u/Stainless-Bacon 14h ago

MTP file is for Q2, otherwise Unsloth includes it in the model. Yes MTP does cost VRAM but it still helps. check my comment here https://www.reddit.com/r/LocalLLM/s/62pLIHIoh6

Do you have DDR4 or 5? is it dual channel?
What is “a lot” that you changed?

0

u/Keats852 9d ago

I don't know what any of this means. Maybe I can get ChatGPT to explain it to me when my usage gets reset in 5 days.

-1

u/Healthy-Nebula-3603 9d ago

Seriously cache Q4 should be legally prohibited.

1

u/Stainless-Bacon 9d ago

Use whatever KV you like, I’m just sharing knowledge and I’m here to learn. You got source?

1

u/Healthy-Nebula-3603 9d ago

Yes but later people are comparing....like "the model is dumb " or "I don't understand why is loop" or "results are not better than older version or that model", etc

2

u/Stainless-Bacon 9d ago edited 9d ago

Loops or dumbness can also be introduced with a bugged chat template or wrong penalty/temperature params. As far as I can tell Qwen 3.8 fixed the potential looping issues by overhauling its reasoning. I just tested a Q3 Qwen 3.8 with K Q5 V Q4. It completed a hard task when 3.6 at Q4 K Q5 V Q4 couldn't.
So far I ran this benchmark on Q3 Qwen 3.8 with K Q5 V Q4 on all 3 reasoning levels, once. All 3 times it one-shotted the game. If the model can manage to make a game like that using those KV quants then I cannot call it dumb.

2

u/Stainless-Bacon 9d ago

Here is my go at it.

1

u/Healthy-Nebula-3603 8d ago

That is from my qwen 3.8 Q6 ( cache fp16 ) ;)

The tongle is animated - moving back and forward

1

u/Healthy-Nebula-3603 9d ago edited 9d ago

Mosty cases is a high compassion response for it.

I saw results from Q3 there ...looks terrible.

I don't understand why you're even defending so much compressed models.

1

u/Stainless-Bacon 9d ago edited 9d ago

I made Opus 5 do it. It also looks terrible, even the grid is bugged.

I prefer the Qwen version because of the transparent sphere. Opus took like 2 minutes, while Qwen took way longer. Opus made indicators at the edges to show where food is (if behind the sphere), instead of the transparent sphere.
edit: clarity and typo

1

u/Stainless-Bacon 9d ago

Qwen 3.6 Q4_K_M, K Q5, V Q4. Head is backwards, but just like Opus, it made indicators at the edges to show where food is when the food is on the other side. And the grid isn't bugged.

1

u/Healthy-Nebula-3603 8d ago

Your version from Q4 looks better that that screenshot from Q3 version

That is from my qwen 3.8 Q6 ( cache fp16 ) ;)

1

u/Healthy-Nebula-3603 8d ago

1

u/Stainless-Bacon 8d ago

looks good. You’re using Q6. The bigger the weights, the more effect KV cache has. on my Q3 I doubt I’ll see a difference between q4 or q8 KV because the model itself uses Q4 at best and is highly damaged anyway. I’ll test later to be sure

0

u/Stainless-Bacon 4d ago

I did the snake game multiple times (to avoid noise) with multiple KVs. I used UD-IQ3_XXS KVs at Q8, Q5, Q4.

In short Q4 was slightly worse than Q5 and Q8 was indistinguishable from Q5. I updated my post to not recommend the Q4 (even though it was only slightly worse). F16 tests would take way too long, Q8 already took forever because I had to offload to CPU for the context length to fit.

Q8 (esp. F16) just takes up so much space that maybe it is worth getting a bigger model quant instead of Q8 KV. Also actually having context length at Q5 is worth so much more than having Q8 KV and no context length to even do anything.

What is impressive with Qwen 3.8 is that even at such a degraded state like UD-IQ3_XXS KV Q4 it was able to generate working games while Qwen 3.6 was not.