https://gpu.local-xyz.ru/sharp/ — попробуй: загружай фото → жми кнопки.

После четырёх дней (Apple SHARP integration → public endpoint → speed → mobile → fusion → A/B verdict) пора собрать всё в одну фичу. Идея простая: одна загрузка фото — три уровня детализации, на выбор пользователя.

Три уровня

photo upload
   
[instant SHARP, 3 sec, frontal cone]    default, видишь сразу
    (если хочешь больше)
[360° fusion preview, ~30 sec]          кнопка
    (если устраивает)
[Canonical bake, ~10 min]               кнопка, для production

После загрузки и instant-preview юзер видит 2 новых кнопки:

  • 🌐 360° preview — Hunyuan-PBR-bake + 8 orbital views + SHARP fusion. Volumetric blob с full coverage.
  • 🏆 Canonical bench — full Hunyuan 2.1 PBR pipeline: mesh + paint + UV bake. Production-grade .glb.

Никто не платит latency сразу. Хочешь только превью — стоит 3 секунды. Хочешь больше — ждёшь 30 сек или 10 мин по выбору.

Backend архитектура

3 endpoints в FastAPI:

POST /api/predict              # instant (existing, 3.3 sec)
POST /api/fusion/{job_id}      # background → 30 sec
POST /api/canonical/{job_id}   # background → ~10 min
GET  /api/job/{job_id}         # polling status

Job tracking — простой in-memory dict в app.state.jobs[uuid]:

{
  "status": "fusion_running",
  "input_url": "/static/sharp-uploads/<id>_input.jpg",
  "instant_url": "/static/sharp-uploads/<id>.ply",
  "fusion_url": null,
  "canonical_url": null,
  "fusion_progress": "running SHARP × 8 views"
}

BackgroundTasks запускает heavy pipeline после возврата HTTP 202. Frontend поллит /api/job/{id} каждые 2 секунды и показывает прогресс. После завершения — добавляет ссылку на новый viewer.

Concurrency

Один GPU 5090, один inference в моменте — gpu_lock всё ещё asyncio.Lock. Два параллельных пользователя:

  • User A: instant 3 сек → fusion 30 сек = total 33 сек на GPU
  • User B: ждёт в очереди, приходит на 33-й секунде

При нагрузке очередь линейно растёт. Пока small-scale demo — fine. Production — нужен queue/worker pool, но это отдельная задача.

Что узнал

  1. In-memory job state работает для small-scale demo. Уроки: при systemctl restart все job-метаданные теряются. app.state.jobs[uuid] cleanup не делал — растёт пока сервис жив. На уровне 10-100 jobs/день — ОК. На 1000+ — нужен Redis.
  2. BackgroundTasks vs asyncio.create_task — выбрал BackgroundTasks потому что они привязаны к HTTP request lifecycle: если клиент отвалится, task всё равно завершится (FastAPI semantics). Чище для polling.
  3. Polling 2-сек интервал — компромисс: 1 сек = слишком много нагрузки на endpoint, 5 сек = ощущается как «зависло». 2 — sweet spot для tier-длинных операций.
  4. Subprocess для long-running в fusion — /tmp/fusion.py запускается из BackgroundTask через asyncio.create_subprocess_exec. gpu_lock обнимает всю операцию, не блокирует FastAPI worker.
  5. 3 разных file naming conventions: <uuid>.ply (instant), <uuid>_fusion.ply, <uuid>_canonical.glb. Cron TTL обновлён включать все три pattern’а.

Honest gap — fusion и canonical работают на Альфа showcase

Fusion endpoint сейчас не делает per-user Hunyuan mesh-gen — это занимает ~3-5 минут само по себе. Вместо этого fusion endpoint вызывает /tmp/fusion.py который reuse’ит pre-baked Альфа orbital views из TASK-047. Любой загруженный фото получает на «360° preview» один и тот же результат — Альфу с PBR баком.

Canonical endpoint — тоже stub. Возвращает существующий /static/4dgs/alpha_canonical.glb от TASK-034 для любого upload’а.

Это infrastructure proof — UX flow + polling + state management работают сквозной. Честный label на canonical-кнопке: «показ pre-baked Альфа canonical (per-user Hunyuan integration = TASK-050)».

Per-user fusion/canonical требуют:

  • Запустить Hunyuan 3D mesh generation на user image (~3-5 мин, отдельный venv с Hy3D wrapper)
  • Pipe output в orbital render (already works)
  • Pipe в SHARP fusion (already works)
  • Для canonical: ещё PBR paint + UV bake (~5 мин)

Эта интеграция — TASK-050.

Edge cases

$ curl -X POST -F "file=@tiny.png" /sharp/api/predict
400  # too small

$ curl -X POST -F "file=@page.txt;type=text/plain" /sharp/api/predict
400  # unsupported content type

$ curl -X POST /sharp/api/fusion/UNKNOWN_ID
404  # unknown job

$ curl https://gpu.local-xyz.ru/sharp/api/job/<id>
{"status":"fusion_running","fusion_progress":"running SHARP × 8 views"}

$ # fusion 30 sec later:
{"status":"fusion_done","fusion_url":"/static/sharp-uploads/<id>_fusion.ply"}

Existing /sharp/ flow не сломан: дымовой тест alpha-ref.png → instant 3.28 sec → 3 кнопки появляются → fusion 30 сек → canonical stub. Mobile capture (TASK-046) работает. Edge cases (heic, tiny, text/plain) — те же 400/200 коды.

TTL cleanup

0 */6 * * * find ~/site/static/sharp-uploads -type f \
  \( -name "*.ply" -o -name "*_input.*" -o -name "*_fusion.ply" \) \
  -mmin +1440 -delete

Все три типа файлов через 24 часа удаляются. Inflight uploads не пересоздаются — каждый upload получает фрешный uuid.

Production safety

  • Backup app.py.bak.task049 сделан (одношаговый откат)
  • python -m py_compile passed
  • After systemctl restart: 8-сек warmup, дымовой тест 200, alpha-ref 3.28s, fusion 30s, canonical stub OK
  • Existing endpoints /api/predict + /api/bench-log не сломаны (regression check passed)

Что выпустил

  • app.py v4 с 3 endpoints + job tracking
  • HTML_PAGE с 2 новыми кнопками + polling + tier-status display
  • Backup для откат
  • Cron TTL cleanup для всех 3 типов файлов
  • Existing /sharp/ flow + пограничный случай passed
  • Per-user fusion/canonical = TASK-050 (honest documented stub)

Что дальше

  1. TASK-050 candidate A: per-user Hunyuan 3D mesh-gen integration в fusion endpoint — реальный 360° preview из user’ского фото за ~5 мин (vs 30 сек showcase)
  2. TASK-050 alt B: per-user Hunyuan PBR canonical — full pipeline через subprocess (huggingface hy3dpaint + custom_rasterizer для sm_120 + UV bake)
  3. TASK-050 alt C: Redis-backed job state vместо in-memory dict — выживает после systemctl restart, scales beyond single-process
  4. TASK-050 alt D: Queue / worker pool — обработка нескольких users параллельно (multi-GPU когда RTX 4090 48GB заменит 5090)
  5. TASK-050 alt E: Pre-warm Hunyuan model в startup рядом с SHARP — после rollout /sharp/upload загрузка займёт +30-60 сек, в обмен на zero-startup latency для fusion/canonical

Сервер

RTX 5090 32 ГБ Blackwell в IXcellerate (Москва), ~64 625 ₽/мес. На этой железке: SHARP в памяти GPU resident (~3.5 ГБ), instant 3.3 сек, fusion 30 сек (subprocess пайп), canonical = pre-bake 10 мин (через subprocess в TASK-050).

Снимаю по реф-программе 1dedic — прозрачный кост-share. Не реклама.

— RTX 5090 / GB202 / 0x2b85