feat(photo-ai): официальный CodeFormer вендорен, веса на этапе сборки

CodeFormer был написан по API сторонней обёртки с PyPI (rohitkhatri):
CodeFormer(..., device=..., fp16=False) и net.device официальный класс
не принимает — вызов падал бы с TypeError. В photo-ai/vendor/codeformer
вендорен официальный sczhou/CodeFormer (codeformer_arch.py +
vqgan_arch.py, b33cc7d, лицензия S-Lab 1.0), устройство передаётся
через net.to(device), чекпоинт читается из params_ema.

Веса больше не качаются лениво при первом запросе: fetch-weights.py на
сборке образа кладёт их в /opt/photo-ai-seed (build-arg
PHOTO_AI_PREFETCH, дефолт codeformer), при старте seed_weights()
переносит их в том photo-ai-models:/models/weights. Том переживает
пересборку образа, BuildKit-кэш не даёт качать повторно, недоступная
сеть на сборке не роняет образ.
This commit is contained in:
dev
2026-10-04 21:05:15 +03:00
parent 1d71e249e4
commit 19be1cc9ef
12 changed files with 955 additions and 21 deletions
+28 -4
View File
@@ -132,7 +132,7 @@ REDIS_PASSWORD=случайная-длинная-строка
| `PHOTO_AI_MAX_PIXELS` | `4000000` | Максимум пикселей входного изображения, вход большего размера уменьшается |
| `PHOTO_AI_DEVICE` | `auto` | Устройство инференса: `auto` (CUDA, если контейнеру выдан GPU, иначе CPU), `cuda`, `cpu`. Явный `cuda` без CUDA не роняет сервис: WARN в лог и работа на CPU |
| `PHOTO_AI_TILE` | `256` | Размер тайла инференса (`0` — без тайлов): меньше тайл — меньше памяти, но медленнее |
| `PHOTO_AI_FACE_MODEL` | `gfpgan` | Модель восстановления лиц: `gfpgan`; `codeformer` доступен только при вендоринге модуля в `photo-ai/vendor` |
| `PHOTO_AI_FACE_MODEL` | `gfpgan` | Модель восстановления лиц: `gfpgan` или `codeformer` (официальный модуль вендорен в `photo-ai/vendor/codeformer`, доступен сразу) |
| `PHOTO_AI_LOAD_ALL` | `0` | Загружать все модели при старте (`1`) или лениво по требованию (`0`) |
| `PHOTO_AI_JPEG_QUALITY` | `92` | Качество JPEG результата, 70..100 |
| `PHOTO_AI_FACE_TIMEOUT_MS` | `600000` | Таймаут заданий с восстановлением лиц (мс) |
@@ -140,6 +140,32 @@ REDIS_PASSWORD=случайная-длинная-строка
| `PHOTO_AI_SOFT_BACKOFF_MS` | `10000` | Первая пауза перед мягким повтором |
| `PHOTO_AI_SOFT_BACKOFF_MAX_MS` | `300000` | Потолок паузы (задержка растёт вдвое) |
### Модели лиц и где лежат веса
Face-модели доступны обе: `gfpgan` (дефолт) и `codeformer`. Модуль `codeformer` — официальный
`sczhou/CodeFormer` (`b33cc7d`), вендорен в `photo-ai/vendor/codeformer/` (`codeformer_arch.py`
+ `vqgan_arch.py`, лицензия S-Lab 1.0 лежит рядом). Отдельный пакет с PyPI не используется: там лежит
сторонняя обёртка `rohitkhatri`, которая тянет свой `facelib` и `lpips`. Модуль попадает в образ
через `COPY vendor/` и подхватывается `sys.path` в `app.py` — правки Dockerfile не требуется.
Веса **не** лежат в репозитории и **не** скачиваются при первом запросе: на сборке образа
`fetch-weights.py` кладёт их в `/opt/photo-ai-seed`, а при старте сервис переносит их в том
`photo-ai-models:/models/weights` (`seed_weights()`). Дальше модель живёт в томе и переживает
пересборку образа; если её нет ни в томе, ни в образе, работает старый ленивый заозагрузчик.
Сам файл качается в BuildKit-кэш `/var/cache/photo-ai-weights`, поэтому повторная сборка
(и сборка с другим `PHOTO_AI_PREFETCH`) берёт его оттуда и заново не качает.
| Сборка | Что скачает |
|---|---|
| `docker compose build photo-ai` | `codeformer` (дефолт `PHOTO_AI_PREFETCH=codeformer`) |
| `PHOTO_AI_PREFETCH=face docker compose build photo-ai` | `codeformer` + `gfpgan` |
| `PHOTO_AI_PREFETCH=all docker compose build photo-ai` | всё: апскейлы, face-модели, веса facexlib |
| `PHOTO_AI_PREFETCH=none docker compose build photo-ai` | ничего, веса качаются лениво в том |
Переменная `PHOTO_AI_PREFETCH` — именно build-arg, он читается при сборке образа, а не контейнера
(в `.env.example` она есть, чтобы задать значение один раз). Скачивание при сборке не роняет образ:
при недоступной сети шаг пишет предупреждение, сервис докачает веса при первом использовании.
### Запуск на NVIDIA GPU
GPU не обязателен: без него сервис работает на CPU. Чтобы включить GPU-вариант, нужен драйвер NVIDIA
@@ -168,9 +194,7 @@ GPU выдаётся контейнеру ключом `gpus: all`, поэтом
генерации, поэтому после переподключения видеокарты или смены порта она начинает ссылаться на
несуществующий узел, и контейнер не стартует с `CDI device injection failed: failed to stat CDI host
device /dev/dri/cardN`. Перегенерация спеки требует sudo и теряется при каждой перегенерации;
`gpus: all` от этого свободен. Если nvidia-runtime уже зарегистрирован в
демоне (`nvidia-ctk runtime configure --runtime=docker`), в оверрайде можно вместо этого указать
`deploy.resources.reservations.devices` с `driver: nvidia, count: 1` — результат тот же.
`gpus: all` от этого свободен.
Проверка результата: в `/health` должны быть `device: cuda:0`, `half: true`, непустые
`vram_total_mb`/`vram_free_mb`. На 4 ГБ (например, RTX 3050 Laptop) реально держатся одновременно