# Phase 0 Hardware Microbench — 13" MacBook Air M5 (`Mac17,3`)

```text
experiment: measure real uncached NVMe SSD throughput across MoE expert read patterns
            (3x512 KiB std GGUF vs 1x1.5 MiB contiguous .experts copy, queue depth 1..8,
            and 4 KiB PLE n-gram row lookups) plus M5 10-core GPU FP16 GEMM ceiling and
            60-second fanless thermal sag, to replace the 3 vs 6 GB/s and ~360 tok/s
            estimates in PLAN.md §5.2.
runs:
  - run_01: bench/m5_air_microbench.m -> bench/run_01_m5_air_microbench.log
  - run_02: bench/m5_air_microbench_v2.m -> bench/run_02_m5_air_microbench_v2.log
            (strictly disjoint offsets, F_NOCACHE=0 RAM control, 60s GPU thermal sag)
  - run_03: bench/m5_air_microbench_v3.m -> bench/run_03_m5_air_microbench_v3.log
            (12 GiB virgin slices, every test verified 100.0% physical NVMe via `iostat -Id` disk0)
execution: /usr/bin/clang -O3 -fobjc-arc -framework Foundation -framework Metal
           -framework MetalPerformanceShaders -lpthread; macOS 27.0 arm64, Apple M5
           (4S+6E CPU, 10-core GPU, 24 GB unified memory, 1 TB SSD).
decision: answered. PLAN.md §2.5, §4.3, and §5.2 updated with measured constants and
          two new runtime squeezes (2-4 thread pread fan-out for experts; 8-thread
          batched pread for layer-2 PLE 4 KiB row lookups).
```

## Key Measurements

### 1. SSD Read Throughput (`run_03`, 100.0% `disk0` hardware counter verified on virgin 1 GiB slices)

| Pattern | Read size | `th=1` (QD1) | `th=2` | `th=4` | `th=8` |
|---|---|---|---|---|---|
| Sequential ceiling | 4 MiB | 5.49 GB/s | — | **7.09 GB/s** | — |
| Standard GGUF (3 reads/expert, random) | 512 KiB | **2.41 GB/s** | 5.52 GB/s | **7.03 GB/s** | 7.04 GB/s |
| Contiguous `.experts` copy (1 read/expert, random) | 1.5 MiB | **3.93 GB/s** | **6.84 GB/s** | **6.89 GB/s** | 6.66 GB/s |
| Prefill strided ascending (`.experts` copy) | 1.5 MiB | **3.85 GB/s** | — | **6.60 GB/s** | — |
| PLE n-gram table random 4 KiB (`run_01`) | 4 KiB | **0.04 GB/s** (~10k IOPS, ~1.6 ms/tok for 16 heads) | — | — | **0.33 GB/s** (~82k IOPS, ~0.2 ms/tok) |
| Control: cached RAM (`F_NOCACHE=0`, `run_02`) | 512 KiB | 25.59 GB/s | — | 45.70 GB/s | — |

**Methodological catch during `run_02` → `run_03`:**
On macOS APFS, repeating `pread` over the exact same file offsets across loop iterations can partially hit APFS cache on iteration 2+ even when the descriptor has `F_NOCACHE=1`. `run_03` eliminated this by allocating a 12 GiB file of unique incompressible blocks, giving every single test its own virgin 1 GiB slice, and verifying `iostat -Id` (`disk0` physical MiB delta == 100.0% of requested MiB) around each test.

### 2. M5 10-Core GPU FP16 GEMM & 60-Second Thermal Sag (`run_01`, `run_02`)

- `MTLDevice`: `Apple M5`, `hasUnifiedMemory = 1`, `recommendedMaxWorkingSetSize = 17.76 GiB`
- Peak burst FP16 GEMM (`K = 2560`):
  - `M = 512, N = 2560`: **12.60 TFLOP/s**
  - `M = 1024, N = 2560`: **13.66 TFLOP/s**
  - `M = 4096, N = 2560`: **14.07 TFLOP/s** (exactly 1/4 of the 40-core M5 Max's ~56 TFLOP/s, confirming the **~356–360 tok/s** resident prefill compute ceiling at ~30.4% MFU)
- **60-second continuous 100% GPU load (`M = 4096, K = 2560, N = 2560` FP16 GEMM):**
  - `t = 0..15s`: **14.00–14.03 TFLOP/s**
  - `t = 30..35s`: **13.98 TFLOP/s**
  - `t = 55..60s`: **13.88 TFLOP/s** (**−0.86% sag over 60 seconds**; the 10-core M5 GPU does not thermally cliff inside a 1-minute window).
