Stage 3 закрыт. Face-режим (off/face/all, strength, warnings) был написан в Stage 1;
здесь доведена приёмка и найден баг, который невозможно было увидеть без GPU-прогона.
Главное: realesные OOM никогда не доходили до run_guarded. И realessrgan/utils.py, и
gfpgan/utils.py ловят RuntimeError вокруг вызова сети и идут дальше
(`except RuntimeError as error: print('Error', error)`), поэтому на нехватку памяти
realesrgan падал уже не RuntimeError, а UnboundLocalError на присваивании
output_tile. Наружу уходил голый 500 «Internal Server Error»: ни лестницы тайлов,
ни деградации на CPU, ни внятного текста. В gfpgan это было тихое ухудшение —
при OOM лицо молча оставалось исходным, а задание уходило в «успех».
Починка: guard_forward() оборачивает forward сетей, которые строим мы
(RealESRGANer.model, restorer.gfpgan, CodeFormer net) и превращает OOM-RuntimeError
в TileOOM. TileOOM не наследует RuntimeError, поэтому проглатывающие except его
пропускают; is_oom и обе точки run_guarded ловят его явно. Обёртка вешается на
экземпляр, идемпотентна по флагу _photo_ai_guarded и не трогает класс.
Также добавлены два предупреждения из чек-листа, которых в коде не было: вход меньше
320×320 (лица могут не найтись) и CodeFormer на не-CUDA. Предупреждение «лица не
найдены» и деградация на CPU были на месте и не менялись.
Проверено на RTX 3050 Laptop (4096 МБ, драйвер 615.71.09, CUDA 12.6):
- tile=2048, x2plus face=all, полное фото: до правки 500 + UnboundLocalError,
после 200 за 28.3 с с единственным warning «не хватило памяти при tile=2048»;
- инъекция OOM: лестница 2048 → 1024 → 512, затем переход на CPU (device: cpu,
half: false) и успешный повтор; при повторе уже на CPU — честная 500 с подсказкой
про PHOTO_AI_TILE / PHOTO_AI_MAX_PIXELS;
- 640×480: off 1.0 с / face 3.8 с / all 2.2 с, faces_found=6; фото без лиц даёт
faces_found=0 и байты, равные face=off;
- I1: 6/6 MATCH байт-в-байт против Stage 2 на CPU (jpg/.jpeg/png+70/webp+100/x4v3/anime).
docker-compose.gpu.yml: GPU выдаётся через CDI (device_ids nvidia.com/gpu=all) —
не требует правки /etc/docker/daemon.json и перезапуска демона, в отличие от
классического резервирования driver: nvidia. TORCH_VARIANT cu124 → cu126: в индексе
cu124 последний torch 2.6.0, а cu126 даёт те же 2.14.0/0.29.0, что и CPU-образ, так
что варианты сборки отличаются только CUDA-библиотеками. Образ тегируется отдельно
(whatido-photo-ai:cu126), чтобы сборка GPU-варианта не перетирала CPU-образ
whatido-photo-ai:latest — откат остаётся обычным docker compose up -d photo-ai.
README: раздел «Запуск на NVIDIA GPU» с установкой NVIDIA Container Toolkit и генерацией
CDI-спеки, оговорками про 4 ГБ VRAM (x2plus + gfpgan влезают, general-x4v3 тяжелее,
LOAD_ALL=1 лучше не включать) и описанием параметров /enhance. .env.example: команда
GPU-запуска и рекомендация по PHOTO_AI_TILE. Журналы раздела 3 и GPU-прогона — в
TODO_PHOTO_FACE_AI.md.
worker.js, server.js, схема БД и фронтенд не тронуты — они в Stage 4…6.
98 lines
7.5 KiB
Bash
98 lines
7.5 KiB
Bash
ADMIN_USERNAME=admin
|
||
ADMIN_PASSWORD=сложный-пароль-админки
|
||
DB_PASSWORD=случайная-длинная-строка
|
||
# Лимит размера загружаемого на восстановление бэкапа, МБ (по умолчанию 500)
|
||
BACKUP_UPLOAD_LIMIT_MB=500
|
||
|
||
# === Redis (кэш, rate limit, баны IP, pub/sub) ===
|
||
# Пароль Redis. Обязателен, если Redis включён в docker-compose.
|
||
REDIS_PASSWORD=замените-на-длинный-секрет
|
||
# Префикс ключей — позволяет держать несколько инстансов в одном Redis.
|
||
REDIS_PREFIX=whatido
|
||
# Лимит памяти Redis. При превышении вытесняются ключи с наименьшим TTL (allkeys-lru).
|
||
REDIS_MAXMEMORY=256mb
|
||
|
||
# Hugging Face token (нужен для закрытых/приватных репозиториев моделей)
|
||
HUGGINGFACE_TOKEN=
|
||
# Имя GGUF-файла модели для text-corrector (скачивается с Hugging Face, если отсутствует)
|
||
AI_MODEL=qwen2.5-1.5b-instruct-q4_k_m.gguf
|
||
# Репозиторий Hugging Face, откуда скачивается модель
|
||
AI_MODEL_REPO=Qwen/Qwen2.5-1.5B-Instruct-GGUF
|
||
# Промпт по умолчанию для автоисправления текстов (переопределяет встроенный, если в настройках не сохранён свой)
|
||
AI_PROMPT=Ты — редактор текстов. Исправь ТОЛЬКО грамматические, орфографические и пунктуационные ошибки в тексте. Приведи к правильному регистру буквы. НЕ меняй слова, структуру предложений, стиль или смысл текста. Верни ТОЛЬКО исправленный текст без пояснений.
|
||
# Таймаут запроса к AI (мс), по умолчанию 120000 (2 минуты)
|
||
AI_REQUEST_TIMEOUT_MS=120000
|
||
|
||
# === Cloudflare Tunnel (контейнер cloudflared) ===
|
||
# По умолчанию — Quick Tunnel на trycloudflare.com: случайный публичный URL,
|
||
# адрес печатается в логах: docker compose logs -f cloudflared.
|
||
# Чтобы привязать свой домен, замените command сервиса cloudflared
|
||
# в docker-compose.yml на: command: ["tunnel", "run", "--token", "${CLOUDFLARE_TUNNEL_TOKEN}"]
|
||
# (токен вида "eyJ..." создаётся в Cloudflare Zero Trust -> Networks -> Tunnels).
|
||
CLOUDFLARE_TUNNEL_TOKEN=
|
||
# Внутренний адрес приложения, на который указывает Quick Tunnel.
|
||
CLOUDFLARE_TUNNEL_URL=http://app:3003
|
||
# Максимальное ожидание WireGuard handshake перед fallback на прямой запуск
|
||
# туннеля без VPN (сек). Если VPN-провайдер не отвечает — сайт всё равно поднимется.
|
||
WG_HANDSHAKE_TIMEOUT=60
|
||
|
||
# === ИИ-улучшение фото (контейнер photo-ai, Real-ESRGAN + GFPGAN) ===
|
||
# По умолчанию фото-ИИ включено: сервис photo-ai поднимается вместе со стеком
|
||
# и работает на CPU. Оставьте значение пустым, чтобы выключить сервис —
|
||
# тогда кнопка «🤖 ИИ» скрыта, а /api/entries/:id/photo/enhance-ai отвечает 503.
|
||
# Ручные проверки сервиса идут по loopback-порту 127.0.0.1:8081 (наружу не публикуется):
|
||
# curl http://127.0.0.1:8081/health
|
||
PHOTO_AI_URL=http://photo-ai:8080
|
||
# Максимум пикселей входного изображения, более крупный вход уменьшается.
|
||
PHOTO_AI_MAX_PIXELS=4000000
|
||
# Устройство инференса: auto (CUDA, если контейнеру выдан GPU, иначе CPU), cuda, cpu.
|
||
# Явный cuda без доступной CUDA не роняет сервис: WARN в лог и работа на CPU.
|
||
# GPU-вариант собирается оверрайдом (отдельный образ whatido-photo-ai:cu126, GPU через CDI):
|
||
# docker compose -f docker-compose.yml -f docker-compose.gpu.yml up -d --build photo-ai
|
||
# Подробности установки NVIDIA Container Toolkit — в README, раздел «Запуск на NVIDIA GPU».
|
||
PHOTO_AI_DEVICE=auto
|
||
# Размер тайла инференса (0 — без тайлов). Меньше тайл — меньше памяти, медленнее.
|
||
# На GPU с 4 ГБ VRAM держите 256: при нехватке сервис сам пройдёт лестницу тайлов и уйдёт на CPU.
|
||
PHOTO_AI_TILE=256
|
||
# Модель восстановления лиц: gfpgan (по умолчанию) или codeformer.
|
||
# codeformer доступен только если модуль вендорен в photo-ai/vendor.
|
||
PHOTO_AI_FACE_MODEL=gfpgan
|
||
# Предзагрузка всех моделей при старте (1) или ленивая загрузка по требованию (0).
|
||
PHOTO_AI_LOAD_ALL=0
|
||
# Качество JPEG результата (70..100).
|
||
PHOTO_AI_JPEG_QUALITY=92
|
||
# Таймаут заданий с восстановлением лиц на стороне приложения (мс).
|
||
PHOTO_AI_FACE_TIMEOUT_MS=600000
|
||
# Сколько раз воркер повторит задание, если photo-ai недоступен (503/обрыв сети),
|
||
# не увеличивая attempts; после исчерпания — одна честная ошибка в задании.
|
||
PHOTO_AI_SOFT_MAX_RETRIES=60
|
||
# Пауза перед мягким повтором и её потолок (мс), задержка растёт вдвое до потолка.
|
||
PHOTO_AI_SOFT_BACKOFF_MS=10000
|
||
PHOTO_AI_SOFT_BACKOFF_MAX_MS=300000
|
||
|
||
# === Хранилище файлов (S3: SeaweedFS по умолчанию / MinIO) ===
|
||
# local — файлы в ./uploads (по умолчанию), s3 — объекты в бакете S3/MinIO.
|
||
STORAGE_DRIVER=local
|
||
# Читать локальную копию, если объект ещё не перенесён в S3 (1 — включено).
|
||
STORAGE_LOCAL_FALLBACK=1
|
||
# Оставлять локальную копию после выгрузки в S3 (1 — оставлять, 0 — удалять).
|
||
STORAGE_KEEP_LOCAL=0
|
||
# Сколько часов хранить локальный кэш оригиналов (для sharp/миниатюр), 0 — не чистить.
|
||
STORAGE_CACHE_MAX_AGE_HOURS=168
|
||
# Образ S3-сервиса. По умолчанию SeaweedFS (свободный S3-сервер).
|
||
# Для MinIO: S3_IMAGE=minio/minio:<тег> и запуск через docker-compose.minio.yml.
|
||
S3_IMAGE=chrislusf/seaweedfs:latest
|
||
S3_ENDPOINT=http://s3:9000
|
||
S3_REGION=us-east-1
|
||
S3_BUCKET=whatido
|
||
# Логин/пароль S3 (для SeaweedFS — AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY,
|
||
# для MinIO — MINIO_ROOT_USER/MINIO_ROOT_PASSWORD).
|
||
S3_ACCESS_KEY=whatido
|
||
S3_SECRET_KEY=замените-на-длинный-секрет
|
||
# Path-style адресация (1 — включено, нужно для MinIO и SeaweedFS).
|
||
S3_FORCE_PATH_STYLE=1
|
||
# Необязательный префикс ключей внутри бакета (например, prod).
|
||
S3_PREFIX=
|
||
# Показывать веб-консоль MinIO (on/off), только для docker-compose.minio.yml
|
||
MINIO_BROWSER=off
|