llm.supples.org

Fitting a 35B model into 16 GB, then checking it gets things right

Four open models, tuned for a local Goose agent on one Radeon RX 9070 XT and then tested on real agent work. Qwen3.6 35B A3B won. It passed all 12 hard runs, and putting each part of it on the right side of the memory bus made long chats 6.5 times faster.

5.5tok/s
generating in a 79K-token chat
Attention
Experts
layer 1layer 40
On the GPU (16 GB VRAM)In system RAM, run by the CPU

The setup

A desktop PC running an AI agent entirely offline.

CPU and RAMAMD Ryzen 7 7700X, 30 GB
GPURadeon RX 9070 XT, 16 GB VRAM. The same card drives the monitors, so filling its VRAM takes the desktop down.
OSBluefin Linux (Fedora Atomic), GNOME on Wayland
Model serverLM Studio 0.4.26, llama.cpp runtime 2.64.0 (Vulkan)
AgentGoose 1.56, with Playwright MCP for browser control
Jobs it needs to doCode, web design, documents, web research, personal finance, GitHub and Cloudflare via MCP, computer control

Web search, GitHub, Cloudflare and browser control come from Goose's extensions and MCP servers, not from the model. The model's job is to call those tools reliably and get the details right. It also needs vision for screen-based computer control.

Starting point: three stacked failures

Goose answered every message with null result from llama cpp. Each problem was hidden behind the one before it.

SymptomCauseFix
missing tensor 'blk.64.ssm_conv1d.weight'Goose's built-in llama.cpp couldn't read a newer hybrid GGUFRun models in LM Studio
Config said LM Studio, chat still used the built-in backendA Goose session keeps the provider it was created withStart a new chat
request (9501 tokens) exceeds the available context size (8192 tokens)Goose sends its system prompt and every tool definition before you typeLoad with a larger context (now 128K)

Read the logs before changing settings. Each error message named its own cause.

Why mixture-of-experts models fit this card

An MoE model stores all its weights but only computes with a few of them per token.

Dense (Qwen3.8 27B)MoE (Qwen3.6 35B A3B)
Weights stored27B35B
Weights used per tokenall 27Babout 3B: a router picks a few experts
File at 4-bit16.8 GB22.1 GB
Past 16 GB of VRAMevery token waits on system RAM, so it slows sharplyrarely used experts sit in RAM, so it slows a little
Measured generation8.9 tok/s29.2 tok/s untuned, 39.3 tuned

Per gigabyte, an MoE model is somewhat less capable than a dense one. But on a card the dense model doesn't fit, MoE wins clearly. The dense model was dropped.

Speed tuning

Most of the gain came from where the model's pieces sit in memory, not from exotic settings.

Generation speed in a 79K-token chat, tok/s
LM Studio automatic placementTuned, in each model's colour
128K context, 1 parallel slot, mmap off. GLM-4.7-Flash's automatic placement overflowed VRAM, so it has no "before".

The big one: keep every layer on the GPU

Raising the context to 128K looked free on a 10K-token prompt. A 79K-token prompt showed the real cost: Qwen3.6's generation fell from 29 to 5.5 tok/s. The real launch arguments explained why. LM Studio's automatic placement put 22 whole layers on the GPU and 18 on the CPU, attention included. So the CPU was doing attention over the entire chat.

The fix is the standard one for MoE models: every layer on the GPU, with only some layers' experts kept in system RAM (llama.cpp's --n-cpu-moe, LM Studio's "MoE experts on CPU"). Attention stays on the GPU, and only the cheap, rarely used expert weights wait on RAM.

Every step of every sweep

Each sweep step changed one setting, reloaded the model and timed a ~10K-token prompt and a ~79K-token prompt. The highlighted row is the setting the picker chose: fastest long-chat generation with VRAM and pinned RAM both at or under 14 GB.

Other settings that mattered

Two memory limits, not one

VRAM is the obvious limit. The second is GTT: system RAM the GPU driver pins for the GPU, capped at about 15.6 GB here. Experts kept in RAM are pinned in GTT, so moving more of them off the GPU trades one limit for the other. With 80% of experts in RAM, GTT filled, the load failed and the kernel logged 2,832 Not enough memory errors in one burst.

Vulkan beat ROCm on every model

LM Studio's ROCm runtime was slower and used about 5 GB more VRAM than estimated, so far less of each model fit:

ModelBest ROCm offloadROCm read / genVulkan read / gen
Gemma 4 26B A4B50%933 / 25.02,776 / 36.3
Qwen3.6 35B A3B20%476 / 21.21,118 / 29.2
Qwen3.8 27Bnone fit–629 / 8.9
tok/s on a ~10K prompt, 32K context, before the 128K tuning.

Every ROCm run also logged thousands of init_user_pages: Failed to get user pages: -1 errors. ROCm was asking for write access to the read-only memory-mapped model file. Turning mmap off removed the errors but didn't change the speed. The server log also showed missing hipBLASLt kernels for this GPU (gfx1201) in LM Studio's ROCm bundle. That's LM Studio's to fix.

GLM-4.7-Flash: a runtime limit

GLM's attention cache was far larger than LM Studio estimated. Its long-prompt reading stuck near 110 tok/s at every placement and batch size, 16× slower than Gemma. That pattern points to the runtime, not the settings: LM Studio's Vulkan build likely lacks a fast path for GLM's attention type. That's inferred from the measurements, not confirmed.

Quality: does it get the job right?

Speed says nothing about accuracy. Each tuned model ran real Goose tasks headless, scored automatically and then checked by hand.

Six easy tasks barely separated the models (Qwen3.6 12/12, Gemma 11/12). So a harder set followed, with traps, conflicting sources and hidden checks. Each model ran each task twice, with at most 40 tool steps and 25 minutes per run.

TaskWhat makes it hard
H1 CodeImplement a spec across three modules; 10 hidden tests the model never sees
H2 ResearchTen facts about three npm packages across several pages, with sources
H3 FinanceBalance transfer vs personal loan: fees, a 0% promo, a grossed-up loan amount, APR vs APY
H4 DocumentA brief from notes, emails and a policy, where a later email changes the date and an owner
H5 WebA pricing page whose monthly/yearly toggle must actually work, plus accessibility and phone layout
H6 AgentFlag the right 4 orders among 40 across four pages, past decoys and confirmation dialogs
Hard-task score, % of available points
2 points for a pass, 1 for a partial, 0 for a fail. Gemma's second H6 run was stopped to clear the queue, so it has 22 points available instead of 24.

What the failures were

Temperature matters

Qwen3.6 runs at its publisher's precise-coding temperature of 0.6, while Gemma ran at Google's general default of 1.0. Rerun at 0.6, Temperature fixed Gemma's coding reliability but not its step efficiency.

The scorers needed checking too

Before any model ran, every scorer was tested against a correct answer and a deliberately wrong one. That caught two wrong expected values in the hidden tests, which would have failed a perfect model. After the runs, three correct answers turned out to be marked down: two briefs that noted a handover in the owner column, and a loan answer that said "cheapest" instead of "cheaper". They were re-scored.

Reading every brief and loan explanation by hand also found slips the scorers passed. Qwen3.6 had two: a brief said someone was "out through mid-November" when the email gave no return date, and one loan breakdown didn't add up despite correct totals. Gemma and Flint had none.

Read the actual answer before trusting either a pass or a fail.

Final setup

Qwen3.6 35B A3B on Vulkan: mmap off, 128K context, every layer on the GPU, 60% of experts in system RAM.

WhereSettingValueWhy
LM StudioGGUF runtimeVulkan llama.cpp 2.64.0Faster than ROCm on every model
LM StudioTry mmap()off61–99% faster prompt reading
LM StudioKeep model in memoryoffLocks RAM; the memlock limit here is 8 MB
LM StudioContext length131,072Long chats without early compaction
LM StudioParallel slots1One user; the default of 4 is wasted memory
LM Studioacceleration.offloadRatio1Without it, loads triggered by Goose fall back to 22 layers
LM StudioMoE experts on CPU0.60.5 overflows VRAM, 0.8 overflows GTT
LM StudioK and V cacheq8_01.2 GB less VRAM for ~3% speed
LM StudioPhysical batch (ubatch)2048About 2× faster prompt reading
GooseModelqwen/qwen3.6-35b-a3bMost accurate, fast, has vision
GooseGOOSE_CONTEXT_LIMIT131072Matches LM Studio, so Goose compacts before LM Studio rejects
GooseExtensionsDeveloper, Todo, Memory, Analyze, Computer Controller, Extension Manager, PlaywrightFewer tools to choose from
GNOMEAutomatic suspendoffSuspending with a model loaded crashed the desktop

The saved LM Studio settings file for Qwen3.6 (~/.lmstudio/.internal/user-concrete-model-default-config/qwen/qwen3.6-35b-a3b.json), load section:

{ "key": "llm.load.contextLength",                  "value": 131072 },
{ "key": "llm.load.numParallelSessions",            "value": 1 },
{ "key": "llm.load.llama.tryMmap",                  "value": false },
{ "key": "llm.load.llama.keepModelInMemory",        "value": false },
{ "key": "llm.load.llama.acceleration.offloadRatio","value": 1 },
{ "key": "llm.load.numCpuExpertLayersRatio",        "value": 0.6 },
{ "key": "llm.load.llama.kCacheQuantizationType",   "value": { "checked": true, "value": "q8_0" } },
{ "key": "llm.load.llama.vCacheQuantizationType",   "value": { "checked": true, "value": "q8_0" } },
{ "key": "llm.load.llama.physicalBatchSize",        "value": 2048 }

The other models

Lessons

Things that cost time, so you don't have to repeat them.

  1. Read the server's real launch arguments. /proc/<pid>/cmdline showed settings the UI implied were applied but weren't.
  2. Benchmark long prompts. 128K context looked free at 10K tokens and cost 80% of generation speed at 79K.
  3. Never run two model servers on a GPU that drives your screen. A leftover ROCm server kept 13.4 GB and the next load crashed GNOME.
  4. Watch GTT as well as VRAM. Moving experts to RAM trades one limit for the other.
  5. Block suspend during long GPU jobs. "Performance" power mode doesn't stop the idle timer. Use systemd-inhibit --what=sleep:idle.
  6. Journal every step. A START with no END marks exactly where a crash happened.
  7. Use the publisher's sampling settings. Gemma at temperature 1.0 made a logic bug it didn't make at 0.6.
  8. Test the tests. Scorers had wrong expected values and false negatives; both were caught only by checking them.
  9. Small things: lms chat prints stats to stderr; a 300 KB prompt is over Linux's 128 KB argument limit, so pipe it on stdin; Playwright MCP blocks file://, so serve test pages over local HTTP.

How it was run

A small Python harness, standard library only, running unattended as systemd user services.

Everything on this page is generated from the run journals: data/results.json. The harness lives in a private repository.

Log

Results so far, newest first.

Next up: rerun H6 with an 80-step limit, and more runs per task so small differences become meaningful.

Sources