feat(hyshape): GPU geometry warmup on page load (ZeroGPU cold start)
Browse files- Default NEAR_HYSHAPE_GEOMETRY_WARMUP_ON_LOAD=1: demo.load + warmup loads Hunyuan once per visit\n- Add libopengl0 to packages.txt for pymeshlab libOpenGL noise\n- Document env var in README/DEPLOY; extend architecture test
Made-with: Cursor
- DEPLOY_HF_SPACE.md +3 -0
- README.md +1 -0
- app_hyshape.py +42 -2
- packages.txt +1 -0
- tests/test_app_hyshape_architecture.py +13 -0
DEPLOY_HF_SPACE.md
CHANGED
|
@@ -78,6 +78,7 @@ If you maintain a separate template tree (e.g. `NeAR_space`), copy changes **int
|
|
| 78 |
| `NEAR_GSPLAT_WARMUP` | `0` | `1` |
|
| 79 |
| `NEAR_GSPLAT_SOURCE_SPEC` | unset unless you have a proven build path | optional if you want build-time source compile |
|
| 80 |
| `NEAR_ZEROGPU_HF_CEILING_S` | `90` | tune to your tier |
|
|
|
|
| 81 |
|
| 82 |
### 2b2. Mirroring DINOv2 and other auxiliary assets
|
| 83 |
|
|
@@ -141,6 +142,8 @@ If the UI shows **no examples** or **`No PNG examples`**, usually:
|
|
| 141 |
| `requirements.txt` | `pip` dependencies. |
|
| 142 |
| `packages.txt` | Optional `apt` packages (OpenGL, etc.). |
|
| 143 |
|
|
|
|
|
|
|
| 144 |
## 4. Git push: large files & binary files on the Hub
|
| 145 |
|
| 146 |
### 4a. Files **> 10 MiB** in plain Git
|
|
|
|
| 78 |
| `NEAR_GSPLAT_WARMUP` | `0` | `1` |
|
| 79 |
| `NEAR_GSPLAT_SOURCE_SPEC` | unset unless you have a proven build path | optional if you want build-time source compile |
|
| 80 |
| `NEAR_ZEROGPU_HF_CEILING_S` | `90` | tune to your tier |
|
| 81 |
+
| `NEAR_HYSHAPE_GEOMETRY_WARMUP_ON_LOAD` | `1` when Space entry is **`app_hyshape.py`** (default in code: load Hunyuan on first page view via `demo.load` + `@spaces.GPU`) | `0` to load geometry only when the user clicks **Generate Mesh** (saves one GPU allocation per visit, but repeats cold start) |
|
| 82 |
|
| 83 |
### 2b2. Mirroring DINOv2 and other auxiliary assets
|
| 84 |
|
|
|
|
| 142 |
| `requirements.txt` | `pip` dependencies. |
|
| 143 |
| `packages.txt` | Optional `apt` packages (OpenGL, etc.). |
|
| 144 |
|
| 145 |
+
Root **`packages.txt`** lists **`libgl1`** and **`libopengl0`** so imports that pull in **`pymeshlab`** are less likely to spam logs about **`libOpenGL.so.0`**. Those plugins are not required for the core NeAR / HyShape paths; if warnings persist after rebuild, they are usually safe to ignore.
|
| 146 |
+
|
| 147 |
## 4. Git push: large files & binary files on the Hub
|
| 148 |
|
| 149 |
### 4a. Files **> 10 MiB** in plain Git
|
README.md
CHANGED
|
@@ -49,6 +49,7 @@ This repository combines:
|
|
| 49 |
## ZeroGPU Runtime Notes
|
| 50 |
|
| 51 |
- The Space is temporarily pointed at **`app_hyshape.py`** (Hunyuan geometry only) for isolating ZeroGPU init issues. Restore **`app_file: app.py`** in the YAML header above when you want the full NeAR UI again.
|
|
|
|
| 52 |
- The full `app.py` Space keeps **page-load image defaults** and **HDRI preview** on lightweight CPU paths so the first page visit does not spend the first ZeroGPU allocation on model initialization.
|
| 53 |
- Runtime loading is split by responsibility: **Hunyuan3D geometry** is loaded only for mesh generation, **NeAR relighting** is loaded only for SLaT/render/export, and **gsplat warmup** is delayed until the first real render.
|
| 54 |
- Binary wheels and mirrored auxiliary assets are stored separately:
|
|
|
|
| 49 |
## ZeroGPU Runtime Notes
|
| 50 |
|
| 51 |
- The Space is temporarily pointed at **`app_hyshape.py`** (Hunyuan geometry only) for isolating ZeroGPU init issues. Restore **`app_file: app.py`** in the YAML header above when you want the full NeAR UI again.
|
| 52 |
+
- **`app_hyshape.py`** defaults to **`NEAR_HYSHAPE_GEOMETRY_WARMUP_ON_LOAD=1`**: opening the Space triggers one GPU callback that loads Hunyuan so **Generate Mesh** does not pay the full ~40s cold start again in the same session. Set to **`0`** to disable (saves GPU seconds per visitor, slower first mesh).
|
| 53 |
- The full `app.py` Space keeps **page-load image defaults** and **HDRI preview** on lightweight CPU paths so the first page visit does not spend the first ZeroGPU allocation on model initialization.
|
| 54 |
- Runtime loading is split by responsibility: **Hunyuan3D geometry** is loaded only for mesh generation, **NeAR relighting** is loaded only for SLaT/render/export, and **gsplat warmup** is delayed until the first real render.
|
| 55 |
- Binary wheels and mirrored auxiliary assets are stored separately:
|
app_hyshape.py
CHANGED
|
@@ -41,7 +41,7 @@ print(
|
|
| 41 |
)
|
| 42 |
|
| 43 |
try:
|
| 44 |
-
import spaces
|
| 45 |
except ImportError:
|
| 46 |
spaces = None
|
| 47 |
|
|
@@ -52,6 +52,21 @@ os.environ.setdefault("TORCH_CUDA_ARCH_LIST", "7.5;8.0;8.6;8.9;9.0")
|
|
| 52 |
|
| 53 |
GPU = spaces.GPU if spaces is not None else (lambda f: f)
|
| 54 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
APP_DIR = Path(__file__).resolve().parent
|
| 56 |
CACHE_DIR = APP_DIR / "tmp_gradio_hyshape"
|
| 57 |
CACHE_DIR.mkdir(exist_ok=True)
|
|
@@ -226,6 +241,24 @@ def ensure_geometry_pipeline() -> Any:
|
|
| 226 |
return GEOMETRY_PIPELINE
|
| 227 |
|
| 228 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 229 |
@GPU
|
| 230 |
@torch.inference_mode()
|
| 231 |
def generate_mesh(
|
|
@@ -284,7 +317,8 @@ This diagnostic app isolates the Hunyuan geometry path.
|
|
| 284 |
|
| 285 |
- Upload an image or click an example.
|
| 286 |
- The upload path only performs lightweight preprocessing.
|
| 287 |
-
- `Generate Mesh` is the
|
|
|
|
| 288 |
"""
|
| 289 |
)
|
| 290 |
|
|
@@ -322,6 +356,12 @@ This diagnostic app isolates the Hunyuan geometry path.
|
|
| 322 |
|
| 323 |
demo.unload(end_session)
|
| 324 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 325 |
image_input.upload(
|
| 326 |
preprocess_image_only,
|
| 327 |
inputs=[image_input],
|
|
|
|
| 41 |
)
|
| 42 |
|
| 43 |
try:
|
| 44 |
+
import spaces # pyright: ignore[reportMissingImports]
|
| 45 |
except ImportError:
|
| 46 |
spaces = None
|
| 47 |
|
|
|
|
| 52 |
|
| 53 |
GPU = spaces.GPU if spaces is not None else (lambda f: f)
|
| 54 |
|
| 55 |
+
|
| 56 |
+
def _truthy_env(name: str, default: str) -> bool:
|
| 57 |
+
value = (os.environ.get(name) if name in os.environ else default).strip().lower()
|
| 58 |
+
return value in ("1", "true", "yes", "on")
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
# Default on for this probe Space: first "Generate Mesh" stays under ZeroGPU budget after weights are on GPU.
|
| 62 |
+
_HYSHAPE_WARMUP_ON_LOAD = _truthy_env("NEAR_HYSHAPE_GEOMETRY_WARMUP_ON_LOAD", "1")
|
| 63 |
+
print(
|
| 64 |
+
f"[HyShape] geometry warmup on page load: "
|
| 65 |
+
f"{'enabled' if _HYSHAPE_WARMUP_ON_LOAD else 'disabled'} "
|
| 66 |
+
f"(NEAR_HYSHAPE_GEOMETRY_WARMUP_ON_LOAD, default 1).",
|
| 67 |
+
flush=True,
|
| 68 |
+
)
|
| 69 |
+
|
| 70 |
APP_DIR = Path(__file__).resolve().parent
|
| 71 |
CACHE_DIR = APP_DIR / "tmp_gradio_hyshape"
|
| 72 |
CACHE_DIR.mkdir(exist_ok=True)
|
|
|
|
| 241 |
return GEOMETRY_PIPELINE
|
| 242 |
|
| 243 |
|
| 244 |
+
@GPU
|
| 245 |
+
@torch.inference_mode()
|
| 246 |
+
def warmup_hunyuan_geometry_on_load():
|
| 247 |
+
"""Pay Hunyuan load + GPU move on first page view so Generate Mesh does not repeat it."""
|
| 248 |
+
started_at = time.time()
|
| 249 |
+
print(
|
| 250 |
+
"[HyShape] warmup_on_load: entered GPU callback "
|
| 251 |
+
f"(cuda_available={torch.cuda.is_available()})",
|
| 252 |
+
flush=True,
|
| 253 |
+
)
|
| 254 |
+
ensure_geometry_pipeline()
|
| 255 |
+
elapsed = time.time() - started_at
|
| 256 |
+
print(f"[HyShape] warmup_on_load: finished in {elapsed:.1f}s", flush=True)
|
| 257 |
+
return (
|
| 258 |
+
f"Geometry ready ({elapsed:.1f}s). Click **Generate Mesh** — model should already be on GPU."
|
| 259 |
+
)
|
| 260 |
+
|
| 261 |
+
|
| 262 |
@GPU
|
| 263 |
@torch.inference_mode()
|
| 264 |
def generate_mesh(
|
|
|
|
| 317 |
|
| 318 |
- Upload an image or click an example.
|
| 319 |
- The upload path only performs lightweight preprocessing.
|
| 320 |
+
- `Generate Mesh` is the main GPU callback and does not touch NeAR or gsplat.
|
| 321 |
+
- With default settings, the first page load runs a **geometry warmup** on GPU so mesh generation does not pay the full cold start again.
|
| 322 |
"""
|
| 323 |
)
|
| 324 |
|
|
|
|
| 356 |
|
| 357 |
demo.unload(end_session)
|
| 358 |
|
| 359 |
+
if _HYSHAPE_WARMUP_ON_LOAD:
|
| 360 |
+
demo.load(
|
| 361 |
+
warmup_hunyuan_geometry_on_load,
|
| 362 |
+
outputs=[status_md],
|
| 363 |
+
)
|
| 364 |
+
|
| 365 |
image_input.upload(
|
| 366 |
preprocess_image_only,
|
| 367 |
inputs=[image_input],
|
packages.txt
CHANGED
|
@@ -1,2 +1,3 @@
|
|
| 1 |
libgl1
|
|
|
|
| 2 |
libglib2.0-0
|
|
|
|
| 1 |
libgl1
|
| 2 |
+
libopengl0
|
| 3 |
libglib2.0-0
|
tests/test_app_hyshape_architecture.py
CHANGED
|
@@ -51,6 +51,19 @@ class AppHyShapeArchitectureTests(unittest.TestCase):
|
|
| 51 |
|
| 52 |
self.assertIn("[HyShape] generate_mesh callback entered", source)
|
| 53 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
|
| 55 |
if __name__ == "__main__":
|
| 56 |
unittest.main()
|
|
|
|
| 51 |
|
| 52 |
self.assertIn("[HyShape] generate_mesh callback entered", source)
|
| 53 |
|
| 54 |
+
def test_page_load_warmup_calls_geometry_loader_only(self) -> None:
|
| 55 |
+
tree = _load_tree()
|
| 56 |
+
warmup = _get_function(tree, "warmup_hunyuan_geometry_on_load")
|
| 57 |
+
called = _called_names(warmup)
|
| 58 |
+
|
| 59 |
+
self.assertIn("ensure_geometry_pipeline", called)
|
| 60 |
+
self.assertNotIn("ensure_near_pipeline", called)
|
| 61 |
+
self.assertNotIn("ensure_gsplat_ready", called)
|
| 62 |
+
|
| 63 |
+
source = APP_PATH.read_text(encoding="utf-8")
|
| 64 |
+
self.assertIn("demo.load(", source)
|
| 65 |
+
self.assertIn("warmup_hunyuan_geometry_on_load", source)
|
| 66 |
+
|
| 67 |
|
| 68 |
if __name__ == "__main__":
|
| 69 |
unittest.main()
|