RTX 3090 at 170 tok/s: Complete Technical Configuration Reference
This is the technical companion to the full tuning diary: How I Pushed an RTX 3090 to 170 Tokens Per Second With Qwen 3.6-35B. The Medium article covers the narrative and crash analysis. This page is the copy-pasteable reference — every flag, config file, and benchmark number.
Hardware Specification
| Component | Specification |
|---|---|
| CPU | Intel Core i9-10900K (10 cores, 20 threads, 3.70GHz) |
| System RAM | 24GB DDR4 (WARNING: insufficient for 131K context + --mlock — see OOM section) |
| GPU | NVIDIA RTX 3090 24GB GDDR6X (Ampere, compute capability 8.6) |
| VRAM Bandwidth | 936 GB/s (theoretical max: ~170 tok/s for this model) |
| OS | Windows 11 + WSL2 Ubuntu 26.04 |
| CUDA | 12.9 |
| Driver | NVIDIA 610.62 (Studio) |
Software Stack
| Component | Version | Purpose |
|---|---|---|
| llama.cpp | b9971 (turboquant fork) | Inference engine |
| Model | Qwen3.6-35B-A3B-UD-IQ4_XS.gguf | 35B MoE, 3B active params |
| mmproj | mmproj-F16.gguf | Multimodal projector |
| llama-swap | Latest | Model routing (4 profiles) |
| Hermes Agent | v0.19.0 | Agent framework |
llama.cpp Build Command
Architecture-specific build for Ampere (compute 8.6). These flags produce native RTX 3090 machine code and enable all flash attention quantization paths:
cmake -B build \
-DGGML_CUDA=ON \
-DCMAKE_CUDA_ARCHITECTURES=86 \
-DGGML_CUDA_FA_ALL_QUANTS=ON \
-DGGML_NATIVE=ON \
-DGGML_LTO=ON \
-DGGML_CUDA_GRAPHS=ON \
-DGGML_CUDA_F16=ON
cmake --build build --config Release -j 10
Critical Build Flags Explained
| Flag | Why It Matters |
|---|---|
CMAKE_CUDA_ARCHITECTURES=86 | Emits native Ampere machine code. Without it, GPU JIT-compiles kernels at runtime (slower, suboptimal paths). |
GGML_CUDA_FA_ALL_QUANTS=ON | Enables flash attention for ALL KV cache quantization formats. Without it, --cache-type-k q8_0 forces slow fallback kernels. |
GGML_CUDA_GRAPHS=ON | CUDA graph capture for reduced kernel launch overhead. |
GGML_CUDA_F16=ON | Use FP16 accumulation for faster GPU computation where precision allows. |
Server Launch Command
The complete, battle-tested launch command:
llama-server --port ${PORT} \
--model Qwen3.6-35B-A3B-UD-IQ4_XS.gguf \
--mmproj mmproj-F16.gguf \
-c 131072 -ngl 99 -fa on -b 1024 -ub 2048 \
-t 10 -tb 16 \
--cache-type-k q8_0 --cache-type-v turbo4 \
-fit off --no-mmap --mlock --jinja \
--poll 100 --prio 2 --reasoning off \
-np 1 \
--spec-type draft-mtp --spec-draft-n-max 2
Flag-by-Flag Reference
| Flag | Value | Purpose |
|---|---|---|
-ngl | 99 | All layers on GPU. No CPU offload = maximum speed. |
-fa | on | Flash Attention. Non-negotiable for Ampere GPUs. |
-c | 131072 | Context window. WARNING: reduce to 65536 if RAM < 48GB. |
-b | 1024 | Batch size. Tuned for 24GB VRAM budget. |
-ub | 2048 | Micro batch for prompt processing chunk size. |
-t | 10 | CPU threads (matching physical cores). |
--cache-type-k | q8_0 | Quantized K cache. Saves VRAM for longer context. |
--cache-type-v | turbo4 | TurboQuant V cache. Best speed-quality ratio. |
--no-mmap | — | Eliminates page-fault jitter (prevents random speed drops from 150→80 tok/s). |
--mlock | — | Locks model pages in RAM. Prevents swap degradation. Requires ≥48GB RAM. |
--jinja | — | Jinja2 chat template processing. Required for agent frameworks using structured tool calls. |
--reasoning | off | Disables thinking blocks. Saves tokens in agent workloads. |
--spec-type | draft-mtp | MTP speculative decoding. +21% on code workloads. |
--spec-draft-n-max | 2 | 2-token draft window. Optimal acceptance/speed balance. |
MTP Speculative Decoding Benchmarks
MTP (Multi-Token Prediction) uses a lightweight draft model to predict the next 1-2 tokens. The main model verifies them in a single forward pass.
| Workload | Without MTP | With MTP | Speedup | Draft Acceptance |
|---|---|---|---|---|
| Code generation | 126.7 tok/s | 169.7 tok/s | +21% | 91% |
| Creative writing | 126.7 tok/s | 125.3 tok/s | -1% | 62% |
| Short responses (<30 tok) | ~120 tok/s | 57.7 tok/s | -52% | 100% |
Key insight: MTP is highly effective for predictable content (code, JSON, tool calls) but net-negative for creative writing and short responses. The draft model setup overhead exceeds savings on short outputs.
Why n-gram Stacking Fails
Stacking n-gram speculative decoding alongside MTP was tested. The result: acceptance went up (62%→85% on creative) but speed went down (125→67 tok/s). Two competing draft engines generate redundant drafts that waste verification compute.
One good speculative engine beats two competing ones.
Sampling Parameters (Agent Workloads)
{
"temperature": 0.6,
"top_p": 0.95,
"top_k": 20,
"repetition_penalty": 1.05
}
| Parameter | Value | Why |
|---|---|---|
| temperature | 0.6 | Lower (0.3) causes repetitive loops on multi-step reasoning. 0.6 explores more solution paths. |
| top_p | 0.95 | Wider candidate pool for format compliance. |
| top_k | 20 | Bounds the candidate pool. |
| repetition_penalty | 1.05 | Standard anti-repetition. |
Quantization Comparison
| Quant | Size | Fits 24GB? | Speed | Quality |
|---|---|---|---|---|
| IQ4_XS | ~17-18 GB | ✅ (room for KV cache) | 170 tok/s | Excellent |
| Q4_K_M | ~18-19 GB | ⚠️ Tight at 131K ctx | 148 tok/s | Excellent |
| Q4_K_XL | ~19-20 GB | ⚠️ Very tight | 135 tok/s | Excellent |
| Q5_K_M | ~24-25 GB | ❌ (CPU offload needed) | 75 tok/s | Marginally better |
IQ4_XS from Unsloth's dynamic quantization gives the best speed-to-quality ratio for 24GB cards. The quality difference vs Q5_K_M is measurable on perplexity but invisible in real agent workloads.
Benchmark Comparison: Published Results
Every documented benchmark we could find for this hardware + model combination:
| Source | Quant | Speed | Notes |
|---|---|---|---|
| This setup | IQ4_XS + MTP | 169.7 tok/s | 13-50% faster |
| Japanese benchmark (zephel01) | IQ4_NL | 149.8 tok/s | Different quant |
| Giles Thomas | UD-IQ4_NL_XL | 140 tok/s | GPU-only mode |
| HN user (thc1006) | UD-Q4_K_XL | 135.7 tok/s | No MTP |
| Reddit r/LocalLLaMA | unspecified | 113 tok/s | Likely default settings |
| HF Discussion | UD-Q3_K_M | 120 tok/s | Lighter quant |
llama-swap Configuration
llama-swap routes requests to the correct model profile. This is the config structure for a 4-profile setup:
# /home/game/.hermes/scripts/llama-swap-config.yaml
models:
default:
cmd: |
llama-server --port ${PORT} \
--model Qwen3.6-35B-A3B-UD-IQ4_XS.gguf \
--mmproj mmproj-F16.gguf \
-c 65536 -ngl 99 -fa on -b 1024 -ub 2048 \
-t 10 --cache-type-k q8_0 --cache-type-v turbo4 \
-fit off --no-mmap --mlock --jinja \
--poll 100 --prio 2 --reasoning off -np 1 \
--spec-type draft-mtp --spec-draft-n-max 2
large-context:
cmd: |
llama-server --port ${PORT} \
--model Qwen3.6-35B-A3B-UD-IQ4_XS.gguf \
--mmproj mmproj-F16.gguf \
-c 131072 -ngl 99 -fa on -b 1024 -ub 2048 \
-t 10 --cache-type-k q8_0 --cache-type-v turbo4 \
-fit off --no-mmap --jinja \
--poll 100 --prio 2 --reasoning off -np 1 \
--spec-type draft-mtp --spec-draft-n-max 2
# Add additional profiles for other models as needed
-c 65536 (not 131072) to avoid OOM crashes on systems with < 48GB RAM. The large-context profile drops --mlock for the same reason.OOM Crash Analysis & Fix
What Happens at 131K Context + --mlock on 24GB RAM
| Resource | Value at Crash |
|---|---|
| llama-server anon-RSS | 19,986 MB (~20 GB) |
| llama-server shmem-RSS | 1,183 MB (~1.1 GB) |
| Total system RAM | 24 GB |
| Swap | 100% full (2 GB, 0 free) |
| Free RAM at crash | ~122 MB |
The kernel OOM killer selected llama-server (highest RSS). Three interacting causes:
- 131K KV cache: q8_0 K cache + turbo4 V cache at 131,072 tokens = 4-6 GB on top of model weights.
--mlockpinned pages: Forces all model pages into resident RAM. Cannot be evicted.- VRAM spillover: Some tensors spill to system RAM. With
--mlock, those are pinned too.
Math: 17-18 GB model weights + 4-6 GB KV cache + 1-2 GB overhead = 24+ GB. Zero headroom.
The Fix
| Fix | Action | Effect |
|---|---|---|
| Context reduction | -c 65536 for default profile | Frees 2-3 GB KV cache memory |
| Drop --mlock on large ctx | Only use --mlock with -c 65536 | Allows OS to manage pages |
| Add RAM (definitive) | +32GB → 56GB total | Eliminates OOM at any context |
Agent Framework Context Optimization
Token savings per turn by optimizing the agent framework config:
| Optimization | Tokens Saved |
|---|---|
| Renamed large AGENTS.md to .bak | ~18,000 |
| Dropped kanban toolset (12 tools) | ~6,000 |
| protect_last_n 30 → 12 | ~10,000-15,000 |
| Disabled unused plugins | ~2,000 |
| Split monolithic config | ~3,000 |
| Total | ~39,000-44,000 tokens/turn |
Hardware Tier Guide (July 2026 Prices)
| Budget | Hardware | Max Model | Expected Speed |
|---|---|---|---|
| $800-900 | Used RTX 3090 24GB | 35B MoE at Q4 | 150-170 tok/s |
| $1,500-1,600 | RTX 4090 24GB | 35B MoE at Q4 | 180-220 tok/s |
| $2,500 | RTX 5090 32GB | 70B at Q4 | 60-80 tok/s |
| $1,500 | AMD Ryzen AI Max+ 395 | 70B at Q4 (shared RAM) | 12-15 tok/s |
RAM recommendation: Whatever GPU you choose, get at least 48GB system RAM. 24GB system RAM is not enough for 131K context with --mlock.
Remaining Headroom: The Last 10%
| Area | Potential | Notes |
|---|---|---|
| WSL2 virtualization overhead | 5-10% | Native Ubuntu could reach 180-187 tok/s |
| Thread tuning (-t 1 or -t 2) | 3-8% | GPU-bound workloads need fewer CPU threads |
| KV cache precision (q4_0) | Variable | 10x faster prompt processing, needs quality testing |
| Draft window (n-max 3-4) | 0-5% | Only if acceptance stays >85% |
Read the Full Story
This page is the technical reference. The full tuning diary — with the narrative, the debugging process, and what I'd do differently — is on Medium.
Read on Medium → Get the ToolkitUse coupon LAUNCH20 for 20% off any premium pack.
All benchmarks measured July 25-26, 2026 · llama.cpp b9971 · Qwen3.6-35B-A3B IQ4_XS · RTX 3090 · CUDA 12.9 · Hermes Agent v0.19.0