luh1124 commited on
Commit
c5d64e1
·
1 Parent(s): 1c6f0e7

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 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 only GPU callback and does not touch NeAR or gsplat.
 
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()