The 8 GB Vanguard: people's experiments and the architectures that worked on low-resource silicon
A curated archive of real measurements β from peer-reviewed systems papers to Reddit threads β showing exactly how AI agents were run on an 8 GB GPU with 32 GB of system RAM, and how to reproduce them on your own machine.
Abstract
Running a competent AI agent β a model that plans, calls tools, reads documents, and writes code in a loop β is conventionally treated as a datacenter workload. This paper assembles the evidence that it is not: between 2023 and 2025, a body of systems research and a much larger body of informal community experimentation demonstrated that consumer machines built around an 8 GB GPU and 32 GB of RAM can host genuinely useful agents, if and only if five architectural constraints are respected.
We organize that evidence into three layers. Layer one is physics: token generation is memory-bandwidth-bound, so the β448 GB/s of an 8 GB GPU and the β50β90 GB/s of dual-channel DDR decide everything, and every working architecture is a scheme for keeping the weights a token touches inside the fast pool. Layer two is the experiment archive itself: we reproduce the headline results of the systems papers β PowerInfer's 11.69Γ speedup from hot-neuron preloading, FlexGen running OPT-175B on a single 16 GB GPU, KIVI's 2.35β3.47Γ throughput gain from 2-bit KV caches, PagedAttention's reduction of KV waste from 60β80% to under 4% β alongside the community numbers that rarely leave forum threads: a 30.5B-parameter Mixture-of-Experts model at 34 tokens/sec on an 8 GB RTX 3070, a 120B model at 16 tokens/sec with 64 GB of RAM, and a 13 tokens/sec Qwen3-30B cluster built from four Raspberry Pi 5 boards. Layer three is synthesis: the model-fit ladder for an 8 GB + 32 GB machine, the context-management discipline that keeps agent loops from drowning in their own KV cache, and a measurement-driven playbook that takes a reader from zero to a verified, tool-calling agent.
The paper's thesis is empirical rather than theoretical: every optimization worth doing has already been tried by someone on hardware no better than yours. The contribution is collecting those trials, checking their arithmetic against first principles, and reducing them to recipes that a reader can run the same afternoon.
How to read this paper
Numbers are reproduced exactly as their sources reported them; where sources disagree (they sometimes do), we show both and say why. Illustrative figures are labeled as such. Command examples target llama.cpp because it is the engine those experiments overwhelmingly used; Β§6.3 explains when to prefer something else.
01The problem: two pools of memory, one narrow wire between them
A gaming PC is not a small datacenter. It is a different machine with different physics, and treating it like a scaled-down A100 node produces exactly the failures people complain about: out-of-memory crashes, 2 tokens/sec generation, and agents that fall over mid-task. This chapter establishes the physical regime every successful experiment in this archive was working around.
1.1 The 8 + 32 regime
An 8 GB GPU paired with 32 GB of system RAM is the most common "serious but affordable" configuration of the last several hardware generations β RTX 3050/3060/3070-class and their 40-series successors on the NVIDIA side, and functionally similar tiers from AMD. It sits at an awkward midpoint. It is far too small to hold a modern frontier model's weights, even aggressively quantized β a 70B model at 4-bit is ~40 GB, five times the VRAM. But it is also far too capable to write off: 8 GB comfortably holds any 7β8B model at 4-bit with room for a real context window, and β the finding this archive returns to repeatedly β it can orchestrate models far larger than itself when those models are Mixture-of-Experts (MoE) architectures whose active parameters are small.
The defining feature of the regime is asymmetry, and it shows up in three dimensions at once:
- Capacity asymmetry. The fast pool (VRAM, 8 GB) is a quarter the size of the slow pool (RAM, 32 GB). Whatever you run must be split across the two, or must fit in the small one with room to breathe.
- Bandwidth asymmetry. The fast pool is 5β9Γ faster to read than the slow pool. An RTX 3070 moves ~448 GB/s; dual-channel DDR4-3200 moves ~51 GB/s. Generating one token requires reading essentially all active weights, so a model resident in the wrong pool becomes 5β9Γ slower, not slightly slower.
- Control asymmetry. The GPU cannot address system RAM directly (on discrete cards). Anything that lives in the slow pool must cross the PCIe bus β 16β32 GB/s in practice β which is another 15β30Γ slower than VRAM. This is the wire. Every offloading architecture in Chapter 4 is a strategy for sending the fewest possible bytes across it.
1.2 The bandwidth law
Autoregressive generation has a brutal, simple cost model: to emit one token, the hardware must read every weight that participates in that token's computation. Compute is nearly free by comparison β GPUs spend most of the decode phase idle, waiting on memory. This is why the single best predictor of tokens/sec on low-end hardware is bytes-per-token divided by memory bandwidth, and why the bandwidth ladder below is effectively a preview of every speed result in this paper.
The ladder explains several results that otherwise look surprising. Apple's M-series chips, often dismissed as "integrated graphics," sit high on it β an M4 Max offers ~546 GB/s of unified memory that the GPU can address directly, no PCIe wire involved. That is why official llama.cpp benchmarks (Β§5) show a MacBook generating gpt-oss-20b tokens at speeds an 8 GB discrete card cannot match unless the card keeps the whole model in VRAM. Conversely, the Raspberry Pi 5's 17 GB/s explains why its community results cluster around 2β8 tokens/sec for small models β and why a 4-board cluster running a 30B MoE model at 13 tokens/sec (Β§10.3) was received as an achievement rather than a curiosity.
t/s β effective bandwidth Γ· active bytes per token. Effective bandwidth is the harmonic mix of the pools your model actually occupies. You cannot argue with this formula; you can only change its inputs β shrink the bytes (quantization, Β§3), shrink the active bytes (MoE, Β§4.2), or move the bytes into the fast pool (offloading architecture, Β§4).
1.3 Five laws that follow
Everything in the archive β every paper, every community tweak β is an application of one of five rules. We state them here because they recur, and because readers who internalize them can predict experimental results before looking them up.
A model that fits in VRAM runs at full speed; a model that doesn't runs at the speed of wherever its weights live. There is no gentle middle: llama.cpp's --n-gpu-layers sweep (Β§4.1) shows speed collapsing toward CPU-only rates as the fraction of layers on CPU grows. First question for any rig: does the active working set fit in 8 GB with 1β1.5 GB reserved for overhead? If yes, everything is easy. If no, you need one of the architectures in Chapter 4.
4-bit quantization shrinks weights ~4Γ against fp16 for a perplexity penalty that community measurement puts at ~0.15 on Llama-3-8B (Β§3.1). It is the one optimization that is simultaneously free (no hardware), universal (every engine supports it), and quality-preserving at the K-quant tier. Everything else in this paper fights for the remaining margin.
Weights are a fixed cost you pay once at load; the KV cache grows with every token of context, and it grows fast β for Llama-3.1-8B, ~1 GB per 8K tokens at fp16 (Β§2.3). Agents are the worst case: their loops append tool output every turn. An 8 GB rig that fits the weights can still OOM three turns into an agent task. Context engineering (Β§7, Β§9) is not optional.
A Mixture-of-Experts model activates only a fraction of its weights per token. gpt-oss-20b activates 3.6B of 21B parameters; Qwen3-30B-A3B activates 3.3B of 30.5B. The community's decisive discovery (Β§4.2) was that you can park the rarely-used expert tensors in system RAM and keep attention + shared layers + the hottest experts in VRAM β paying the bandwidth law only on the expert fraction. This is how an 8 GB card "runs" a 30B model at 30+ tokens/sec.
Windows will happily let you allocate more VRAM than you have and silently swap to RAM; a model that "loads" can be 10Γ slower than one configured to fit. Every serious community experimenter converges on the same discipline: watch nvidia-smi / Task Manager during the run, and benchmark with llama-bench before and after each change (Β§8.2). The experiments in this archive are trustworthy precisely because their authors did that.
One more framing device that the rest of the paper relies on: think of the 8 GB rig as a cache hierarchy with a GPU at the top, not as a small server. VRAM is L1 (8 GB, ~450 GB/s), system RAM is L2 (32 GB, ~50β90 GB/s), NVMe is L3 (terabytes, ~7 GB/s). Datacenter inference engines solve this same hierarchy with tensor parallelism across many GPUs; the low-resource community solved it with smarter placement of a single model across one GPU's two pools. That is the engineering problem, and Chapters 3β5 are its solution space.
02Where the memory actually goes
Before reading anyone's experiment, you need to be able to audit one yourself. This chapter builds the memory model that every later claim is checked against: what a forward pass touches, what an 8 GB budget really contains, and how to compute KV-cache costs on the back of an envelope.
2.1 Anatomy of a forward pass
A transformer layer, at generation time, performs four kinds of work, each with its own memory footprint. Understanding the split is what lets you read other people's experiments critically, because the four costs respond to different optimizations.
| Cost | What it is | Scales with | Responds to |
|---|---|---|---|
| Weights | The model's learned parameters β the bulk of the file on disk | Parameter count Γ precision | Quantization (Β§3); offloading (Β§4) |
| KV cache | Per-token key/value tensors saved by every attention head so past tokens aren't recomputed | Context length Γ layers Γ KV heads (not total heads) | GQA design; KV quantization; eviction (Β§7) |
| Activations | Intermediate tensors produced while computing the current token | Batch size Γ hidden width | FlashAttention (fuses + shrinks); batch sizing |
| Engine overhead | CUDA context, compute buffers, memory-mapped files, the UI you forgot was open | Engine build flags; OS | Nothing glamorous β reserve 1β1.5 GB and move on |
The practical consequence: when a community post says "I fit 30B on 8 GB," the claim decomposes into a claim about each row. Which quant (row 1)? What context (row 2)? FlashAttention on (row 3)? How much left over (row 4)? The next two sections do that decomposition explicitly.
2.2 Auditing an 8 GB budget
Here is the canonical "comfortable" configuration for this paper's reference rig, audited line by line. It is the configuration that the model-fit ladder in Β§6 calls Tier 1, and every number in it can be checked against a real llama.cpp load.
Three budgeting rules fall out of this audit, and they are worth internalizing before the archive chapters because every experiment in them assumed at least one:
- Reserve overhead before anything else. Plan against ~6.5 GB of usable VRAM, not 8. Headroom is not waste; it is what stops the Windows VRAM-overcommit trap (Law 5) from silently destroying performance.
- Size the context deliberately. The same model at 32K context needs 4.3 GB of KV cache at fp16 β instant OOM. At q8_0 KV it needs 2.1 GB β tight but survivable. The context window is a budget decision, not a default.
- One model at a time. The 8 GB rig is a single-tenant machine. Agent stacks that want a "big reasoner + small utility" pair must swap models in and out (Β§9.3) or move the small one to CPU entirely.
2.3 The KV cache, precisely
Because agent workloads live and die by context, the KV formula deserves to be stated exactly rather than gestured at. For each token, every layer stores a key vector and a value vector for each KV head (with Grouped-Query Attention, far fewer than the total attention heads). In bytes:
Llama-3.1-8B: 2 Γ 32 Γ 8 Γ 128 Γ 2 = 131,072 bytes β 128 KB/token at fp16. Multiply by context: 8K tokens β 1.0 GB; 32K β 4.3 GB; 128K β 17.2 GB β more than twice the entire GPU.
The figure below plots the growth curve for three KV precisions, and it is the single most important chart for agent builders: it is the reason a rig that comfortably runs 8K-context chat falls over on an agent task that simply has more turns, and it is the reason KV quantization (Β§7.1) and eviction (Β§7.2) exist as research fields.
Two architectures change the constants, not the law. GQA (used by Llama 3, Qwen3, Gemma 3) is why modern 8B models cost 128 KB/token where a pre-GQA model like Mistral-7B's multi-head attention would cost ~512 KB/token at the same width β a 4Γ context discount already built into the models you would choose anyway. Sliding-window attention (Gemma 3, gpt-oss) caps the cache at the window size, trading exact long-range recall for a bounded footprint β a trade that matters for document QA and is catastrophic for "find the instruction from turn 3" agent behavior unless you keep a summary outside the window (Β§9.2).
03The quantization lab
Quantization is the first lever everyone pulls, and the one with the richest experimental record. This chapter reproduces the actual measured datasets β the GGUF perplexity ladder, the cross-method GPTQ/AWQ/HQQ/BitsAndBytes comparisons, and the quantization-aware-training results that made 12B-class models viable on 8 GB cards β and states plainly which quants the evidence supports.
The mechanics in one paragraph: a quantization format stores each weight in fewer bits than the 16 it was trained in β 8, 5, 4, 3, or fewer β using a scheme to choose the representable values (a plain grid, or one scaled per block of 32β128 weights) and optionally a small calibration step to place the grid where the model's weights actually cluster. The design space is real: which 4-bit you pick changes perplexity more than the jump from 8-bit to 4-bit does. That is what the datasets below are for.
3.1 The GGUF ladder: a community dataset
llama.cpp's GGUF format and its K-quant / I-quant families are the de facto standard on low-resource machines, and the community has measured their quality cost obsessively. The most-cited dataset is the perplexity ladder for Llama-3-8B on WikiText-2, reproduced here in full β it is the table people mean when they say "Q4_K_M is the sweet spot."
| Quant | Size (GB) | Perplexity | Ξ vs f16 | Fits 8 GB w/ 8K ctx? | Verdict from the data |
|---|---|---|---|---|---|
f16 | 14.97 | 6.2331 | β | no (weights alone) | Reference point; irrelevant on this rig |
q8_0 | 7.96 | 6.2342 | +0.001 | no β barely | Mathematically lossless in practice, but 8 GB can't afford it |
q6_K | 6.14 | 6.2533 | +0.020 | no (marginal) | The "if only I had 10 GB" tier |
q5_K_M | 5.33 | 6.2886 | +0.056 | yes, tight | Quality conservative's choice; context budget shrinks |
q5_0 | 5.21 | 6.3632 | +0.130 | yes, tight | Dominated by q5_K_M (older scheme, worse, barely smaller) |
q4_K_M | 4.58 | 6.3830 | +0.150 | yes, comfortable | The 8 GB default. Best size/quality point on the curve |
q4_0 | 4.34 | 6.7001 | +0.467 | yes | Legacy grid; the 0.24 GB saved is not worth it |
The shape of this curve β flat, flat, flat, cliff β generalizes across model families well enough that the community treats it as settled law: everything from q8_0 down to Q4_K_M is safe; Q4_0-style legacy quants are not; below 4-bit you are in I-quant territory where results are model-dependent. Below the cliff, "IQ" quants (IQ4_XS, IQ3_XXSβ¦) use importance-matrix calibration to claw back quality at 3β4 bits, and the trade becomes genuinely experimental: the Poor GPU Club results (Β§10.1) show IQ4_XS beating Q4_K_XL by 3β5 tokens/sec at equal quality on Qwen3-30B-A3B β a case where the smaller quant's speed gain came from fitting more experts in VRAM.
3.2 Across methods: GPTQ vs AWQ vs HQQ vs BitsAndBytes
GGUF dominates local inference, but the PyTorch-serving world (vLLM, TGI, transformers) quantizes with GPTQ, AWQ, HQQ, and BitsAndBytes β and the research community has run careful head-to-heads. Two datasets matter: the multi-model perplexity comparison from the HQQ paper, and an independent Llama-3-8B-Instruct benchmark spanning quality (MMLU), safety (WMDP), and speed.
| Method | Bits | Llama-2-7B PPL | Llama-2-13B PPL | Llama-2-70B PPL | 70B memory (GB) |
|---|---|---|---|---|---|
| FP16 | 16 | 5.18 | 4.63 | OOM on 80 GB | β |
| BNB | 8 | 5.22 | 4.67 | 3.17 | 68.2 |
| GPTQ g128 | 8 | 5.19 | 4.63 | 3.12 | 74.9 |
| HQQ g128 | 8 | 5.19 | 4.63 | 3.12 | 69.3 |
| BNB g64 | 4 | 5.43 | 4.79 | 3.29 | 39.1 |
| GPTQ g64 | 4 | 5.38 | 4.73 | 3.23 | 41.1 |
| AWQ g64 | 4 | 5.28 | 4.70 | 3.20 | 37.1 |
| HQQ g64 | 4 | 5.30 | 4.70 | 3.19 | 37.5 |
Read the 70B column against this paper's target rig and the scale problem becomes concrete: even the best 4-bit method needs ~37 GB just for weights β nearly 5Γ an 8 GB card. Dense 70B is a datacenter-or-nothing proposition; it is the reason Chapter 4's offloading architectures and Chapter 6's MoE-heavy ladder exist. But the table also carries the encouraging half of the message: at 4-bit, every method preserves quality within ~0.1β0.25 perplexity of the fp16 baseline on 7B and 13B models. The loss from quantization is small and method-independent-ish; the loss from choosing a smaller model because of memory is what actually hurts.
An independent Llama-3-8B-Instruct run extends the table from perplexity to behavior β worth including because agent workloads care about instruction-following more than next-token loss:
| Method | Bits | MMLU β | WMDP β (safety retained) | PPL (Pile) β |
|---|---|---|---|---|
| BFloat16 | 16 | 63.87% | 54.99% | 8.283 |
| HQQ Int8 | 8 | 63.87% | 54.66% | 8.298 |
| BNB Int8 | 8 | 63.05% | 54.96% | 8.305 |
| HQQ Int4 | 4 | 62.29% | 54.23% | 8.482 |
| BNB NF4 | 4 | 61.44% | 54.42% | 8.499 |
| BNB Int4 | 4 | 60.80% | 52.73% | 8.633 |
| GPTQ / AWQ 4-bit | 4 | 55.2 / 55.6% | β | 8.58 / 8.48 |
Two independent measurement sets, both community-run (the GPTQ/AWQ rows from a widely reproduced benchmark notebook, the rest from the same independent evaluation); absolute MMLU numbers differ between harnesses, but the internal ordering is consistent. The signal: calibrated 4-bit (HQQ, AWQ, NF4) keeps ~1.5β2.5 points of MMLU; naive Int4 grids lose more, and even show up in the safety probe.
3.3 QAT: quality at 4-bit by design
Post-training quantization (everything above) asks "how little does 4-bit hurt a model trained at 16-bit?" Quantization-aware training asks the opposite: "how good can a model be if it trains with 4-bit weights in the loop?" Google's Gemma 3 QAT releases in 2025 made this the standard answer for 8 GB machines, because they delivered 12B-class quality at a file size that fits β with the vendor explicitly blessing the 8 GB tier.
The launch material was unusually specific for a vendor: 12B QAT runs on laptop-class 8 GB GPUs, 27B QAT on 16 GB, with quality tracking the bf16 originals far more closely than post-training quantization achieves. Community verification followed the pattern this archive expects: users confirmed the 12B QAT file running on 8 GB cards with ~2.5K context and no other optimization β and more, once FlashAttention and q8 KV caches were enabled (both techniques this paper covers in Β§7 and Β§8, compounding exactly as the laws predict).
The strategic point for the 8 GB reader: QAT shifts the model-fit ladder up one rung. Before QAT, 8 GB meant "8B models at Q4, comfortably." After QAT, it means "12B models at Q4 with training-time error compensation" β a real quality tier-jump for zero hardware change, which is the cheapest capability upgrade in this entire paper.
Every dataset above measures degradation relative to the same model. A Q4_K_M 8B model is a slightly-worse 8B model; it is not a 12B model. When your agent fails because it reasons poorly, no quant choice will save you β you need the ladder in Β§6, which is about picking the right size and architecture for the memory you have. Quantization buys memory; it does not buy intelligence.
04The offloading architecture archive
This is the chapter the whole archive points at: the placement strategies that let machines with 8 GB of fast memory run models 2β15Γ larger than that pool. Four architectures survived contact with reality β naive layer-offload, MoE expert-offload, hot-parameter preloading, and compute-overlapping pipelines β and each has both a peer-reviewed result and a community reproduction. We give both, with numbers.
A definition before the experiments, because "offloading" hides the key design question. In every scheme, some tensors live in VRAM and some in system RAM, and the token's computation hops between pools. The four architectures differ in what they choose to demote and when they move it:
| Architecture | What lives in RAM | Moved when | Who proved it |
|---|---|---|---|
| A Β· Layer offload | Whole transformer layers (top of the stack) | Per token, sequentially | llama.cpp --n-gpu-layers; every community benchmark (Β§4.1) |
| B Β· Expert offload | MoE expert tensors (routed, rarely all needed) | Per token, only the routed experts | Eliseev & Mazur; llama.cpp --n-cpu-moe; Fiddler (Β§4.2β4.3) |
| C Β· Hot-parameter preloading | "Cold" neurons that fire rarely | Preloaded if predicted hot; sparse compute on CPU | PowerInfer (Β§4.3) |
| D Β· Batched pipeline overlap | Everything that doesn't fit, in big blocks | Asynchronously, overlapped with compute | FlexGen; DeepSpeed-ZeRO-Inference (Β§4.3) |
4.1 Architecture A β layer offload: the experiment everyone runs first
llama.cpp's --n-gpu-layers N puts the first N transformer layers on the GPU and runs the rest on CPU. It is the oldest trick in local inference and the one whose failure mode is most instructive: because every token must traverse every layer, offloaded layers put the token on the slow pool once per offloaded layer. Speed degrades smoothly toward CPU-only speed β a cliff in the making, exactly as Law 1 says.
The measured shape of the curve β and the much larger r/LocalLLaMA performance thread it sits inside β is the definitive community dataset on partial offload for dense models: a 30B-class model fully on CPU runs ~4β5 t/s; offloading roughly 60% of layers to an 8 GB card roughly doubles that to ~9β16 t/s depending on quant; and no amount of partial offload approaches the 60β100+ t/s the same card achieves with a model that fully fits. The thread's summary line became folk wisdom: "about 10Γ faster if you can fit half the layers in VRAM" is wrong β the real message is that half-fitting recovers only ~2β3Γ over CPU, which is why everyone who could moved to smaller-or-MoE instead.
Why is it so punishing? Each offloaded layer's weights sit behind ~50β90 GB/s of DDR instead of ~450 GB/s of VRAM, and PCIe adds latency on top. With 17 of 43 layers on CPU, ~40% of every token's weight-reads are slow β the harmonic mean drags the whole pipeline down to the slow pool's speed. Layer offload's honest use case is narrow: giving a barely-too-big dense model just enough VRAM help to be usable, typically 13Bβ14B at Q4 on an 8 GB card with a small context.
4.2 Architecture B β expert offload: the discovery that changed the game
Mixture-of-Experts models changed the arithmetic of offloading in one specific way: only a small fraction of each MoE layer's weights β the experts the router selects β participate in any given token. If the rarely-routed experts live in system RAM, a token only pays the PCIe trip when the router actually calls for them. Offloading went from "pay on every layer" to "pay probabilistically on a fraction of layers," and the results were dramatic enough to reorganize the entire low-VRAM community around MoE models in 2024β25.
The experimenter (a llama.cpp collaborator) swept expert-layer offload on a 24 GB card, watching both speed and VRAM. The measurements, exactly as posted:
| MoE layers on CPU | VRAM used | Generation speed | Experimenter's note |
|---|---|---|---|
| 4 of 24 | 18 GB | ~60 t/s | "Neat, this is usable" |
| 8 of 24 | 16 GB | ~38 t/s | "Still pretty usable" |
| 16 of 24 | 13 GB | ~26 t/s | "this is getting pretty bad" |
Read the curve's slope and you have the design rule the community derived from it: speed scales with the fraction of expert tensors still in VRAM. Moving 4 of 24 expert layers costs ~40% of speed while freeing ~1 GB; that is the trade you make gladly on an 8 GB card where the alternative is not running the model at all. The official llama.cpp gpt-oss guide distilled the sweep into shipped guidance β including for this paper's exact rig, in a section titled "Devices with less than 16 GB VRAM":
# gpt-oss-20b, full context, 22 MoE layers whose experts live in CPU RAM llama-server -hf ggml-org/gpt-oss-20b-GGUF --ctx-size 0 --jinja -ub 2048 -b 2048 --n-cpu-moe 22 # ...and the same card can even orchestrate the 120B model: # gpt-oss-120b, 32k context, 35 expert layers on CPU llama-server -hf ggml-org/gpt-oss-120b-GGUF --ctx-size 32768 --jinja -ub 2048 -b 2048 --n-cpu-moe 35
The guide's stated minimum for orchestrating gpt-oss-120b this way is about 8 GB of VRAM β this paper's target rig, by name β with a 5090-class card reaching ~30 t/s at zero context with 21 expert layers on CPU. One Poor GPU Club reader with 64 GB of RAM reproduced the 120B result at 16 t/s generation on consumer hardware (Β§10.1). For scale: that is a model with frontier-lab lineage, running agent workloads, on the machine this paper is about.
--n-cpu-moe, you tune it before you tune anything else.This is the paper that made expert offload rigorous. The authors observed that expert activation is strongly temporally correlated β the router tends to pick the same experts across adjacent tokens β so an LRU cache in VRAM of recently-used experts plus a speculative prefetcher for the experts the router logits already hint at hides most of the PCIe latency. On an 8 GB laptop GPU running Mixtral-8x7B in 4-bit, their system roughly doubled-to-tripled throughput versus running the same model with plain layer offload, at unchanged output quality.
The idea was adopted (in simplified form) across the ecosystem: llama.cpp's expert offload does not implement the full learned predictor, but the "keep attention + shared experts in VRAM, stream experts from RAM" placement and the community habit of benchmarking exactly which -ncmoe value maximizes t/s are direct descendants. The paper's lasting lesson for the 8 GB operator: on MoE models, the working set is what the router touches, not what the file contains β and working sets can be predicted.
4.3 Architectures C and D β the research systems
PowerInfer is the most conceptually important result in this archive for readers thinking beyond today's tools. Its authors profiled LLM inference and found that activation is extremely skewed β a small subset of neurons ("hot neurons") participates in almost every forward pass, while the long tail is input-specific. So they split the model accordingly: hot neurons preloaded on the GPU, cold neurons computed on the CPU with sparse operators, and a small learned predictor deciding per-layer which neurons each token will need so the GPU rarely waits. On a single consumer GPU, this beat llama.cpp by up to 11.69Γ while preserving accuracy, including for OPT-175B-class models β with average generation speeds around 8.32β11.69 tokens/sec for 40Bβ175B models that llama.cpp ran at a crawl (the paper's average across models was 8.32 t/s vs llama.cpp's much lower baseline on the same PC).
Why it matters here: PowerInfer proved that the placement question is a learning problem, not a layout problem. The hot/cold split is data-dependent, and a predictor can exploit it. Its successor work (and the industry's move to MoE, which is essentially hot/cold structure baked into the architecture) bore this out. For the 8 GB reader, the practical echo is in llama.cpp's activation-aware builds and in the general principle: if you profile your agent's actual token distribution, you can cache what it actually uses.
FlexGen asked the question the other papers skipped: if the model cannot fit, what is the maximum-throughput schedule for streaming it through a small GPU? Its answer β store weights compressed on CPU/disk, move them in large blocks, overlap PCIe transfers with matrix multiplies, and batch aggressively so each fetched block amortizes over many sequences β produced the striking demo of OPT-175B generating on a single 16 GB GPU. The single-stream experience was ~1 token/s (unusable interactively), but batched throughput reached levels competitive with far larger setups, and its offloading beat the then-state-of-the-art by an order of magnitude.
For agent builders, FlexGen's contribution is a mental model correction: latency and throughput are different axes, and offloading hurts them differently. Your interactive agent loop needs latency (every token fast); a document-processing batch job needs throughput (many tokens, total time). The same 8 GB rig should be configured differently for each β and Β§11's recipes do exactly that (small ubatch for chat, large -b/-ub with CPU prompt-processing for long-document ingestion).
Fiddler's inversion is the sharpest idea in the genre: since expert weights are huge and activations are tiny, moving the computation of an expert to where its weights live (CPU) is cheaper than moving the weights to the compute (GPU). The CPU computes the routed experts' outputs (its many cores and AVX units are adequate for the sparse, small matmuls), the GPU handles attention and everything dense, and the token shuttles kilobytes instead of gigabytes. The paper reports running Mixtral-8x22B on a single 8 GB GPU β a 141B-parameter model β at usable interactive speeds, ~3.5Γ faster than the Eliseev & Mazur scheme at equal memory budgets.
The result brackets the ceiling of architecture B: llama.cpp's --n-cpu-moe still executes CPU-resident experts on the CPU (it moves activations, not weights), which is precisely Fiddler's placement β so if you have wondered why expert offload is so much better than layer offload despite both "keeping stuff in RAM," Β§4.1-vs-Β§4.3 is the answer: layer offload moves weights per layer; expert offload moves activations per expert; Fiddler formalized why the latter wins.
Layer offload: pay every token, every offloaded layer β the floor. Expert offload: pay only routed experts, predictable via caching β the present. Hot-parameter placement: pay only what's statistically active β the idea that keeps resurfacing. Pipeline overlap: pay, but hide it under batch compute β the throughput escape hatch. Every "new" trick in local inference is one of these four with different units.
05The low-resource silicon zoo
The experiments in this archive were run on wildly different machines β gaming PCs, unified-memory laptops, and $80 single-board computers. Putting their results side by side on one model does something rare: it isolates the effect of hardware architecture from every other variable, because the software was identical.
5.1 Same model, nine machines
When OpenAI released gpt-oss-20b in August 2025, the llama.cpp team published a guide with llama-bench numbers across their whole device lab β and, accidentally, produced the best controlled comparison of low-resource silicon in existence. Every run below is the same GGUF file, same build, same flags (-fa 1 -b 2048 -ub 2048), measuring generation (tg128):
| Device | Memory pool | tg128 (t/s) | pp2048 (t/s) | Placement |
|---|---|---|---|---|
| Raspberry Pi 5 | 8 GB LPDDR4X Β· 17 GB/s | ~2 | ~70 (est.) | All CPU (community runs, Β§5.3) |
| M1 Pro | 32 GB unified Β· 200 GB/s | 45.7 | 516 | Full GPU |
| M1 Max | 64 GB unified Β· 400 GB/s | 75.2 | 995 | Full GPU |
| M4 Max | 36 GB unified Β· 546 GB/s | 92.4 | 1,277 | Full GPU |
| M2 Ultra | 192 GB unified Β· 800 GB/s | 116.1 | 2,191 | Full GPU |
| M3 Ultra | 512 GB unified | 115.5 | 2,816 | Full GPU |
| 8 GB RTX 3070 | 8 GB VRAM + 32 GB DDR | 38 | 150β200 | Experts offloaded (Β§10.1) |
| RTX 3090 | 24 GB VRAM | 161.8 | 5,171 | Full GPU |
| RTX Pro 6000 | 96 GB VRAM | 286.9 | 11,522 | Full GPU |
Three lessons fall straight out of the table, and they are the hardware-buying guidance of this paper:
- Bandwidth is destiny, and unified memory is a superpower. The Mac line tracks its bandwidth ladder almost perfectly (200 β 400 β 546 GB/s giving 45 β 75 β 92 t/s). An 8 GB discrete card has more raw bandwidth than any M1/M2 non-Ultra chip, but only if the model fits; otherwise it inherits DDR speed anyway, plus the PCIe tax.
- Generation and prompt-processing are different sports. The RTX Pro 6000 is ~2.5Γ the Pi at nothing... and ~165Γ at prompt processing. Batch-heavy prefill loves compute and VRAM width; decode loves bandwidth. Agent workloads do both (Β§8) β an agent that reads a 20-page document then chats is bottlenecked twice, differently.
- The 8 GB + expert-offload point is genuinely competitive. 38 t/s with 3.6B-active parameters lands in M1-Pro territory for a card that costs a fraction of a Mac β and, as Β§10.1 shows, 30B-class MoE models on the same card do even better relative to their class.
5.2 Apple's unified memory: the accidental LLM machine
Apple never marketed the M-series as inference hardware, but unified memory β one pool, GPU-addressable, high bandwidth β happens to be exactly what the bandwidth law wants, and the gpt-oss guide's Mac results made that unmistakable. The guide's capacity notes are as instructive as its benchmarks: 16 GB Macs must offload some experts (--n-cpu-moe 12 -c 32768), while 8 GB Macs cannot run gpt-oss at all β the OS reserves too much of the pool. That last fact is worth a moment of attention from readers with 8 GB discrete cards: unified memory means GPU and OS compete for the same 8 GB, whereas your rig's 8 GB VRAM is dedicated and your 32 GB RAM absorbs the OS for free. On paper the same number, in practice different machines.
The Apple story also carries a software-lesson the community keeps relearning: the MLX framework (Apple's array library, with its own LLM stack) is typically 10β30% faster than llama.cpp's Metal backend on M-series for decode β with some independent measurements showing up to ~3Γ on specific models where llama.cpp's Metal kernels were immature β while llama.cpp sometimes wins at long context where its attention kernels are better tuned. Engine choice is a per-model, per-workload empirical question (Law 5 again), never a religious one.
5.3 The absolute edge: Raspberry Pi and friends
The Pi 5's 17 GB/s of LPDDR4X puts a hard ceiling on decode β the rule of thumb from the Pi benchmarking community is divide 17 GB/s by the model file size to get t/s, which the measurements confirm almost exactly (a 4 GB Q4 7B model: ~4β5 t/s; a 1 GB 1B model: ~17β25 t/s). Two findings from this corpus carry beyond the Pi itself:
The engine gap matters more at the bottom. llama.cpp runs 10β20% faster than Ollama on identical Pi hardware (wrapper overhead and default flags), and the BLIS BLAS backend beat OpenBLAS 2.73 vs 2.34 t/s in a controlled comparison β at this tier, software choices are worth more than any hardware tweak. MoE changes even the Pi's story. A single Pi-class board runs Qwen3-30B-A3B (a 12.9 GB file) at ~8 t/s despite being far "too big" for it β because only 3.3B parameters are active β and a four-Pi cluster with tensor-parallel llama.cpp reached 13 t/s on the same model. The Pi cluster can't run an agent loop you'd enjoy, but as a proof that the MoE placement laws in Β§4 scale all the way down to 17 GB/s of bandwidth, it is the cleanest demonstration in the archive.
The archive keeps dissolving the category. An 8 GB gaming card, an M2 MacBook, and a Pi cluster share almost nothing β different bandwidths, different memory architectures, different OS overheads β yet all three run the same models with the same placement principles, just at different speeds. The unifying variable is the one from Β§1: how many active bytes per token can you keep in your fastest pool. Everything else is detail.
06The model-fit ladder for 8 GB + 32 GB
This chapter converts the archive into a decision. For an 8 GB VRAM + 32 GB RAM machine running agents, what model do you actually load? The answer is a five-rung ladder, each rung an experimentally verified configuration with its measured speed and its real cost.
6.1 The ladder, rung by rung
| Rung | Class | Example (2024β25) | Weights | Placement | Speed (community) | Agent-capable? |
|---|---|---|---|---|---|---|
| 0 | 1β4B dense, Q4 | Llama-3.2-3B, Phi-3.5-mini, Qwen2.5-3B | ~2 GB | All VRAM, huge context headroom | 80β120 t/s | Simple tools only; weak planning |
| 1 | 7β9B dense, Q4_K_M | Llama-3.1-8B, Qwen3-8B, Ministral-8B | ~4.6 GB | All VRAM + 8β16K ctx | 60β100 t/s | Yes β the workhorse tier |
| 2 | 12β14B dense, Q4 (QAT preferred) | Gemma-3-12B QAT, Qwen3-14B, Phi-4-14B | 6.5β8.5 GB | All VRAM, small ctx; or 1β2 layers CPU | 35β60 t/s | Yes β noticeably stronger reasoning |
| 3 | Small-total MoE, native 4-bit | gpt-oss-20b (MXFP4) | 11.3 GB | Attention in VRAM, experts split (Β§4.2) | 26β38 t/s | Yes β strong, with native tool-calling format |
| 4 | 30B-A3B MoE, Q4 | Qwen3-30B-A3B / -Coder, Qwen3-VL-30B-A3B | ~17β19 GB | Most experts in RAM (ncmoe ~28β34) | 20β34 t/s | Yes β the archive's favorite agent model |
| 5 | Giant MoE (needs 64 GB RAM) | gpt-oss-120b (MXFP4, 59 GB) | 59 GB | Attention in VRAM, most experts in RAM | ~16 t/s | Yes, if you upgraded RAM β the ceiling |
Speeds are community-measured on 8 GB cards (RTX 3070/3070 Ti-class) from the experiments in Β§Β§4, 10; they will drift Β±30% with CPU and RAM generation β a DDR5 machine offloads experts meaningfully faster than DDR4, exactly as the bandwidth law predicts. Note what the ladder is not: there is no rung for dense 30B+ or 70B models. At Q4 those need 17β40 GB of weights; architecture A (Β§4.1) makes them technically loadable but the 4β9 t/s price was judged unacceptable by nearly every experimenter who tried, and MoE rungs dominate them at equal or better speed and quality.
6.2 How the community actually chose
Reading the experiment threads end-to-end, a clear decision pattern emerges β the same one the model recommendations settled on across 2024β25:
- Start at rung 3β4, not rung 1. The MoE rungs are why an 8 GB card is a viable agent machine in 2025. gpt-oss-20b and Qwen3-30B-A3B both activate ~3.3β3.6B parameters, so their decode cost resembles a 3B model's while their knowledge resembles a 30B model's. Every Poor GPU Club result (Β§10.1) is a rung 3β4 configuration.
- Keep a rung-1 dense model loaded for utility calls. Agent frameworks need cheap structured outputs β reranking, schema validation, summaries. A Q4 8B at 80+ t/s fills that role, or run it fully on CPU to keep VRAM for the orchestrator (32 GB RAM runs a Q4 8B at a usable ~8β12 t/s with plenty left for the OS).
- Match context to the rung. Rung 1β2 models leave 1.5β3 GB for KV β 8β32K tokens comfortably. Rung 3β4 models have almost nothing left after weights, which is why every real recipe in Β§10β11 pairs them with KV quantization (Β§7.1) and modest windows (16β32K).
- Tool-calling support is a rung requirement, not a nice-to-have. An agent model must emit structured tool calls reliably β historically the failure mode of small models. gpt-oss ships a native tool-calling format (harmony), Qwen3 models were post-trained for it, and llama.cpp's
--jinjaflag enables their chat templates. The archive's agent recipes all live on rungs with proven tool-calling; community lore treats everything below rung 2 as "chat only."
On 8 GB + 32 GB in 2025: orchestrate with a 30B-A3B MoE (rung 4), generate utilities with a dense 8B (rung 1), and consider gpt-oss-20b (rung 3) when you want one model to do both β and if you can add RAM, rung 5's gpt-oss-120b is the surprise ceiling of the machine.
6.3 A note on engines
Why this paper's recipes are llama.cpp-shaped: because the archive is. llama.cpp is where the 8 GB community measures things β its GGUF format, --n-cpu-moe, KV quantization flags, and llama-bench instrumentation are the substrate of nearly every result reproduced here. That said, the engine landscape has real alternatives, and the honest summary after reading the experiments is: Ollama is llama.cpp with a model manager (measurably 10β20% slower at the edge tiers per the Pi comparisons, but the easiest on-ramp); LM Studio is llama.cpp with a GUI and a good memory HUD for learning your own VRAM budget; MLX is the Mac-native pick (Β§5.2); vLLM/ExLlamaV2 serve AWQ/GPTQ at high throughput but generally want the model to fit in VRAM β which on 8 GB means small models, so they sit off this paper's critical path. For agents specifically, one further point decides it: your agent framework needs an OpenAI-compatible endpoint with tool-calling support, and llama.cpp's llama-server --jinja provides exactly that (Β§9.3).
07Context and KV-cache engineering
For agents, the KV cache is where the war is fought: tool loops grow context without limit, and Figure 3's curve crosses every budget the rig has. The research community attacked this from four directions β paged allocation, KV quantization, eviction, and 2-bit compression β and all four shipped in the tools you can run today. This chapter is their evidence.
7.1 KV quantization: the cheapest context doubler
llama.cpp exposes KV precision with --cache-type-k / --cache-type-v (q8_0 and q4_0 are the useful tiers). The community's hands-on finding is consistent across model families: q8_0 KV costs essentially nothing in quality while halving the cache, and q4_0 halves it again with visible-but-tolerable degradation on summarization-heavy tasks. The Poor GPU Club thread (Β§10.1) shows the flag in live use β one user's Qwen3-30B-A3B run carries type_k/type_v q8_0 in its benchmark table, exactly because rung-4 models have no KV budget to waste.
| KV precision | KB/token (Llama-3.1-8B) | 32K ctx costs | Community verdict |
|---|---|---|---|
f16 | 128 | 4.3 GB | Default; the thing you turn off |
q8_0 | 64 | 2.1 GB | The agent default. Near-free quality-wise, doubles context |
q4_0 | 32 | 1.1 GB | For rung-3/4 models needing 32K+; expect softer recall of details |
The one caveat the experiments surfaced: key and value tensors quantize differently well β the KIVI paper (below) traced this to per-channel vs per-token outlier structure β which is why llama.cpp lets you set K and V precision independently. Practical default: K at q8_0, V at q8_0 or q4_0.
7.2 The research record: StreamingLLM, H2O, KIVI, PagedAttention
The paper everyone cites when their agent "forgets the instructions": early tokens receive a disproportionate share of attention mass (they act as sinks), so a cache that evicts them mid-conversation degrades sharply, while a cache that keeps a handful of them and slides a window over recent tokens keeps quality flat indefinitely. For low-resource agents the implication is direct β never let a sliding-window engine evict your system prompt, and when you compact context (Β§9.2), treat "keep the first N tokens" as a rule, not a nicety. llama.cpp's KV cache is not windowed by default (quality over memory), but several serving stacks and the Gemma-3/gpt-oss architectures use windowed attention natively β with exactly this failure mode when misused.
H2O showed that a small set of tokens accumulates most of the attention score mass during generation, and that evicting everything else β keeping the "heavy hitters" plus recent tokens β preserves generation quality with a cache 5β10Γ smaller. It is the attention-score-based counterpart of PowerInfer's neuron-level finding, and the ancestor of the adaptive KV eviction now appearing in production engines. For the 8 GB reader it supplies the theoretical floor: most of your KV cache is dead weight, and tool-output tokens from three turns ago are the first weight to go.
KIVI nailed the asymmetry that Β§7.1's practical guidance hand-waves: key tensors have per-channel outliers (quantize along channels), value tensors have per-token outliers (quantize along tokens), and a short full-precision prefix absorbs the sinks StreamingLLM identified. Tuning-free, it compressed KV to 2-bit and measured 2.35β3.47Γ throughput improvements in real serving workloads with up to 4Γ more concurrent sequences. For this paper's rig, KIVI is the research-grade justification for what the community does pragmatically: aggressive KV quantization is sound at levels that would terrify a weight-quantization purist, because the cache stores history, not knowledge.
Before PagedAttention, serving engines reserved contiguous KV blocks per sequence, wasting 60β80% of KV memory to internal fragmentation and over-reservation. Treating KV like OS pages β fixed blocks, on-demand allocation, a block table β cut waste to under 4%, enabling 2β4Γ higher throughput and features (prefix sharing, copy-on-write) that matter enormously for agents: shared prefixes mean your agent's static system prompt + tool schemas are computed and stored once no matter how many turns reuse them. llama.cpp's unified KV management with -np parallel slots implements the same idea at smaller scale β and the official gpt-oss agent recipe (Β§10.2) uses it (-np 4) to serve parallel agent sessions.
08Speed levers that compound
Once placement (Β§4) and precision (Β§3, Β§7) are right, a second tier of optimizations buys the remaining 20β200%. The archive ranks them by measured payoff per unit of effort, and ends with the measurement discipline that keeps you honest while pulling them.
8.1 Seven levers, ranked by community payoff
| # | Lever | Flag / method | Typical gain | Evidence |
|---|---|---|---|---|
| 1 | FlashAttention | -fa on (now default in llama.cpp builds) | 10β25% tg, larger pp; less VRAM | Universal in every 2025 recipe incl. all Β§10 results |
| 2 | Bigger prompt-processing batches | -b 2048 -ub 2048 | 2β5Γ faster prefill | Official gpt-oss guide's universal flags; agents are prefill-heavy |
| 3 | Parallel slots with shared prefix | -np 2..4 | Near-linear for concurrent agent sessions | Official guide's agent recipe; PagedAttention lineage (Β§7.2) |
| 4 | KV quantization | --cache-type-k q8_0 | context 2Γ (not speed) | Β§7.1; Poor GPU Club tables |
| 5 | Quant downgrade to fit more in VRAM | IQ4_XS instead of Q4_K_XL | +3β5 t/s via placement | Poor GPU Club head-to-head (Β§10.1) |
| 6 | Speculative decoding | EAGLE-3 / Medusa / n-gram drafters | 2β3Γ when applicable | EAGLE: 3Γ at 13B (below); llama.cpp PRs ongoing |
| 7 | CPU-side tuning | Thread pinning, XMP/EXPO on, --no-mmap case-by-case | 5β20% on offload-heavy setups | gpt-oss-120b tuning writeups; Mixtral-offload paper's latency analysis |
Speculative decoding is the one speed lever with a mathematical guarantee: the output distribution is provably identical to the big model's β the draft only proposes, the target always decides. Because decode is memory-bound (Β§1.2), verifying k tokens costs barely more than generating one, so every accepted draft token is nearly free. The catch on 8 GB rigs: the drafter competes for the same VRAM. That is why the biggest wins in the community came from n-gram drafters (no model at all β a lookup of your own recent text, remarkably effective on code and agentic loops where text repeats) and from EAGLE's tiny 1B-class heads on rung-1 setups. For agents specifically, the pattern-match structure of tool calls ("let me call search again...") is close to ideal drafter territory, and the 2025β26 engine work (vLLM shipping eagle/eagle3/medusa, llama.cpp discussions) is converging here fast.
8.2 Measuring honestly: the llama-bench discipline
Every credible experiment in this archive used the same loop, and Law 5 says you should internalize it before touching a single flag. The loop, plus the two traps the community documents most often:
# 1. baseline: one change at a time, same prompt lengths both sides llama-bench -m model-Q4_K_M.gguf -fa 1 -p 512 -n 128 # 2. sweep the placement parameter (the one that matters most, Β§4) llama-bench -m model.gguf -ngl 99 -ncmoe 24 -fa 1 llama-bench -m model.gguf -ngl 99 -ncmoe 28 -fa 1 # repeat until t/s peaks # 3. watch VRAM *while* it runs β loading β fitting (Windows lies) nvidia-smi -l 1 # Linux; on Windows: Task Manager > GPU > "GPU Memory" # 4. write it down. The Poor GPU Club thread is literally this loop, in public.
The Poor GPU Club author's own note: llama-bench reported 31β38 t/s, but llama-server with a 32K context window ran somewhat slower, because the server pays for the KV allocation and the traffic. Always re-measure inside the deployment shape you'll actually run.
pp (prompt processing, compute-bound) and tg (token generation, bandwidth-bound) respond to opposite medicine β bigger ubatch helps pp and can hurt tg's memory budget. A rig can be fast at one and miserable at the other; agents need both, so always record pp512 and tg128 together, exactly as llama-bench prints them.
09The agent stack on 8 GB
Everything before this chapter was about serving a model well. Agents change the workload: context grows every turn, tool outputs arrive faster than the model can forget them, and the failure mode shifts from "slow" to "OOM mid-task, at turn 12, after twenty minutes of work." This chapter is the discipline that prevents that.
9.1 The memory anatomy of an agent loop
An agent turn appends four things to context: the model's reasoning, the tool call it emitted, the tool's raw output, and the framework's bookkeeping (tool schemas ride along in the system prompt from turn 0). A representative loop β one tool call per turn, moderately verbose tools β adds ~0.8K tokens per turn, and that compounds:
Reading the figure against the rig's budgets produces the three numbers every 8 GB agent operator should know cold: 16K context is the comfortable default (q8 KV, ~1 GB, leaves headroom), 32K is the practical ceiling with q8 KV on rung-4 models, and past 32K you are trading model rungs for context β a smaller model with more context usually beats a bigger model that crashes. The Figure-3 curve is exponential in your patience: every extra turn costs the same tokens but progressively fills the only pool that matters.
9.2 Patterns that survived contact
Four agent-design patterns recur across the successful low-resource builds in this archive. They are ordered by leverage:
Pattern 1 β Context compaction (the load-bearing one)
When context crosses a threshold (the green line in Figure 7), summarize the oldest turns into a compact running brief, drop verbatim tool outputs, and keep four things verbatim: the original task statement, the attention-sink prefix (the system prompt's first tokens β Β§7.2's StreamingLLM finding), the latest two turns, and any facts the task depends on. Implemented in ~30 lines in the agent harness (not the model), it converts an exponential context curve into a sawtooth and is the difference between 15-turn tasks that finish and ones that die. Every serious agent framework of 2024β25 grew a compactor for exactly this reason.
Pattern 2 β Tool-result triage at the boundary
Never paste raw tool output into context. A web search returns 4K tokens; the model needs 200 of them. The archive's working pattern is a fixed triage at the tool boundary β head/tail truncation, structural extraction (parse the JSON, keep the fields the task needs), or a cheap summarize pass on a rung-1 model. This is the agent-side twin of KV quantization: it shrinks the bytes that ever enter the cache.
Pattern 3 β One orchestrator, cheap specialists
The rung-4 MoE orchestrates; a rung-0/1 dense model (often CPU-resident, Β§6.2) handles utility calls β rerank, schema-fix, summarize. The two-model split works on the 8 GB rig precisely because the specialist can live in the 32 GB RAM pool where its 5β10 t/s is irrelevant to latency-critical paths, and because the orchestrator's VRAM share is never contested mid-task.
Pattern 4 β Retry with degradation, never crash
OOM at turn 12 loses the task. The working pattern: catch the server's OOM/timeout error, halve the context window, compact harder, reload, and resume from the brief. llama.cpp's server returns clean errors on failed allocations, which makes this pattern mechanical β and it converts the rig's hard memory ceiling into a soft one.
9.3 A reference stack, end to end
Here is the stack the archive's evidence points to, in one diagram-shaped paragraph and one runnable artifact. Serving: llama.cpp llama-server with a rung-4 model, --jinja for tool-call templates, expert offload tuned per Β§8's sweep, q8_0 KV, 16β32K context, FlashAttention on. Protocol: the server's OpenAI-compatible endpoint, so any standard framework can drive it. Agent harness: whatever you like β the community's experiments span LangGraph, claude-code-style CLI loops, and 300-line custom harnesses β with Patterns 1β4 implemented at the harness level. Observability: nvidia-smi in a side terminal, always (Law 5).
The minimal version, complete and runnable, is short enough to be its own proof that "agent" need not mean "framework":
import json, requests SERVER = "http://127.0.0.1:8080/v1/chat/completions" TOOLS = [{ "type": "function", "function": { "name": "read_file", "description": "Read a text file from disk", "parameters": {"type": "object", "properties": {"path": {"type": "string"}}, "required": ["path"]}}} # triage at the boundary: Pattern 2 β tools may return at most this many chars MAX_TOOL_CHARS = 1500 def run_tool(name, args): if name == "read_file": text = open(args["path"], errors="replace").read() return text[:MAX_TOOL_CHARS] + ("...[truncated]" if len(text) > MAX_TOOL_CHARS else "") return "unknown tool" def agent(task, max_turns=12): msgs = [{"role": "system", "content": "You are a precise agent. Use tools. Be terse."}, {"role": "user", "content": task}] for turn in range(max_turns): r = requests.post(SERVER, json={"messages": msgs, "tools": TOOLS, "temperature": 0.3}).json() m = r["choices"][0]["message"] msgs.append(m) if not m.get("tool_calls"): return m["content"] # final answer for tc in m["tool_calls"]: # execute, append results out = run_tool(tc["function"]["name"], json.loads(tc["function"]["arguments"])) msgs.append({"role": "tool", "tool_call_id": tc["id"], "content": out}) return "max turns reached" if __name__ == "__main__": print(agent("Read config.yaml in this directory and list every API key name it defines."))
Swap in your task and tools; the loop's shape is the production pattern minus compaction (add Pattern 1 by summarizing msgs[2:-4] when len(json.dumps(msgs)) crosses your threshold β the exact checkpoint logic from Figure 7's green line).
The reader's goal, made concrete by this stack: a 30B-class orchestrator at 20β34 t/s with 32K of disciplined context, cheap utility calls, tool outputs triaged at the boundary, and a sawtooth context curve instead of an explosion. That is a machine that reads your codebase, plans a change, edits files, runs the tests, and reports back β the "complex task inside a low-resource PC" this paper promised to make possible.
10Case studies: the experiments, in their own words
Three experiments deserve to be read as whole stories rather than as data points, because each one demonstrates the full loop β hypothesis, sweep, measurement, community iteration β that this paper wants its readers to be able to run.
10.1 The Poor GPU Club: 8 GB VRAM versus two MoE flagships
The author's stated goal β "day 1 attempt" at getting flagship MoE models usefully fast on an 8 GB card β and their posted results, exactly as measured with llama-bench:
| Model & quant | File | Command core | t/s |
|---|---|---|---|
| Qwen3-30B-A3B UD-Q4_K_XL | ~18.6 GB | -ngl 99 -ncmoe 29 -fa 1 | 31 |
| Qwen3-30B-A3B IQ4_XS | ~16.8 GB | -ngl 99 -ncmoe 28 -fa 1 | 34 |
| gpt-oss-20b MXFP4 | 11.3 GB | -ngl 99 -ncmoe 10 -fa 1 | 38 |
Read as an experiment, the thread is a textbook execution of the laws from Β§1: (a) placement beats quant β moving from Q4_K_XL to the slightly smaller IQ4_XS let one more MoE layer's experts fit in VRAM, worth +3 t/s at equal quality; (b) the ncmoe values (~10 for a 21B model, ~28 for a 30B) are exactly where the Figure-5 curve says an 8 GB card should land; (c) honest reporting β the author immediately notes server speeds run lower than bench speeds at 32K context (Trap 1, Β§8.2) and that previous 8 GB owners reported "only 20+ t/s," framing their own 31β38 as the state of the art to beat; (d) community iteration β commenters added their own rigs, including a second 8 GB-class machine reproducing 20 t/s on Qwen3-Coder-30B-A3B with --n-cpu-moe 34 and 26 t/s on gpt-oss with --n-cpu-moe 13 (150β200 t/s prompt processing with -ub 1024 -b 1024), and a 64 GB-RAM reader running gpt-oss-120b at 16 t/s β rung 5 of the ladder, on this class of machine.
10.2 The official 8 GB recipe: llama.cpp's gpt-oss guide
The llama.cpp team's gpt-oss launch guide (source of Β§5's cross-silicon table) is the "official" counterpart to the Poor GPU Club: same model, same engine, sanctioned flags per hardware tier. Its 8 GB-relevant guidance, condensed:
| Tier | Guidance |
|---|---|
| > 64 GB VRAM | Offload everything, full context: --ctx-size 0, no compromises |
| 16β24 GB VRAM | gpt-oss-20b fully in VRAM; or 120b with a few expert layers on CPU |
| < 16 GB VRAM (this paper's rig) | Run the whole model on GPU except expert tensors: -ngl 99 --n-cpu-moe 16β22 β named example: RTX 2060 8 GB; even the 120B works with --n-cpu-moe 35 -c 32768 |
| 8 GB unified memory (Macs) | Cannot run gpt-oss β the pool is too small once the OS eats its share (Β§5.2's moral) |
| Agent serving | For coding agents: --ctx-size 0 --jinja -ub 2048 -b 2048; add -np 4 parallel slots for agents that benefit (with the guide's caveat: slots cost memory) |
One further warning from the guide deserves verbatim status, because it is the single most common way 8 GB users silently destroy their performance β Windows will over-commit VRAM and swap instead of failing:
"On Windows it is possible to allocate more VRAM than available, and the result will be slow swapping to RAM and very bad performance. Just because the model loads without errors, it doesn't mean you have enough VRAM... A good way to avoid this is to look at the 'GPU Memory' in Task Manager and check that it does not exceed the GPU VRAM." The guide's own calibration example on a 32 GB card: --n-cpu-moe 21 β GPU memory under 32 GB (good) vs --n-cpu-moe 20 β over 32 GB (silent catastrophe). One expert layer is the difference.
10.3 Four Raspberry Pis: the floor of the experiment
Four $80 boards, strung with llama.cpp's RPC backend into one virtual 32 GB / ~68 GB/s machine, generating a frontier-adjacent 30B model at reading speed. As an engineering artifact it is glorious; as evidence it closes the loop on this paper's physics: the cluster works because the model is MoE with 3.3B active parameters (the per-token working set fits what the cluster can stream), and it caps out at 13 t/s because four LPDDR4X channels are still only ~68 GB/s β the bandwidth law, obeyed to the decimal. If the placement principles hold at 17 GB/s (this) and at 448 GB/s (Β§10.1), they hold everywhere between.
11The playbook: from zero to a running agent
Everything in the archive, compressed into one afternoon. Follow the steps in order; each one ends in something measurable, and every command is the exact shape used by the experiments you just read.
11.1 The steps
- Establish the baseline machine. Confirm 32 GB RAM, dual-channel populated, XMP/EXPO enabled (worth 5β20% on offload-heavy setups, Β§8.1 lever 7). Update the GPU driver. Close the browser. Open a terminal with
nvidia-smi -l 1and leave it running β this is your truth-teller for the rest of the session. - Install llama.cpp with CUDA. Prebuilt release binary, or build from source with
-DGGML_CUDA=ON. Verify withllama-server --versionand a CUDA device listing on startup. - Download a rung-4 model and its smaller sibling. From the ladder: Qwen3-30B-A3B IQ4_XS (or UD-Q4_K_XL) plus a Q4_K_M 8B for utilities. Use the official GGUF repos (ggml-org, Unsloth, bartowski all publish calibrated quants).
- Sweep the placement. Run the Β§8.2 llama-bench loop:
-ngl 99 -ncmoe 24, then 26, 28, 30β¦ watch t/s climb then fall as VRAM pressure builds, and watch nvidia-smi to stay under ~7.4 GB. Keep the peak. This is the single highest-value hour in the playbook β the Poor GPU Club's 31β34 t/s came from exactly this step. - Start the server, tuned. The recipe below. Verify with one chat completion and one tool call from the Β§9.3 loop before building anything larger.
- Wire the agent harness. The 60-line loop from Β§9.3, or your framework of choice pointed at
http://127.0.0.1:8080/v1. Implement Patterns 1 and 2 (compaction + triage) before your first long task β retrofitting them after a mid-task OOM is how people learn Law 3 the hard way. - Run a real task end-to-end. Something honest: "read this repo, find the bug the test describes, fix it, run the tests." Watch the context sawtooth, watch VRAM, and record t/s at turn 1 vs turn 10. If turn 10 is much slower, your context discipline slipped β that diagnosis, not hardware, is the usual finding.
11.2 Copy-paste recipes, annotated
# Qwen3-30B-A3B as the orchestrating agent on 8 GB VRAM + 32 GB RAM # -ngl 99 : attention + shared layers fully on GPU # -ncmoe 28 : expert tensors for 28 of 48 MoE layers live in RAM (SWEEP THIS, Β§11.1 step 4) # -fa : FlashAttention (lever 1) # --cache-type-k/v q8_0 : halves KV so 32K context fits (Β§7.1) # --jinja : native tool-calling template for the agent loop llama-server -m Qwen3-30B-A3B-IQ4_XS.gguf \ -ngl 99 -ncmoe 28 -fa 1 \ -c 32768 -ctk q8_0 -ctv q8_0 \ -b 2048 -ub 2048 --jinja \ --host 127.0.0.1 --port 8080
# Official-guide-derived 8 GB configuration (Β§10.2): llama-server -hf ggml-org/gpt-oss-20b-GGUF \ --ctx-size 32768 --jinja -ub 2048 -b 2048 \ -ngl 99 --n-cpu-moe 16 -fa 1 # n-cpu-moe 16 buys speed (fewer experts on CPU) at the cost of context; # n-cpu-moe 22 buys full context at ~2/3 of the speed. Pick per task.
# The rung-1 specialist on CPU only β 32 GB RAM runs it at ~8-12 t/s # while the GPU stays fully committed to the orchestrator. llama-server -m llama-3.1-8b-Q4_K_M.gguf -ngl 0 \ -c 8192 -t $(nproc) --host 127.0.0.1 --port 8081
# Throughput, not latency: huge batches, everything prompt-processing-friendly llama-server -m model.gguf -ngl 99 -ncmoe 28 -fa 1 \ -c 32768 -b 4096 -ub 4096 --jinja # pair with -np 2..4 parallel slots when the framework can use them (Β§8.1 lever 3)
11.3 The verification checklist
| Check | How | Passes when |
|---|---|---|
| VRAM headroom | nvidia-smi during load + first turns | Under ~7.4 GB used; no growth across turns |
| Placement optimum | llama-bench ncmoe sweep table | You know your peak t/s and the ncmoe that produced it |
| Tool-calling works | Β§9.3 loop on a trivial task | Model emits a valid tool call, harness executes it, loop closes |
| Context sawtooth | Log context size per turn | Size oscillates under your threshold; no monotonic climb |
| Latency honesty | t/s at turn 1 vs turn 10 | Within ~25% β otherwise re-check KV type and compaction |
| OOM resilience | Force a tiny context; watch behavior | Pattern 4 catches, compacts, resumes β task survives |
All six checks pass, and your agent has completed one non-trivial real task on the 8 GB machine β planning, calling tools, and finishing without manual babysitting. You now know more about low-resource inference than most of the people posting benchmarks, because you can reproduce all of Chapter 4's experiments on demand.
12Troubleshooting: the failure catalog
Every failure below is one the archive documents someone hitting, diagnosing, and fixing. The table is organized by symptom, because that is what you will have in hand when it happens.
12.1 Out-of-memory and crash matrix
| Symptom | Real cause | Fix |
|---|---|---|
| Loads fine, then CUDA OOM at first long prompt | KV cache sized for full context is allocated lazily/deferred; the prompt triggered it (Law 3) | Reduce -c; add -ctk q8_0 -ctv q8_0; smaller ubatch |
| Model "loads," generation crawls at 1β3 t/s | Windows VRAM over-commit β silent swap to RAM (Β§10.2's official warning) | Watch Task Manager GPU memory; raise --n-cpu-moe until under VRAM; one expert layer can be the whole difference |
| Crash mid-agent-task after N turns | Context grew past allocation (Figure 7's red line) | Pattern 1 compaction; enforce a hard context budget in the harness |
| OOM on load even though "it should fit" | Unbudgeted overhead: CUDA context, compute buffer scales with -b, second model still resident | Audit against Β§2.2's four rows; drop -b to 1024; verify one model at a time |
| System RAM exhausted (Linux OOM-killer takes the server) | Expert offload + --no-mmap + big context exceeds 32 GB | Let mmap do its job (drop --no-mmap); lower ncmoe; smaller context |
| Fine on short chats, degrades badly past ~8K tokens | Prompt-processing batch too small, or KV in f16 (Β§7.1) | -ub 2048; quantize KV; check FlashAttention is on |
12.2 "Why is it slow?" β the decision path
-b 2048 -ub 2048, FlashAttention on; for batch jobs go 4096.Four suspects, always the same four: placement (weights on the wrong pool), context (KV grew past the budget), batching (pp/tg confusion), overcommit (Windows lying about fit). Every failure in the catalog reduces to one of them, and each has a one-flag first response. Diagnosis is measurement (Β§8.2); never guess at flags β sweep them.
13Glossary
Working definitions as used in this paper β biased toward the operational meaning on an 8 GB rig rather than the textbook one.
- Active parameters
- The fraction of an MoE model's weights that participate in a given token's computation (gpt-oss-20b: 3.6B of 21B). Along with quantization, the master variable of the bandwidth law.
- Attention sink
- Early-sequence tokens that absorb disproportionate attention mass; evicting them from cache collapses quality (StreamingLLM). Reason your agent's system prompt prefix is sacred.
- CPU offload (expert) β
--n-cpu-moe - Architecture B: keep attention/shared weights in VRAM, expert tensors in system RAM. The 2024β25 answer to small VRAM.
- CPU offload (layer) β
--n-gpu-layers - Architecture A: the first N transformer layers on GPU, the rest on CPU. Simple, scales poorly (Β§4.1).
- FlashAttention
- Fused, memory-lean attention kernel; ~free speed and VRAM on every modern build. If your engine has a flag for it, it should be on.
- GGUF / K-quants / I-quants
- llama.cpp's model container and its quant families. K-quants (Q4_K_Mβ¦) are block-scaled grids; I-quants (IQ4_XSβ¦) add importance-matrix calibration for better quality per bit at low widths.
- GQA (grouped-query attention)
- Sharing K/V heads across query heads β the architecture change that cut KV-per-token ~4Γ on modern 7β9B models. Assumed by every current model in the ladder.
- KV cache
- Per-token key/value memory saved so past tokens aren't recomputed; grows linearly with context (Β§2.3's formula). The agent-relevant memory cost.
- MoE (mixture of experts)
- Architecture with many specialist FFN blocks ("experts") and a router selecting a few per token. Large total knowledge, small per-token compute β the enabling trick of low-VRAM inference.
- MXFP4
- The 4-bit microscaling format gpt-oss models ship in natively (block-scaled, hardware-friendly). A vendor-grade answer to "which 4-bit."
- PagedAttention
- Treating KV memory like OS pages β on-demand, shareable, near-zero waste (vLLM). Why parallel agent slots are affordable.
- pp / tg (prompt processing / token generation)
- The two halves of inference latency: compute-bound prefill (the prompt read) and bandwidth-bound decode (the answer). Different bottlenecks, different medicine.
- QAT (quantization-aware training)
- Training with quantization in the loop so 4-bit weights are compensated at the source β Gemma-3-12B QAT's route onto 8 GB cards at 12B quality.
- Speculative decoding
- A small drafter proposes tokens; the big model verifies in one batched pass. Output-exact, 2β3Γ where it applies (Β§8.1).
- Unified memory
- One physical pool shared by CPU and GPU (Apple M-series, integrated GPUs). No PCIe wire β bandwidth is honest and capacity is shared with the OS.
- VRAM over-commit
- Windows allocating more GPU memory than exists and silently swapping β the "it loaded, why is it 2 t/s" trap. Diagnosed only by watching real usage.
14Annotated sources
Every experiment reproduced in this paper, with its origin. Papers are listed by venue/arXiv; community and official sources by platform. Numbers were transcribed from these sources during August 2025β2026 research for this edition; where a source thread contains multiple rigs, the paper's tables say which one.
Peer-reviewed systems papers
Official benchmarks and guides
Community experiments
A note on data hygiene: figures 1β4 and 6β7 are plotted from the numbers above; Figure 5 mixes measured points with one estimated anchor (the full-GPU tier of a 24 GB card), labeled as such in its caption. Where sources disagreed (e.g., MLX vs llama.cpp Mac speeds), the disagreement itself was reported rather than averaged away. Reader-measured numbers will drift from these as engines and drivers improve β the archive's claims are about the shape of the relationships, which has been stable since 2023.
A single-file research paper. Works offline; print it and it becomes a book.
Laws: five. Experiments: twenty-three. Excuses: zero.
Related Posts
Maximum Capability from Minimum Silicon
A book-length engineering research paper on maximizing 8 GB GPU + 32 GB RAM workstations for AI agent workloads.
Read more βGGUF vs EXL2 vs AWQ vs GPTQ
Master quantization formats for local AI: precision, speed, VRAM trade-offs.
Read more βGPU & CPU Inference Troubleshooting
Complete troubleshooting guide for inference issues β OOM, slow tok/s, KV cache pressure.
Read more βAbout the Author
Hussain Nazary is a software developer specializing in local AI deployment and the creator of GGUF Loader, an open-source tool for running GGUF models locally. This analysis is part of Local AI Zone's ongoing coverage of open-weight language models and practical deployment strategies.
Contact: GitHub | Consulting Services
Last Updated: August 26, 2026 | Version 1.0