Commit Graph
4 Commits
Author SHA1 Message Date
dev 7367d66ac3 build(photo-ai): CPU/GPU сборка, gfpgan+facexlib без dev-зависимостей, healthcheck
Stage 2: photo-ai/Dockerfile получил ARG TORCH_VARIANT/TORCH_INDEX (один образ,
cu124 — вариант сборки) и явные пины gfpgan==1.3.8 / facexlib==0.3.0 через
--no-deps: tb-nightly и yapf (dev-зависимости gfpgan/basicsr) больше не попадают
в рантайм, matplotlib остаётся как зависимость filterpy (требование facexlib).
Патч basicsr/data/degradations.py сохранён байт-в-байт — без него basicsr падает
на torch >= 2.0.

Пин numpy<2 был невыполним: opencv-python-headless 5.0.0.93 требует numpy >= 2.
Зафиксирована фактическая версия numpy==2.2.6 в общем вызове pip install, а
opencv-python (не-headless) больше не ставится — раньше он приходил через gfpgan
и перезаписывал headless-сборку (активным был cv2 с GUI: QT5). I1 после этого
перепроверен: 5/5 MATCH байт-в-байт против эталона Stage 0.

ENV PHOTO_AI_MODELS_DIR=/models, MODEL_PATH остаётся валидным алиасом.
COPY vendor/ ./vendor/ + vendor/.gitkeep — вендоренный CodeFormer (D5)
подхватится из sys.path без правок Dockerfile.

docker-compose.yml: у photo-ai новые env (DEVICE, TILE, FACE_MODEL, LOAD_ALL,
JPEG_QUALITY, MODELS_DIR) и healthcheck со start_period 300s; MAX_INPUT_PIXELS
заменён на канонический PHOTO_AI_MAX_PIXELS (старое имя читается app.py как алиас).
У app — PHOTO_AI_FACE_MODEL и PHOTO_AI_FACE_TIMEOUT_MS (воркер читает их в Stage 4).
Новый docker-compose.gpu.yml (по образцу minio): TORCH_VARIANT=cu124,
PHOTO_AI_DEVICE=cuda, deploy.resources.reservations.devices для nvidia.

.env.example и таблица переменных README описывают ровно те переменные, которые
теперь подставляет compose; блок про GPU и NVIDIA Container Toolkit — Stage 6 (D6).

Проверено на живом стеке: сборка EXIT=0 (2.63 ГБ), /health отдаёт device=cpu,
face_models=[gfpgan], контейнер healthy, /api/photo-jobs/status отдаёт
service.device, I3 (пустой PHOTO_AI_URL -> 503 + 8 маршрутов 200), I4
(PHOTO_AI_DEVICE=cuda без CUDA -> WARN + CPU), docker compose config валиден для
базы, minio и gpu. GPU-пуск не выполнялся: nvidia-ctk на хосте отсутствует.
2026-09-29 09:35:40 +03:00
dev e8c15cc425 feat(photo-ai): реестр моделей и авто-выбор устройства
Stage 1: переписан photo-ai/app.py. Реестр моделей вместо одной модели,
авто-выбор cuda/mps/cpu, ModelPool с ленивой загрузкой, прогревом и LRU
на 2 записи, OOM-деградация по лестнице тайлов, расширенные /health
и /models, /enhance с выбором модели, режима лиц и качества JPEG.

Инвариант I1 держится: запрос только с image + scale=2 по-прежнему даёт
байт-в-байт тот же JPEG (5/5 MATCH против эталона Stage 0). Расширение
выводится по имени файла, а не по content_type, и .jpeg нормализуется
в .jpg — этого хватает для совместимости с воркером, который шлёт
image/jpeg для всего.

/enhance отдаёт сырой JPEG по умолчанию и JSON при Accept: application/json.
Пока модель грузится — 503 с Retry-After: 5; worker.js относит status >= 500
к мягким, поэтому попытка не тратится (I5).

Новые переменные (PHOTO_AI_DEVICE, PHOTO_AI_MODELS_DIR, PHOTO_AI_TILE,
PHOTO_AI_LOAD_ALL, PHOTO_AI_WARMUP, PHOTO_AI_FACE_MODEL,
PHOTO_AI_JPEG_QUALITY) пока не документированы в .env.example: compose их
ещё не подставляет, это Stage 2.

Найдено: на хосте есть GPU (nvidia-smi, драйвер 615.71.09), не хватает
только nvidia-container-toolkit. Установка — решение оператора, ветка CUDA
осталась непроверенной прогоном.
2026-09-29 00:36:16 +03:00
dev 88dbff0136 chore(photo-ai): Stage 0 закрыт, решения D1–D6 зафиксированы
Stage 0: чекбоксы отмечены, приёмка перепроверена — эталон воспроизводится
байт-в-байт (5/5 MATCH), /health -> {"ok":true}, сценарий «🤖 ИИ» -> done.

D1 — порт photo-ai на 127.0.0.1:8081 (loopback), применён и отражён в
README/.env.example.
D2 — отдельная photoAiHealth() вместо aiHealthCheck() в статусе заданий,
контракт service = {configured, reachable, latency_ms, error} + passthrough
полей photo-ai; правка и проверка в предыдущем коммите.
D3 — PHOTO_JOB_ACTIONS выносится на уровень модуля и используется в restore
и в валидации enhance-ai; CHECK в БД не добавляем (I6).
D4 — вендорить gfpgan/facexlib не нужно: пакеты есть на PyPI и уже в образе
(реalesrgan тянет их транзитивно). Зафиксированы проверенные URL весов и
расхождение: пин numpy<2 в Dockerfile не действует (в образе 2.2.6).
D5 — CodeFormer внедряется только при явной необходимости; на PyPI лишь
сторонняя обёртка, дефолт gfpgan, при отсутствии модуля -> 400 с текстом.
D6 — дефолт PHOTO_AI_URL не меняем (фото-ИИ включено из коробки на CPU).
2026-09-28 23:38:48 +03:00
dev 403574fe79 chore(photo-ai): раздел 0 — жёсткие инварианты I1–I7 зафиксированы
План фото-ИИ с восстановлением лиц разбит на этапы; этот коммит закрывает
раздел 0 — семь инвариантов, которые нельзя ломать дальше. Два из них были
нарушены в текущем коде и исправлены здесь.

I5 (мягкие ошибки не сжигают попытки). Раньше любой сбой photo-ai —
503, обрыв сети, таймаут — попадал в общий catch, инкрементил attempts и
через три попытки переводил задание в error. Теперь ошибки разделены:
5xx/429/425/408 и сетевая недоступность возвращают задание в pending без
инкремента attempts, с экспоненциальной паузой 10 с → 300 с; лимит мягких
повторов (по умолчанию 60) даёт одну честную ошибку с понятным текстом.
Таймаут AbortSignal.timeout — жёсткая ошибка с попытками, как и раньше.
Счётчик мягких повторов живёт в памяти процесса и в счётчиках воркера,
метаданные повтора — в audit_log.target (soft_attempt/soft_limit), без
новых колонок. Новый аудит-код photo.job.soft_retry и подпись в audit.js.
Переменные PHOTO_AI_SOFT_MAX_RETRIES, PHOTO_AI_SOFT_BACKOFF_MS,
PHOTO_AI_SOFT_BACKOFF_MAX_MS описаны в .env.example и отдаются в
worker.config в GET /api/photo-jobs/status.

I7 (никаких прямых fs.* по uploads/). runAiEnhance писал результат
fs.writeFileSync в uploads/ и только потом persist в S3; enhanceWithSharp
делал то же через sharp toFile. Оба теперь считают буфер и пишут его
через storage.put — драйвер выбирает сам, локальной копии не остаётся.
Из worker.js убраны require('fs'), require('path') и параметр uploadsDir.

I3 (photo-ai не обязателен). photo_ai_enabled вычислялся внутри
cacheWrap('public-settings'), поэтому после перезапуска с пустым
PHOTO_AI_URL кнопка «🤖 ИИ» оставалась видимой до истечения кэша (60 с),
хотя enhance-ai уже отдавал 503. Флаг вынесен из кэша: он выводится из
PHOTO_AI_URL в памяти процесса и всегда актуален.

Проверено на стенде (журнал — в TODO_PHOTO_FACE_AI.md, раздел 0):
- I1: эталон /enhance снят на 5 фото (3 реальных, 2 синтетических),
  два независимых прогона и прогон после правок совпали байт-в-байт
  (sha256), /health отдаёт ok. Скрипты и эталон — в backups/ (вне git)
- I2: задание с params IS NULL и action='ai' дошло до done при
  attempts=0, результат отдан из S3 (200)
- I3: с пустым PHOTO_AI_URL photo_ai_enabled=false сразу после старта,
  enhance-ai → 503, остальные маршруты API живы
- I4: nvidia-ctk и nvidia-container-runtime на хосте отсутствуют, runtime
  только runc — фото-ИИ поднялся на CPU, /health не падает. Проверка
  PHOTO_AI_DEVICE переносится на приёмку Stage 1 (переменной ещё нет)
- I6: db/ не тронут, состав колонок photo_jobs прежний
- I7: node --check для всех изменённых JS, комментариев в диффе нет,
  весь SQL параметризован

api.smoketest.js: контракт фото-воркера — согласованность
photo_ai_enabled и ai_configured, ключи мягких повторов в worker.config,
503/404 для enhance-ai на несуществующей записи (тест не создаёт реальных
заданий). Вместе с планом и чек-листом этапов.
2026-09-28 23:19:16 +03:00