SPB Git

spb/modelmap Public License

Internal cartography of local LLMs on Apple Silicon — registered, gated, negative-first. Public atlas at modelmap.io.

Python 66.3% JavaScript 24.5% CSS 8.1% Shell 0.7%

expH runs #2-#3: first falsified hypothesis + first quantized capture

Run #2 (cold cache, M3U96a, purge/repeat): FALSIFIED — warm ordering
inverts; zarr-uncompressed 0.62 GB/s beats raw-mmap 0.14 on cold random
batches (page-fault QD1 IO vs 32MiB chunk reads). Rule: IO granularity
decides, not the container. Store design revised; run #4 registered.

Run #3 (mlx-lm Qwen3-0.6B-4bit): CONFIRMED — retain 1.004x plain prefill
(capture free under lazy eval), retain+write 1.28x. First quantized-model
activation capture in Python tooling.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Simon-Pierre Boucher committed 3 h ago (Aug 12, 2026) parent f3dd60e

Showing 7 changed files with +739 and −0

modified experiments/micro/expH_capture_cost_frontier/analysis.md +74 −0
@@ -76,3 +76,77 @@ Next experiment : run #2 = cold-cache pass (purge/F_NOCACHE) to test
76 76 roughly linearly.
77 77 - All timing cells and raw seconds are in the results JSON with the hardware
78 78 manifest; the figure-ready aggregation is deferred to the atlas pipeline.
79 +
80 +---
81 +
82 +# Analysis — expH run #2 (cold cache, second hardware) — **HYPOTHESIS FALSIFIED**
83 +
84 +Run: `results/expH_capture_cost_frontier/*_run2_M3U96a/results.json`
85 +Hardware: Apple M3 Ultra (Mac Studio, MacLustr M3U96a), 96 GB, macOS.
86 +Store enlarged to 200k×4096 fp16 (~1.6 GB); `sudo purge` before **every**
87 +timed read repetition. Hypothesis registered before the run.
88 +
89 +```text
90 +Hypothesis : warm ordering survives cold — mmap ≥ 1.5× zarr on
91 + random-batch reads.
92 +Falsification criterion : dies if any zarr variant beats mmap (ratio <1.25×).
93 +Result : FALSIFIED — the ordering INVERTS cold.
94 + Random-batch (cold): zarr-uncompressed 0.62 GB/s,
95 + safetensors(mmap) 0.32, zarr-zstd 0.19,
96 + raw-mmap 0.14 → zarr-uncompressed beats mmap 4.4×.
97 + Sequential (cold): zarr-uncompressed 3.47,
98 + zarr-zstd 3.18, raw-mmap 0.64, safetensors 0.53.
99 +Interpretation : Level 0–1, and the mechanism is legible: cold mmap
100 + reads are page-fault-driven — effectively ~8–16 KiB
101 + random reads at queue depth 1, which localvm's
102 + cold-cache SSD grid already showed run at
103 + ~0.07–0.14 GB/s. zarr instead read()s whole 32 MiB
104 + chunks, exactly the ≥256 KiB / high-QD regime where
105 + Apple NVMe delivers. The warm-cache run #1 numbers
106 + measured memory, not disk. Cross-project agreement
107 + (modelmap run #2 ↔ localvm expH) is strong
108 + convergent evidence for the real rule:
109 + ***IO granularity decides, not the container.***
110 + Design decision REVISED: for stores larger than
111 + RAM, either use chunked containers or access mmap
112 + in chunk-aligned batched reads; never row-level
113 + random access. Caveat: run #1 was M5 Max, run #2
114 + M3 Ultra — the warm-vs-cold contrast within run #2's
115 + own machine is what carries the claim; hardware
116 + confound noted for the cross-run comparison.
117 +Next experiment : run #4 (registered idea, not yet run): mmap with
118 + chunk-sorted, ≥1 MiB batched reads + F_RDAHEAD vs
119 + zarr-uncompressed, cold, same machine — decides the
120 + final store design. Also replicate run #2 on the
121 + M5 Max laptop for the hardware axis.
122 +```
123 +
124 +---
125 +
126 +# Analysis — expH run #3 (real 4-bit checkpoint via mlx-lm) — CONFIRMED
127 +
128 +Run: `results/expH_capture_cost_frontier/20260812T061742Z/results.json`
129 +Model: mlx-community/Qwen3-0.6B-4bit (28 layers, d_model 1024), prefill of a
130 +901-token prompt, M5 Max 48 GB. Hypothesis registered before the run.
131 +
132 +```text
133 +Hypothesis : retain ≤ 1.15× plain prefill; retain+write ≤ 1.5×.
134 +Result : CONFIRMED, stronger than predicted.
135 + plain 28.5 ms/prefill (31,644 tok/s) →
136 + retain 28.6 ms (1.004× — capture is FREE) →
137 + retain+copy+write 36.4 ms (1.28×, 24,733 tok/s).
138 +Interpretation : Level 0–1 (3 repeats, single machine/model). The
139 + synthetic run #1 MLX result (1.02×) transfers to a
140 + real quantized checkpoint. This run is also the
141 + project's first QUANTIZED-model activation capture
142 + in Python — the capability the Phase 1 survey found
143 + nowhere (notes §4.9). Engineering note: quantized
144 + embeddings pack weight shapes, so d_model must be
145 + inferred from a runtime activation, and bf16
146 + activations must be cast in MLX before NumPy
147 + conversion (numpy has no bfloat16).
148 +Next experiment : same three modes at 1.7B/4B to trace the overhead
149 + curve vs model size; then wire this Tap pattern
150 + into src/modelmap/capture as the standard MLX
151 + capture layer (expA depends on it).
152 +```
modified experiments/micro/expH_capture_cost_frontier/hypothesis.md +56 −0
@@ -63,3 +63,59 @@ case; a purge/F_NOCACHE cold pass is a registered follow-up (run #2), and the
63 63 sister project's cold-cache NVMe ceiling (localvm expH: ~13.1 GB/s at
64 64 ≥256 KiB, QD≥4) bounds what run #2 can find. Synthetic model ≠ real
65 65 checkpoint: run #3 will repeat mode timings on a real 0.5B model via mlx-lm.
66 +
67 +---
68 +
69 +# Hypothesis — expH run #2 (cold-cache storage, second hardware)
70 +
71 +Registered 2026-08-12 **before** the run. Host: MacLustr node M3U96a
72 +(Mac Studio, 32 cores, 96 GB), where `sudo purge` empties the unified buffer
73 +cache between repetitions — also our first cross-hardware replication point.
74 +
75 +```text
76 +Hypothesis : The warm-cache format ordering survives cold:
77 + raw mmap sustains ≥ 1.5× zarr-uncompressed on
78 + random-batch reads with a purged cache; absolute
79 + throughputs drop for all formats; zarr-zstd's
80 + relative gap narrows (decode overlaps disk IO)
81 + but does not close.
82 +Falsification criterion : Dies if any zarr variant matches or beats mmap
83 + cold (ratio < 1.25×) — that would rehabilitate
84 + chunked stores and overturn run #1's design call.
85 +Method : Same grid as run #1 Part A, store enlarged to
86 + 200k×4096 fp16 (~1.6 GB), `sudo purge` before
87 + every timed read repetition, 3 repeats/cell,
88 + writes measured warm (unchanged claim).
89 +Baseline / null : run #1 warm numbers on M5 Max; sequential scan
90 + as the random-read reference.
91 +Result : (pending)
92 +Interpretation : (pending)
93 +Next experiment : (pending)
94 +```
95 +
96 +---
97 +
98 +# Hypothesis — expH run #3 (real quantized checkpoint via mlx-lm)
99 +
100 +Registered 2026-08-12 **before** the run. Local M5 Max, model =
101 +mlx-community Qwen3-0.6B (4-bit), capture by wrapping decoder layers.
102 +
103 +```text
104 +Hypothesis : On a real 0.6B 4-bit model under MLX, per-layer
105 + residual retention costs ≤ 1.15× plain prefill
106 + (lazy-graph retention, as in run #1's synthetic
107 + result), and retention + NumPy conversion + mmap
108 + write stays ≤ 1.5×.
109 +Falsification criterion : Dies at retain > 1.5× or retain+write > 2× —
110 + would mean synthetic overheads do not transfer to
111 + real checkpoints and all run #1 Part B numbers
112 + must be re-derived on real models.
113 +Method : Prefill of a fixed 1024-token prompt, batch 1,
114 + 10 timed forwards after 3 warmups, 3 repeats;
115 + modes = plain / retain-all-layers / retain+write;
116 + mx.eval barriers around each forward.
117 +Baseline / null : plain prefill throughput (tok/s).
118 +Result : (pending)
119 +Interpretation : (pending)
120 +Next experiment : (pending)
121 +```
added experiments/micro/expH_capture_cost_frontier/implementation/benchmark_cold.py +184 −0
@@ -0,0 +1,184 @@
1 +#!/usr/bin/env python3
2 +# =============================================================================
3 +# Project : modelmap
4 +# File : experiments/micro/expH_capture_cost_frontier/implementation/benchmark_cold.py
5 +# Purpose : Run #2 — cold-cache storage-format throughput (purge per repeat)
6 +# Author : Simon-Pierre Boucher
7 +# Contact : contact@spboucher.ai
8 +# Website : https://modelmap.io
9 +# Created : 2026-08-12
10 +# Modified : 2026-08-12
11 +# Platform : macOS / Apple Silicon (arm64)
12 +# License : All rights reserved (research code)
13 +# =============================================================================
14 +"""expH run #2 (see hypothesis.md, registered before this run).
15 +
16 +Storage-only cold-cache variant: `sudo purge` empties the unified buffer
17 +cache before EVERY timed read repetition. Designed to run standalone on a
18 +MacLustr node (numpy + zarr + safetensors only, no torch/mlx). Results JSON
19 +is self-contained (embeds a local hardware manifest) and is collected back
20 +into results/ by the driver on the laptop.
21 +
22 +Usage: python3 benchmark_cold.py --workdir /path --out results.json \
23 + [--purge-cmd "sudo -n purge"]
24 +"""
25 +
26 +from __future__ import annotations
27 +
28 +import argparse
29 +import json
30 +import platform
31 +import shutil
32 +import subprocess
33 +import time
34 +from pathlib import Path
35 +
36 +import numpy as np
37 +
38 +REPEATS = 3
39 +ROWS, DIM = 200_000, 4096 # ~1.6 GB fp16 per format
40 +WRITE_CHUNK = 4_096
41 +BATCH = 4_096
42 +N_BATCHES = 24
43 +SEED = 0
44 +
45 +
46 +def sysctl(key: str) -> str:
47 + try:
48 + return subprocess.run(["sysctl", "-n", key], capture_output=True,
49 + text=True, check=True).stdout.strip()
50 + except subprocess.CalledProcessError:
51 + return ""
52 +
53 +
54 +def local_manifest() -> dict:
55 + return {
56 + "author": "Simon-Pierre Boucher",
57 + "chip": sysctl("machdep.cpu.brand_string"),
58 + "cores": int(sysctl("hw.ncpu") or 0),
59 + "unified_gb": round(int(sysctl("hw.memsize") or 0) / 2**30, 1),
60 + "os": platform.mac_ver()[0],
61 + "python": platform.python_version(),
62 + "numpy": np.__version__,
63 + "host": platform.node(),
64 + }
65 +
66 +
67 +def main() -> int:
68 + ap = argparse.ArgumentParser()
69 + ap.add_argument("--workdir", required=True)
70 + ap.add_argument("--out", required=True)
71 + ap.add_argument("--purge-cmd", default="")
72 + args = ap.parse_args()
73 +
74 + workdir = Path(args.workdir)
75 + workdir.mkdir(parents=True, exist_ok=True)
76 +
77 + def purge():
78 + if args.purge_cmd:
79 + subprocess.run(args.purge_cmd, shell=True, check=True,
80 + capture_output=True)
81 +
82 + def timed_cold(fn):
83 + out = []
84 + for _ in range(REPEATS):
85 + purge()
86 + t0 = time.perf_counter()
87 + fn()
88 + out.append(time.perf_counter() - t0)
89 + return out
90 +
91 + rng = np.random.default_rng(SEED)
92 + data = rng.standard_normal((WRITE_CHUNK, DIM)).astype(np.float16)
93 + batches = [rng.integers(0, ROWS, BATCH) for _ in range(N_BATCHES)]
94 + total_bytes = ROWS * DIM * 2
95 + batch_bytes = BATCH * DIM * 2 * N_BATCHES
96 + results = []
97 +
98 + def record(fmt, op, times, nbytes):
99 + results.append({"format": fmt, "op": op, "bytes": nbytes, "seconds": times,
100 + "gb_per_s_mean": nbytes / 2**30 / np.mean(times),
101 + "cache": "cold" if op != "write" else "warm"})
102 + print(f" {fmt:18s} {op:12s} {nbytes/2**30/np.mean(times):8.2f} GB/s", flush=True)
103 +
104 + # ---- raw np.memmap
105 + p = workdir / "acts.raw"
106 + def write_raw():
107 + m = np.memmap(p, dtype=np.float16, mode="w+", shape=(ROWS, DIM))
108 + for i in range(0, ROWS, WRITE_CHUNK):
109 + end = min(i + WRITE_CHUNK, ROWS)
110 + m[i:end] = data[: end - i]
111 + m.flush(); del m
112 + t0 = time.perf_counter(); write_raw()
113 + record("raw-mmap", "write", [time.perf_counter() - t0], total_bytes)
114 + def seq_raw():
115 + m = np.memmap(p, dtype=np.float16, mode="r", shape=(ROWS, DIM))
116 + float(np.asarray(m).sum(dtype=np.float32)); del m
117 + def rnd_raw():
118 + m = np.memmap(p, dtype=np.float16, mode="r", shape=(ROWS, DIM))
119 + for b in batches:
120 + m[b].sum(dtype=np.float32)
121 + del m
122 + record("raw-mmap", "seq-scan", timed_cold(seq_raw), total_bytes)
123 + record("raw-mmap", "random-batch", timed_cold(rnd_raw), batch_bytes)
124 +
125 + # ---- safetensors
126 + from safetensors import safe_open
127 + from safetensors.numpy import save_file
128 + ps = workdir / "acts.safetensors"
129 + full = np.memmap(p, dtype=np.float16, mode="r", shape=(ROWS, DIM))
130 + t0 = time.perf_counter(); save_file({"acts": np.asarray(full)}, str(ps))
131 + record("safetensors", "write", [time.perf_counter() - t0], total_bytes)
132 + del full
133 + def seq_st():
134 + f = safe_open(str(ps), framework="np")
135 + float(f.get_tensor("acts").sum(dtype=np.float32))
136 + def rnd_st():
137 + f = safe_open(str(ps), framework="np")
138 + t = f.get_tensor("acts")
139 + for b in batches:
140 + t[b].sum(dtype=np.float32)
141 + record("safetensors", "seq-scan", timed_cold(seq_st), total_bytes)
142 + record("safetensors", "random-batch", timed_cold(rnd_st), batch_bytes)
143 +
144 + # ---- zarr variants
145 + import zarr
146 + for codec, name in ((None, "zarr-uncompressed"), ("default", "zarr-zstd")):
147 + pz = workdir / f"acts_{name}.zarr"
148 + kwargs = {} if codec == "default" else {"compressors": None}
149 + if pz.exists():
150 + shutil.rmtree(pz)
151 + t0 = time.perf_counter()
152 + z = zarr.create_array(store=str(pz), shape=(ROWS, DIM),
153 + chunks=(WRITE_CHUNK, DIM), dtype=np.float16, **kwargs)
154 + for i in range(0, ROWS, WRITE_CHUNK):
155 + end = min(i + WRITE_CHUNK, ROWS)
156 + z[i:end] = data[: end - i]
157 + record(name, "write", [time.perf_counter() - t0], total_bytes)
158 + def seq_z(pz=pz):
159 + zz = zarr.open_array(store=str(pz), mode="r")
160 + float(zz[:].sum(dtype=np.float32))
161 + def rnd_z(pz=pz):
162 + zz = zarr.open_array(store=str(pz), mode="r")
163 + for b in batches:
164 + zz[np.sort(b)].sum(dtype=np.float32)
165 + record(name, "seq-scan", timed_cold(seq_z), total_bytes)
166 + record(name, "random-batch", timed_cold(rnd_z), batch_bytes)
167 +
168 + doc = {
169 + "experiment": "expH_capture_cost_frontier",
170 + "run": 2,
171 + "scope": "storage formats, COLD cache (purge per repeat), second hardware",
172 + "config": {"rows": ROWS, "dim": DIM, "write_chunk": WRITE_CHUNK,
173 + "batch": BATCH, "n_batches": N_BATCHES, "repeats": REPEATS,
174 + "purge_cmd": args.purge_cmd or "(none — warm!)", "seed": SEED},
175 + "manifest": local_manifest(),
176 + "storage": results,
177 + }
178 + Path(args.out).write_text(json.dumps(doc, indent=2) + "\n")
179 + print(f"results -> {args.out}")
180 + return 0
181 +
182 +
183 +if __name__ == "__main__":
184 + raise SystemExit(main())
added experiments/micro/expH_capture_cost_frontier/implementation/benchmark_real.py +149 −0
@@ -0,0 +1,149 @@
1 +#!/usr/bin/env python3
2 +# =============================================================================
3 +# Project : modelmap
4 +# File : experiments/micro/expH_capture_cost_frontier/implementation/benchmark_real.py
5 +# Purpose : Run #3 — capture overhead on a real quantized checkpoint (mlx-lm)
6 +# Author : Simon-Pierre Boucher
7 +# Contact : contact@spboucher.ai
8 +# Website : https://modelmap.io
9 +# Created : 2026-08-12
10 +# Modified : 2026-08-12
11 +# Platform : macOS / Apple Silicon (arm64) — MLX / Metal
12 +# License : All rights reserved (research code)
13 +# =============================================================================
14 +"""expH run #3 (see hypothesis.md, registered before this run).
15 +
16 +Loads a real 4-bit model via mlx-lm, wraps every decoder layer with a
17 +retaining tap, and measures prefill throughput in three modes:
18 +plain / retain-all-layers / retain + NumPy conversion + mmap write.
19 +This is the first quantized-model activation capture in the project —
20 +the capability the Phase 1 survey found nowhere in Python tooling.
21 +"""
22 +
23 +from __future__ import annotations
24 +
25 +import json
26 +import shutil
27 +import subprocess
28 +import sys
29 +import tempfile
30 +import time
31 +from pathlib import Path
32 +
33 +import numpy as np
34 +
35 +ROOT = Path(__file__).resolve().parents[4]
36 +sys.path.insert(0, str(ROOT / "benchmarks"))
37 +from hardware_manifest import manifest
38 +
39 +MODEL = "mlx-community/Qwen3-0.6B-4bit"
40 +SEQ = 1024
41 +N_FWD, WARMUP, REPEATS = 10, 3, 3
42 +SEED = 0
43 +
44 +
45 +class Tap:
46 + """Wraps a decoder layer; optionally retains its output."""
47 +
48 + def __init__(self, layer):
49 + self.layer = layer
50 + self.retained = None
51 + self.enabled = False
52 +
53 + def __call__(self, *args, **kwargs):
54 + out = self.layer(*args, **kwargs)
55 + if self.enabled:
56 + self.retained = out
57 + return out
58 +
59 + def __getattr__(self, name): # delegate attribute access (e.g. .self_attn)
60 + return getattr(self.layer, name)
61 +
62 +
63 +def main() -> int:
64 + import mlx.core as mx
65 + from mlx_lm import load
66 +
67 + mx.random.seed(SEED)
68 + model, tokenizer = load(MODEL)
69 + layers = model.model.layers
70 + n_layers = len(layers)
71 + taps = [Tap(l) for l in layers]
72 + model.model.layers = taps
73 +
74 + text = ("The internal cartography of local language models requires "
75 + "systematic measurement of every layer. ") * 60
76 + tokens = tokenizer.encode(text)[:SEQ]
77 + x = mx.array([tokens])
78 +
79 + # infer d_model at runtime (quantized embeddings pack their weight shapes)
80 + taps[0].enabled = True
81 + mx.eval(model(x))
82 + d_model = int(taps[0].retained.shape[-1])
83 + taps[0].enabled = False
84 + taps[0].retained = None
85 + print(f"model={MODEL} layers={n_layers} d_model={d_model} seq={len(tokens)}")
86 +
87 + workdir = Path(tempfile.mkdtemp(prefix="modelmap_expH3_"))
88 + store = np.memmap(workdir / "capture.raw", dtype=np.float16, mode="w+",
89 + shape=(N_FWD * n_layers * len(tokens), d_model))
90 +
91 + def run(capture: bool, to_disk: bool) -> float:
92 + for t in taps:
93 + t.enabled = capture
94 + t.retained = None
95 + row = 0
96 + for _ in range(WARMUP):
97 + out = model(x)
98 + mx.eval(out, *[t.retained for t in taps if t.retained is not None])
99 + t0 = time.perf_counter()
100 + for _ in range(N_FWD):
101 + out = model(x)
102 + retained = [t.retained for t in taps] if capture else []
103 + mx.eval(out, *[r for r in retained if r is not None])
104 + if to_disk:
105 + for r in retained:
106 + # model runs bf16 — cast in MLX (numpy has no bfloat16)
107 + a = np.array(r.astype(mx.float16), copy=False).reshape(-1, d_model)
108 + store[row:row + a.shape[0]] = a
109 + row += a.shape[0]
110 + return (time.perf_counter() - t0) / N_FWD
111 +
112 + results = []
113 + try:
114 + for mode, cap, disk in (("plain", False, False),
115 + ("retain", True, False),
116 + ("retain+copy+write", True, True)):
117 + times = [run(cap, disk) for _ in range(REPEATS)]
118 + results.append({"backend": "mlx-lm", "mode": mode,
119 + "s_per_forward": times,
120 + "tokens_per_s_mean": len(tokens) / np.mean(times)})
121 + print(f" {mode:22s} {np.mean(times)*1000:8.1f} ms/prefill "
122 + f"({len(tokens)/np.mean(times):8.0f} tok/s)")
123 + finally:
124 + store.flush()
125 + shutil.rmtree(workdir, ignore_errors=True)
126 +
127 + commit = subprocess.run(["git", "rev-parse", "HEAD"], cwd=ROOT,
128 + capture_output=True, text=True, check=False).stdout.strip()
129 + ts = time.strftime("%Y%m%dT%H%M%SZ", time.gmtime())
130 + outdir = ROOT / "results" / "expH_capture_cost_frontier" / ts
131 + outdir.mkdir(parents=True)
132 + doc = {
133 + "experiment": "expH_capture_cost_frontier",
134 + "run": 3,
135 + "scope": "capture overhead on a real 4-bit checkpoint (mlx-lm prefill)",
136 + "commit": commit,
137 + "config": {"model": MODEL, "seq": len(tokens), "n_layers": n_layers,
138 + "d_model": int(d_model), "n_forwards": N_FWD,
139 + "warmup": WARMUP, "repeats": REPEATS, "seed": SEED},
140 + "manifest": manifest(),
141 + "compute": results,
142 + }
143 + (outdir / "results.json").write_text(json.dumps(doc, indent=2) + "\n")
144 + print(f"results -> {outdir / 'results.json'}")
145 + return 0
146 +
147 +
148 +if __name__ == "__main__":
149 + sys.exit(main())
modified research/LOG.md +37 −0
@@ -240,3 +240,40 @@ mlx-lm); then expA first run (hypothesis registered: dataset variance >
240 240 seed variance; harness validity gate = selectivity<0.05 on random-init).
241 241 expA's output becomes the first real map card. Replicate expH grid on
242 242 M3U96a/M2U64 to move the cost table cross-hardware.
243 +
244 +---
245 +
246 +## 2026-08-12 06:30 EDT — expH runs #2 & #3: first FALSIFIED hypothesis + first quantized capture
247 +
248 +**Questions.** Does the warm-cache storage ordering survive a cold cache
249 +(run #2, on M3U96a with `sudo purge` per repetition — also the first
250 +cross-hardware point)? Do synthetic capture overheads transfer to a real
251 +4-bit checkpoint (run #3, mlx-lm Qwen3-0.6B-4bit)?
252 +
253 +**Run #2 — FALSIFIED (the project's first registered-and-killed
254 +hypothesis).** Cold random-batch reads on M3 Ultra: zarr-uncompressed
255 +0.62 GB/s > safetensors-mmap 0.32 > zarr-zstd 0.19 > raw-mmap 0.14 —
256 +the warm ordering (mmap 3.2–10.8× ahead) fully INVERTS. Mechanism: cold
257 +mmap is page-fault IO (~8–16 KiB, QD1), exactly the regime localvm's SSD
258 +grid measured at ~0.07–0.14 GB/s; zarr read()s 32 MiB chunks — the
259 +≥256 KiB regime where Apple NVMe delivers. Run #1 measured memory, not
260 +disk. **The rule is IO granularity, not the container.** Store design
261 +revised: chunk-aligned batched access mandatory for larger-than-RAM
262 +stores; run #4 registered (chunk-sorted ≥1 MiB mmap reads vs zarr, cold).
263 +Cross-project convergence with localvm expH is strong — the bridge works
264 +in both directions.
265 +
266 +**Run #3 — CONFIRMED, stronger than predicted.** Real Qwen3-0.6B-4bit
267 +prefill (901 tokens, 28 layers): plain 28.5 ms → retain 28.6 ms
268 +(**1.004× — capture is free under MLX lazy evaluation**) → retain+cast+
269 +mmap-write 36.4 ms (1.28×). First quantized-model activation capture in
270 +Python tooling (the §4.9 gap, now demonstrated). Engineering notes:
271 +d_model must be inferred from runtime activations (quantized embeddings
272 +pack weights); bf16 must be cast in MLX before NumPy.
273 +
274 +**Decisions.** (1) Promote the Tap-wrapper pattern into
275 +src/modelmap/capture as the standard MLX capture layer — expA depends on
276 +it. (2) The published cost table will carry cache-regime as a first-class
277 +dimension (warm ≠ cold columns), and run #2's negative goes on the site's
278 +negative-results ledger once the atlas pipeline exists. (3) expA first
279 +run is now unblocked (capture layer + probes + stats all in place).
added results/expH_capture_cost_frontier/20260812T061742Z/results.json +75 −0
@@ -0,0 +1,75 @@
1 +{
2 + "experiment": "expH_capture_cost_frontier",
3 + "run": 3,
4 + "scope": "capture overhead on a real 4-bit checkpoint (mlx-lm prefill)",
5 + "commit": "f3dd60ef6bed19c96456381481abb0aa50b83b73",
6 + "config": {
7 + "model": "mlx-community/Qwen3-0.6B-4bit",
8 + "seq": 901,
9 + "n_layers": 28,
10 + "d_model": 1024,
11 + "n_forwards": 10,
12 + "warmup": 3,
13 + "repeats": 3,
14 + "seed": 0
15 + },
16 + "manifest": {
17 + "author": "Simon-Pierre Boucher",
18 + "contact": "contact@spboucher.ai",
19 + "website": "https://modelmap.io",
20 + "chip": {
21 + "brand": "Apple M5 Max",
22 + "cores_total": 18,
23 + "cores_performance": 6,
24 + "cores_efficiency": 12
25 + },
26 + "memory": {
27 + "unified_gb": 48.0,
28 + "pagesize": 16384
29 + },
30 + "os": {
31 + "system": "Darwin",
32 + "version": "27.0",
33 + "arch": "arm64"
34 + },
35 + "software": {
36 + "python": "3.14.4",
37 + "numpy": "2.5.2",
38 + "mlx": "0.32.0",
39 + "torch": "2.13.0",
40 + "safetensors": "0.8.0"
41 + }
42 + },
43 + "compute": [
44 + {
45 + "backend": "mlx-lm",
46 + "mode": "plain",
47 + "s_per_forward": [
48 + 0.02832533750006405,
49 + 0.028470029100026294,
50 + 0.02862457089995587
51 + ],
52 + "tokens_per_s_mean": 31643.66632788203
53 + },
54 + {
55 + "backend": "mlx-lm",
56 + "mode": "retain",
57 + "s_per_forward": [
58 + 0.028357170900017083,
59 + 0.028654929099866423,
60 + 0.028654850000020816
61 + ],
62 + "tokens_per_s_mean": 31552.424826645736
63 + },
64 + {
65 + "backend": "mlx-lm",
66 + "mode": "retain+copy+write",
67 + "s_per_forward": [
68 + 0.03822784169988154,
69 + 0.0356740749999517,
70 + 0.03538348749989382
71 + ],
72 + "tokens_per_s_mean": 24733.40351159859
73 + }
74 + ]
75 +}
added results/expH_capture_cost_frontier/20260812T061824Z_run2_M3U96a/results.json +164 −0
@@ -0,0 +1,164 @@
1 +{
2 + "experiment": "expH_capture_cost_frontier",
3 + "run": 2,
4 + "scope": "storage formats, COLD cache (purge per repeat), second hardware",
5 + "config": {
6 + "rows": 200000,
7 + "dim": 4096,
8 + "write_chunk": 4096,
9 + "batch": 4096,
10 + "n_batches": 24,
11 + "repeats": 3,
12 + "purge_cmd": "echo 492592 | sudo -S purge",
13 + "seed": 0
14 + },
15 + "manifest": {
16 + "author": "Simon-Pierre Boucher",
17 + "chip": "Apple M3 Ultra",
18 + "cores": 28,
19 + "unified_gb": 96.0,
20 + "os": "26.6",
21 + "python": "3.14.5",
22 + "numpy": "2.5.2",
23 + "host": "MacStudnpierre2"
24 + },
25 + "storage": [
26 + {
27 + "format": "raw-mmap",
28 + "op": "write",
29 + "bytes": 1638400000,
30 + "seconds": [
31 + 0.9577836249954998
32 + ],
33 + "gb_per_s_mean": 1.5931353036623166,
34 + "cache": "warm"
35 + },
36 + {
37 + "format": "raw-mmap",
38 + "op": "seq-scan",
39 + "bytes": 1638400000,
40 + "seconds": [
41 + 2.311261749942787,
42 + 2.483271957957186,
43 + 2.3612381250131875
44 + ],
45 + "gb_per_s_mean": 0.6397125042046533,
46 + "cache": "cold"
47 + },
48 + {
49 + "format": "raw-mmap",
50 + "op": "random-batch",
51 + "bytes": 805306368,
52 + "seconds": [
53 + 5.556866124970838,
54 + 5.391185167012736,
55 + 4.8900218330090865
56 + ],
57 + "gb_per_s_mean": 0.14206273593026125,
58 + "cache": "cold"
59 + },
60 + {
61 + "format": "safetensors",
62 + "op": "write",
63 + "bytes": 1638400000,
64 + "seconds": [
65 + 1.8997217500582337
66 + ],
67 + "gb_per_s_mean": 0.8032117894124369,
68 + "cache": "warm"
69 + },
70 + {
71 + "format": "safetensors",
72 + "op": "seq-scan",
73 + "bytes": 1638400000,
74 + "seconds": [
75 + 2.492816833080724,
76 + 2.518700583023019,
77 + 3.5813771249959245
78 + ],
79 + "gb_per_s_mean": 0.5327234841362526,
80 + "cache": "cold"
81 + },
82 + {
83 + "format": "safetensors",
84 + "op": "random-batch",
85 + "bytes": 805306368,
86 + "seconds": [
87 + 2.43777041696012,
88 + 2.4196586669422686,
89 + 2.2487994579132646
90 + ],
91 + "gb_per_s_mean": 0.31662364737640725,
92 + "cache": "cold"
93 + },
94 + {
95 + "format": "zarr-uncompressed",
96 + "op": "write",
97 + "bytes": 1638400000,
98 + "seconds": [
99 + 0.7715847919462249
100 + ],
101 + "gb_per_s_mean": 1.9775906966765944,
102 + "cache": "warm"
103 + },
104 + {
105 + "format": "zarr-uncompressed",
106 + "op": "seq-scan",
107 + "bytes": 1638400000,
108 + "seconds": [
109 + 0.43666258302982897,
110 + 0.4407244159374386,
111 + 0.4407702499302104
112 + ],
113 + "gb_per_s_mean": 3.4727546524352753,
114 + "cache": "cold"
115 + },
116 + {
117 + "format": "zarr-uncompressed",
118 + "op": "random-batch",
119 + "bytes": 805306368,
120 + "seconds": [
121 + 1.3796995830489323,
122 + 1.139681207947433,
123 + 1.1073772499803454
124 + ],
125 + "gb_per_s_mean": 0.6203887809935233,
126 + "cache": "cold"
127 + },
128 + {
129 + "format": "zarr-zstd",
130 + "op": "write",
131 + "bytes": 1638400000,
132 + "seconds": [
133 + 1.9146007499657571
134 + ],
135 + "gb_per_s_mean": 0.7969697631620015,
136 + "cache": "warm"
137 + },
138 + {
139 + "format": "zarr-zstd",
140 + "op": "seq-scan",
141 + "bytes": 1638400000,
142 + "seconds": [
143 + 0.47764337505213916,
144 + 0.4796952080214396,
145 + 0.48077000002376735
146 + ],
147 + "gb_per_s_mean": 3.183095332684026,
148 + "cache": "cold"
149 + },
150 + {
151 + "format": "zarr-zstd",
152 + "op": "random-batch",
153 + "bytes": 805306368,
154 + "seconds": [
155 + 3.880511499941349,
156 + 4.199512624996714,
157 + 3.8756266250275075
158 + ],
159 + "gb_per_s_mean": 0.18819552754219418,
160 + "cache": "cold"
161 + }
162 + ],
163 + "commit": "f3dd60ef6bed19c96456381481abb0aa50b83b73"
164 +}
\ No newline at end of file
165