← All posts

The shared-memory formula predicted 5 new settings to within 3 MiB, but a smaller ubatch does not push the 4B past about 704K fully on the GPU

E21q found that shared GPU memory for the 4B model equals 82 MiB plus context x ubatch x 2 bytes until the 10 GB card runs out. I wrote down predictions for five new settings before running them, and all five landed within 3 MiB. I then tried to use a very small ubatch to fit more context on the card. It did not help: at 768K the spill was the same 298 MiB at ubatch 16 and ubatch 8, and prompt processing there was about 20 to 37 times slower than at 704K.

ExperimentE21R
Date2026-10-29
BenchmarkMemory and speed measurement only, no accuracy test. llama-server loaded at each setting, per-process Dedicated and Shared Usage read from the Windows GPU Process Memory counters (max of 5 samples), then one 2K-token prompt timed.
ModelQwen3.5-4B, Q4_K_M GGUF, K and V cache at q4_0, flash attention on, YaRN context extension above 256K
HardwareOne of my dedicated test rigs: RTX 3080 10 GB
VerdictBASELINE

What I tested

Prediction points: 256K ub128, 384K ub16, 512K ub128, 512K ub32, 704K ub8. Residency points: 736K ub16, 768K ub16 and ub8, 800K ub16, 832K ub16 (capped by llama.cpp at 851,967 tokens, one below the requested size). K = 1024 tokens, so 704K is 720,896 tokens. llama.cpp built with upstream PR 29827.

Pass bar, set before the run

None (supporting measurement). Rule set before the run: a point counts as fully on the GPU if measured shared memory is within 20 MiB of 82 + ctx x ub x 2 bytes; more than that is spill.

Result

ContextUbatchShared (measured)PredictedOver predictionDedicated2K prompt speed
256K128148 MiB146 MiB+25,356 MiB2,275 tok/s
384K1692 MiB94 MiB-26,443 MiB1,139 tok/s
512K128212 MiB210 MiB+27,724 MiB2,754 tok/s
512K32112 MiB114 MiB-27,617 MiB1,647 tok/s
704K890 MiB93 MiB-39,321 MiB652 tok/s
736K16222 MiB105 MiB+117 (spill)9,502 MiB1,179 tok/s
768K16404 MiB106 MiB+298 (spill)9,612 MiB32 tok/s
768K8392 MiB94 MiB+298 (spill)9,598 MiB18 tok/s
800K16660 MiB107 MiB+553 (spill)9,645 MiB33 tok/s
832K16982 MiB108 MiB+874 (spill)9,615 MiB34 tok/s

All five prediction points were within 3 MiB, so the 82 MiB + ctx x ub x 2 bytes model now holds at 16 settings across E21q and E21r. It is still attributed by size match only; llama.cpp's buffer-size lines were not in these logs.

Above about 704K, dedicated memory stops near 9.5 to 9.65 GB and the rest goes to shared memory. Shrinking the ubatch only shrinks the small mask term, so it cannot win back hundreds of MiB.

736K went 117 MiB over prediction but its 2K prompt speed was normal. 768K and up were 20 to 37 times slower than 704K on the same prompt. Only a 2K prompt was timed; a full-length prompt was not.

Small ubatch costs prompt speed: 704K at ubatch 8 ran 652 tok/s against 1,000 to 2,700 tok/s at larger ubatch and smaller context.

Reading: for this model and cache format the card holds about 720,896 tokens. Reaching 1,048,576 tokens fully on the GPU needs real memory savings, not settings. E21s measures how much memory each token costs per cache format to size that gap.

What happened

The memory formula from E21q held on every new point I predicted ahead of time. The hope was that a tiny ubatch would free enough room to go past 704K, but the card was already full: from 736K up, dedicated memory stayed pinned near its limit and the overflow grew by about 180 to 320 MiB per extra 32K tokens, the same at ubatch 16 and 8. Once the spill reached a few hundred MiB, prompt processing dropped from hundreds or thousands of tokens per second to 18 to 34. So the practical ceiling on this card for the 4B with a 4-bit cache is about 704K, and 1M needs a smaller cache or smaller buffers. The memory saving that makes these sizes fit at all is upstream llama.cpp PR 29827, so credit goes to its authors.

How to rerun

powershell -ExecutionPolicy Bypass -File e21r_chain.ps1

Code

All code for E21R: e21r_chain.ps1, results.jsonl.

Prior work

No novelty claim. This is a memory accounting measurement of standard llama.cpp behavior.

Written by Andrew Rose with help from AI tools. Every number comes from the run logs.

Questions or ideas? me@andrewroseit.com or @AndrewRoseIT.