Local LLM inference on AMD APU hardware. Built on CachyLLama, our fork of llama.cpp with a persistent on-disk KV cache, MoE expert residency, Lightning Indexer, and tight CLIO integration. Self-contained — no system ROCm install needed. Vulkan (RADV) is the default backend for best stability on RDNA3 iGPUs.
llama-ai exists to make agentic AI development practical on AMD APUs without network access. No API keys, no per-token costs, no cloud dependency. The primary target is a Nimo Axis N161 (Strix Halo, Ryzen AI Max+ 395, Radeon 8060S, 128 GB unified memory). It also runs on the Ayaneo Flip KB (7840U / 780M / 32 GB) and the Minisforum UM580 (5800H / 16 GB), plus any other AMD APU in the detection map.
Profiles are computed at runtime by an optimistic-first solver
(scripts/optimize.sh) that reads GGUF metadata and iteratively detunes until
the configuration fits the GPU budget. A legacy preset table is preserved as a
fallback (--noauto).
git clone --recurse-submodules https://github.com/fewtarius/llama-ai.git
cd llama-ai
# Build Vulkan backend (default on Linux AMD)
./scripts/rebuild.sh
# Drop a GGUF model in models/, then:
./llama-run.sh --server
# -> http://localhost:9090# List models found in models/
./llama-run.sh --list-models
# Start server (auto-detects model)
./llama-run.sh --server
# Specific model and backend
./llama-run.sh --server gemma-4-26b --backend vulkan
# Download a model
./llama-run.sh --download Qwen3-14B --quant Q4_K_M
# List available backends
./llama-run.sh --list-backends
# Rebuild
./scripts/rebuild.sh # Vulkan only (default)
./scripts/rebuild.sh --rocm # ROCm
./scripts/rebuild.sh --both # Vulkan + ROCm
./scripts/rebuild.sh --rebuild # Full rebuild from scratch
# Print the solver's chosen profile for a model
./llama-run.sh --print-profile Qwen3.6-35B-A3BReasoning models — DeepSeek-R1, Qwen3.6, GLM-4.7 — emit thinking blocks before
each response. By default the runner strips these from prior assistant messages
in the conversation history so they don't waste prompt tokens. To preserve them
across turns, pass --preserve-reasoning. Use --reasoning-budget N to cap
thinking tokens per response (default: 2048).
llama-run.sh runs an optimistic-first solver by default (in
scripts/optimize.sh). The solver reads actual GGUF metadata and system
memory/GPU budgets to pick the best configuration -- it is the single
configuration path. The legacy preset table is only used with --noauto.
Solver pipeline:
-
Read GGUF metadata via
scratch/read_gguf_kv.py-- block count,head_count_kv,key/value_length,full_attention_interval(for hybrid SSM models),nextn_predict_layers(for MTP),expert_count(for MoE), training context. -
Start optimistically -- f16/f16 KV, training context (or 4x capped), SSD cache on (Halo: off), draft model enabled if found, ubatch 2048 (Halo) / 1024 (standard) / 512 (handheld), threads = physical cores (batch) / half (gen), MoE strategy =
gpu. -
Score and search -- builds a priority list of all valid combinations:
- Strategy:
gpu(300),cpu(250),residency(200) - Context: 128K (100), 96K (95), 196K (90), 262K (85), 64K (80)
- KV type: f16 (30), q8_0 (20), q4_0 (10)
- Draft: enabled (5), disabled (0)
Score =
strategy*1000 + ctx*10 + kv + draft. First combo fitting both GPU and system budgets with minimum cache RAM wins.Strategy selection rules:
- MoE model >80% of GPU budget and fits in system RAM (8 GiB OS reserve)
->
cpupreferred,residencyskipped entirely. - Otherwise:
gpu->residency->cpu. - Low-VRAM (<32 GiB GPU budget), MoE: context capped at 64K, f16 KV removed (only q8_0/q4_0).
- Strategy:
-
Cache RAM allocation -- from system memory leftover, not GPU leftover: total RAM - OS reserve - config system cost, capped at 25% of total RAM with 10% headroom, split between prompt cache and SSD (SSD capped at 1 GiB).
-
Detune safety net -- if no fit: ctx = 65536, KV = q4_0/q4_0. If still no fit, exits with error. Phase 2 fine-grained detunes: reduce KV (q8->q4), reduce NGL, reduce SSD RAM, drop draft, drop SSD, reduce ubatch -- each step once.
Override precedence (low -> high):
| Level | Source |
|---|---|
| 1 | Built-in defaults (solver start) |
| 2 | System detection (GPU budget, hardware tier) |
| 3 | Solver output |
| 4 | User overrides (env vars / CLI flags) |
User overrides recognized:
| Override | CLI Flag | Env Var |
|---|---|---|
| Context size | --ctx-size |
USER_CTX_SIZE |
| KV cache K/V | --kv-cache-type |
KV_CACHE_K_OVERRIDE / KV_CACHE_V_OVERRIDE |
| Threads | --threads |
LLAMA_THREADS_OVERRIDE |
| Ubatch | --ubatch-size |
OVERRIDE_UBATCH_SIZE / MOE_UBATCH_OVERRIDE |
| Cache RAM | --cache-ram |
OVERRIDE_CACHE_RAM |
| Disable SSD | --no-ssd-cache |
_SSD_DISABLE |
| GPU layers | -ngl / --gpu-layers |
OVERRIDE_NGL |
| Fit mode | --fit on |
OVERRIDE_FIT |
| Reasoning budget | --reasoning-budget |
OVERRIDE_REASONING_BUDGET |
Note: LLAMA_SSD_HOT_RAM, LLAMA_SSD_WARM_RAM, LLAMA_THREADS,
LLAMA_KV_CACHE_TYPE_K are defaults (level 2), not overrides. Use the
*_OVERRIDE variants to win over the solver.
Legacy preset table (--noauto only -- kept for script compatibility):
| Preset | When | ctx | KV | batch/ubatch | SSD | cpu-moe |
|---|---|---|---|---|---|---|
halo-moe-large |
Strix Halo, MoE, >50 GB | 131072 | q8_0 | 2048/512 | off | — |
halo-moe-small |
Strix Halo, MoE, ≤50 GB | 196608 | f16 | 2048/512 | off | — |
halo-dense |
Strix Halo, dense (any) | 131072 | f16 | 2048/512 | off | — |
std-moe-large |
non-Halo, MoE, >18 GB | 65536 | q8_0 | 1024/256 | on | auto¹ |
std-moe-small |
non-Halo, MoE, ≤18 GB | 32768 | q8_0 | 1024/256 | on | auto¹ |
std-dense-large |
non-Halo, dense, >15 GB | 32768 | q4_0 | 1024/256 | on | — |
std-dense-small |
non-Halo, dense, ≤15 GB | 32768 | q8_0 | 1024/256 | on | — |
ssm |
Mamba / Jamba / RWKV | 65536 (262144 on halo) | q8_0 | 1024/512 | off | — |
¹ --cpu-moe + --load-mode none added when model ≥23 GiB to avoid the mmap
--moe-expert-residencypage-fault pathology. Halo never triggers this.
The solver maps its output to a legacy profile name for --print-profile
so existing scripts keep working. See SOLVER.md for the full
algorithm, benchmark data, and hardware notes.
Strix Halo (Nimo) — 128 GB RAM, 112 GB GPU budget
- SSD cache disabled (serialization overhead reduces prompt throughput 20–30%).
- Large MoE models (DeepSeek-V4-Flash 97 GB) run with CPU-MoE +
--load-mode none: model in system RAM, GPU handles attention + KV only. Avoids Vulkan OOM.
AYANEO Flip — 26 GB RAM, 22 GB GPU budget
- SSD cache enabled but capped ~1 GiB.
- MoE models: residency (default) or CPU-MoE if small enough.
- MoE context limited to 64K; KV is q8_0 or q4_0.
Low-RAM systems (<32 GB total)
- Prompt cache capped at 2048 MiB.
- SSD default: 512 MiB hot + 512 MiB warm.
- Hybrid SSM (Qwen3.5/3.6, Qwen3Next): reads
full_attention_interval, scales KV cache (~25% attention layers). - DeepSeek MLA: KV cache very small; f16 at full context usually fits.
- Pure SSM (Mamba, Jamba, RWKV): no KV cache; solver uses SSM profile.
- MTP models: adds 5% model size (capped 0.5–2 GiB) to GPU+system estimates for speculative decoding overhead.
Uses the Mesa RADV driver. Best stability on RDNA3 iGPUs (Phoenix, Hawk Point, Strix Point) and earlier GCN/RDNA generations. CPU offloading works for models that don't fit in GPU memory.
CachyLLama's Vulkan backend carries a wide surface of performance work:
- DeepSeek-V4 Lightning Indexer — fused op for compressed-KV attention
- DSV4 hyper-connection fused ops — replaces softmax-scale-iterate sequences
- Quantized-KV FA dequant-once — dequantizes K/V cache once into f16 scratch before flash attention (with host-RAM safety gate)
- APU
nodes_per_submitauto-lower — defaults to 8 on UMA to stay under amdgpulockup_timeout - Subgroup size pinning — 32-wide on RDNA3 wave64 for coopmat1 FA
Has known issues on RDNA3 — GLM-4.7-Flash and DeepSeek2 MLA models produce zero
generation tokens. Use Vulkan unless you have a specific reason to try ROCm.
ROCm components carry AMD's license and are downloaded by scripts/rebuild.sh.
Apple Silicon and Intel Macs with Metal-capable GPUs. Build with
./scripts/rebuild.sh on macOS — it auto-detects the platform and builds the
Metal backend.
AMD APUs share system RAM between CPU and GPU. On the Nimo Axis N161 (Strix
Halo, AI Max+ 395), the BIOS currently carves out 512 MiB of VRAM — the OS
sees ~125 GB RAM, and GTT defaults to ~62.5 GB. GPU-visible total is ~63 GB.
detect-gpu.sh recognizes Strix Halo via PCI ID (1002:1586) regardless of the
BIOS VRAM allocation.
Use apply-ttm-kernel-params.sh to configure GTT for your hardware:
# Phoenix/Hawk Point: cap firmware VRAM at 6GB, add 18GB GTT
sudo ./scripts/apply-ttm-kernel-params.sh 18
# Strix Halo: raise GTT to 104 GB (the practical ceiling)
sudo ./scripts/apply-ttm-kernel-params.sh 104The script writes amdgpu.gttsize, ttm.pages_limit, and ttm.page_pool_size
to your bootloader config (GRUB or systemd-boot). SteamFork dual-ESP installs
have a redundant /boot — the script probes other vfat partitions and edits the
real loader entry in place. Re-run after OS updates.
Verify after reboot:
cat /proc/cmdline | tr ' ' '\n' | grep -E "amdgpu|ttm"RADV on APUs (has_dedicated_vram=false) reports only 2/3 of (VRAM + GTT) as
the DEVICE_LOCAL heap and 1/3 as host heap (game-compat heuristic in
radv_physical_device.c). ~/.drirc enables
radv_enable_unified_heap_on_apu for llama-server/llama-cli/llama-bench
so DEVICE_LOCAL = full VRAM + GTT. Without it, models > 2/3 of GPU-visible memory
crash with vk::DeviceLostError at load.
scripts/detect-gpu.sh identifies your AMD GPU via PCI device ID and sets:
| Variable | Description |
|---|---|
LLAMA_IS_STRIX_HALO |
1 on Strix Halo (1002:1586), 0 elsewhere |
LLAMA_HARDWARE_TIER |
halo / standard / handheld (back-compat) |
LLAMA_APU_VRAM_GB |
VRAM carveout in GB |
LLAMA_TOTAL_RAM_GB |
Total system RAM in GB |
LLAMA_THREADS |
Optimal thread count |
LLAMA_GFX_ARCH |
e.g. gfx1151 (Strix Halo), gfx1103 (7840U) |
LLAMA_GPU_NAME |
e.g. Radeon 8060S, Radeon 780M |
The script also detects CPU ISA level and generates cmake flags so ISA extensions (AVX-512 BF16 on Zen 4, AVX2 on Zen 3) are enabled at build time.
Supported generations: Cezanne, Phoenix, Hawk Point, Strix Point, Strix Halo, Sephiroth, Rembrandt, Mendocino, Renoir, Lucienne.
Overrides:
LLAMA_GFX_VERSION_OVERRIDE=11.0.3 # skip GPU detection
LLAMA_IS_STRIX_HALO_OVERRIDE=1 # force halo tier
LLAMA_HARDWARE_TIER_OVERRIDE=halo # force tier stringOr via CLI:
./llama-run.sh --model ... --hardware-tier halo
./llama-run.sh --model ... --is-strix-halo
./llama-run.sh --model ... --no-strix-haloModels are detected by filename and assigned a profile automatically — no manual configuration needed. The optimistic-first solver reads GGUF metadata and computes the best configuration for your hardware. MoE models get checkpoint strategies and reasoning format support. SSM/Mamba models get context-shift disabled. Large dense models get optimized batch sizes.
The profile is logged at startup, e.g.:
Auto profile (solver): std-moe-large (35GB, MoE=true, SSM=true)
Solver chose: ctx=262144 KV=f16/f16 ubatch=4096 batch=4096 threads=8/16
Solver detune steps: reduce SSD RAM, increase context, enable f16 KV
Qwen3.6 (both dense and MoE variants), Qwen3-Coder-Next, and Qwen3.5-122B mix attention layers with recurrent (Mamba-2) layers. Laguna-S-2.1 is a DFlash target with shared experts and sliding window. CachyLLama handles these correctly: KV cache shifting, attention-only memory clearing that preserves recurrent state, and checkpoint overflow prevention — so same-conversation state is always accepted regardless of size.
MoE models activate only a subset of experts per token — typically 3-8 out of
128-256. CachyLLama tracks which experts activate through GET /expert-stats
and POST /expert-tracking endpoints. This is instrumentation for now; future
work will use it to reorder experts for cache locality.
Standard loading OOMs when a MoE model exceeds physical memory, even though only
1-3% of its weights are touched per token. CachyLLama's residency subsystem
keeps only the active subset paged in via madvise(MADV_WILLNEED / MADV_DONTNEED) and lets cold pages spill to SSD. On the Flip KB (25 GB RAM),
this loads and runs MoE models up to 86 GB.
A per-layer recency+frequency cache scores and evicts cold experts (FlashMoE
showed pure LRU evicts hot experts 34% of the time). A co-activation matrix
persists across sessions at ~/.cachylla/coactivation/{model}.json and informs
future prewarm.
Enabled in llama-run.sh automatically for the MoE profile when the hardware
tier is plugged into AC (heavy SSD I/O drains battery). CLI:
--moe-expert-residency [--moe-resident-per-layer N] [--moe-prewarm-top-k N].
See CachyLLama docs/moe-expert-residency.md
for architecture, API, tuning, and limitations.
Multi-tenant deployments get per-user KV cache namespacing, slot affinity
(prefer slots already owned by the same user), and configurable concurrency caps
that return HTTP 429 when exceeded. Pass llama_user_id in the request body to
enable.
CLIO serializes tool definitions with deterministic JSON key ordering and reuses conversation state across agentic turns. System prompts, tool descriptions, and compressed context — the static content sent on every API call — are cached and persisted to disk so they're available immediately on restart.
The feature that makes all of this practical on APU hardware. Cache hits skip prompt evaluation entirely for the cached prefix, dropping TTFT from minutes to seconds.
- Hot — Current session, in RAM. Instant restore within the same conversation.
- Warm — Previous sessions, same server run. In RAM until memory pressure pushes them to cold.
- Cold — On disk. Survives server restarts.
Tiers promote and demote automatically based on turn activity. Old checkpoints are evicted when the ring buffer fills. The cache just works — no manual management needed.
SSD caching is disabled on Strix Halo (halo tier) — the 63 GB GPU-visible
budget holds the working set comfortably, and SSD writes are pure overhead on
the prefill critical path (3 SSD writes per turn at 62 MiB each = 4s of disk
time on a 15K-token prefill). In-memory checkpoints (--cache-ram plus
--ctx-checkpoints) handle prefix reuse within a server run.
SSD caching is enabled on standard and handheld tiers — cold TTFT drops from minutes to seconds, and the GPU budget is tight enough that prompt cache reuse offsets the serialization cost.
The --no-ssd-cache flag forces SSD caching off on any tier.
By default nothing caps total disk usage — a long MoE conversation can grow to
hundreds of GiB on its own. Set --cache-ssd-cold-maxsize (MiB) to bound the
global cold tier footprint; when the cap is hit, the oldest conversation
directories get evicted first. Pair it with --cache-ssd-max-cold for
per-conversation checkpoint count limits.
A global cross-conversation cache stores the system section of any prompt after first evaluation. On a warm server restart, the cache restores the entire system prompt in under a second — the difference between instant response and waiting minutes for the first turn.
The cache lives at kv-cache/{model-stem}/sys-{hash}.bin, keyed by the first N
tokens of the prompt. Defaults to 8 entries per model with 30-day expiry.
Radeon 8060S, Vulkan backend. Speedup is prompt eval speedup: cold prompt_ms
divided by warm prompt_ms (with warm path restoring the prefix from SSD rather
than re-evaluating it). The benchmarks/ directory holds per-model server logs
from local benchmark runs.
| Model | Cold TTFT | Warm TTFT | Speedup | Gen t/s |
|---|---|---|---|---|
| DeepSeek-V4-Flash-0731 UD-IQ3_XXS (256x8.4B MoE, Lightning Indexer) | 97.0s | 0.34s | 282x | 6.3-17.5 |
| MiniMax-M2.7 Q2_K_XL (256x4.9B MoE) | 60.3s | 3.19s | 19x | 12.0-17.2 |
| Qwen3-235B-A22B Thinking-2507 IQ2_M (235B-A22B MoE) | 100.5s | 1.04s | 96x | 10.6-13.6 |
| Qwen3.5-122B-A10B Q5_K_M (122B-A10B MoE, Mamba hybrid) | 52.9s | 0.31s | 171x | 9.5-10.6 |
| Qwen3.5-122B-A10B UD-Q4_K_XL (122B-A10B MoE, Mamba hybrid) | 53.5s | 0.28s | 189x | 23.5-32.7 |
| gpt-oss-120b Q8_K_XL (120B MoE) | 25.0s | 0.24s | 104x | 18.2-19.9 |
| Qwen3-Coder-Next Q8_K_XL (512x2.5B MoE, Mamba hybrid) | 48.5s | 0.22s | 221x | 18.9-19.9 |
| Qwen3.6-35B-A3B Q8_K_XL (35B-A3B MoE, Mamba hybrid) | 20.5s | 0.14s | 149x | 14.2-17.5 |
| GLM-4.7-Flash Q8_K_XL (64x2.6B MoE, MLA) | 85.5s | 0.18s | 478x | 13.9-20.3 |
| gemma-4-26B-A4B Q5_K_M (26B-A4B MoE, sliding window) | 16.7s | 0.14s | 123x | 14.0-14.7 |
| Qwen3.6-27B Q8_K_XL (27B dense, Mamba hybrid) | 85.3s | 0.36s | 238x | 4.7-4.8 |
| Laguna-S-2.1 Q4_K_XL (256x4.5B MoE, DFlash target) | 50.0s | 0.48s | 104x | 11.2-16.9 |
| gpt-oss-20b Q6_K_XL (20B MoE) | 11.1s | 0.10s | 112x | 22.3-23.8 |
MoE vs dense. Qwen3.6-27B is the only dense model in the table — all 27B parameters activate per token, trading generation speed (4.7-4.8 t/s) for prompt eval throughput. MoE models activate 2.6-22B parameters per token, hitting 9-32 t/s. Dense models show the largest speedup numbers from SSD caching (238x) because cold eval is proportionally slower, but generation speed is the real-world tradeoff.
Mamba hybrids. Qwen3.6 (both dense and MoE variants), Qwen3-Coder-Next, and Qwen3.5-122B interleave attention layers with recurrent (Mamba-2) layers. These require attention-only KV cache clearing to preserve recurrent state across cache restores. Qwen3-235B (qwen3moe) is pure attention MoE — no Mamba. GLM-4.7 uses MLA (multi-head latent attention, no Mamba), Gemma 4 uses interleaved sliding-window/global attention, and DeepSeek-V4-Flash uses Lightning Indexer. None of these are Mamba hybrids.
Quantization. Most models run at Q8_K_XL (30-82 GB on disk). Qwen3.5-122B- A10B at Q4_K_XL hits 32.7 t/s (vs 9.5 t/s at Q5_K_M) due to lower memory-bandwidth pressure on decode. MiniMax-M2.7 at Q2_K_XL pushes the frontier — 230B parameters, 256 experts, 4.9B active per token, running in 63 GB GPU-visible. DeepSeek-V4- Flash IQ3_XXS sits at the top of the model size chart at ~1.6T parameters (256 experts, 8.4B active).
All warm runs restore the cached prefix from SSD; cold from scratch. Warm TTFT stays under 1.5s across most models — MiniMax-M2.7 at 3.19s is the outlier (its 300 MiB warm-path SSD payload for a 15K-token q8_0 KV cache costs more on the SSD restore path than smaller models).
Radeon 780M (gfx1103, Phoenix), 32 GB unified memory (26 GB OS-visible), 2 TB SSD,
Vulkan/RADV backend, GGML_VK_NODES_PER_SUBMIT=100. Tested with ./scripts/benchmark.sh
on v1.0.0-rc1 (CachyLLama a461a1fce). The benchmarks/vulkan/ directory holds
per-model server logs and drilldowns.
| Model | Size | Profile | pp 2K (t/s) | pp 8K (t/s) | tg 128 (t/s) | tg 512 (t/s) | Cold->Warm TTFT (15K) | Batched @8 |
|---|---|---|---|---|---|---|---|---|
| gpt-oss-20b-UD-Q6_K_XL | 11.2 GiB | dense-large | 398.0 | 369.9 | 32.0 | 31.6 | 45.8s -> 101ms (181x) | 281.2 t/s |
| Qwen3.6-35B-A3B-UD-Q4_K_XL | 21.3 GiB | moe-optimized (+residency) | 325.0 | 303.3 | 25.5 | 25.0 | 60.6s -> 150ms (140x) | 239.9 t/s |
| gemma-4-26B-A4B-it-UD-Q5_K_M | 16.8 GiB | — | — | — | 0.0 | 0.0 | failed | — |
pp N (t/s) = prompt processing throughput at N tokens (llama-bench, tg=128).
tg N (t/s) = text generation throughput at N tokens (llama-bench, pp=512).
Cold->Warm TTFT (15K) = server-side prompt eval for ~15K tokens, cold vs warm (SSD cache restored).
Batched @8 = total throughput with 8 parallel prompts (llama-batched-bench, 2048 pp / 128 tg).
| Model | pp 512 | pp 2K | pp 8K | pp 16K | tg 128 | tg 512 |
|---|---|---|---|---|---|---|
| gpt-oss-20b-UD-Q6_K_XL | 401.8 | 398.0 | 369.9 | 337.5 | 32.0 | 31.6 |
| Qwen3.6-35B-A3B-UD-Q4_K_XL | 328.2 | 325.0 | 303.3 | 280.9 | 25.5 | 25.0 |
Repetitions: 5. Lower variance at larger pp (1-2% sd).
| Model | Cold TTFT | Warm TTFT | Speedup | Warm TTFT (steady) |
|---|---|---|---|---|
| gpt-oss-20b large (15K) | 45,834 ms | 101 ms | 181x | 63 ms |
| gpt-oss-20b medium (5K) | 14,589 ms | 69 ms | 119x | 55 ms |
| gpt-oss-20b small (1K) | 3,803 ms | 52 ms | 58x | 48 ms |
| Qwen3.6-35B large (15K) | 60,623 ms | 150 ms | 140x | 63 ms |
| Qwen3.6-35B medium (5K) | 19,067 ms | 106 ms | 76x | 55 ms |
| Qwen3.6-35B small (1K) | 4,583 ms | 84 ms | 28x | 48 ms |
Warm TTFT (steady) = mean of turns 2-5 after first warm restore. First warm turn includes checkpoint load overhead.
| Model | 1 prompt | 2 prompts | 4 prompts | 8 prompts |
|---|---|---|---|---|
| gpt-oss-20b-UD-Q6_K_XL | 232.0 t/s | 258.4 t/s | 277.2 t/s | 281.2 t/s |
| Qwen3.6-35B-A3B-UD-Q4_K_XL | 185.8 t/s | 215.9 t/s | 233.9 t/s | 239.9 t/s |
Total aggregate throughput. Per-slot throughput drops with concurrency (281.2/8 = 35 t/s).
-
gpt-oss-20b is a dense 20B model — all parameters activate per token. Higher generation throughput (32 t/s) and best cache speedup (181x at 15K) because the KV cache is smaller and the model fits comfortably in the 22 GiB GPU budget. Runs the
dense-largeprofile (f16 KV, no SSD on Halo; on Flip SSD is enabled with q8_0 KV, ubatch=512, batch=1024, threads=4/8). -
Qwen3.6-35B-A3B is a 35B MoE model (3B active, A3B = 3B active experts). Runs the
moe-optimizedprofile with--moe-expert-residency(madvise MADV_FREE eviction, 64 resident experts/layer) andkv-unified. The residency subsystem keeps hot experts paged in so cold-path expert loads don't dominate decode. Generation is memory-bandwidth bound (~25 t/s) on this hardware. Context capped at 65536 by the solver for MoE on handheld tier. -
gemma-4-26B-A4B failed to generate (0 t/s across all tests). The model loads but produces no output tokens — likely a tokenizer/chat template mismatch or Vulkan kernel issue with the sliding-window attention pattern. Investigation needed.
-
GGML_VK_NODES_PER_SUBMIT=100 was used (vs conservative default 8). On the 7840U/Phoenix this gives ~4.6% higher tg64 throughput (22.65 vs 21.65 t/s) with no amdgpu lockup_timeout observed. The conservative default (8) is kept in CachyLLama to avoid regressions on other APUs.
-
All models use the optimistic-first solver (
scripts/optimize.sh) — no--noautolegacy presets. Profiles are computed from GGUF metadata and the 26 GB OS-visible / 22 GiB GPU budget. -
Full per-model logs and drilldowns in
benchmarks/vulkan/<model>/:summary.md(this table),cache/analysis.md,bench/report.md,batched/report.md,summary.json.
CLIO sends 18-30K tokens of system prompt, tool definitions, and conversation context on every API call. Every prompt has a static prefix (~18K tokens -- system prompt, tool definitions) that's identical across all turns, and a dynamic tail that grows as the conversation progresses.
Four sessions, all given the same task -- "Spend a few minutes studying this
project and then explain it" (read files, check git history, run commands, write
a final analysis). Two ran on the Nimo Axis N161 (Strix Halo, Ryzen AI Max+ 395,
128 GB unified memory, 4 TB NVMe RAID) and two on the Ayaneo Flip KB (784U, 32 GB
unified memory, 2 TB SSD). Qwen3.6 uses self-MTP speculative decoding
(--spec-type draft-mtp, n_max=2); DeepSeek and Laguna do not use spec decoding
(Laguna uses DFlash as a target architecture). All sessions use flash attention
and reasoning budget 4096.
Metrics are from scripts/log_analyzer.py on the paired server logs in
scratch/. PP and Gen are weighted by token count (total prompt tokens divided
by total PP time; total decode tokens divided by total decode time).
| # | Hardware | Model | Context | Draft | KV | Batch/Ubatch | Threads | GPU strat. | SSD | Cache-RAM | Turns | Time | PP (t/s) | Gen (t/s) | Draft acc. | LCP sim. | f_keep |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | Nimo (Strix Halo) | Qwen3.6-35B-A3B Q8_K_XL | 131072 | Yes (self-MTP) | f16/f16 | 4096/2048 | 16/32 | gpu | off | 31 GB | 3 | 2:24 | 68 | 47 | 84% | 0.71 | 0.99 |
| 2 | Flip KB (784U) | Qwen3.6-35B-A3B Q4_K_XL | 65536 | Yes (self-MTP) | q8_0/q8_0 | 1024/512 | 4/8 | residency | 124 MiB | 5 GB | 6 | 5:19 | 22 | 21 | 85% | 0.93 | 0.99 |
| 3 | Nimo (Strix Halo) | DeepSeek-V4-Flash IQ3_XXS | 131072 | No | q4_0/q4_0 | 4096/2048 | 16/32 | gpu | off | 5 GB | 4 | 6:03 | 184 | 12 | -- | 0.90 | 1.00 |
| 4 | Nimo (Strix Halo) | Laguna-S-2.1 Q5_K_XL | 131072 | No (DFlash) | q8_0/q8_0 | 4096/2048 | 16/32 | gpu | off | 11 GB | 16 | 9:36 | 376 | 16 | -- | 0.90 | 1.00 |
First turn evaluates the full system prompt cold. Every subsequent turn uses in-memory checkpoints to restore the cached prefix, evaluating only the new content. Draft acceptance is the per-turn average; overall acceptance across all generated tokens is 72.5% (Nimo) and 76.8% (Flip). LCP sim. = Longest Common Prefix similarity score (mean across warm turns, threshold 0.20); f_keep = fraction of prompt tokens restored from checkpoint (prompt cache hit rate).
Qwen3.6-35B-A3B-UD-Q8_K_XL, 131072 context, self-MTP draft (n_max=2), f16/f16 KV, flash attention, 16/32 threads, batch/ubatch 4096/2048, GPU strategy, SSD off, 31 GB cache-RAM. GPU budget: 112 GiB. LCP sim: 0.948, 0.471; f_keep: 0.993, 0.986.
| Turn | Prompt tokens | Decode tokens | TTFT (s) | PP (t/s) | Gen (t/s) |
|---|---|---|---|---|---|
| T0 | 24,668 | 222 | 31.8 | 775.6 | 51.7 |
| T87 | 1,410 | 427 | 1.9 | 741.5 | 56.1 |
| T240 | 29,417 | 1,312 | 47.9 | 613.5 | 43.4 |
Total: 2:24 (55,495 prompt + 1,961 decode = 57,456 tokens). Draft acceptance: 94.2%, 92.3%, 64.4%.
Qwen3.6-35B-A3B-UD-Q4_K_XL, 65536 context, self-MTP draft (n_max=2), q8_0/q8_0 KV,
flash attention, 4/8 threads, batch/ubatch 1024/512, MoE residency strategy,
SSD on (124 MiB), 5 GB cache-RAM, --moe-expert-residency + --load-mode mmap.
GPU budget: 22 GiB. LCP sim: 0.969, 0.888, 0.880, 0.946, 0.971; f_keep: 0.995, 0.993, 0.994, 0.991, 0.994.
| Turn | Prompt tokens | Decode tokens | TTFT (s) | PP (t/s) | Gen (t/s) |
|---|---|---|---|---|---|
| T0 | 24,432 | 182 | 109.1 | 224.0 | 24.5 |
| T89 | 843 | 237 | 5.1 | 164.2 | 24.4 |
| T176 | 3,251 | 213 | 19.0 | 171.0 | 22.7 |
| T258 | 3,954 | 303 | 23.4 | 168.9 | 20.9 |
| T384 | 1,840 | 214 | 11.6 | 158.3 | 21.7 |
| T467 | 1,009 | 1,274 | 6.6 | 152.6 | 20.5 |
Total: 5:19 (35,329 prompt + 2,423 decode = 37,752 tokens). Draft acceptance: 96.0%, 92.2%, 90.8%, 76.9%, 87.8%, 68.5%.
DeepSeek-V4-Flash-731-UD-IQ3_XXS, 131072 context, no draft, q4_0/q4_0 KV, flash attention, 16/32 threads, batch/ubatch 4096/2048, GPU strategy, SSD off, 5 GB cache-RAM. GPU budget: 112 GiB. LCP sim: 0.964, 0.824, 0.917; f_keep: 0.997, 0.996, 0.995.
| Turn | Prompt tokens | Decode tokens | TTFT (s) | PP (t/s) | Gen (t/s) |
|---|---|---|---|---|---|
| T0 | 24,786 | 171 | 115.0 | 215.5 | 11.8 |
| T178 | 1,023 | 170 | 10.0 | 102.1 | 11.9 |
| T349 | 5,607 | 238 | 39.0 | 143.9 | 11.8 |
| T589 | 2,934 | 1,332 | 22.8 | 128.6 | 11.7 |
Total: 6:03 (34,350 prompt + 1,911 decode = 36,261 tokens).
Laguna-S-2.1-UD-Q5_K_XL, 131072 context, no spec-decode draft (DFlash target architecture), q8_0/q8_0 KV, flash attention, 16/32 threads, batch/ubatch 4096/2048, GPU strategy, SSD off, 11 GB cache-RAM. GPU budget: 112 GiB. LCP sim: 0.998, 0.957, 0.707, 0.979, 0.999, 0.753, 0.920, 0.852; f_keep: 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 0.996.
| Turn | Prompt tokens | Decode tokens | TTFT (s) | PP (t/s) | Gen (t/s) |
|---|---|---|---|---|---|
| T0 | 24,113 | 225 | 48.0 | 502.7 | 17.2 |
| T233 | 54 | 102 | 0.9 | 63.4 | 17.5 |
| T337 | 1,093 | 136 | 3.0 | 359.1 | 17.4 |
| T475 | 10,634 | 206 | 26.0 | 408.7 | 16.8 |
| T686 | 773 | 165 | 2.7 | 285.6 | 16.8 |
| T853 | 54 | 189 | 0.9 | 62.8 | 16.8 |
| T1044 | 12,403 | 198 | 33.6 | 368.9 | 16.2 |
| T1247 | 4,364 | 224 | 13.2 | 329.4 | 16.0 |
| T1474 | 9,519 | 298 | 28.8 | 330.2 | 15.6 |
| T1776 | 2,472 | 389 | 8.7 | 283.4 | 15.4 |
| T2168 | 1,658 | 225 | 5.7 | 289.8 | 15.4 |
| T2395 | 2,032 | 286 | 6.9 | 293.5 | 15.3 |
| T2683 | 2,323 | 377 | 8.7 | 265.6 | 15.2 |
| T3063 | 1,008 | 155 | 4.0 | 250.4 | 15.2 |
| T3220 | 63 | 51 | 1.0 | 60.2 | 15.3 |
| T3273 | 63 | 2,114 | 1.1 | 59.9 | 15.1 |
Total: 9:36 (72,626 prompt + 5,340 decode = 77,966 tokens).
- Strix Halo dominates. Session 1 (Qwen3.6 Q8_K_XL, halo) hits 68 t/s prompt and 47 t/s generation, versus 22/21 t/s on Flip (Session 2) with the same model at Q4_K_XL. The 112 GiB GPU budget and f16 KV cache let the Lightning Indexer run at full throughput.
- DeepSeek IQ3_XXS is compute-bound. At 184 t/s prompt and 12 t/s generation, the 1.6T-parameter model pushes the GPU hard -- generation is 3-4x slower than Qwen3.6 despite similar parameter count, because DeepSeek's compressed-KV MLA architecture has higher per-token compute.
- Laguna has the highest token throughput. With 376 t/s prompt and 16 t/s generation over 16 turns and 77,966 tokens, it sustains steady performance across a long conversation. The DFlash target architecture and q8_0 KV keep memory pressure low on the halo budget.
- Flip's small budget limits batch/ubatch. The 22 GiB GPU budget forces batch/ubatch down to 1024/512 (from 4096/2048 on halo), plus MoE residency and SSD caching. Despite this, generation (21 t/s) is close to half of halo's (47 t/s), not the 4x you might expect from the GPU gap -- the bottleneck shifts to decode rather than prefill.
- Draft model helps Qwen3.6. The self-MTP draft (n_max=2) delivers 84-85% acceptance, cutting generated tokens roughly in half. DeepSeek and Laguna run without spec decoding.
llama-ai/
├── llama-run.sh # Main entry point — model detection, server launch
├── CachyLLama/ # Submodule — our fork of llama.cpp
├── scripts/
│ ├── rebuild.sh # Build script (Vulkan default, optional ROCm)
│ ├── env.sh # Environment setup (source before ROCm tools)
│ ├── detect-gpu.sh # GPU/APU and CPU ISA auto-detection
│ ├── optimize.sh # Optimistic-first solver (sourced by llama-run.sh)
│ ├── benchmark.sh # KV cache performance testing
│ ├── apply-ttm-kernel-params.sh # GPU memory config (GRUB + systemd-boot)
│ ├── install-deps.sh # Dependency installer
│ ├── lib-discover-models.sh # HuggingFace model discovery
│ ├── read_gguf_kv.py # GGUF metadata reader
│ └── log_analyzer.py # Benchmark log analysis
├── src/
│ ├── cachy-llama-vulkan/ # Vulkan build output + build.sh
│ ├── cachy-llama-rocm/ # ROCm build output + build.sh
│ └── cachy-llama-metal/ # Metal build output + build.sh (macOS)
├── deps/ # ROCm SDK (downloaded by rebuild.sh, gitignored)
├── models/ # GGUF files (gitignored)
├── kv-cache/ # SSD-backed KV cache (gitignored)
├── scratch/ # Transient working files (gitignored)
└── benchmarks/ # Results with full server logs
See AGENTS.md for the technical reference (build commands, code style, directory conventions).
Source code: GPL-3.0-or-later Documentation: CC-BY-NC-SA-4.0
CachyLLama (the inference engine) is MIT-licensed (same as upstream llama.cpp, Copyright (c) 2023-2026 The ggml authors). The llama-ai runner scripts, GPU detection, solver, and benchmark harness are GPL-3.0-or-later. ROCm components carry AMD's license. See the CachyLLama LICENSE and CachyLLama README for details.