Top-level at-a-glance table with PPL, NIAH, speed ratios across all tested hardware, compression ratios, and link to decode regression analysis doc. Co-Authored-By: tturney@psyguard.ai Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
23 KiB
TurboQuant Diagnostic Scripts
Hardware profiling, benchmarking, and quality validation for TurboQuant cache types.
Quality & Speed At A Glance
Quality (Qwen3.5-35B-A3B MoE Q8_0, M5 Max 128GB):
| Config | PPL vs q8_0 | NIAH Single (4K-32K) | NIAH Multi-Key (4K-32K) |
|---|---|---|---|
| Uniform turbo3 | +1.1% | 18/20 (q8_0: 17/20) | 4/4 perfect |
| Mode 2 (layer-adaptive) | +0.14% | — | — |
turbo3 matches q8_0 on needle retrieval. The few single-needle misses at 32K overlap with positions where q8_0 also fails.
Speed (turbo3/q8_0 ratio):
| Hardware | Prefill | Decode (short) | Decode (32K) | Notes |
|---|---|---|---|---|
| M5 Max 128GB | 0.95-1.00x | 0.92x | 0.75x | Healthy gradient |
| M2 Pro 32GB | 1.32-1.73x | ~0.73x | pending | Prefill wins big on lower bandwidth |
| M1 Max 64GB | 0.93-0.98x | 0.83x | 0.39x | Constant memory LUT regression |
| Dual 4090 (CUDA) | 0.98x | 0.37x (MoE, 42K) | — | buun investigating, fix in progress |
Pre-M5 decode regression is a known hardware limitation, not a quality issue. Fully documented in decode-speed-hardware-analysis.md. 12 kernel approaches tested, root cause is constant memory LUT divergence on Apple7/Apple8 GPU families.
Compression:
| Config | Effective ratio | Bits per value |
|---|---|---|
| Uniform turbo3 | 4.6x | 3.5 |
| Mode 2 (q8_0 last 8 layers) | 3.5x | ~4.6 |
| + Temporal decay | +30-34% savings at 32K+ | — |
Quick Start
# 1. Clone the repo
git clone https://github.com/TheTom/turboquant_plus.git
cd turboquant_plus
# 2. Run the diagnostic
bash scripts/turbo-diag /path/to/llama.cpp /path/to/model.gguf
That's it. The launcher automatically:
- Finds or creates an isolated Python venv (
.turbo-diag-venv/) - Installs
richfor a color-coded terminal UI (falls back to ASCII if it fails) - Runs the full diagnostic suite
Estimated runtime: 20–40 minutes depending on hardware. Use --skip-ppl and --skip-stress to cut that roughly in half.
Requirements: Python 3.10+, a built llama.cpp with turbo3 support, and a .gguf model file.
Which fork do I build?
| Hardware | Fork | Branch |
|---|---|---|
| Apple Silicon (M1-M5) | TheTom/llama-cpp-turboquant | feature/turboquant-kv-cache |
| NVIDIA (CUDA) | spiritbuun/llama-cpp-turboquant-cuda | feature/turboquant-kv-cache |
| AMD (ROCm) | spiritbuun's fork (has RDNA v_dot2 path) |
feature/turboquant-kv-cache |
AMD users: ROCm support is untested and experimental. Flash attention is reportedly broken on RDNA 2 (6800 XT) with ROCm > 7.0.1, and turbo3 requires flash attention. RDNA 3/4 may work but nobody has confirmed yet. Expect rough edges and please report what you find.
# Apple Silicon
git clone https://github.com/TheTom/llama-cpp-turboquant.git
cd llama-cpp-turboquant && git checkout feature/turboquant-kv-cache
cmake -B build -DGGML_METAL=ON -DGGML_METAL_EMBED_LIBRARY=ON -DCMAKE_BUILD_TYPE=Release
cmake --build build -j
# NVIDIA CUDA
git clone https://github.com/spiritbuun/llama-cpp-turboquant-cuda.git
cd llama-cpp-turboquant-cuda && git checkout feature/turboquant-kv-cache
cmake -B build -DGGML_CUDA=ON -DCMAKE_BUILD_TYPE=Release
cmake --build build -j
# AMD ROCm
git clone https://github.com/spiritbuun/llama-cpp-turboquant-cuda.git
cd llama-cpp-turboquant-cuda && git checkout feature/turboquant-kv-cache
cmake -B build -DGGML_HIP=ON -DCMAKE_BUILD_TYPE=Release
cmake --build build -j
Important: Flash attention must be enabled. Add -fa on (server) or -fa 1 (bench/CLI) to all commands. Without it, turbo3 will silently fail on pre-M5 hardware.
How It Runs
turbo-diag
|
+-------------+-------------+
| |
Creates venv Installs rich
(.turbo-diag-venv/) (optional UI)
| |
+-------------+-------------+
|
turbo_hardware_diag.py
|
+------------------+------------------+
| | |
Detect Hardware Start Monitor Start Display
(CPU/GPU/RAM) (10s CSV poll) (rich or ASCII)
| | |
+------------------+------------------+
|
Run 13 Diagnostic Sections
+---------------------------------+
| 1. Hardware Inventory |
| 2. System Load (pre) |
| 3. Model Info |
| 4. GPU Capabilities |
| 5. Build Validation |
| 6. Prefill Speed (5 depths) | <-- llama-bench
| 7. Decode Speed (5 depths) | <-- llama-bench
| 8. Stress Test (11 depths) | <-- llama-bench
| 9. Combined Workload | <-- llama-bench
| 10. Perplexity (quality gate) | <-- llama-perplexity
| 11. Memory Breakdown | <-- llama-cli
| 12. System Load (post) |
| 13. Summary + Anomaly Report |
+---------------------------------+
|
+-------------+-------------+
| | |
.txt log .json profile .csv monitor
| | |
+-------------+-------------+
|
turbo-diag-*.zip
(send this to us!)
What It Generates
The diagnostic produces a single zip file you send back to the team:
turbo-diag-20260326-143022.zip
├── turbo-diag-20260326-143022.txt # Human-readable log (all sections)
├── turbo-hwprofile-20260326-143022.json # Machine-parseable hardware profile
└── turbo-monitor-20260326-143022.csv # Background system metrics (polled every 10s)
Output Files
| File | Format | Purpose |
|---|---|---|
turbo-diag-*.txt |
Plain text | Full diagnostic log — hardware inventory, benchmark tables, anomaly flags, and how-to-read guide. Compatible with hw_replay.py for programmatic parsing. |
turbo-hwprofile-*.json |
JSON | Structured hardware profile: CPU, RAM, GPU family, model metadata, and build info. Used for cross-machine comparison. |
turbo-monitor-*.csv |
CSV | Background metrics captured every 10 seconds during the entire run. |
Monitor CSV Columns
| Column | Description |
|---|---|
timestamp |
UTC ISO 8601 timestamp |
load_1m |
1-minute load average |
mem_pressure_pct |
Memory pressure percentage (active+wired pages) |
swap_used_mb |
Swap usage in MB |
gpu_temp_c |
GPU temperature in Celsius (NVIDIA only; N/A on macOS) |
cpu_speed_limit |
macOS CPU speed limit percentage (100 = no throttling) |
gpu_mem_used_mb |
GPU memory used in MB (unified on Apple Silicon) |
gpu_util_pct |
GPU utilization percentage (NVIDIA only) |
How to Read Results
The .txt log is organized into 13 numbered sections. Here's what to look at, in order of importance:
Check quality first (Section 10)
Perplexity (PPL) must be within 2% of q8_0. If PPL is broken, all speed numbers are meaningless.
Decode speed — the critical metric (Section 7)
This is the test that matters most. Look at the turbo3/q8_0 ratio at each context depth:
- Healthy (M5 Max):
0.92x (short) → 0.72x (48K)— gradual, predictable degradation - Problem (M1):
0.90x (short) → 0.09x (42K)— constant cache thrashing, falls off a cliff
Prefill speed (Section 6)
turbo3/q8_0 ratio should be flat at 0.95–1.00x across all depths. If it degrades with context length, that's a context scaling regression.
Stress test inflection point (Section 8)
Fine-grained decode scaling gradient. Shows the exact context depth where turbo3 decode starts falling apart. This is the most useful data for diagnosing constant cache issues on specific hardware.
Thermal comparison (Sections 2 vs 12)
Compare pre-benchmark and post-benchmark system load. If CPU_Speed_Limit dropped below 100 during the test, results may be artificially low due to thermal throttling.
Memory breakdown (Section 11)
KV cache sizing at multiple context lengths. If the system is near its recommendedMaxWorkingSetSize, swap pressure will kill decode performance.
Section-by-Section Reference
| Section | Title | What It Tells You |
|---|---|---|
| 1 | Hardware Inventory | CPU, RAM, GPU, cache hierarchy, and power state (no PII) |
| 2 | System Load (pre-benchmark) | Baseline load, memory pressure, top CPU consumers, and disk I/O |
| 3 | Model Info | Model architecture, params, layer count, expert count, and file type |
| 4 | GPU Device Capabilities | GPU family, Metal version, Tensor API support, and unified memory |
| 5 | Build Validation | Confirms turbo3 type is available, Metal library loads, and build commit |
| 6 | Prefill Speed | Prefill tok/s at 2K/4K/8K/16K/32K for q8_0, turbo3, and mode 2 |
| 7 | Decode Speed | Decode tok/s at short/4K/8K/16K/32K — the critical test |
| 8 | Constant Cache Stress Test | Fine-grained decode gradient at 2K–32K in ~2K increments |
| 9 | Combined Prefill+Decode | Realistic workload simulation (prefill then decode) |
| 10 | Perplexity | Quality validation — PPL for q8_0, turbo3, and turbo3 mode 2 |
| 11 | Memory Breakdown | KV cache sizing at multiple context lengths |
| 12 | System Load (post-benchmark) | Post-test load, thermal throttling check |
| 13 | Diagnostic Summary | All anomalies, how-to-read guide, and next steps |
Known Baselines
M5 Max Reference Numbers (Qwen 35B MoE Q8_0)
| Metric | Expected | Notes |
|---|---|---|
| Prefill ratio (turbo3/q8_0) | 0.95–1.00x | Flat across all depths |
| Decode ratio (short context) | ~0.92x | Small overhead from WHT transform |
| Decode ratio (32K context) | ~0.75x | Gradual degradation, still usable |
| PPL delta vs q8_0 | < 2% | Typically < 1% |
M1 Max 64GB Community Results (Qwen 35B MoE Q8_0)
Tester 1: @Ambisphaeric
2026-03-26. GPU: Apple M1 Max, 32 cores, 64GB unified.
Prefill (tok/s):
| Context | q8_0 | turbo3 | Ratio |
|---|---|---|---|
| 2K | 855.7 | 836.1 | 0.977x |
| 4K | 805.2 | 784.0 | 0.974x |
| 8K | 724.6 | 696.3 | 0.961x |
| 16K | 600.5 | 569.6 | 0.949x |
| 32K | 444.9 | 414.0 | 0.931x |
Decode (tok/s, tg128):
| Context | q8_0 | turbo3 | Ratio | Flag |
|---|---|---|---|---|
| short | 41.2 | 34.5 | 0.837x | |
| 4K | 31.9 | 20.4 | 0.640x | |
| 8K | 27.5 | 14.8 | 0.538x | |
| 16K | 20.7 | 9.4 | 0.454x | ANOMALY |
| 32K | 13.7 | 5.3 | 0.387x | ANOMALY |
Tester 2: @mariotomich
2026-03-26. GPU: Apple M1 Max, 32 cores, 64GB unified. Clean system (94% free memory, low load). Full diagnostic run with mode 2.
Prefill (tok/s):
| Context | q8_0 | turbo3 | mode2 | turbo3 ratio | mode2 ratio |
|---|---|---|---|---|---|
| 2K | 859.2 | 844.5 | 848.2 | 0.983x | 0.987x |
| 4K | 810.5 | 789.6 | 795.6 | 0.974x | 0.982x |
| 8K | 731.2 | 698.9 | 707.1 | 0.956x | 0.967x |
| 16K | 599.9 | 570.9 | 575.9 | 0.952x | 0.960x |
| 32K | 450.0 | 416.4 | 423.5 | 0.925x | 0.941x |
Decode (tok/s, tg128):
| Context | q8_0 | turbo3 | mode2 | turbo3 ratio | mode2 ratio | Flag |
|---|---|---|---|---|---|---|
| short | 42.9 | 35.6 | 36.9 | 0.830x | 0.860x | |
| 4K | 34.3 | 20.8 | 22.1 | 0.606x | 0.645x | |
| 8K | 28.1 | 14.8 | 16.3 | 0.526x | 0.580x | ANOMALY |
| 16K | 21.0 | 8.7 | 9.7 | 0.414x | 0.462x | ANOMALY |
| 32K | 13.8 | 5.4 | 6.1 | 0.391x | 0.442x | ANOMALY |
Cross-validation: Both M1 Max testers show nearly identical results, confirming the M1 decode regression is hardware-dependent, not user-specific. Mode 2 (layer-adaptive) consistently adds ~5% decode improvement over uniform turbo3.
Decode degrades significantly at long context. Known M1 issue: constant memory LUT contention in the flash attention dequant path. M5 Max (with tensor API) does not have this problem.
M2 Pro 32GB (Mac Mini) Results (Qwen2.5-7B Q4_K_M) — Partial
Internal test (2026-03-26). GPU: Apple M2 Pro, 19-core GPU, Apple8 family (1008), has_tensor=false, 32GB unified, Metal 4. Build: 02268fc (norm correction). Clean run after killing competing workloads.
Prefill (tok/s):
| Context | q8_0 | turbo3 | Ratio |
|---|---|---|---|
| 2K | 155.7 | 263.6 | 1.69x |
| 4K | 143.2 | 245.3 | 1.71x |
| 8K | 119.5 | 206.3 | 1.73x |
| 16K | 93.5 | 144.3 | 1.54x |
| 32K | 63.3 | 83.7 | 1.32x |
turbo3 prefill is 32-73% faster than q8_0 on M2 Pro. Much bigger gap than M5 Max (~parity). Hypothesis: M2 Pro's lower memory bandwidth (200 GB/s vs M5 Max 546 GB/s) makes the compressed cache win harder since turbo3 moves less data per token.
Decode: Test hung at 32K context, no decode data collected. Rerun pending with timeout fix. Expect M1-like decode regression (has_tensor=false, same constant memory LUT issue).
Mode 2 prefill: Hung at pp16384. High variance at 8K+ suggests memory pressure beginning (32GB with 4GB model + KV cache + system overhead).
What "Good" vs "Bad" Looks Like
| Indicator | Good | Bad |
|---|---|---|
| Decode ratio curve | Gradual slope, stays above 0.70x at 32K | Cliff drop below 0.50x at any depth |
| Prefill ratio | Flat 0.95–1.00x | Drops with context depth |
| PPL delta | < 2% | > 5% (quality regression) |
| CPU Speed Limit | 100 before and after | Drops below 90 during test |
| Swap growth | < 50 MB | > 100 MB (memory pressure) |
Anomaly Flags
The diagnostic auto-detects these issues and tags them [ANOMALY] in the log:
- Steep decode degradation — ratio drops >15% between consecutive depths
- Thermal throttling —
CPU_Speed_Limitdropped below 100% - Memory pressure/swapping — swap grew >100 MB during benchmarks
- Low q8_0 baseline — system was under load (q8_0 decode < 5 tok/s)
- Quality regression — turbo3 PPL >10% worse than q8_0
- Unreliable 1K measurement — Metal async dispatch artifact (>10,000 tok/s at 1K context)
Hardware Replay System
The hw_replay.py module (turboquant/hw_replay.py) parses diagnostic output into structured Python objects for programmatic analysis.
Load a diagnostic profile
from turboquant.hw_replay import HardwareProfile
profile = HardwareProfile.from_diag_file("turbo-diag-20260326.txt")
# Or from the JSON profile
profile = HardwareProfile.from_json("turbo-hwprofile-20260326.json")
Extract performance curves
# Decode speed vs context depth
decode_curve = profile.get_decode_curve("turbo3")
# {0: 42.1, 4096: 38.7, 8192: 35.2, ...}
# turbo3/q8_0 ratio at each depth
ratio_curve = profile.get_ratio_curve("turbo3", "q8_0", "decode")
# {0: 0.92, 4096: 0.89, 8192: 0.85, ...}
# Find the inflection point (where decode falls off a cliff)
inflection = profile.find_decode_inflection("turbo3")
# 16384 (or None if the curve is smooth)
Compare two profiles
from turboquant.hw_replay import compare_profiles
m5 = HardwareProfile.from_diag_file("diag-m5-max.txt")
m1 = HardwareProfile.from_diag_file("diag-m1-ultra.txt")
report = compare_profiles(m5, m1)
print(report.to_markdown())
Predict performance on untested hardware
from turboquant.hw_replay import predict_decode_from_baseline
# Predict M1 (family 1007, no tensor API) from M5 baseline
predicted = predict_decode_from_baseline(
baseline=m5,
target_gpu_family_id=1007,
target_has_tensor=False,
)
# {0: 0.92, 4096: 0.85, 8192: 0.71, 16384: 0.34, ...}
Command Reference
usage: turbo_hardware_diag.py [-h] [--model MODEL] [--llama-dir LLAMA_DIR]
[--skip-ppl] [--skip-stress] [--verbose]
[--output-dir OUTPUT_DIR]
[llama_dir] [model_path]
Positional Arguments
| Argument | Description |
|---|---|
llama_dir |
Path to llama.cpp directory (default: current directory) |
model_path |
Path to .gguf model file (auto-detected if not specified) |
Optional Flags
| Flag | Description |
|---|---|
--model MODEL |
Path to .gguf model file (alternative to positional arg) |
--llama-dir DIR |
Path to llama.cpp directory (alternative to positional arg) |
--skip-ppl |
Skip perplexity tests — saves ~10 minutes |
--skip-stress |
Skip the fine-grained stress test (Section 8) |
--verbose, -v |
Enable verbose/debug-level logging |
--output-dir DIR, -o DIR |
Output directory for diagnostic files (default: current directory) |
Examples
# Positional args (most common)
bash scripts/turbo-diag ~/llama.cpp ~/models/Qwen3.5-35B-A3B-Q8_0.gguf
# Named flags
bash scripts/turbo-diag --llama-dir ~/llama.cpp --model ~/models/model.gguf
# Quick run — skip slow tests
bash scripts/turbo-diag ~/llama.cpp ~/models/model.gguf --skip-ppl --skip-stress
# Verbose output to a specific directory
bash scripts/turbo-diag ~/llama.cpp ~/models/model.gguf -v -o ~/diag-results/
Before You Run
Plug in your charger and close heavy apps. On Apple Silicon Macs, battery power triggers CPU/GPU throttling that produces artificially low speed numbers — we observed 2-3x slower benchmarks on an M5 Max running on battery vs plugged in. The diagnostic will detect throttling (CPU_Speed_Limit in Section 12), but the damage is already done to earlier results. NVIDIA laptops have similar power management behavior.
Close Chrome, video calls, and anything GPU-heavy before running. The diagnostic captures top CPU/GPU consumers (Section 2), so interference will be visible in the log, but clean results are always better.
Troubleshooting
"turbo3 not recognized" or cache type errors
Your llama.cpp build doesn't have turbo types compiled in. Rebuild with the TurboQuant patches applied:
cd /path/to/llama.cpp
cmake -B build -DGGML_METAL=ON -DGGML_METAL_EMBED_LIBRARY=ON -DCMAKE_BUILD_TYPE=Release
cmake --build build -j
"No .gguf model found"
The auto-detection searches llama.cpp/models/, ../models/, and ~/local_llms/models/. If your model lives somewhere else, pass the path explicitly:
bash scripts/turbo-diag ~/llama.cpp /path/to/your/model.gguf
Benchmarks are painfully slow
Use the skip flags to cut runtime roughly in half:
bash scripts/turbo-diag ~/llama.cpp model.gguf --skip-ppl --skip-stress
PPL tests take ~10 minutes, and the stress test adds another ~10 minutes of fine-grained decode sweeps.
Script crashes on startup
- Python version: Requires 3.10+. The launcher checks this automatically, but if you're running
turbo_hardware_diag.pydirectly, verify withpython3 --version. - Missing binaries: The script needs
llama-bench,llama-perplexity, andllama-cliin<llama-dir>/build/bin/. If any are missing, rebuild llama.cpp. - Permissions: On macOS,
system_profilerandpmsetmay require user consent the first time. If hardware detection fails, the diagnostic still continues — it just logs warnings.
Rich terminal UI not showing
If you see plain ASCII instead of color tables, the rich package didn't install. This is cosmetic — all results are identical. To fix:
pip install rich
# Or delete the venv and let turbo-diag recreate it:
rm -rf scripts/.turbo-diag-venv
Other Scripts
turbo-quality-gate.sh
Pre-push quality and speed gate. Run before pushing any TurboQuant changes.
What it checks:
- Perplexity — turbo3 PPL must be within 5% of q8_0 baseline (8 chunks of wikitext-2)
- Context scaling — turbo3/q8_0 speed ratio at 4K context must be > 0.95x
Usage:
bash scripts/turbo-quality-gate.sh
# Exit 0 = PASS, Exit 1 = FAIL
Configure paths via environment variables: LLAMA, MODEL, WIKI.
turbo-realworld-bench.sh
Real-world decode benchmark using a long PDF document (70+ pages). Compares turbo3 vs q8_0 at realistic context depths by spinning up llama-server instances, sending the full PDF as a prompt, and measuring prefill + decode speed.
Usage:
bash scripts/turbo-realworld-bench.sh [path-to-pdf]
Requirements: A long PDF file (not included in repo), pdftotext (from poppler), and a built llama.cpp with turbo3 support. Configure via LLAMA, MODEL, PORT_BASE, THREADS, and MAX_TOKENS env vars.
Sharing Results
After the diagnostic completes, you'll see:
============================================
DIAGNOSTIC PACKAGE READY
============================================
Zip file: /path/to/turbo-diag-20260326-143022.zip
Contents:
turbo-diag-20260326-143022.txt
turbo-hwprofile-20260326-143022.json
turbo-monitor-20260326-143022.csv
Send this zip file to the TurboQuant team.
How to share:
- GitHub issue (preferred): Open an issue at turboquant_plus with title "Diagnostic: [your hardware]" and attach the zip. GitHub supports attachments up to 25MB — the zip is typically under 100KB.
- DM on X/Twitter: Send the zip file directly to @no_stp_on_snek
The zip is self-contained — everything we need is inside. No need to copy-paste logs or screenshot terminal output. The .txt log is human-readable if you want to look at it yourself first.
Privacy
No PII is collected. The diagnostic is designed to be safe to share publicly.
What IS collected
- Hardware specs: CPU model, core count, RAM size, GPU family, cache hierarchy
- GPU capabilities: Metal/CUDA version, Tensor API support, VRAM/unified memory size
- Benchmark numbers: tok/s for prefill, decode, combined, and perplexity
- System load: load average, memory pressure, swap usage, CPU speed limit
- Build info: llama.cpp commit hash
- Model metadata: filename, file size, architecture, layer/head/expert counts
What is NOT collected
- Usernames, home directory paths, or hostnames
- Network configuration, IP addresses, or MAC addresses
- File system contents or directory listings
- Environment variables (except
TURBO_LAYER_ADAPTIVEwhen explicitly set) - Model weights or prompt content
- Any data that could identify you or your machine beyond hardware specs