← All posts

How far a 4B model's context fits on one RTX 3080: 704K tokens, and a 9B at 262K

With upstream llama.cpp PR 29827 and a q4_0 KV cache, Qwen3.5-4B found a single needle in a 715,243-token prompt (704K context, YaRN x2.75) at 9,827 MiB peak VRAM with 170 MiB shared. At 768K the model loaded already spilling 582 MiB into shared memory, so it was skipped. Qwen3.5-9B at its native 262K passed at 50% and 90% depth (7,936 MiB). At 10% depth it started a thinking block and gave no passcode within the 24-token limit, so that run did not pass.

ExperimentE21F
Date2026-10-29
BenchmarkNeedle in a haystack: one passcode sentence at 10%, 50% or 90% depth in repeated filler text, greedy decoding, 24-token answer limit, pass if the answer text contains 7319-4826
ModelQwen3.5-4B and Qwen3.5-9B, Q4_K_M GGUF, flash attention on, q4_0 K and V cache, ubatch 64
HardwareOne of my dedicated test rigs: RTX 3080 10 GB
VerdictBASELINE

What I tested

My build of upstream llama.cpp PR 29827, "CUDA: cap FA convert buffer for quantized KV" (not my work, unmerged when tested). 4B at 640K (YaRN x2.5), 704K (x2.75) and 768K (x3); 9B at its native 262,144 context. A run is skipped if the server uses more than 300 MiB of shared GPU memory right after loading.

Pass bar, set before the run

None. Supporting measurement. A pass on a row means the answer text contained 7319-4826.

Result

Model and contextDepthPrompt tokensAnswerPeak VRAMSharedPrefillTime
4B, 640K (YaRN x2.5)50%648,0437319-4826 (pass)9,241 MiB162 MiB712 tok/s15.3 min
4B, 704K (YaRN x2.75)50%715,2437319-4826 (pass)9,827 MiB170 MiB657 tok/s18.3 min
4B, 768K (YaRN x3)50%not runskipped, does not fit9,930 MiB idle582 MiB idle
9B, 262K (native)50%256,8437319-4826 (pass)7,936 MiB116 MiB1,184 tok/s3.7 min
9B, 262K (native)10%256,843began a thinking block, no passcode in 24 tokens (not passed)7,936 MiB116 MiB1,183 tok/s3.7 min
9B, 262K (native)90%256,8437319-4826 (pass)7,936 MiB116 MiB1,183 tok/s3.7 min

704K is the largest 4B context that stayed under our 300 MiB shared-memory line with a stock q4_0 cache. At 768K the q4_0 KV cache alone is 6,912 MiB.

The script's automatic pass flag checked the whole server reply instead of the answer text, so it marked the 9B 10% run as a pass. I regraded every needle run from E21c to E21f on the answer text; this was the only one affected. The next round uses the fixed check and a 96-token limit to rerun it.

Shared GPU memory is under the 300 MiB line but not zero.

Correction: "fully resident" in this post's address overstates it. llama.cpp keeps the token embedding table and a small staging buffer in system memory; attention and the whole KV cache are on the GPU. With no model loaded the card shows 20 MiB of shared memory, so the 162 to 170 MiB here is the server's own.

One needle in simple repeated filler is an easy haystack. This is not a RULER score.

The 4B at 1M still needs a new KV compression method, which is the actual research goal.

What happened

The 4B model kept a 715K-token prompt on the card, with only 170 MiB in shared memory, and still found the needle, using 9.8 of 10 GB. One step further, 768K, spilled into system memory on load, so the stock q4_0 cache tops out around 704K for this model. The 9B found the needle at 50% and 90% depth at 262K, but at 10% it started reasoning out loud and ran out of answer tokens, so that one counts as not passed until the rerun. The memory saving that made these fit is upstream PR 29827, so credit goes to its authors.

How to rerun

powershell -ExecutionPolicy Bypass -File e21f_chain.ps1

Code

All code for E21F: README.md, e21f_chain.ps1, results.jsonl.

Prior work

No novelty claim. This measures an existing upstream llama.cpp pull request with a standard needle test.

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

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