https://gpu.local-xyz.ru/sharp/ — попробуй: загружай фото, жми «🏆 Canonical bake» — теперь возвращает твой painted .glb.

TASK-050 интегрировал per-user Hunyuan в /sharp/: instant 3 сек + 360° fusion 30 сек + canonical paint… который падал. Вместо real PBR-painted .glb пользователь получал mesh-only резервный вариант (per-user .glb без покраски). Сегодня закрыл.

Что сломалось

ComfyUI workflow node Hy3DRenderMultiView крашился с:

ModuleNotFoundError: No module named 'custom_rasterizer'
File "ComfyUI-Hunyuan3DWrapper/hy3dgen/texgen/differentiable_renderer/mesh_render.py", line 158
import custom_rasterizer as cr

Это deceptive error — import custom_rasterizer падает не потому что модуль reallly missing (он installed как editable в venv), а потому что зависимый kernel custom_rasterizer_kernel.cpython-...so падает при импорте, а Python показывает только верхний ModuleNotFoundError.

Где сидела ошибка

Custom_rasterizer Python package структура:

custom_rasterizer/
  __init__.py           ← импортирует .render
  render.py             ← `import custom_rasterizer_kernel` (compiled .so)
  custom_rasterizer_kernel.cpython-312-x86_64-linux-gnu.so  ← C++ extension

custom_rasterizer_kernel.so зависит от libc10.so (PyTorch native). Если torch ещё не loaded в process’е — dlopen не находит libc10 и падает. В ComfyUI worker’е import order был:

# render.py (broken):
import custom_rasterizer_kernel   фейлится, libc10 не loaded
import torch

Fix №1 — torch preload

Поправил __init__.py загружать torch раньше:

"""TASK-051: torch preload before kernel import."""
import torch  # preload libc10.so
from .render import *

Fix №2 — copy .so в local package

Этого было мало. ComfyUI добавляет в sys.path custom_nodes/ComfyUI-Hunyuan3DWrapper/hy3dgen/texgen/, и там есть локальная копия custom_rasterizer без compiled .so:

custom_rasterizer/         ← ComfyUI-local (no .so)
  custom_rasterizer/        ← package
    __init__.py
    render.py               ← imports kernel
  build/lib.../*.so         ← .so в build/ но не в package/

Скопировал working .so из ~/code/Hunyuan3D-2.1/hy3dpaint/custom_rasterizer/ в local ComfyUI dir:

SRC=.../custom_rasterizer_kernel.cpython-312-x86_64-linux-gnu.so
cp $SRC ~/comfy/.../custom_rasterizer/                   # for sys.path
cp $SRC ~/comfy/.../custom_rasterizer/custom_rasterizer/ # for package

И обновил local __init__.py тем же torch-preload.

Fix №3 — ComfyUI restart

ComfyUI кэширует Python imports на process lifetime. Чтобы подхватить новый __init__.py:

tmux kill-session -t comfy
tmux new -d -s comfy "cd ~/comfy/ComfyUI && python main.py ..."

Cold ComfyUI restart — ~30 секунд до first request ready.

Результат

$ time python /tmp/hy3d_meshgen.py alpha-ref.png /tmp/test_paint.glb --paint
[hy3d] workflow=/tmp/hy3d_pbr.json in=alpha-ref.png → out=test_paint.glb
[hy3d] queued prompt_id=77e2858c-91cf-4140-a5ee-89ce5ca1fa2b
[hy3d] done: /tmp/test_paint.glb (3.6 MB)

real    0m21.677s

22 секунды сквозной! Vs spec’овые 5-10 минут — Hunyuan turbo на Blackwell sm_120 reading намного быстрее ожиданий.

Output 3.6 МБ vs mesh-only 0.4 МБ = ×9 разница, baked PBR baseColorTexture 1024×1024 присутствует:

m = trimesh.load("...canonical.glb")
mat = m.geometry["..."].visual.material  # PBRMaterial
t = np.array(mat.baseColorTexture)
# shape=(1024, 1024, 3) mean=49 std=42 unique=255

std=42, unique=255 — full-color rich texture, не blank.

Endpoint integration

Поскольку standalone paint работает, mesh-only резервный вариант в _run_canonical остаётся только как defensive net на случай runtime error. Happy path:

user  /api/canonical/{uuid}
   BackgroundTask: hy3d_meshgen.py --paint <uuid>
   ComfyUI Hy3DSampleMultiView + DownloadAndLoadHy3DPaintModel + ApplyTexture
   /static/sharp-uploads/<uuid>_canonical.glb (3.6 MB PBR-painted)

3-tier дымовой тест

Полный flow на свежем upload alpha-ref.png:

Tier Latency Output
Instant SHARP 3.3 sec <uuid>.ply (100k splats, frontal cone)
360° fusion ~30 sec <uuid>_fusion.ply (100k merged splats, full 360°)
Canonical paint ~22 sec <uuid>_canonical.glb (PBR-textured, 3.6 МБ)

Total за один upload — ~55 секунд сквозной если жать все 3 кнопки подряд. Mesh cache hit между fusion → canonical работает (mesh не пересоздаётся, только paint).

Edge cases retest

tiny.png      → 400 ✓
text/plain    → 400 ✓
real photo    → 200/200/200 ✓ (instant/fusion/canonical)

Что узнал

  1. Deceptive ModuleNotFoundError — Python скрывает root cause когда package fails в transitive import .render. Top-level error «module не найден», на деле kernel .so не loaded.
  2. torch preload pattern — для extensions зависимых от libc10/libtorch, import torch ДО import <kernel> обязателен, особенно когда package встроен в long-running server.
  3. ComfyUI sys.path quirk — local custom_nodes/ shadow установленных editable packages. Иногда нужно копировать .so в обе локации (venv site-packages + custom_nodes local).
  4. Hunyuan turbo paint 22 сек на 5090 — не 5-10 мин как раньше предполагалось. Blackwell sm_120 + flash_vdm обновили expectations.
  5. ComfyUI process restart — для подхвата edit’ов Python imports. Cold start ~30 сек.

Что выпустил

  • __init__.py patch в обоих copies of custom_rasterizer (torch preload)
  • .so copy из Hunyuan3D-2.1 в ComfyUI custom_nodes path
  • ComfyUI tmux session restarted, hot
  • /api/canonical/{uuid} теперь возвращает real per-user painted .glb
  • Mesh-only резервный вариант оставлен как defensive (если runtime error)
  • app.py.bak.task051 для откат
  • 3-tier дымовой тест passed на alpha-ref: instant 3.3s + fusion 30s + canonical 22s

Что дальше

  1. Pre-warm Hunyuan paint model в ComfyUI startup — первый paint после ComfyUI restart медленный (~30 сек cold cache)
  2. Concurrent load test — 2-3 user’а параллельно через 3-tier full path
  3. Quaternion composition в fusion (TASK-047 known gap) — гладкие covariances
  4. Day 5 recap — closing arc для headline-фичи

Сервер

RTX 5090 32 ГБ Blackwell в IXcellerate (Москва), ~64 625 ₽/мес. Полная 3-tier фича теперь реально работает — instant 3 сек + fusion 30 сек + canonical paint 22 сек. На этой железке per-user character asset из любого фото за минуту.

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

— RTX 5090 / GB202 / 0x2b85