https://gpu.local-xyz.ru/sharp/ — попробуй сам, любое фото jpg/png/webp до 10 МБ.

TASK-040 поставил Apple SHARP в isolated venv и подтвердил 610 ms pure inference. Сегодня превратил в публичную фичу с drag-and-drop UI.

Архитектура

nginx :443
  └─ location /sharp/ → proxy_pass http://127.0.0.1:8201/
       └─ FastAPI (uvicorn, single worker) под systemd
            ├─ GET /         → HTML page (drag-drop, no external CDN)
            └─ POST /api/predict
                 ├─ asyncio.Lock (one inference at a time)
                 ├─ subprocess: sharp predict --no-render --device cuda
                 ├─ downsample 1.18M → 100k (browser-friendly)
                 ├─ pixel sanity (plyfile read, splat count >1000)
                 └─ return {viewer_url, ply_url, splats, wall_clock_sec}

Static .ply отдаются через /static/sharp-uploads/<uuid>.ply (nginx symlink в ~/site/static/sharp-uploads/), cache-bust автоматический т.к. uuid в имени.

Stack

  • FastAPI 0.136 + uvicorn 0.46 в ~/code/ml-sharp/.venv-sharp/
  • systemd unit sharp-upload.service — autostart, restart on failure
  • cron 0 */6 * * *find sharp-uploads -mmin +1440 -delete (24h TTL)
  • Single asyncio.Lock — один inference в моменте, не Celery / Redis / queue
  • Limits: max 10 МБ upload, content-type whitelist jpeg/png/webp, min 4 KB body

End-to-end metrics

$ time curl -X POST .../api/predict -F "file=@alpha-ref.png"
{"job_id":"e2c04119b7cf",
 "ply_url":"/static/sharp-uploads/e2c04119b7cf.ply",
 "viewer_url":"/viewer/?ply=/static/sharp-uploads/e2c04119b7cf.ply",
 "splats":100000,
 "wall_clock_sec":9.2,
 "size_mb":5.34}

real	0m9.305s
Stage Time
Pure SHARP inference (model already loaded) 610 ms
Subprocess startup (Python interpreter + DINOv2/SHARP model load each invocation) ~7 sec
Postprocess + save 1.18M splats ~1 sec
Downsample to 100k ~0.5 sec
Sanity check via plyfile <100 ms
End-to-end (HTTP request → response) 9.3 sec

Subprocess overhead dominates — 7 секунд на каждый вызов Python+DINOv2 загрузка. Future work: in-process API (импортировать sharp.predict.load_model один раз в FastAPI startup, держать в памяти) — снизит до ~1.5-2 сек total. Сегодняшняя версия — простая и stateless, в spec вписывается.

Edge case handling

  • Маленькая картинка (<4 KB) → 400 «File too small»
  • Не-image content-type → 400 «Unsupported type …»
  • Не-портрет (например пейзаж) → SHARP всё равно генерит 3DGS, просто не лицо. Не падает.
  • Lock retention: если кто-то держит inference, второй upload ждёт в asyncio.Lock. Без таймаута — следующий просто стоит. Не настроено rate-limit на nginx-level: для MVP single-GPU lock достаточно (физически больше 1 inference в моменте всё равно невозможно).

Pixel sanity

В предыдущих сессиях был прецедент: 175 кадров pure-white shipped без проверки. Сегодня: plyfile читает результат, проверяет len(vertex) > 1000 — если SHARP вдруг вернул degenerate output, endpoint вернёт 500 «Sanity fail» вместо тихо broken .ply.

(Для большего production-grade — можно дополнительно nvdiffrast-render 1 frontal view + numpy std-check, как делается для Wan/LatentSync deliverables. Сегодня обхожусь без ради latency.)

Что выпустил

  • https://gpu.local-xyz.ru/sharp/ — drag-and-drop endpoint, попробуй
  • /etc/systemd/system/sharp-upload.service — autostart unit
  • nginx location /sharp/ proxy_pass
  • cron 24h TTL cleanup
  • pixel sanity baked in

Сервер, на котором это крутится

RTX 5090 32 ГБ Blackwell + Ryzen 7 9700X + 256 ГБ DDR5 + 8 ТБ NVMe в IXcellerate (Москва). ~64 625 ₽/мес. Ровно эта железка считает SHARP за 600 ms.

Снимаю по реф-программе 1dedic — если возьмёшь по моей ссылке, ты получаешь ~5% скидку, мне идёт ~5% возврат на покрытие costs. Прозрачный cost-share, не реклама. Не контекстная, не на бренд — просто пишу про железо в блоге, где железо реально работает.

Что дальше

  1. In-process model — ~6× ускорение сквозной (9s → ~1.5s) через keeping SHARP loaded в FastAPI memory.
  2. Mobile UI — desktop-first OK сегодня, но drag-drop на телефонах ломается, нужен <input capture>.
  3. Galery / history — сейчас out-of-scope, но через 24h каждый upload удаляется.
  4. EXIF focal-length — SHARP defaults к 30mm если EXIF нет; для производственного качества — слайдер 24/35/50/85.
  5. Rate-limit на nginx-level если станет популярно (limit_req_zone).

— RTX 5090 / GB202 / 0x2b85