metalgrow 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. metalgrow-0.1.0/.github/workflows/ci.yml +46 -0
  2. metalgrow-0.1.0/.github/workflows/release.yml +106 -0
  3. metalgrow-0.1.0/.gitignore +16 -0
  4. metalgrow-0.1.0/.pre-commit-config.yaml +7 -0
  5. metalgrow-0.1.0/.python-version +1 -0
  6. metalgrow-0.1.0/CHANGELOG.md +65 -0
  7. metalgrow-0.1.0/CONTRIBUTING.md +86 -0
  8. metalgrow-0.1.0/LICENSE +21 -0
  9. metalgrow-0.1.0/PKG-INFO +16 -0
  10. metalgrow-0.1.0/README.md +163 -0
  11. metalgrow-0.1.0/benchmarks/README.md +52 -0
  12. metalgrow-0.1.0/benchmarks/__init__.py +0 -0
  13. metalgrow-0.1.0/benchmarks/run.py +368 -0
  14. metalgrow-0.1.0/docs/README.md +7 -0
  15. metalgrow-0.1.0/docs/adr/0001-sr-backbone-dependency.md +56 -0
  16. metalgrow-0.1.0/docs/architecture.md +205 -0
  17. metalgrow-0.1.0/docs/benchmarks.md +61 -0
  18. metalgrow-0.1.0/docs/models.md +89 -0
  19. metalgrow-0.1.0/docs/releasing.md +72 -0
  20. metalgrow-0.1.0/docs/usage.md +155 -0
  21. metalgrow-0.1.0/pyproject.toml +64 -0
  22. metalgrow-0.1.0/requirements-dev.txt +6 -0
  23. metalgrow-0.1.0/requirements.txt +9 -0
  24. metalgrow-0.1.0/src/metalgrow/__init__.py +5 -0
  25. metalgrow-0.1.0/src/metalgrow/backbones/__init__.py +8 -0
  26. metalgrow-0.1.0/src/metalgrow/backbones/base.py +47 -0
  27. metalgrow-0.1.0/src/metalgrow/backbones/bicubic.py +27 -0
  28. metalgrow-0.1.0/src/metalgrow/backbones/realesrgan.py +54 -0
  29. metalgrow-0.1.0/src/metalgrow/backbones/registry.py +30 -0
  30. metalgrow-0.1.0/src/metalgrow/backbones/swinir.py +58 -0
  31. metalgrow-0.1.0/src/metalgrow/batch.py +163 -0
  32. metalgrow-0.1.0/src/metalgrow/cli.py +137 -0
  33. metalgrow-0.1.0/src/metalgrow/device.py +30 -0
  34. metalgrow-0.1.0/src/metalgrow/upscaler.py +177 -0
  35. metalgrow-0.1.0/src/metalgrow/weights.py +152 -0
  36. metalgrow-0.1.0/tests/test_backbones.py +50 -0
  37. metalgrow-0.1.0/tests/test_batch.py +172 -0
  38. metalgrow-0.1.0/tests/test_benchmarks.py +83 -0
  39. metalgrow-0.1.0/tests/test_cli.py +68 -0
  40. metalgrow-0.1.0/tests/test_device.py +38 -0
  41. metalgrow-0.1.0/tests/test_models_cli.py +71 -0
  42. metalgrow-0.1.0/tests/test_realesrgan.py +83 -0
  43. metalgrow-0.1.0/tests/test_swinir.py +82 -0
  44. metalgrow-0.1.0/tests/test_tiled_inference.py +145 -0
  45. metalgrow-0.1.0/tests/test_upscaler.py +49 -0
  46. metalgrow-0.1.0/tests/test_weights.py +93 -0
  47. metalgrow-0.1.0/uv.lock +1034 -0
@@ -0,0 +1,46 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ concurrency:
9
+ group: ${{ github.workflow }}-${{ github.ref }}
10
+ cancel-in-progress: true
11
+
12
+ jobs:
13
+ lint-test:
14
+ name: ${{ matrix.os }} / py${{ matrix.python-version }}
15
+ runs-on: ${{ matrix.os }}
16
+ strategy:
17
+ fail-fast: false
18
+ matrix:
19
+ os: [ubuntu-latest, macos-14]
20
+ python-version: ["3.11", "3.12"]
21
+
22
+ steps:
23
+ - uses: actions/checkout@v4
24
+
25
+ - name: Install uv
26
+ uses: astral-sh/setup-uv@v3
27
+ with:
28
+ enable-cache: true
29
+ cache-dependency-glob: pyproject.toml
30
+
31
+ - name: Set up Python ${{ matrix.python-version }}
32
+ run: uv python install ${{ matrix.python-version }}
33
+
34
+ - name: Create venv and install
35
+ run: |
36
+ uv venv --python ${{ matrix.python-version }}
37
+ uv pip install -e ".[dev]"
38
+
39
+ - name: Ruff check
40
+ run: .venv/bin/ruff check .
41
+
42
+ - name: Ruff format check
43
+ run: .venv/bin/ruff format --check .
44
+
45
+ - name: Pytest
46
+ run: .venv/bin/pytest -q
@@ -0,0 +1,106 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags: ["v*"]
6
+
7
+ permissions:
8
+ contents: write # attach artifacts to the GitHub Release
9
+ id-token: write # OIDC exchange for PyPI trusted publishing
10
+
11
+ concurrency:
12
+ group: release-${{ github.ref }}
13
+ cancel-in-progress: false
14
+
15
+ jobs:
16
+ build:
17
+ name: Build sdist + wheel
18
+ runs-on: ubuntu-latest
19
+ outputs:
20
+ version: ${{ steps.version.outputs.version }}
21
+ steps:
22
+ - uses: actions/checkout@v4
23
+
24
+ - name: Resolve version from tag
25
+ id: version
26
+ run: |
27
+ TAG="${GITHUB_REF#refs/tags/}"
28
+ VERSION="${TAG#v}"
29
+ echo "tag=$TAG" >> "$GITHUB_OUTPUT"
30
+ echo "version=$VERSION" >> "$GITHUB_OUTPUT"
31
+
32
+ - name: Verify tag matches pyproject version
33
+ run: |
34
+ PY_VERSION=$(python -c "import tomllib, pathlib; \
35
+ print(tomllib.loads(pathlib.Path('pyproject.toml').read_text())['project']['version'])")
36
+ if [ "$PY_VERSION" != "${{ steps.version.outputs.version }}" ]; then
37
+ echo "::error::tag ${{ steps.version.outputs.tag }} (version ${{ steps.version.outputs.version }}) does not match pyproject version $PY_VERSION"
38
+ exit 1
39
+ fi
40
+
41
+ - name: Install uv
42
+ uses: astral-sh/setup-uv@v3
43
+
44
+ - name: Set up Python
45
+ run: uv python install 3.11
46
+
47
+ - name: Build distributions
48
+ run: |
49
+ uv venv
50
+ uv pip install build
51
+ uv run python -m build
52
+
53
+ - name: Upload build artifacts
54
+ uses: actions/upload-artifact@v4
55
+ with:
56
+ name: dist
57
+ path: dist/
58
+ if-no-files-found: error
59
+
60
+ publish-pypi:
61
+ name: Publish to PyPI
62
+ needs: build
63
+ runs-on: ubuntu-latest
64
+ # Scoped environment lets PyPI's trusted-publisher config pin this job.
65
+ environment:
66
+ name: pypi
67
+ url: https://pypi.org/p/metalgrow
68
+ permissions:
69
+ id-token: write
70
+ steps:
71
+ - name: Download build artifacts
72
+ uses: actions/download-artifact@v4
73
+ with:
74
+ name: dist
75
+ path: dist/
76
+
77
+ - name: Publish to PyPI (trusted publishing)
78
+ uses: pypa/gh-action-pypi-publish@release/v1
79
+
80
+ github-release:
81
+ name: Attach artifacts to GitHub Release
82
+ needs: [build, publish-pypi]
83
+ runs-on: ubuntu-latest
84
+ permissions:
85
+ contents: write
86
+ steps:
87
+ - uses: actions/checkout@v4
88
+
89
+ - name: Download build artifacts
90
+ uses: actions/download-artifact@v4
91
+ with:
92
+ name: dist
93
+ path: dist/
94
+
95
+ - name: Create or update GitHub Release
96
+ env:
97
+ GH_TOKEN: ${{ github.token }}
98
+ TAG: ${{ github.ref_name }}
99
+ run: |
100
+ if gh release view "$TAG" >/dev/null 2>&1; then
101
+ gh release upload "$TAG" dist/* --clobber
102
+ else
103
+ gh release create "$TAG" dist/* \
104
+ --title "$TAG" \
105
+ --generate-notes
106
+ fi
@@ -0,0 +1,16 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ .venv/
5
+ venv/
6
+ dist/
7
+ build/
8
+ .pytest_cache/
9
+ .ruff_cache/
10
+ .DS_Store
11
+ *.pt
12
+ *.pth
13
+ *.safetensors
14
+ models/
15
+ out/
16
+ .claude
@@ -0,0 +1,7 @@
1
+ repos:
2
+ - repo: https://github.com/astral-sh/ruff-pre-commit
3
+ rev: v0.6.9
4
+ hooks:
5
+ - id: ruff
6
+ args: [--fix]
7
+ - id: ruff-format
@@ -0,0 +1 @@
1
+ 3.11
@@ -0,0 +1,65 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project
5
+ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.1.0] — 2026-04-13
10
+
11
+ First public release. Everything below is an introduction rather than a diff
12
+ against a prior version.
13
+
14
+ ### Added
15
+
16
+ - **Core pipeline.** `Upscaler` class (public API), a single `torch.device`
17
+ selector (`auto → mps → cuda → cpu`), and a `typer` CLI with `upscale`,
18
+ `info`, and `models {list,download,rm}` commands.
19
+ - **Backbones.** Pluggable registry behind a `Backbone` ABC. Ships with
20
+ `bicubic` (analytical, weightless), Real-ESRGAN (`realesrgan-x2`,
21
+ `realesrgan-x4`), and SwinIR (`swinir-x2`, `swinir-x4`). Learned backbones
22
+ route through [spandrel](https://github.com/chaiNNer-org/spandrel).
23
+ - **Tiled inference.** `Upscaler._tiled_forward` with feathered overlap
24
+ blending, auto-triggered when either image dimension exceeds the
25
+ backbone's `default_tile`. Backbones declare tile geometry; the control
26
+ flow is shared.
27
+ - **Batch mode.** `metalgrow upscale SRC DST` accepts a directory or glob
28
+ and writes mirrored filenames into `DST` with `--skip-existing` and
29
+ parallel I/O workers. Inference stays serial on the device.
30
+ - **Model registry.** `metalgrow.weights` caches weights under
31
+ `~/.cache/metalgrow/` (overridable via `METALGROW_CACHE_DIR`) with
32
+ sha256 verification on every call; corrupt files are removed so a retry
33
+ can re-download. `metalgrow models` inspects and manages the cache.
34
+ - **Alpha handling.** `Upscaler` runs RGB-only backbones on the RGB plane
35
+ and bicubic-upscales the alpha channel in parallel, then recombines —
36
+ preserving transparency without corrupting it.
37
+ - **MPS ergonomics.** Automatic `PYTORCH_ENABLE_MPS_FALLBACK=1` so missing
38
+ ops fall back to CPU silently instead of aborting; opt out with
39
+ `METALGROW_DISABLE_MPS_FALLBACK=1`.
40
+ - **Benchmarks.** Reproducible harness at `benchmarks/run.py` driven by
41
+ seeded synthetic fixtures, with device-aware peak-memory reporting and
42
+ markdown / JSON output. Not part of CI.
43
+ - **Release automation.** Tag-triggered workflow builds sdist + wheel,
44
+ publishes to PyPI via trusted publishing (OIDC), and attaches artifacts
45
+ to a GitHub Release. See [`docs/releasing.md`](docs/releasing.md).
46
+ - **Docs.** Usage guide (CLI + library), model comparison with benchmark
47
+ numbers, full benchmark tables, architecture overview, and release
48
+ process. See [`docs/README.md`](docs/README.md).
49
+ - **CI.** Lint (ruff) + test (pytest) matrix on macOS and Linux,
50
+ Python 3.11 + 3.12.
51
+
52
+ ### Known limitations
53
+
54
+ - CUDA benchmark numbers are not yet populated — GitHub-hosted runners
55
+ don't expose GPUs. Run `benchmarks/run.py` on any CUDA-enabled machine
56
+ and append to [`docs/benchmarks.md`](docs/benchmarks.md).
57
+ - SwinIR on MPS is only ~2× faster than CPU at these sizes: several
58
+ attention-adjacent ops fall back to CPU via the MPS fallback. Tracked
59
+ as a quality-of-life issue against upstream torch.
60
+ - Peak memory on MPS is measured as the high-water mark of
61
+ `driver_allocated_memory`, which includes allocator pool reservations —
62
+ interpret it as "working set size", not a strict activation peak.
63
+
64
+ [Unreleased]: https://github.com/joaodotwork/metalgrow/compare/v0.1.0...HEAD
65
+ [0.1.0]: https://github.com/joaodotwork/metalgrow/releases/tag/v0.1.0
@@ -0,0 +1,86 @@
1
+ # Contributing to metalgrow
2
+
3
+ Thanks for your interest in contributing! This guide covers the local setup
4
+ and the conventions we follow.
5
+
6
+ ## Dev setup
7
+
8
+ We use [`uv`](https://docs.astral.sh/uv/) but plain `pip` also works.
9
+
10
+ ```bash
11
+ git clone https://github.com/joaodotwork/metalgrow.git
12
+ cd metalgrow
13
+
14
+ # uv
15
+ uv venv && source .venv/bin/activate
16
+ uv pip install -e ".[dev]"
17
+
18
+ # or plain pip
19
+ python -m venv .venv && source .venv/bin/activate
20
+ pip install -e ".[dev]"
21
+ ```
22
+
23
+ Install the pre-commit hooks once per clone:
24
+
25
+ ```bash
26
+ pre-commit install
27
+ ```
28
+
29
+ This runs `ruff` and `ruff-format` on every commit. The same checks run in
30
+ CI, so installing the hook saves a round trip.
31
+
32
+ ## Running tests and lint
33
+
34
+ ```bash
35
+ pytest # full test suite (CPU only, no weights required)
36
+ ruff check . # lint
37
+ ruff format . # auto-format
38
+ ruff format --check . # CI-equivalent format check
39
+ ```
40
+
41
+ The test suite is intentionally CPU-only so it passes on any machine and on
42
+ Linux CI runners without Apple Silicon.
43
+
44
+ ## Branch and PR conventions
45
+
46
+ - Branch from `main`. Name branches by intent: `feat/...`, `fix/...`,
47
+ `chore/...`, `docs/...`, `test/...`.
48
+ - Keep commits focused. Prefer one commit per issue when a branch closes
49
+ multiple issues; otherwise group related changes.
50
+ - Commit subject line uses the same conventional prefix (`feat:`, `fix:`,
51
+ `chore:`, `docs:`, `test:`, `ci:`) followed by a short imperative summary.
52
+ - Reference the issue in the commit body with `Closes #N`.
53
+ - Before opening a PR: `ruff check .`, `ruff format --check .`, and
54
+ `pytest` must all pass. CI will re-run them on macOS and Linux.
55
+
56
+ ## Adding a new backbone
57
+
58
+ The public API (`Upscaler`, CLI flags) is stable. New super-resolution
59
+ models plug in behind it. See
60
+ [`docs/architecture.md`](docs/architecture.md#plugging-in-a-learned-backbone)
61
+ for the expected shape of that change — briefly:
62
+
63
+ 1. Add `src/metalgrow/backbones/<name>.py` exposing a `load_model(device)`
64
+ that returns an `nn.Module` in `eval()` mode.
65
+ 2. Load the backbone lazily in `Upscaler.__init__` so `metalgrow info` and
66
+ unit tests stay fast.
67
+ 3. Replace the `F.interpolate` call in `Upscaler.upscale` with the model
68
+ forward pass. Pre/post-processing belongs on the backbone, not on
69
+ `Upscaler`.
70
+
71
+ Weights must not be committed; a model registry is planned (see the M3
72
+ milestone).
73
+
74
+ ## Cutting a release
75
+
76
+ Releases are fully automated: tag `vX.Y.Z` on `main`, push, and the
77
+ `release.yml` workflow builds, publishes to PyPI (trusted publishing), and
78
+ attaches the artifacts to a GitHub Release. See
79
+ [`docs/releasing.md`](docs/releasing.md) for the full process — including
80
+ the one-time PyPI publisher setup required before the first release.
81
+
82
+ ## Reporting issues
83
+
84
+ Bugs, questions, and feature requests are all welcome on the
85
+ [issue tracker](https://github.com/joaodotwork/metalgrow/issues). Include
86
+ your OS, Python version, and the output of `metalgrow info` when relevant.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 joaodotwork
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,16 @@
1
+ Metadata-Version: 2.4
2
+ Name: metalgrow
3
+ Version: 0.1.0
4
+ Summary: AI-powered image upscaler accelerated on Apple Metal (MPS).
5
+ License-File: LICENSE
6
+ Requires-Python: >=3.11
7
+ Requires-Dist: numpy>=1.26
8
+ Requires-Dist: pillow>=10.0
9
+ Requires-Dist: spandrel>=0.4
10
+ Requires-Dist: torch>=2.3
11
+ Requires-Dist: torchvision>=0.18
12
+ Requires-Dist: typer>=0.12
13
+ Provides-Extra: dev
14
+ Requires-Dist: pre-commit>=3.7; extra == 'dev'
15
+ Requires-Dist: pytest>=8.0; extra == 'dev'
16
+ Requires-Dist: ruff>=0.6; extra == 'dev'
@@ -0,0 +1,163 @@
1
+ # metalgrow
2
+
3
+ > AI-powered image upscaler accelerated on Apple Metal (MPS).
4
+
5
+ [![CI](https://github.com/joaodotwork/metalgrow/actions/workflows/ci.yml/badge.svg)](https://github.com/joaodotwork/metalgrow/actions/workflows/ci.yml)
6
+ [![Python](https://img.shields.io/badge/python-3.11%2B-blue.svg)](https://www.python.org/downloads/)
7
+ [![PyTorch](https://img.shields.io/badge/PyTorch-2.3%2B-ee4c2c.svg?logo=pytorch&logoColor=white)](https://pytorch.org/)
8
+ [![Apple Silicon](https://img.shields.io/badge/Apple%20Silicon-MPS-000000.svg?logo=apple&logoColor=white)](https://developer.apple.com/metal/pytorch/)
9
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
10
+ [![Status](https://img.shields.io/badge/status-alpha-orange.svg)](#roadmap)
11
+ [![Ruff](https://img.shields.io/badge/lint-ruff-261230.svg)](https://github.com/astral-sh/ruff)
12
+ [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](#contributing)
13
+
14
+ `metalgrow` runs super-resolution on Apple Silicon GPUs through PyTorch's MPS
15
+ backend, with graceful fallbacks to CUDA and CPU. It ships with a bicubic
16
+ baseline so the pipeline is runnable end-to-end, and exposes a clean seam for
17
+ plugging in a learned backbone (Real-ESRGAN, SwinIR, etc.).
18
+
19
+ ---
20
+
21
+ ## Table of contents
22
+
23
+ - [Features](#features)
24
+ - [Requirements](#requirements)
25
+ - [Install](#install)
26
+ - [Usage](#usage)
27
+ - [Project layout](#project-layout)
28
+ - [Roadmap](#roadmap)
29
+ - [Contributing](#contributing)
30
+ - [License](#license)
31
+
32
+ ## Features
33
+
34
+ - 🍎 **Apple Metal first** — PyTorch MPS backend, zero config on Apple Silicon
35
+ - 🔁 **Portable** — automatic fallback to CUDA / CPU when MPS isn't available
36
+ - 🧩 **Pluggable backbones** — bicubic, Real-ESRGAN (x2/x4), SwinIR (x2/x4)
37
+ - 🧱 **Tiled inference** — process arbitrarily large images with feathered overlap blending
38
+ - 📂 **Batch mode** — upscale whole directories or globs with a progress bar
39
+ - 📦 **Model registry** — `metalgrow models` manages cached weights with sha256 verification
40
+ - 🧪 **Tested** — pytest suite covering the CPU baseline, tiling, batch mode, and registry
41
+
42
+ ## Requirements
43
+
44
+ - Python **3.11+**
45
+ - macOS with Apple Silicon for MPS acceleration (optional — CPU/CUDA also work)
46
+
47
+ ## Install
48
+
49
+ ```bash
50
+ git clone https://github.com/joaodotwork/metalgrow.git
51
+ cd metalgrow
52
+ uv venv && source .venv/bin/activate
53
+ uv pip install -e .
54
+ ```
55
+
56
+ Or with plain pip:
57
+
58
+ ```bash
59
+ python -m venv .venv && source .venv/bin/activate
60
+ pip install -e .
61
+ ```
62
+
63
+ ## Usage
64
+
65
+ ### CLI
66
+
67
+ Single file:
68
+
69
+ ```bash
70
+ metalgrow info
71
+ metalgrow upscale input.jpg out.png --scale 2
72
+ metalgrow upscale input.jpg out.png --scale 4 --device mps --backbone realesrgan-x4
73
+ ```
74
+
75
+ Whole directory or glob (writes to a target directory, mirroring filenames):
76
+
77
+ ```bash
78
+ metalgrow upscale ./photos ./photos-upscaled --scale 2 --backbone realesrgan-x2
79
+ metalgrow upscale "./photos/*.png" ./out --scale 2 --skip-existing
80
+ ```
81
+
82
+ | Flag | Default | Description |
83
+ | --------------------- | ------------- | --------------------------------------------------------------- |
84
+ | `--scale`, `-s` | `2.0` | Upscale factor (1.01–8.0) |
85
+ | `--device`, `-d` | `auto` | `auto` \| `mps` \| `cuda` \| `cpu` |
86
+ | `--backbone`, `-b` | `bicubic` | `bicubic` \| `realesrgan-x{2,4}` \| `swinir-x{2,4}` |
87
+ | `--dtype` | `fp32` | `fp32` \| `fp16` (fp16 is MPS-only and noisier) |
88
+ | `--tile` | backbone hint | Tile size in input px for tiled inference (`0` disables) |
89
+ | `--tile-pad` | backbone hint | Context padding per tile edge (covers backbone receptive field) |
90
+ | `--skip-existing` | off | Batch mode: skip outputs that already exist |
91
+ | `--workers`, `-j` | `4` | Batch mode: parallel I/O workers (inference stays serial) |
92
+
93
+ #### Manage cached model weights
94
+
95
+ ```bash
96
+ metalgrow models list
97
+ metalgrow models download realesrgan-x4
98
+ metalgrow models rm realesrgan-x4
99
+ ```
100
+
101
+ Weights cache to `~/.cache/metalgrow/` by default; override with the
102
+ `METALGROW_CACHE_DIR` env var. Every download is sha256-verified.
103
+
104
+ See [`docs/models.md`](./docs/models.md) for a quality / speed / memory
105
+ comparison (with benchmark numbers) and guidance on which backbone to pick,
106
+ or [`docs/usage.md`](./docs/usage.md) for the full CLI / library reference.
107
+
108
+ ### Library
109
+
110
+ ```python
111
+ from PIL import Image
112
+ from metalgrow import Upscaler
113
+
114
+ upscaler = Upscaler(backbone="realesrgan-x2", device="auto") # auto | mps | cuda | cpu
115
+ result = upscaler.upscale(
116
+ Image.open("input.jpg").convert("RGB"),
117
+ scale=2.0,
118
+ tile=256, # optional — backbone has sensible defaults
119
+ tile_pad=16,
120
+ )
121
+ result.save("out.png")
122
+ ```
123
+
124
+ ## Project layout
125
+
126
+ ```
127
+ src/metalgrow/
128
+ device.py # device auto-selection (MPS → CUDA → CPU)
129
+ upscaler.py # Upscaler class + tiled inference with overlap blending
130
+ batch.py # directory / glob batch mode
131
+ weights.py # weight registry, sha256 verification, cache management
132
+ cli.py # typer CLI (upscale, info, models)
133
+ backbones/ # bicubic, realesrgan, swinir; plugin registry
134
+ tests/
135
+ docs/
136
+ usage.md # CLI recipes, library API, env vars, pitfalls
137
+ models.md # backbone comparison, benchmark snapshot, tiling
138
+ benchmarks.md # full (device × backbone × scale) tables
139
+ architecture.md # module layout, data flow, extension points
140
+ ```
141
+
142
+ ## Roadmap
143
+
144
+ - [x] Integrate a learned SR backbone (Real-ESRGAN default)
145
+ - [x] Tiled inference for large images
146
+ - [x] Batch / directory mode
147
+ - [x] Model registry + weight download
148
+ - [x] Second backbone family (SwinIR)
149
+ - [x] GitHub Actions CI (lint + test on macOS + Linux)
150
+ - [x] Benchmarks: MPS vs CPU (CUDA pending GPU runner)
151
+ - [ ] v0.1.0 release
152
+
153
+ ## Contributing
154
+
155
+ Issues and PRs are welcome. Please run `ruff` and `pytest` before opening a PR.
156
+
157
+ ## License
158
+
159
+ MIT — see [LICENSE](LICENSE).
160
+
161
+ ---
162
+
163
+ <sub>Made with 🔩 on Apple Silicon.</sub>
@@ -0,0 +1,52 @@
1
+ # Benchmarks
2
+
3
+ Reproducible benchmark harness for metalgrow backbones.
4
+
5
+ ## Running locally
6
+
7
+ ```bash
8
+ # All backbones × scales on the auto-selected device, skip uncached weights
9
+ python -m benchmarks.run --skip-uncached
10
+
11
+ # Pin to CPU (works everywhere, useful for contributor machines)
12
+ python -m benchmarks.run --device cpu --backbones bicubic --scales 2,4
13
+
14
+ # Write a markdown fragment into the docs
15
+ python -m benchmarks.run --device auto --output docs/benchmarks.md
16
+ ```
17
+
18
+ ### Inputs
19
+
20
+ The runner generates synthetic RGB noise with a fixed seed (`SEED = 0`). No
21
+ fixtures are downloaded and the exact same pixel data is produced on every
22
+ machine, so results are only shaped by device + backbone + scale + size —
23
+ not by which image was handy.
24
+
25
+ ### Metrics
26
+
27
+ - **Median (s)** — wall time of the median iteration, after `--warmup` iters.
28
+ - **MP/s** — output megapixels per second = `out_w * out_h / median_s`.
29
+ - **Peak (MiB)** — device-side peak allocation during the measured run:
30
+ - `cuda`: `torch.cuda.max_memory_allocated()` after `reset_peak_memory_stats()`
31
+ - `mps`: high-water of `torch.mps.driver_allocated_memory()` from before the
32
+ `Upscaler` is constructed, so the number covers weights + activations.
33
+ The MPS allocator pool does **not** shrink between cells inside one
34
+ process, so only the *first* cell per run reports a truthful peak —
35
+ later cells reuse the already-sized pool and can report 0. To get
36
+ per-backbone peaks, invoke the runner once per backbone (see
37
+ `docs/models.md` for the per-backbone numbers).
38
+ - `cpu`: not reported (no reliable device-side counter; RSS deltas are noisy).
39
+
40
+ ## CUDA numbers
41
+
42
+ GitHub's hosted runners don't have GPUs, so CUDA rows are produced on a
43
+ CUDA-enabled runner (cloud or self-hosted) via `workflow_dispatch`. The
44
+ rendered markdown is committed under `docs/benchmarks.md`; JSON output can
45
+ be combined across devices with any script that concatenates `results[]`.
46
+
47
+ ## Not part of CI
48
+
49
+ This harness intentionally lives outside `tests/` — it is **not** executed
50
+ on push/PR. A long-running benchmark in CI produces flaky numbers (noisy
51
+ neighbours, runner variability) and blocks contributor workflows. Run it
52
+ manually when weights, backbones, or kernels change.
File without changes