You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 1233435
Browse filesBrowse the repository at this point in the historyBrowse files
- Fix: Prevent prompt cache from decoding TurboQuant compressed polar buffers back to fp16, saving ~19GB GPU allocation at 100K context.
- Feat: Add GPU allocation tracking via ioreg to capture true memory demand including swap memory.
- Docs: Update README with benchmark summary and multi-device profiling results structure.
- Add run-benchmark workflow skill.
- Add total memory and active GPU memory monitoring in MemoryUtils.
description: Run the extreme context benchmark suite for SwiftLM profiling
3
+
---
4
+
5
+
# Run Extreme Context Benchmark
6
+
7
+
This workflow runs the full 12-test profiling matrix (4 configurations × 3 context depths) for a given model and captures performance + memory metrics.
8
+
9
+
## Prerequisites
10
+
11
+
// turbo
12
+
1. Ensure the SwiftLM binary is built:
13
+
```bash
14
+
swift build -c release
15
+
```
16
+
17
+
## Run the Benchmark
18
+
19
+
2. Kill any existing SwiftLM server:
20
+
```bash
21
+
killall SwiftLM 2>/dev/null; sleep 2
22
+
```
23
+
24
+
// turbo
25
+
3. Run the profiling suite with the target model:
26
+
```bash
27
+
python3 -u scripts/profiling/profile_runner.py \
28
+
--model gemma-4-26b-a4b-it-4bit \
29
+
--contexts "512,40000,100000" \
30
+
--out ./profiling_results_$(hostname -s).md
31
+
```
32
+
33
+
The profiler will:
34
+
- Start SwiftLM with each configuration (`Dense/Vanilla`, `SSD Stream`, `TurboQuant`, `SSD + TurboQuant`)
35
+
- Send requests at each context depth (512, 40K, 100K tokens)
36
+
- Measure TTFT, TPS, Active RAM, and GPU Alloc (via `ioreg AGXAccelerator`)
37
+
- Save results to the output markdown file
38
+
39
+
## Expected Runtime
40
+
41
+
| Context Size | Approximate Time per Config |
42
+
|---|---|
43
+
| 512 |~10 seconds |
44
+
| 40,000 |~40 seconds |
45
+
| 100,000 |~120 seconds |
46
+
47
+
**Total**: ~12 minutes for the full 12-test matrix (4 configs × 3 contexts)
48
+
49
+
## Customizing
50
+
51
+
-**Different model**: Replace `gemma-4-26b-a4b-it-4bit` with any MLX model ID
52
+
-**Different contexts**: Change `--contexts` (comma-separated list of token counts)
53
+
-**Output file**: Change `--out` path
54
+
55
+
## After the Benchmark
56
+
57
+
4. Review the generated markdown file and check for any `FAILED / OOM` entries.
58
+
59
+
5. If contributing results back to the project, append the device section to `profiling_results.md`:
Benchmark results for `gemma-4-26b-a4b-it-4bit` (26B MoE, 4-bit) on M5 Pro 64 GB. Full results and methodology at **[profiling_results.md](profiling_results.md)**.
> Values shown as `generation speed · GPU memory allocated`
27
+
28
+
**Key takeaways:**
29
+
- 🖥️ **8 GB Mac Mini**: SSD Stream runs a 26B model at **4.6 GB Active RAM**
30
+
- 📄 **40K context on 24 GB MacBook Pro**: SSD + TurboQuant fits in **22.7 GB**
31
+
- 📚 **100K context on 32 GB Mac Studio**: SSD + TurboQuant fits in **33.3 GB** — previously required 64 GB
32
+
33
+
Run the benchmark on your device:
34
+
```bash
35
+
python3 scripts/profiling/profile_runner.py \
36
+
--model gemma-4-26b-a4b-it-4bit \
37
+
--contexts "512,40000,100000"
38
+
```
39
+
40
+
> We welcome PRs with results from other Apple Silicon devices! See [profiling_results.md](profiling_results.md#contributing-your-results) for details.
41
+
42
+
---
43
+
11
44
## 🚀 Features
12
45
13
46
- 🍎 **100% Native Apple Silicon**: Powered natively by Metal and Swift.
SwiftLM is designed to leverage Apple Silicon Unified Memory limits to their absolute maximum. All first-party benchmarks are generated on an **Apple M5 Pro (64 GB Unified Memory)**.
53
-
54
-
Since we have limited access to the full spectrum of Apple hardware, **we welcome Pull Requests with benchmark results** from other devices (especially base M1/M2/M3 chips and Mac Studios).
55
-
56
-
To run the extreme context benchmark suite on your device, execute:
57
-
```bash
58
-
bash tests/run_extreme_context.sh <model-id>
59
-
```
83
+
## 💻 Additional Benchmarks
60
84
61
85
### Prompt Cache & Sliding Window Regression Test
62
86
To verify the stability of the prompt cache when interleaving long contexts with sliding window attention (e.g. Gemma 4/Mistral 3), run this extreme test sequence:
@@ -73,15 +97,6 @@ echo "=== ALL 4 PASSED ==="
73
97
```
74
98
If you see `ALL 4 PASSED` without `SIGTRAP` or `curl: (52) Empty reply from server`, the memory bounds are stable.
75
99
76
-
### Extreme Context Performance (100K Tokens)
77
-
Tested on M5 Pro (64GB) handling a monolithic **100,000 token** system prompt with **TurboKV Acceleration** enabled.
78
-
79
-
| Model | Configuration | Time To First Token (TTFT) | Peak GPU Memory (w/ TurboKV) |
Tested by rendering exactly 20 tokens under standard conversational evaluation (`--prefill-size 512`) to capture precise Token Generation (TPS) and Apple Metal memory footprint limits:
print("[SwiftLM] 🧠 Skipping prompt cache save — TurboQuant has compressed \(cache.compactMap{($0 as?KVCacheSimple)?.compressedOffset }.max()??0) tokens. Saving would decode ~37 GB back to fp16.")
Copy file name to clipboardExpand all lines: journal.md
+60Lines changed: 60 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -137,3 +137,63 @@ You maintain a strict budget of RAM (e.g., 2 GB for active experts). The experts
137
137
#### Summary
138
138
What you are poking at is exactly the future of local LLMs. Models are getting too big for VRAM, but SSDs are getting fast enough to bridge the gap if the model architecture cooperates. By changing the training objective to favor temporal blocks and expert knockout, you are effectively "hardware-aware" training the model to be friendly to the SSD PCIe lane.
139
139
It completely shifts the bottleneck from the hardware (bus speed) to the algorithm (routing predictability).
With Gemma 4-26B stable and generating, we needed to answer the real deployment question: *How does this model behave at extreme context lengths across different memory configurations?* We built `scripts/profiling/profile_runner.py` — an automated profiling framework that:
148
+
- Iterates through 4 configurations: Dense/Vanilla, SSD Stream, TurboQuant, SSD+TurboQuant
149
+
- Tests across 3 context depths: 512, 40K, and 100K tokens
150
+
- Captures both **Active RAM** (OS physical footprint via `mach_task_basic_info`) and **GPU Memory Allocated** (Apple GPU driver allocation via `ioreg AGXAccelerator`)
151
+
152
+
### 11. The `ioreg` Breakthrough
153
+
154
+
The initial profiling used only `phys_footprint` — the OS physical memory metric. But at 100K context, both Dense/Vanilla (49.3 GB) and TurboQuant (49.3 GB) showed identical numbers. This made no sense — TurboQuant was clearly compressing the KV cache.
155
+
156
+
The problem: `phys_footprint` is **capped by available physical RAM**. On a 64 GB machine, it tops out at ~49 GB regardless of actual demand. We needed to see the *total GPU allocation* including memory swapped to SSD.
157
+
158
+
Following the same pattern used by the Aegis-AI `HardwareDetector`, we queried Apple's `AGXAccelerator` GPU driver via `ioreg` for the `"Alloc system memory"` counter. This metric **CAN exceed physical RAM** — revealing the true memory demand:
The 52.6 GB vs 35.0 GB difference was invisible in the OS metric but clearly visible via `ioreg`.
166
+
167
+
### 12. 🐛 The Prompt Cache Bug
168
+
169
+
At 100K context, even with the `ioreg` metric, TurboQuant (52.5 GB) and Dense/Vanilla (52.1 GB) were nearly identical. Tracing through the code revealed the root cause:
170
+
171
+
1. During prefill, the full 100K fp16 KV cache is built (~37 GB)
172
+
2. After the first generation token, TurboQuant compresses it to ~3 GB of polar buffers ✅
173
+
3. But then `onPrefillDone` fires → the prompt cache calls `cache.state`
174
+
4. The `state` getter **decodes ALL compressed polar buffers back to full fp16** to create a restorable snapshot
175
+
5. The `eval()` call materializes this decoded copy — a fresh **~37 GB allocation**
176
+
6. Net result: compression savings completely negated
177
+
178
+
The key insight: for Dense/Vanilla, `cache.state` returns **views** (zero-copy references) of existing buffers. For TurboQuant, it creates **new arrays** via `turboDecodeK/V` — an O(N) memory allocation the size of the entire context.
179
+
180
+
### 13. 🔧 The Fix
181
+
182
+
One targeted change: skip prompt cache save when TurboQuant has actively compressed data.
**29.6 GB Active RAM for a 26B model at 100K tokens.** This fits in a 32 GB Mac Studio — previously required 64 GB.
192
+
193
+
### 📋 Files Changed
194
+
-`Sources/SwiftLM/MemoryUtils.swift` — Added GPU active memory and total demand metrics
195
+
-`Sources/SwiftLM/Server.swift` — OS_RAM + MEM_DEMAND + GPU_MEM logging at prefill and post-generation; prompt cache TurboQuant guard
196
+
-`scripts/profiling/profile_runner.py` — Full profiling framework with `ioreg` GPU allocation tracking
197
+
198
+
### 🎯 Key Lesson: Measure What Matters
199
+
The OS `phys_footprint` metric is what Activity Monitor shows — but it lies by omission. It's capped by physical RAM and doesn't reveal how much memory the GPU driver has actually allocated (including SSD-swapped pages). For memory-constrained deployment, the `ioreg AGXAccelerator "Alloc system memory"` counter is the ground truth.
0 commit comments