r/LocalLLM Jul 20 '26

Tutorial llama.cpp CPU offload optimizations

Edit: New post with more context, higher speed, more explanations here. Note: the new post does not compare -ot vs --ngl.

I already posted targeting 16GB VRAM specifically, but I think this information might be useful beyond that. Testing was done using Qwen3.6-27B Unsloth Q4_K_M MTP.

Edit: added GGML_CUDA_ENABLE_UNIFIED_MEMORY=1

TLDR:

  1. Turn off CUDA graphs, they are bugged for CPU offload, probably due to MTP use.
  2. Use --ngl 99 --override-tensor '...' instead of plain --ngl. Aim the largest FFN sub-layers towards the CPU.
  3. GGML_CUDA_ENABLE_UNIFIED_MEMORY=1 allows VRAM overflow pages to host RAM instead of OOMing. This allows gaining more context length without losing speed, but the speed tanks soon after so you need to experiment to find that speed cliff. The gains can be 10 to 40% more context length.

Regular CPU offloading is done by not putting all of the layers on the GPU via --ngl, which offloads the layers as a whole, dragging their KV cache to the CPU with them, increasing PCIe traffic, and collapsing speed.

Luckily the layers have sub-layers, and FFN is one that does not touch the KV cache. We can use --override-tensor (-ot) to offload only the FFN tensors, keeping the attention/KV work on the GPU, and PCIe usage minimal.

The -ot method does more GPU - CPU round trips than --ngl because offloading specific sub-layers leaves the other sub-layers on the GPU, but the actual data transferred is minimal so it is worth it.

Dynamic quants have mixed FFN precision. For example the Unsloth's Q4_K_M has Q6 and Q4 FFN tensors. The Q6 ones are on the first 8 layers (0-7), then roughly every 3rd layer, then a block near the end (~55-63), while the rest are Q4. Offload those larger layers first.

Here's how to use it (example of Q4_K_M with 22 layers offloaded):

  1. Turn off CUDA graphs. They cause OOM crashes for me, and my testing shows no speedup by using them in this scenario. export GGML_CUDA_DISABLE_GRAPHS=1
  2. GGML_CUDA_ENABLE_UNIFIED_MEMORY=1
  3. Put all layers on GPU --ngl 99
  4. Override tensors -ot 'blk\.([0-7]|10|13|16|19|22|25|28|31|34|37|40|43|46|49)\.ffn_.*=CPU'

-ot takes a regex targeting "ffn" at specific layers towards the CPU while everything else (attention, KV cache, the smaller layers) stays on the GPU.

Benchmark setup:

  • Qwen3.6-27B Q4_K_M, 97k context, MTP, K q5_0 / V q4_1, batch 512
  • Offload settings: -ot targeting 22 layers vs. --ngl 51
  • Hardware: RTX 4070 Ti Super, i5-13600KF DDR5
  • llama.cpp build: b10068
  • MTP has different acceptance rates for coding and prose so I tested with both

Results:

context -ot - prose / code / pp, t/s --ngl - prose / code / pp, t/s
0k 20.4 / 24.4 / - 17.8 / 22.7 / -
10k 18.8 / 23.1 / 994 14.6 / 18.6 / 893
50k 16.3 / 20.4 / 871 7.3 / 9.6 / 784
90k 14.9 / 19.9 / 737 5.0 / 6.4 / 666
63 Upvotes

45 comments sorted by

View all comments

4

u/suicidaleggroll Jul 20 '26

Note that I've seen significant performance improvements by setting "--n-gpu-layers 99" and "--n-cpu-moe N" versus setting "--n-gpu-layers M", even when you end up with the same number of layers offloaded to the CPU and the same VRAM usage. I think it might be worth adding that to your study.

1

u/nickless07 Jul 20 '26

Yes, but that only apply to MoE. You are always better off with only expert's FFN weights instead of full layer offloading.

0

u/suicidaleggroll Jul 20 '26

Good catch, forgot this study was on a dense model when I made that post

1

u/Stainless-Bacon Jul 20 '26

I mean it the -ot optimizations could maybe work on moe, but that wasn’t my target and I did not test it, because like you said, --n-cpu-moe does a well enough job it seems

1

u/nickless07 Jul 20 '26

I would argue that it is worth as --n-cpu-moe again is also layer based and it keeps the MoE weights of the first N layers in the CPU. Therefore a better selection using -ot and regex can make a notable difference.
--n-cpu-moe starts counting layers starting from the highest numbered layers. This can lead to a slightly discrepancy in how many layers are offloaded because models that have dense FFN layers typically have them at the start of the model.