Commit Graph

58 Commits

Author SHA1 Message Date
modelbeast
1cb02ec4fa docs: 8 machines — tiled keys 2048 on the 2020 M1 mini 8GB, same accuracy
Completes every Apple Silicon GPU generation ever shipped (M1 2020 -> M5).
Headline: tiled 768/64+compile keys a full 2048 frame in ~2.2GB on EVERY
machine with IDENTICAL alpha (MAE 0.00849) — M3 Ultra 1949ms ... M1 mini 8GB
14269ms. Full-frame needs 26GB and is unreachable for most Macs ever sold, so
tiled should arguably be the default, not the fallback. The README's 'might
even work on your old MacBook Pro' undersells it.
Also: real CorridorKey ablation on M1 Max (stage_gc harmful at 32GB too:
0.81x @512), and the OG 8-core M1 mini confirms the hdim-56 fallback (1.00x
x3, padding 1.9x) — now 8/8 machines.
2026-07-17 15:03:08 +10:00
modelbeast
dfc2bf4a51 docs: M4 mini (7th) completes the matched-width M3->M4->M5 generation ladder
hdim-56 fallback is textbook here (1.00x x3); padding = 2.0x. Fits the
corrected fused-kernel-quality theory as a prediction, not a fit: 0.50x
fused/unfused -> 2.0x padding win, exactly between M3 Air (0.40x->2.6x) and
M1 Max (0.87x->1.3x). At 10c matched width the ladder is 138.9/48.8/44.0ms at
hdim56 but 54.3/24.4/8.9ms at hdim64 — M5's gains live almost entirely in the
fused kernel, making the head-dim fix most valuable on the newest silicon.
2026-07-17 14:30:57 +10:00
modelbeast
e8f1721996 docs: remove the now-refuted width framing; intro says six machines 2026-07-17 14:03:44 +10:00
modelbeast
5f87aec717 docs: M1 Max (6th machine) refutes the width theory — report the correction
Padding 56->64 helps everywhere (1.3x-4.9x) and the hdim-56 silent fallback
reproduces on all six machines. But the M1 Max (24c) gains LEAST of all,
breaking our 'narrower GPU => bigger win' reading (M5 10c gained most).
Real predictor: fused-kernel quality vs that chip's own matmul throughput —
M1 Max's fused path is only 0.87x unfused at every size, so missing it costs
little; M5's is 0.14x, so missing it costs a lot. Corrected in place rather
than left as a tidy-but-wrong story.
2026-07-17 14:02:54 +10:00
modelbeast
6b651f6f8e docs: intro reflects 5 machines 2026-07-17 01:09:00 +10:00
modelbeast
6f0fc4998b docs: M3 Air — matched-width generational delta + fanless negative result
Fifth machine. Two additions:
- M3 Air (10c) vs M5 (10c): same GPU width, 2 generations apart -> M5 is 7.7x
  at seq 4096. Isolates architecture from width (the Ultras can't). head_dim-56
  fallback reproduces here too (padding = 2.6x), so it now holds on every
  Apple GPU generation shipping today.
- Fanless sustained load: NO meaningful throttle over 5 min (1.01x at 300s).
  We predicted a decay curve and found none — reporting the negative, since it
  validates that the burst numbers aren't short-run artifacts.
2026-07-17 01:08:19 +10:00
modelbeast
64be04aeac docs: retitle for the 4-machine matrix (M1/M3/M4/M5, Ultra->laptop)
Also validated the tiled-compile patch against the repo's own standards:
ruff check + ruff format clean, pytest 64 passed / 0 failed.
2026-07-16 23:56:38 +10:00
modelbeast
fd00c31b1d docs: add M5 — head_dim padding is worth ~5x on laptops (vs ~2x on Ultras)
M5 MacBook Pro (10-core GPU, 16GB) reproduces the head_dim=56 silent fallback
and shows the fix matters far MORE on narrow GPUs: padding 56->64 gives 4.9x
(1,16,4096), 5.1x (1,16,2048), 3.2x (4096,2,64). Raw fused-vs-unfused at
4096/fp32 is 0.14x on M5 vs ~0.53x on M3 Ultra — an M5 laptop matches an M1
Ultra on fused attention but is ~4x slower unfused. This reframes the fix as a
laptop optimization, not a big-iron one.
2026-07-16 23:54:49 +10:00
modelbeast
df28940d81 docs: root-cause the sdpa finding — head_dim=56 misses the fast path
Shape-spy on a real 2048 forward + exact-shape sweeps on M1/M3 Ultra:
fused==unfused at hdim 56 (silent fallback) on both machines, so use_sdpa
buys nothing and its transpose choreography costs ~4.5x on M1. Padding to
hdim 64 engages the fast kernel: 2.2x (M3) / 2.7x (M1) on the dominant
global-attention shape despite 14% extra FLOPs.
2026-07-16 23:05:13 +10:00
modelbeast
a9f6b75ef2 docs: M-series fleet ablation (M3 Ultra / M1 Ultra / M4 Pro 24GB)
Ran scripts/bench_optimizations.py unmodified across three machines + an
engine-level tiled×compile extension with ground-truth alpha scoring.
Headlines: sdpa = 4.5x regression on M1 at 2048 (bf16 innocent — isolated
per-toggle on both Ultras); stage_gc harmful on Ultra-class; tiled 768/64
+ compile = best 2048 config on both Ultras; 24GB Macs swap-bound at 2048
full-frame (tiled required).
2026-07-16 22:48:06 +10:00
modelbeast
4c660dff3c engine: honor compile flag in tiled mode
Tiles are fixed-shape, so fused compilation applies the same as full-frame.
Forcing compile=False in the tiled branch left performance on the table:
measured on the same 12-frame 2048px green-screen set (alpha output
bit-identical, IoU 0.9316):
  M3 Ultra: 3.64 -> 2.48 s/frame (1.47x)
  M1 Ultra: 4.65 -> 4.19 s/frame (1.11x)
Peak memory unchanged (~2.3 GB).
2026-07-16 21:37:22 +10:00
Niko
04503e7970
Merge pull request #9 from cmoyates/experiment/mlx-memory-optimizations
feat: reduce peak memory for 8GB Macs via bf16, fused decode, and deterministic GC
2026-03-09 16:41:52 -07:00
cmoyates
e91089a804
docs: wave2 ablation benchmarks + optimization plan + brainstorm
Ablation sweep across 512/1024/2048 resolutions with 8 configs.
Tiled 768/64 = 1.5x faster + 11.4x less memory vs full-frame at 2048.
stage_gc net negative at small res, breaks even at 2048.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 19:32:04 -02:30
cmoyates
d13ba1639d
feat: add optimization toggle flags + benchmark matrix script
use_sdpa, stage_gc flags propagated through backbone/model/pipeline.
bench_optimizations.py runs exhaustive/ablation/key sweep of all 6
toggles (slim, stage_gc, sdpa, bf16, fused_decode, gpu_preprocess)
with latency + peak memory reporting.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 17:11:28 -02:30
cmoyates
31d83e218b
feat: GPU-side ImageNet normalize + concat for full-frame path
PIL bicubic vs MLX cubic diff too large (0.22-0.59) — resize stays
on CPU. Only normalize+concat moved to MLX. Tiled path unchanged.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 17:06:47 -02:30
cmoyates
f80bec92b6
feat: replace manual attention with mx.fast.scaled_dot_product_attention
Fold window dim into batch (transpose before reshape), call SDPA,
unfold back. All parity tests pass with 0 regression.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 17:04:51 -02:30
cmoyates
6787a7fd21
feat: stage-boundary GC between backbone/decoder/refiner
mx.eval + gc + clear_cache at backbone→decoder and decoder→refiner
boundaries in eager mode. _compiled flag skips GC under mx.compile.
Compilation tests updated to use compile_model() API.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 17:02:38 -02:30
cmoyates
ce30eddcff
feat: slim forward mode — drop intermediate tensor refs
slim=True returns 4-key dict, engine uses it, pipeline.infer keeps full.
2 new tests: key count + bit-exact match vs full output.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 17:00:21 -02:30
cmoyates
4d7a29c7c7
feat: add mx.clear_cache() to engine cleanup after postprocessing
Del MLX array refs + gc + cache clear after numpy extraction.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 17:00:16 -02:30
cmoyates
ee23f27735
feat: add tiled inference support to engine
tile_size/overlap params on CorridorKeyMLXEngine — model loads at
tile_size, input stays full-res, forward uses tiled_inference w/ GC.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 22:41:45 -02:30
cmoyates
f30ab9255b
chore: fix lint, format, type errors
- ruff format: 2 files reformatted
- ruff check: fix import sort in scripts/infer_pytorch.py
- ty: suppress tree_flatten arg-type (MLX stub imprecision) and
  optional torch import

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 21:29:06 -02:30
cmoyates
f73aabf5af
chore: default bf16+fused decode on, add benchmark results to plan
load_model() now defaults to dtype=bf16, fused_decode=True. Both are
free (zero parity regression, bit-exact fused path). Backbone/sigmoid
stay fp32.

Plan updated with benchmark results: tiled+GC = 12x peak memory
reduction at 2048x2048 (27.6GB → 2.3GB), all acceptance criteria met.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 21:27:56 -02:30
cmoyates
b3b7df271d
feat: MLX memory optimizations — bf16, fused decode, deterministic GC
Phase 1: selective bf16 mixed precision (backbone fp32, decoders bf16,
sigmoid always fp32). dtype param on GreenFormer, all outputs guaranteed
fp32.

Phase 2: FusedDecoderPair batches 3 upsample ops by concatenating
alpha+fg projections along channel axis. Bit-exact parity with unfused
path.

Phase 3: deterministic GC pipeline in tile loop (del + gc.collect +
mx.clear_cache) and engine.py intermediate tensor cleanup.

7 new tests: bf16 dtype/range/nan, fused-vs-unfused parity, fp32
default unchanged. 80 passed, 0 failures.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 21:21:01 -02:30
cmoyates
830e88f050
docs: add brainstorm + plan for MLX memory optimizations
bf16 mixed precision, batched decoder upsample, deterministic GC pipeline.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 21:12:59 -02:30
cmoyates
838bd0dd39
refactor(tests): restructure suite — 15 files to 8, consolidate parity
- Add conftest.py: shared paths, tolerances, skip markers, fixtures
- Consolidate imports/shapes/smoke/forward → test_model_contract.py
- Consolidate all parity (decoder/refiner/backbone/e2e) → test_parity.py
- Fold 2048 slow test into test_engine.py
- Rework test_conversion.py to use public convert_checkpoint() API
- Simplify test_weights.py: drop CLI/env var tests, keep checksum+config
- Rename tiling/compilation test files for consistency
- Delete 10 redundant files

80 tests collected (75 pass, 4 skip, 1 deselected)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 09:58:53 -03:30
cmoyates
62645a2e93
chore: add TDD skill definitions
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 09:45:43 -03:30
cmoyates
88a0cd9cb8
docs: add deep modules refactor plan
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 09:45:43 -03:30
cmoyates
1a992a853d
chore: ignore safetensors files in gitignore
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 09:45:43 -03:30
cmoyates
e95bef4716
feat(weights): add CLI for downloading weights from GitHub Releases
Streaming download w/ rich progress, SHA256 verification, platformdirs
caching. Supports --tag, --asset, --force, --print-path flags + env
var overrides. Console script: corridorkey-weights.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 09:45:43 -03:30
cmoyates
7f1c2ee221
fix: match original CorridorKey decoder concat order [c4,c3,c2,c1]
PyTorch reference script had wrong concat order (forward) vs the
original nikopueringer/CorridorKey which uses torch.cat([c4,c3,c2,c1]).
Golden fixtures regenerated. Relax 3 tight e2e tolerances for Metal
vs CPU float32 drift.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 22:58:08 -03:30
cmoyates
db2a8cecf0
chore: migrate type checker from mypy to ty
Replace mypy with Astral's ty for faster type checking. Fix
Image.BICUBIC deprecation and inline **kwargs for ty compatibility.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 22:25:14 -03:30
cmoyates
b95c1f9e31
fix: pass img_size to GreenFormer + gitignore .DS_Store
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 09:46:31 -03:30
cmoyates
9cd800d37b
fix: reverse decoder concat order to match Torch (c4,c3,c2,c1)
Torch decoder concatenates feature projections as [c4,c3,c2,c1] but
MLX was using [c1,c2,c3,c4]. The linear_fuse conv was trained with
the Torch order, producing scrambled features. Verified fix gives
correlation=1.0 against Torch on identical inputs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 09:44:41 -03:30
Cristopher Yates
81967ccc7c
Merge pull request #7 from cmoyates/feat/smoke-2048
feat: add 2048 smoke test
2026-03-01 07:08:22 -03:30
cmoyates
98e02db630
chore: gitignore output/ directory
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 07:06:01 -03:30
cmoyates
8be8d003e7
feat: add 2048 smoke test for native-resolution validation
Script + pytest tests to verify MLX inference works at CorridorKey's
training resolution. Uses samples/ by default, synthetic fallback.
Reports timing, peak memory, output diagnostics.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 07:04:28 -03:30
cmoyates
a3e723791f
add PyTorch reference inference script + gitignore samples/
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 06:56:04 -03:30
Cristopher Yates
6e427fde99
Merge pull request #6 from cmoyates/feat/engine-integration-surface
feat: add CorridorKeyMLXEngine integration surface
2026-03-01 06:47:48 -03:30
cmoyates
6c711e85bd
fix: remove double normalize_rgb in engine preprocessing
preprocess() already calls normalize_rgb() internally; calling it
before passing to preprocess() applied ImageNet normalization twice.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 06:47:22 -03:30
cmoyates
4bad586112
feat: add CorridorKeyMLXEngine integration surface
Drop-in MLX backend for main CorridorKey repo. Engine class wraps
existing model/inference with process_frame() API returning alpha,
fg, comp, processed as uint8 numpy arrays. Lowers Python to >=3.11,
removes unused deps, adds smoke script and 16 contract tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 06:43:07 -03:30
Cristopher Yates
b0c7d8086c
Merge pull request #5 from cmoyates/feat/phase6-optimization
feat(phase6): optimization, benchmarking, and tiled inference
2026-03-01 06:27:57 -03:30
cmoyates
3d330d0e0b
fix: guard against overlap >= tile_size in tiled inference
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 06:27:31 -03:30
cmoyates
57c3aff913
feat(phase6): optimization, benchmarking, and tiled inference
- Cache nn.Upsample instances in DecoderHead/GreenFormer __init__
  (eliminated ~7 allocations per forward pass)
- Add mx.compile() support via load_model(compile=True)
- Benchmark harness: eager vs compiled, multi-resolution, parity checks
- Tiled inference with overlap blending for large images
- Profiling utilities with forced mx.eval for accurate timing
- Reference comparison script (scripts/compare_reference.py)
- 12 new tests (compiled consistency + tiling)
- README performance section with Apple Silicon guidance

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 06:24:10 -03:30
Cristopher Yates
a14f8a74e8
Merge pull request #4 from cmoyates/feat/phase5-assembly-parity
feat(phase5): full model assembly + e2e parity
2026-03-01 06:12:04 -03:30
cmoyates
329db3fc43
fix: resize inputs to model img_size, use infer_and_save in CLI
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 06:11:57 -03:30
cmoyates
0481955cfd
feat(phase5): full model assembly + e2e parity
Wire GreenFormer (backbone + decoders + refiner), add image I/O,
inference pipeline, CLI entry point, and 3 test files (23 new tests).

E2e parity vs PyTorch: max abs err < 1.5e-4 across all outputs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 06:07:43 -03:30
Cristopher Yates
fd6ca78935
Merge pull request #3 from cmoyates/feat/phase4a-hiera-backbone
feat(phase4): MLX Hiera backbone port
2026-03-01 05:59:19 -03:30
Cristopher Yates
c2e7967538
feat(phase4): MLX Hiera backbone port (#2)
* feat(phase4a): MLX Hiera backbone with unroll/reroll parity

Port timm hiera_base_plus_224 to MLX: PatchEmbed, MaskUnitAttention,
HieraBlock, unroll/reroll, HieraBackbone. 4 NHWC feature maps at
strides 4/8/16/32. backbone.py now re-exports from hiera.py.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(phase4c): weight loading + pos_embed bicubic interpolation

HieraBackbone.load_checkpoint() loads safetensors, strips encoder.model.
prefix, bicubic-interpolates pos_embed from 512x512 to 128x128 tokens.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(phase4d): shape + parity tests, fix attention transpose

Fix MaskUnitAttention output transpose (0,2,3,1,4 → 0,3,2,1,4) to
match PyTorch transpose(1,3) token ordering for windowed attention.

Parity results (4 stages):
  Stage 0: max_abs 2.9e-4, mean 1.6e-5
  Stage 1: max_abs 1.3e-4, mean 1.3e-5
  Stage 2: max_abs 1.1e-2, mean 5.2e-5 (16 blocks, expected drift)
  Stage 3: max_abs 5.8e-4, mean 2.6e-5

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: clarify pos_embed parameter intent in HieraBackbone

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 05:55:24 -03:30
cmoyates
ac4393fcb4
docs: clarify pos_embed parameter intent in HieraBackbone
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 05:55:01 -03:30
cmoyates
cc2d31b435
feat(phase4d): shape + parity tests, fix attention transpose
Fix MaskUnitAttention output transpose (0,2,3,1,4 → 0,3,2,1,4) to
match PyTorch transpose(1,3) token ordering for windowed attention.

Parity results (4 stages):
  Stage 0: max_abs 2.9e-4, mean 1.6e-5
  Stage 1: max_abs 1.3e-4, mean 1.3e-5
  Stage 2: max_abs 1.1e-2, mean 5.2e-5 (16 blocks, expected drift)
  Stage 3: max_abs 5.8e-4, mean 2.6e-5

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 05:51:36 -03:30