diff --git a/.github/workflows/uv-lock-checks.yml b/.github/workflows/uv-lock-checks.yml index d57163165fb..a9ba203fc4e 100644 --- a/.github/workflows/uv-lock-checks.yml +++ b/.github/workflows/uv-lock-checks.yml @@ -1,6 +1,8 @@ -# Check the `uv` lockfile for consistency with `pyproject.toml`. +# Check the `uv` lockfile and `pins.json` for consistency with `pyproject.toml`. # -# If this check fails, you should run `uv lock` to update the lockfile. +# If the lockfile check fails, you should run `uv lock` to update the lockfile. +# If the pins check fails, update the torch index URLs in `pins.json` to match +# the `[[tool.uv.index]]` entries in `pyproject.toml` (see scripts/check_pins.py). name: 'uv lock checks' @@ -54,15 +56,24 @@ jobs: uvlock-pyprojecttoml: - 'pyproject.toml' - 'uv.lock' + - 'pins.json' + - 'scripts/check_pins.py' - name: setup uv if: ${{ steps.changed-files.outputs.uvlock-pyprojecttoml_any_changed == 'true' || inputs.always_run == true }} uses: astral-sh/setup-uv@v8.1.0 with: - version: '0.6.10' + # Keep this at or above the uv version used to generate uv.lock (revision 3 lock + # format requires a modern uv; 0.6.x cannot parse it). + version: '0.11.28' enable-cache: true - name: check lockfile if: ${{ steps.changed-files.outputs.uvlock-pyprojecttoml_any_changed == 'true' || inputs.always_run == true }} run: uv lock --locked # this will exit with 1 if the lockfile is not consistent with pyproject.toml shell: bash + + - name: check pins.json + if: ${{ steps.changed-files.outputs.uvlock-pyprojecttoml_any_changed == 'true' || inputs.always_run == true }} + run: python3 scripts/check_pins.py # pins.json is consumed by the launcher; keep its torch index URLs in sync with pyproject.toml + shell: bash diff --git a/pyproject.toml b/pyproject.toml index de665d621e3..ba880d70b93 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,9 @@ dependencies = [ # Loosely pinned, will respect requirement of `diffusers[torch]`. Split by platform: linux/win allow # >=2.10 so the rocm extra can use torch 2.10.0+rocm7.1, while macOS stays on 2.7.x — newer macOS torch # wheels exercise MPS on CI runners (no usable Metal GPU) and fail with MPS OOM. - "torch>=2.7.0,<3.0; sys_platform != 'darwin'", + # Capped <2.12: torch 2.12.x+rocm7.1 breaks generation (#9328), and legacy (pre-6.14) launcher + # installs resolve this range live against the pytorch wheel indexes rather than using uv.lock. + "torch>=2.7.0,<2.12; sys_platform != 'darwin'", "torch>=2.7.0,<2.8.0; sys_platform == 'darwin'", "torchsde", # diffusers needs this for SDE solvers, but it is not an explicit dep of diffusers "torchvision", diff --git a/scripts/check_pins.py b/scripts/check_pins.py new file mode 100644 index 00000000000..7a296e3d195 --- /dev/null +++ b/scripts/check_pins.py @@ -0,0 +1,60 @@ +"""Check that pins.json is consistent with pyproject.toml. + +``pins.json`` is not used anywhere in this repo — it is fetched (at the release +tag) by the Invoke Launcher (https://github.com/invoke-ai/launcher), which uses +its ``torchIndexUrl`` entries to pick the torch wheel index for legacy +(pre-6.14.0) installs. Because nothing in-repo consumes it, it can silently +drift from the ``[[tool.uv.index]]`` URLs in pyproject.toml — which is exactly +what happened when ROCm moved from 6.3 to 7.1 (issue #9328). + +This script fails if any ``torchIndexUrl`` entry in pins.json differs from the +URL of the corresponding ``torch-`` index in pyproject.toml. + +Run from anywhere: python scripts/check_pins.py +""" + +import json +import sys +import tomllib +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parent.parent + + +def main() -> int: + pins = json.loads((REPO_ROOT / "pins.json").read_text()) + pyproject = tomllib.loads((REPO_ROOT / "pyproject.toml").read_text()) + + indexes = {i["name"]: i["url"] for i in pyproject["tool"]["uv"]["index"]} + + errors: list[str] = [] + + for platform, backends in pins["torchIndexUrl"].items(): + for backend, pinned_url in backends.items(): + index_name = f"torch-{backend}" + expected_url = indexes.get(index_name) + if expected_url is None: + errors.append( + f"pins.json torchIndexUrl.{platform}.{backend}: no [[tool.uv.index]] named '{index_name}' in pyproject.toml" + ) + elif pinned_url != expected_url: + errors.append( + f"pins.json torchIndexUrl.{platform}.{backend} is '{pinned_url}' but pyproject.toml index '{index_name}' is '{expected_url}'" + ) + + if errors: + print("pins.json is out of sync with pyproject.toml:", file=sys.stderr) + for error in errors: + print(f" - {error}", file=sys.stderr) + print( + "\nUpdate pins.json to match the [[tool.uv.index]] URLs in pyproject.toml (or vice versa).", + file=sys.stderr, + ) + return 1 + + print("pins.json is consistent with pyproject.toml") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/uv.lock b/uv.lock index 6d1b0a462f8..aff88d7d410 100644 --- a/uv.lock +++ b/uv.lock @@ -1176,7 +1176,7 @@ requires-dist = [ { name = "sentencepiece", specifier = "==0.2.0" }, { name = "snakeviz", marker = "extra == 'dev'" }, { name = "spandrel" }, - { name = "torch", marker = "sys_platform != 'darwin'", specifier = ">=2.7.0,<3.0" }, + { name = "torch", marker = "sys_platform != 'darwin'", specifier = ">=2.7.0,<2.12" }, { name = "torch", marker = "sys_platform == 'darwin'", specifier = ">=2.7.0,<2.8.0" }, { name = "torch", marker = "sys_platform == 'linux' and extra == 'rocm'", specifier = "==2.10.0+rocm7.1", index = "https://download.pytorch.org/whl/rocm7.1", conflict = { package = "invokeai", extra = "rocm" } }, { name = "torch", marker = "extra == 'cpu'", specifier = "==2.7.1+cpu", index = "https://download.pytorch.org/whl/cpu", conflict = { package = "invokeai", extra = "cpu" } }, @@ -3710,10 +3710,10 @@ name = "torch" version = "2.7.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.12' and sys_platform == 'darwin'", - "python_full_version < '3.12' and sys_platform == 'darwin'", "(python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'linux') or (python_full_version >= '3.12' and sys_platform == 'win32')", "(python_full_version < '3.12' and platform_machine == 'x86_64' and sys_platform == 'linux') or (python_full_version < '3.12' and sys_platform == 'win32')", + "python_full_version >= '3.12' and sys_platform == 'darwin'", + "python_full_version < '3.12' and sys_platform == 'darwin'", ] dependencies = [ { name = "filelock", marker = "(platform_machine == 'x86_64' and sys_platform == 'linux' and extra != 'extra-8-invokeai-cpu' and extra != 'extra-8-invokeai-cuda' and extra != 'extra-8-invokeai-rocm') or (sys_platform == 'darwin' and extra == 'extra-8-invokeai-rocm') or (sys_platform == 'darwin' and extra != 'extra-8-invokeai-cpu' and extra != 'extra-8-invokeai-cuda') or (sys_platform == 'win32' and extra != 'extra-8-invokeai-cpu' and extra != 'extra-8-invokeai-cuda' and extra != 'extra-8-invokeai-rocm') or (extra == 'extra-8-invokeai-cpu' and extra == 'extra-8-invokeai-cuda') or (extra == 'extra-8-invokeai-cpu' and extra == 'extra-8-invokeai-rocm') or (extra == 'extra-8-invokeai-cuda' and extra == 'extra-8-invokeai-rocm')" }, @@ -3862,10 +3862,10 @@ name = "torchvision" version = "0.22.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.12' and sys_platform == 'darwin'", - "python_full_version < '3.12' and sys_platform == 'darwin'", "(python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'linux') or (python_full_version >= '3.12' and sys_platform == 'win32')", "(python_full_version < '3.12' and platform_machine == 'x86_64' and sys_platform == 'linux') or (python_full_version < '3.12' and sys_platform == 'win32')", + "python_full_version >= '3.12' and sys_platform == 'darwin'", + "python_full_version < '3.12' and sys_platform == 'darwin'", ] dependencies = [ { name = "numpy", marker = "(platform_machine == 'x86_64' and sys_platform == 'linux' and extra != 'extra-8-invokeai-cpu' and extra != 'extra-8-invokeai-cuda' and extra != 'extra-8-invokeai-rocm') or (sys_platform == 'darwin' and extra == 'extra-8-invokeai-rocm') or (sys_platform == 'darwin' and extra != 'extra-8-invokeai-cpu' and extra != 'extra-8-invokeai-cuda') or (sys_platform == 'win32' and extra != 'extra-8-invokeai-cpu' and extra != 'extra-8-invokeai-cuda' and extra != 'extra-8-invokeai-rocm') or (extra == 'extra-8-invokeai-cpu' and extra == 'extra-8-invokeai-cuda') or (extra == 'extra-8-invokeai-cpu' and extra == 'extra-8-invokeai-rocm') or (extra == 'extra-8-invokeai-cuda' and extra == 'extra-8-invokeai-rocm')" },