7 Commits
Author SHA1 Message Date
flanandClaude Sonnet 4.6 b2be19e259 feat: add ROCm (AMD GPU) and Intel GPU support (v0.2.12)
New image variants:
- :rocm  — InsightFace via ROCmExecutionProvider, SigLIP via PyTorch ROCm 6.3
- :intel — InsightFace via OpenVINOExecutionProvider (onnxruntime-openvino);
           Intel GPU compute runtime auto-installed from Intel graphics repo;
           OPENVINO_DEVICE=GPU opts into Arc/iGPU inference (default: CPU)

Also adds:
- pyproject-rocm.toml + uv-rocm.lock, pyproject-intel.toml + uv-intel.lock
- compose.yml device passthrough snippets for AMD and Intel
- CI: build-rocm and build-intel jobs in docker-publish.yml; all four
  variants built and tagged in release.yml
- README reworked: cleaner structure, GPU variant quick-start examples,
  OPENVINO_DEVICE env var documented
- CHANGELOG entry and version bump to 0.2.12

Fix: IntPrompt in dict literal was eagerly evaluated in the no-embedding
fallback path of _get_strategy_choice, prompting users for a custom count
regardless of which strategy they picked.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 02:31:09 +00:00
flanandClaude Sonnet 4.6 85e499a677 fix: audit — score falsy-zero, crop person mismatch, adaptive cap, suppress_output, ldconfig glob, scheduler sleep
- immich_api: `score or confidence` treated 0.0 score as falsy; use explicit None check
- diversity: same falsy-zero fix in _get_face_confidence
- diversity: _crop_face_from_thumbnail scale loop now filters by person_id (was
  using first person's imageWidth/imageHeight regardless of target in group photos)
- jobs: partially-trained auto mode kept limit="auto" for adaptive stopping, then
  caps result to remaining capacity (was converting to int, silently disabling FPS
  adaptive threshold and early-stop)
- embeddings: _suppress_output finally block wraps first dup2 in try/finally so
  stderr is always restored even if stdout restore raises OSError
- Dockerfile: ldconfig find uses python3.* glob instead of hardcoded python3.13
- scheduler: sleep until next_run instead of fixed 60s; eliminates late-fire jitter
  and unnecessary wakeups on long schedules

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 16:48:24 +00:00
flanandClaude Sonnet 4.6 3db52c2141 fix+logging: model load logging, CPU fallback, log level audit
Bugs fixed (from high-effort review):
- InsightFace CPU fallback now passes providers=['CPUExecutionProvider'] and
  wraps in _suppress_output() so broken GPU drivers don't cause fallback to
  try the same broken provider again, and C-extension noise stays suppressed
- scheduler.py: BaseException → Exception (KeyboardInterrupt already re-raised;
  winnow has no sys.exit() calls, so SystemExit would not occur, but Exception
  is the correct scope)
- compose.yml: fix inverted AUTO_MODE comment (docker run -it enables
  interactive mode via TTY, not non-interactive)

Model loading logging (embeddings.py):
- InsightFace: disk cache check, "not cached — downloading now (~300 MB)",
  "loading into memory on GPU/CPU...", "ready on GPU/CPU (Xs)"
- SigLIP: same treatment; cache path derived dynamically from model_name
  via HuggingFace slug convention (models--org--model) so it stays correct
  if the model variant ever changes

Logging level audit (INFO/DEBUG/WARNING/ERROR):
- diversity.py: internal algo steps (clustering, medoids, adaptive threshold,
  auto-stop decision) → DEBUG; final selection summaries stay INFO
- immich_api.py: "Fetching assets" and "Retained N assets" → DEBUG (callers
  already print this to the console via rprint)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 15:23:44 +00:00
flanandClaude Sonnet 4.6 af8bae1c45 fix: embedding cache key mismatch, scheduler path/exception, log handler leak
embeddings.py: face embedding cache used 'immich' for lookup but
'insightface' for storage, so the cache was never hit for locally-
computed embeddings. Unified to 'insightface'/'siglip' throughout.
This affects all users since ENABLE_CACHE now defaults to true.

scheduler.py: INSIGHTFACE_HOME=/models/.insightface was having
'.insightface' appended again, making buffalo_l check always report
'will download'. Also catch BaseException (not just Exception) so a
SystemExit from a library call can't silently kill all future runs.

log_config.py: handlers.clear() abandoned open FileHandler fds on
each scheduled main() call. Close each handler properly before removal.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 15:10:44 +00:00
flanandClaude Sonnet 4.6 981a86f28a fix: register all nvidia pip lib dirs with ldconfig; improve GPU warnings
The static LD_LIBRARY_PATH only covered cudnn and cuda_runtime — missing
cublas, cufft, curand, cusolver, cusparse, nvjitlink, etc. onnxruntime-gpu
needs libcublasLt.so at minimum, so GPU mode silently fell back to CPU.
Replace with a one-shot ldconfig call over every nvidia site-packages lib/
dir, which covers all packages regardless of what gets installed.

Also: remove the ambiguous directory="" from preload_dlls (use auto-search
default) and add a clear warning when CUDAExecutionProvider is absent so
the user sees actionable guidance instead of silent CPU fallback.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 14:54:31 +00:00
flanandClaude Sonnet 4.6 af92fe5fcc fix: bugs and code quality in embeddings, jobs, diversity, config
- embeddings: initialize ctx_id=-1 before try block so the except
  handler cannot NameError; move insightface_home out of try for the
  same reason
- embeddings: replace contextlib.redirect_stdout/stderr (Python-level
  only) with fd-level dup2 suppression — actually silences C extension
  noise from InsightFace during model loading
- jobs: fix frigate_count==0 falling through `or` chain; use explicit
  `is not None` check so a real zero is not treated as missing data
- diversity: thread person_id through select_diverse_assets →
  _select_by_embedding → _get_face_bbox / _get_face_confidence /
  _crop_face_from_thumbnail so group-photo assets embed the target
  person's face rather than whichever person is listed first
- config: replace type() hack for ConfigManager with a proper class

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 05:24:35 +00:00
flanandClaude Sonnet 4.6 eea7baa19f refactor: rename project to winnow
- Rename Python package directory if_curator/ → winnow/
- Update all imports, entry points, and CLI references
- Update pyproject.toml: name, scripts, package list, repository URL
- Update Dockerfile, compose.yml, entrypoint.sh, scheduler.py
- Update GitHub Actions workflow image names
- Update README

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 01:07:46 +00:00