From 001dd2c575f4e8898b0c6ecfc263992652828156 Mon Sep 17 00:00:00 2001 From: Holden Date: Wed, 17 Jun 2026 02:53:05 +0000 Subject: [PATCH 1/2] fix: reinstall onnxruntime-gpu after uv sync to guarantee GPU binary wins MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit insightface depends on onnxruntime (CPU) as a direct dependency. During uv sync --extra gpu, both onnxruntime (CPU, 24.6 MB binary) and onnxruntime-gpu (GPU, 24.7 MB binary) are installed in parallel — both claim onnxruntime/capi/onnxruntime_pybind11_state.so. The last writer wins, which is non-deterministic in uv's parallel installer. On GitHub Actions (no GPU, different scheduler ordering), the CPU binary consistently wins, leaving onnxruntime-gpu's pybind11_state.so as the CPU version. CUDAExecutionProvider then silently disappears because the CPU binary's provider registration code has no CUDA EP. Fix: after uv sync, reinstall onnxruntime-gpu explicitly using the already- cached wheel. Since uv pip install is synchronous and runs after the parallel sync completes, the GPU binary is guaranteed to be on disk when the build layer commits. --- Dockerfile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 6963cb0..0cfcf4c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -50,7 +50,9 @@ RUN if [ "$VARIANT" = "cpu" ]; then \ elif [ "$VARIANT" = "intel" ]; then \ uv sync --frozen --no-dev --extra intel; \ elif [ "$VARIANT" = "gpu" ]; then \ - uv sync --frozen --no-dev --extra gpu; \ + uv sync --frozen --no-dev --extra gpu && \ + ORT_GPU_VER=$(.venv/bin/python -c "import importlib.metadata; print(importlib.metadata.version('onnxruntime-gpu'))") && \ + uv pip install --python .venv/bin/python --no-deps --reinstall "onnxruntime-gpu==$ORT_GPU_VER"; \ else \ echo "Unknown VARIANT: '$VARIANT'. Must be one of: cpu, rocm, intel, gpu" >&2; \ exit 1; \ From c1f04be15b3972e4f5b02e6196d4d7244c327bf5 Mon Sep 17 00:00:00 2001 From: Holden Date: Wed, 17 Jun 2026 03:06:27 +0000 Subject: [PATCH 2/2] release: v0.6.5 --- CHANGELOG.md | 32 ++++++++++++++++++++++++++++++++ pyproject.toml | 2 +- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 58c90b3..6966e91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.6.5] - 2026-06-17 + +### Added + +- **Version displayed in startup banner**: winnow now prints its installed version at launch. + +### Fixed + +- **GPU image: `CUDAExecutionProvider` missing due to parallel install race**: `insightface` declares `onnxruntime` (CPU) as a dependency, causing `uv sync` to install both `onnxruntime` and `onnxruntime-gpu` in parallel — both packages claim the same `pybind11_state.so` binary. On GitHub Actions the CPU binary consistently won the race, leaving the GPU build without CUDA support at runtime despite all CUDA libraries being present. Fixed by reinstalling `onnxruntime-gpu` sequentially after `uv sync` to guarantee its GPU binary is on disk. + +- **GPU extra was missing three required nvidia pip packages**: `onnxruntime-gpu` 1.26.0 gates CUDA EP loading on the Python-importability of `nvidia-cuda-runtime-cu12`, `nvidia-cufft-cu12`, and `nvidia-curand-cu12`. These packages were not declared in the `gpu` extra and were absent on fresh installs, silently disabling GPU inference. + +- **`_handle_duplicate_people` raises `KeyError` on id-less person records**: bare `p["id"]` subscripts in the auto-merge loop and `_smaller_duplicate_ids` raised `KeyError` when Immich returned a person dict without an `id` field (e.g. unconfirmed face clusters). Fixed by using `p.get("id")` and filtering `None` from `skip_ids`. + +- **`_smaller_duplicate_ids` could include `None` in the skip set**: `p.get("id")` without a `None` guard populated `skip_ids` with `None`, causing `p.get("id") not in skip_ids` to pass for every id-less person, so unnamed face clusters were silently re-included in all return paths. + +- **`_handle_duplicate_people` dead code removed**: guards `if not survivor_id` and `if not merge_ids` became unreachable after the id-gate fix; their presence suggested they still ran. + +- **`_valid_people` in `jobs.py` used wrong name filter**: whitespace-only names (e.g. `" "`) passed the `p.get("name")` truthiness check and were included in the person list. Fixed using `(p.get("name") or "").strip()` consistent with the cli.py gate. + +- **`interactive_configure` queued-marker check was O(N²)**: `[j for j in jobs if j["person"]["id"] == p.get("id")]` ran a full scan over jobs for every person in the display loop. Replaced with a `queued_ids` set hoisted before the loop. + +- **`executor.py` slot restore did not clear `min_quality_score_for_slot`**: when a replacement upload failed all retries after a deletion, `effective_count` was restored but the stale quality-score floor from the deleted file remained, blocking the next candidate from filling the slot. + +- **`get_immich_version` swallowed `KeyError` on unexpected schema**: bare `data["major"]` / `data["minor"]` / `data["patch"]` subscripts were silently caught by the surrounding `except Exception`, returning `None` without logging. Replaced with `.get()` calls that log a debug warning on unexpected schemas. + +- **Face embedding selects nearest face to crop centre, not largest by area**: a 25 % margin on the crop window can pull a larger neighbouring face into the bounding box; selecting the biggest face by area then embeds the wrong person. Centre-proximity is now used instead. + +- **Zero-norm face embeddings skipped before diversity selection**: InsightFace occasionally returns a zero vector for low-quality detections; zero embeddings pass deduplication with similarity 0 and score distance 1.0, causing them to be selected first as maximally diverse. + +- **`executor.py` slot restore did not clear `min_quality_score_for_slot`**: stale quality floor from the deleted file blocked the next candidate from filling the restored slot in quality-replacement mode. + ## [0.6.4] - 2026-06-17 ### Fixed diff --git a/pyproject.toml b/pyproject.toml index e4999e1..27d8aff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "winnow" -version = "0.6.4" +version = "0.6.5" description = "Selects diverse, high-quality photos from Immich as training data for Frigate face recognition." license = "AGPL-3.0-or-later" requires-python = ">=3.13"