corridorkey-mrp-mlx/README.md
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

275 lines
8.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# corridorkey-mlx
MLX inference port of [CorridorKey](https://github.com/nikopueringer/CorridorKey) for Apple Silicon.
## Architecture
```
RGB image + coarse alpha hint (4ch)
┌──────────────────┐
│ Hiera backbone │ (timm, features_only)
│ → 4 multiscale │
│ feature maps │
└──────────────────┘
┌────┴────┐
▼ ▼
┌───────┐ ┌───────┐
│ Alpha │ │ FG │
│ head │ │ head │
│ (1ch) │ │ (3ch) │
└───────┘ └───────┘
│ │
└────┬────┘
┌──────────────────┐
│ CNN Refiner │ RGB + coarse preds (7ch)
│ → delta logits │ → sigmoid
└──────────────────┘
final alpha + fg
```
## Phased Roadmap
| Phase | Scope | Status |
|-------|-------|--------|
| 1 | PyTorch reference harness + fixture dump | Done |
| 2 | MLX decoder/refiner blocks + parity tests | Done |
| 3 | Checkpoint conversion (PyTorch → MLX) | Done |
| 4 | Hiera backbone port | Done |
| 5 | Full model assembly + e2e parity | Done |
| 6 | Optimization + benchmarking | Done |
See `prompts/` for detailed phase instructions.
## Usage
### Setup
```bash
uv sync --group dev
```
### Convert weights
Convert the PyTorch checkpoint to MLX safetensors (one-time):
```bash
uv run python scripts/convert_weights.py \
--checkpoint checkpoints/CorridorKey_v1.0.pth \
--output checkpoints/corridorkey_mlx.safetensors
```
### Single-image inference
```bash
uv run python scripts/infer.py \
--image input.png \
--hint alpha_hint.png \
--output-dir output/
```
Outputs `output/alpha.png` (alpha matte) and `output/foreground.png` (foreground).
Options:
- `--checkpoint PATH` — MLX safetensors file (default: `checkpoints/corridorkey_mlx.safetensors`)
- `--img-size N` — model input resolution (default: 512)
- `--output-dir DIR` — output directory (default: `output/`)
### Python API
```python
from corridorkey_mlx.inference.pipeline import load_model, infer_and_save
model = load_model("checkpoints/corridorkey_mlx.safetensors", img_size=512)
results = infer_and_save(model, "input.png", "alpha_hint.png", "output/")
```
## Development
```bash
uv run pytest # tests
uv run ruff check . # lint
uv run ruff format . # format
uv run mypy src/ # type check
```
For PyTorch reference work:
```bash
uv sync --group reference
```
## Reference Fixtures
Phase 1 generates golden reference tensors from PyTorch for MLX parity testing.
**Format:** single `reference/fixtures/golden.npz` (numpy compressed archive)
**Generate:**
```bash
uv run --group reference python scripts/dump_pytorch_reference.py \
--checkpoint checkpoints/CorridorKey_v1.0.pth
```
**Contents (all float32, NCHW, batch=1, img_size=512):**
| Key | Shape | Description |
|-----|-------|-------------|
| `input` | (1, 4, 512, 512) | Random input (seed=42) |
| `encoder_feature_0` | (1, 112, 128, 128) | Backbone stride-4 |
| `encoder_feature_1` | (1, 224, 64, 64) | Backbone stride-8 |
| `encoder_feature_2` | (1, 448, 32, 32) | Backbone stride-16 |
| `encoder_feature_3` | (1, 896, 16, 16) | Backbone stride-32 |
| `alpha_logits` | (1, 1, 128, 128) | Alpha decoder output (H/4) |
| `fg_logits` | (1, 3, 128, 128) | FG decoder output (H/4) |
| `alpha_logits_up` | (1, 1, 512, 512) | Alpha logits upsampled |
| `fg_logits_up` | (1, 3, 512, 512) | FG logits upsampled |
| `alpha_coarse` | (1, 1, 512, 512) | sigmoid(alpha_logits_up) |
| `fg_coarse` | (1, 3, 512, 512) | sigmoid(fg_logits_up) |
| `delta_logits` | (1, 4, 512, 512) | Refiner output (10x scaled) |
| `alpha_final` | (1, 1, 512, 512) | Final alpha prediction |
| `fg_final` | (1, 3, 512, 512) | Final FG prediction |
## Parity Results
End-to-end parity vs PyTorch reference (512×512, float32):
| Tensor | Max Abs Error | Mean Abs Error |
|--------|--------------|----------------|
| alpha_logits | 8.8e-05 | 1.6e-05 |
| fg_logits | 1.5e-04 | 7.2e-06 |
| alpha_coarse | 9.7e-06 | 1.1e-06 |
| fg_coarse | 6.7e-06 | 1.1e-06 |
| delta_logits | 1.1e-04 | 4.3e-06 |
| alpha_final | 2.6e-05 | 8.7e-08 |
| fg_final | 9.5e-06 | 1.1e-06 |
## Performance
### Compiled inference
Use `compile=True` for fused execution on fixed-resolution inputs:
```python
model = load_model("checkpoints/corridorkey_mlx.safetensors", img_size=512, compile=True)
```
The first call incurs a one-time compilation cost. Subsequent calls at the same
resolution run faster. Shapeless compilation (`shapeless=True`) is **not recommended**
due to shape-dependent reshapes in the Hiera backbone.
### Benchmarking
```bash
uv run python scripts/bench_mlx.py
uv run python scripts/bench_mlx.py --resolutions 256 512 1024 --bench-runs 20
```
Reports eager vs compiled latency, warmup cost, and parity check per resolution.
### Large images (tiled inference)
For images larger than the model's input resolution, use tiled inference with
overlap blending:
```python
from corridorkey_mlx.inference.tiling import tiled_inference
model = load_model("checkpoints/corridorkey_mlx.safetensors", img_size=512)
x = preprocess(rgb, alpha_hint) # full-resolution (1, H, W, 4)
result = tiled_inference(model, x, tile_size=512, overlap=64)
```
### Recommended settings for Apple Silicon
| Setting | Value | Notes |
|---------|-------|-------|
| `img_size` | 512 | Good speed/quality balance |
| `compile` | True | ~1.52x faster after warmup |
| `tile_size` | 512 | Match `img_size` for tiling |
| `overlap` | 64 | Smooth blending at tile boundaries |
### Comparing against PyTorch reference
```bash
uv run python scripts/compare_reference.py
```
## Using as a CorridorKey backend
This repo can be consumed as a drop-in MLX backend by the main CorridorKey app.
### Install (editable, from sibling checkout)
```bash
# from the main CorridorKey repo directory
uv pip install -e ../corridorkey-mlx
```
### Engine API
```python
from corridorkey_mlx import CorridorKeyMLXEngine
engine = CorridorKeyMLXEngine(
checkpoint_path="/abs/path/to/corridorkey_mlx.safetensors",
img_size=2048, # production (512 for dev)
use_refiner=True,
compile=True, # faster after first call
)
result = engine.process_frame(rgb_uint8, mask_uint8)
# result["alpha"] — (H, W) uint8 alpha matte
# result["fg"] — (H, W, 3) uint8 foreground
# result["comp"] — (H, W, 3) uint8 fg composited over black
# result["processed"] — (H, W, 3) uint8 (placeholder, same as fg)
```
### Expected inputs
- **image**: numpy uint8 `(H, W, 3)` RGB. sRGB color space (standard).
- **mask**: numpy uint8 `(H, W)` or `(H, W, 1)` grayscale alpha hint.
- **checkpoint**: `.safetensors` format, converted from PyTorch via `scripts/convert_weights.py`.
Inputs are resized internally to `img_size` for inference, then outputs are
resized back to the original input resolution.
### Smoke test
```bash
uv run python scripts/smoke_engine.py \
--image input.png --hint hint.png \
--checkpoint checkpoints/corridorkey_mlx.safetensors \
--img-size 512
```
### Standalone scripts vs engine usage
| | Standalone (`scripts/infer.py`) | Engine (`CorridorKeyMLXEngine`) |
|---|---|---|
| Input | file paths | numpy arrays |
| Output | saved PNGs | in-memory dict |
| Returns | `alpha`, `foreground` | `alpha`, `fg`, `comp`, `processed` |
| Default img_size | 512 | 2048 |
| Use case | one-off CLI inference | app backend integration |
### Stubs (not yet implemented)
- `despill_strength` — accepted but ignored (warns once)
- `auto_despeckle` / `despeckle_size` — accepted but ignored (warns once)
- `input_is_linear` — accepted but no-op (model expects sRGB)
### Python version
Requires Python >=3.11. Compatible with the main CorridorKey repo's 3.11 target.
## Current Status
Phases 16 complete. Full model assembly with end-to-end parity verified.
Optimization, benchmarking, and tiled inference available.
Engine integration surface available for backend consumption.