docs.na.id.au

The VRAM budget equation

02 — Context Sizing & Model Quantisation

How to choose quantisation and context size so the model, its KV cache, and your working memory all fit on the card.

The VRAM budget equation

Every model you run costs three things in VRAM:

VRAM ≈ weights(quant) + KV_cache(ctx × parallel) + overhead (~0.5 GB)

The trick is not to fill the card to 100%: the OS, the desktop compositor (if present), and peak batch work all need headroom. A good target is ~70–85% of VRAM at worst case, and on an 8 GB card, be conservative.

Weights quantisation

Rule of thumb for consumer cards:

CardSweet spotNotes
8 GB3–7B at Q5/Q6, 12–14B at Q4Q6_K is the sweet spot for ≤7B models — near-Q8 quality at ~30% more size than Q4
12–16 GB12–14B at Q6/Q8, 24B at Q4
24 GB27B at Q4_K_M, 12B at Q8UD (uncensored/modified) Q4_K_M builds are the workhorse for 20–30B models

Don’t over-quantise small models: Q3/Q2 on a 4B model is a bigger quality hit than Q4 on a 27B, because you’re cutting a model that is already small. If a model fits comfortably, pay for the extra quant.

Context sizing — the part people get wrong

Context is where most setups break, because the KV cache is what silently eats the VRAM you budgeted for “later”. Worked examples from the local setup:

24 GB card, gemma4-12b (Q8-ish weights ~12 GB): 256k context (ctx-size = 262144) with q8_0/q4_0 KV cache is viable because the card has ~11 GB of headroom after weights. This is the “main agent” profile — long documents, long tool conversations, compaction of other agents’ histories.

24 GB card, qwen3.8-27b (Q4_K_M weights ~16 GB): Only ~7 GB left for KV, so context drops to ~185k (ctx-size = 185000). Note the asymmetry: a 256k context on the smaller model beats an 185k context on the bigger model for pure throughput and cost — pick the model for the task, not the other way around.

8 GB card, qwen3.5-4b (Q6_K weights ~3.4 GB) plus a resident embedding model, bge-m3 (~1.75 GB):

3.4 (weights) + 1.75 (resident model) + ~0.5 (overhead) = 5.65 GB
remaining for KV: ~2.3 GB  →  8192 ctx × 2 slots at q8_0/q4_0

That leaves ~1.5 GB headroom at worst case — comfortable. The same card with a 7B Q4 model at 128k context is a 500 model failed to load (weights ~4.5 GB + KV ~5 GB > 8 GB). Context size is not a knob you set once and forget — it is a per-model budget line.

The working-memory rule of thumb: a model’s useful context is far smaller than its configured ctx-size. After the system prompt and tool schemas, a 4k context has maybe 2k tokens of actual working memory — too little for multi-step agent work, which is why an otherwise-fine 9B at 4k lost to a 7B at 16k as a worker model. For agent/sub-agent work, target at least 8k context and keep the system prompt lean.

Resident vs swap-in (load-on-startup, --models-max)

A models.ini preset lets one server hold several models. Two behaviours to choose per model:

A pattern that works well on a small card: resident for the models that must be instant (a small embedding model, a small always-available chat worker), swap-in for the large models (the primary server keeps its GPU empty at boot and loads whichever of its two models is requested). If VRAM pressure appears, the fix ladder is: drop ctx-size, set load-on-startup = false on a profile, or re-enable idle sleep (sleep-idle-seconds).

Pitfalls learned the hard way

These were all verified failures on the local setup — expect the same ones:

A preset in practice

The two-tier split shows up directly in the presets — minimal, and every line earns its place:

# models-primary.ini — 24 GB card, swap-in (GPU empty at boot)
[*]                      # globals: inherited by every profile
sleep-idle-seconds = 3600   # free the GPU when idle
load-on-startup = false     # swap-in by default
flash-attn = true
batch-size = 1024
ubatch-size = 256
threads = 6
gpu-layers = 99             # all layers on GPU

[gemma4-12b]               # main agent: long documents, compaction
model = /usr/local/llama/models/Gemma4-12B-Q4_K_M.gguf
ctx-size = 262144
context-shift = true        # shift old tokens out when the context overflows
keep = 1024                 # ...but always keep this prefix (system prompt etc.)
cache-type-k = q8_0
cache-type-v = q4_0

[qwen3.8-27b]              # bigger brain, shorter context
model = /usr/local/llama/models/Qwen3.8-27B-UD-Q4_K_M.gguf
ctx-size = 185000
context-shift = true
keep = 1024
cache-type-k = q8_0
cache-type-v = q4_0
jinja = true
chat-template-file = /usr/local/llama/templates/froggeric_chat_template.jinja

# models-secondary.ini — 8 GB card, both models resident
[*]
flash-attn = true
threads = 6
gpu-layers = 99
chat-template-kwargs = {"enable_thinking": false}   # 4B is a reasoning model (see pitfalls)

[bge-m3]                   # resident embedding model
model = /usr/local/llama/models/BGE-M3.gguf
load-on-startup = true
pooling = cls               # BGE-M3 pools on the CLS token — required
ctx-size = 2048
cache-type-k = f16          # embeddings don't need a quantised KV cache
cache-type-v = f16
batch-size = 1024
ubatch-size = 1024          # physical batch = max single embedding input

[qwen3.5-4b]               # resident chat/worker model
model = /usr/local/llama/models/Qwen3.5-4B-Q6_K.gguf
load-on-startup = true
parallel = 2
ctx-size = 8192
kv-unified = true           # each slot gets the FULL ctx (see pitfalls)
cache-type-k = q8_0
cache-type-v = q4_0
batch-size = 2048
ubatch-size = 512

Note that cache-type-k/cache-type-v are set per profile, not globally: the chat models get quantised q8_0/q4_0 caches, but the embedding model keeps f16 — a 2048-token KV cache is tiny anyway, and there’s no point quantising a model that never generates tokens.


Source Disclaimer