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.
- metalgrow-0.1.0/.github/workflows/ci.yml +46 -0
- metalgrow-0.1.0/.github/workflows/release.yml +106 -0
- metalgrow-0.1.0/.gitignore +16 -0
- metalgrow-0.1.0/.pre-commit-config.yaml +7 -0
- metalgrow-0.1.0/.python-version +1 -0
- metalgrow-0.1.0/CHANGELOG.md +65 -0
- metalgrow-0.1.0/CONTRIBUTING.md +86 -0
- metalgrow-0.1.0/LICENSE +21 -0
- metalgrow-0.1.0/PKG-INFO +16 -0
- metalgrow-0.1.0/README.md +163 -0
- metalgrow-0.1.0/benchmarks/README.md +52 -0
- metalgrow-0.1.0/benchmarks/__init__.py +0 -0
- metalgrow-0.1.0/benchmarks/run.py +368 -0
- metalgrow-0.1.0/docs/README.md +7 -0
- metalgrow-0.1.0/docs/adr/0001-sr-backbone-dependency.md +56 -0
- metalgrow-0.1.0/docs/architecture.md +205 -0
- metalgrow-0.1.0/docs/benchmarks.md +61 -0
- metalgrow-0.1.0/docs/models.md +89 -0
- metalgrow-0.1.0/docs/releasing.md +72 -0
- metalgrow-0.1.0/docs/usage.md +155 -0
- metalgrow-0.1.0/pyproject.toml +64 -0
- metalgrow-0.1.0/requirements-dev.txt +6 -0
- metalgrow-0.1.0/requirements.txt +9 -0
- metalgrow-0.1.0/src/metalgrow/__init__.py +5 -0
- metalgrow-0.1.0/src/metalgrow/backbones/__init__.py +8 -0
- metalgrow-0.1.0/src/metalgrow/backbones/base.py +47 -0
- metalgrow-0.1.0/src/metalgrow/backbones/bicubic.py +27 -0
- metalgrow-0.1.0/src/metalgrow/backbones/realesrgan.py +54 -0
- metalgrow-0.1.0/src/metalgrow/backbones/registry.py +30 -0
- metalgrow-0.1.0/src/metalgrow/backbones/swinir.py +58 -0
- metalgrow-0.1.0/src/metalgrow/batch.py +163 -0
- metalgrow-0.1.0/src/metalgrow/cli.py +137 -0
- metalgrow-0.1.0/src/metalgrow/device.py +30 -0
- metalgrow-0.1.0/src/metalgrow/upscaler.py +177 -0
- metalgrow-0.1.0/src/metalgrow/weights.py +152 -0
- metalgrow-0.1.0/tests/test_backbones.py +50 -0
- metalgrow-0.1.0/tests/test_batch.py +172 -0
- metalgrow-0.1.0/tests/test_benchmarks.py +83 -0
- metalgrow-0.1.0/tests/test_cli.py +68 -0
- metalgrow-0.1.0/tests/test_device.py +38 -0
- metalgrow-0.1.0/tests/test_models_cli.py +71 -0
- metalgrow-0.1.0/tests/test_realesrgan.py +83 -0
- metalgrow-0.1.0/tests/test_swinir.py +82 -0
- metalgrow-0.1.0/tests/test_tiled_inference.py +145 -0
- metalgrow-0.1.0/tests/test_upscaler.py +49 -0
- metalgrow-0.1.0/tests/test_weights.py +93 -0
- 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 @@
|
|
|
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.
|
metalgrow-0.1.0/LICENSE
ADDED
|
@@ -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.
|
metalgrow-0.1.0/PKG-INFO
ADDED
|
@@ -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
|
+
[](https://github.com/joaodotwork/metalgrow/actions/workflows/ci.yml)
|
|
6
|
+
[](https://www.python.org/downloads/)
|
|
7
|
+
[](https://pytorch.org/)
|
|
8
|
+
[](https://developer.apple.com/metal/pytorch/)
|
|
9
|
+
[](LICENSE)
|
|
10
|
+
[](#roadmap)
|
|
11
|
+
[](https://github.com/astral-sh/ruff)
|
|
12
|
+
[](#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
|