akashch1512 commited on
Commit
c2cea37
·
1 Parent(s): 777c30b

minor chnages in app

Browse files
Files changed (1) hide show
  1. app.py +84 -17
app.py CHANGED
@@ -55,12 +55,21 @@ _VIEWER_MAX_SIDE = 1024
55
  _PAYLOAD_SLOT = "__DW_EMBED_JSON__"
56
 
57
 
58
- def _load_model_once(device: torch.device):
 
 
 
 
 
 
 
 
 
59
  global MODEL_PATH, MODEL, MODEL_SPEC, MODEL_CFG
60
  from infer.predict import load_model
61
 
62
  with MODEL_LOCK:
63
- if MODEL is None or next(MODEL.parameters()).device != device:
64
  if not MODEL_PATH_ON_BUCKET.is_file():
65
  raise RuntimeError(
66
  f"Model checkpoint not found at {MODEL_PATH_ON_BUCKET}. "
@@ -74,7 +83,7 @@ def _load_model_once(device: torch.device):
74
  )
75
  MODEL_PATH = str(MODEL_PATH_ON_BUCKET)
76
  MODEL, MODEL_SPEC, MODEL_CFG = load_model(
77
- MODEL_PATH, device, hf_token
78
  )
79
  return MODEL, MODEL_SPEC, MODEL_CFG
80
 
@@ -255,32 +264,69 @@ def _bundle(out_dir: Path, files: list[str]) -> list[str]:
255
 
256
 
257
  # ---------------------------------------------------------------------
258
- # TTA is roughly 4x the work, and a 1600 px scene tiles into a lot of windows.
259
- @spaces.GPU(duration=180)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
260
  def predict(image_path: str | None, gsd: float, tta: bool = False):
261
  if not image_path:
262
  raise gr.Error("Upload an image first.")
263
- device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
264
  try:
265
  from dwdata.preprocess import read_scene
266
- from infer.engine import predict_scene
267
 
268
- model, spec, cfg = _load_model_once(device)
 
269
  rgb, scene_meta = read_scene(
270
  image_path,
271
  user_gsd_m=float(gsd or 0),
272
  assumed_gsd_m=spec.canonical_gsd_m,
273
  max_side=1600,
274
  )
275
- amp_dtype = (
276
- (torch.bfloat16 if cfg.amp_dtype == "bf16" else torch.float16)
277
- if device.type == "cuda"
278
- else None
279
- )
280
- height, _ = predict_scene(
281
- model, rgb, scene_meta.gsd_m, spec, device,
282
- tta=bool(tta), amp_dtype=amp_dtype, overlap=0.5, batch_tiles=4,
283
- )
284
 
285
  height = _sanitise(height)
286
  low, high = _height_bounds(height)
@@ -341,7 +387,18 @@ def predict(image_path: str | None, gsd: float, tta: bool = False):
341
  (str(out_dir / "hillshade.png"), "Hillshade (315° / 45°)"),
342
  ]
343
  return gallery, height16, viewer_html, _bundle(out_dir, files), info
 
 
344
  except Exception as exc:
 
 
 
 
 
 
 
 
 
345
  raise gr.Error(f"Inference failed: {exc}") from exc
346
 
347
 
@@ -389,5 +446,15 @@ with gr.Blocks(title="Single View Height Estimator") as demo:
389
  )
390
 
391
 
 
 
 
 
 
 
 
 
 
 
392
  if __name__ == "__main__":
393
  demo.launch()
 
55
  _PAYLOAD_SLOT = "__DW_EMBED_JSON__"
56
 
57
 
58
+ def _load_model_once():
59
+ """Build the model **on the CPU**, once, outside any GPU window.
60
+
61
+ On ZeroGPU the GPU is attached only for the duration of a `@spaces.GPU`
62
+ call and the whole reservation is billed against the caller's daily quota,
63
+ so reading a checkpoint off /data and pulling the DINOv3 encoder from the
64
+ hub must not happen in there. CUDA also cannot be initialised in the main
65
+ process, hence `torch.device("cpu")` here and `.to("cuda")` inside the
66
+ worker.
67
+ """
68
  global MODEL_PATH, MODEL, MODEL_SPEC, MODEL_CFG
69
  from infer.predict import load_model
70
 
71
  with MODEL_LOCK:
72
+ if MODEL is None:
73
  if not MODEL_PATH_ON_BUCKET.is_file():
74
  raise RuntimeError(
75
  f"Model checkpoint not found at {MODEL_PATH_ON_BUCKET}. "
 
83
  )
84
  MODEL_PATH = str(MODEL_PATH_ON_BUCKET)
85
  MODEL, MODEL_SPEC, MODEL_CFG = load_model(
86
+ MODEL_PATH, torch.device("cpu"), hf_token
87
  )
88
  return MODEL, MODEL_SPEC, MODEL_CFG
89
 
 
264
 
265
 
266
  # ---------------------------------------------------------------------
267
+ # ZeroGPU bookkeeping
268
+ # ---------------------------------------------------------------------
269
+ # A `@spaces.GPU(duration=N)` call reserves N seconds against the caller's
270
+ # daily GPU allowance *up front*: if their remaining quota is below N the
271
+ # request is refused with "GPU time exhausted" no matter how many GPUs are
272
+ # idle. The old code asked for 180 s around the whole handler — checkpoint
273
+ # load, hub download, meshing, PNG encoding and zipping included — which put
274
+ # the demo out of reach of anonymous visitors (~60 s) and burned a signed-in
275
+ # user's quota on work that never touches the GPU.
276
+ #
277
+ # So: only the forward pass is decorated, the durations are sized to it, and
278
+ # TTA (~4x the work) gets its own longer window instead of everyone paying for
279
+ # the worst case.
280
+ _GPU_DURATION = 45
281
+ _GPU_DURATION_TTA = 120
282
+
283
+
284
+ def _run_model(rgb, gsd_m, tta: bool):
285
+ """The only GPU-bound step. Runs inside a ZeroGPU reservation."""
286
+ from infer.engine import predict_scene
287
+
288
+ model, spec, cfg = _load_model_once()
289
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
290
+ model = model.to(device).eval()
291
+ amp_dtype = (
292
+ (torch.bfloat16 if cfg.amp_dtype == "bf16" else torch.float16)
293
+ if device.type == "cuda"
294
+ else None
295
+ )
296
+ try:
297
+ height, _ = predict_scene(
298
+ model, rgb, gsd_m, spec, device,
299
+ tta=bool(tta), amp_dtype=amp_dtype, overlap=0.5, batch_tiles=4,
300
+ )
301
+ finally:
302
+ # hand the card back clean; the next reservation is a fresh attach
303
+ model.to("cpu")
304
+ if device.type == "cuda":
305
+ torch.cuda.empty_cache()
306
+ return height
307
+
308
+
309
+ _run_model_gpu = spaces.GPU(duration=_GPU_DURATION)(_run_model)
310
+ _run_model_gpu_tta = spaces.GPU(duration=_GPU_DURATION_TTA)(_run_model)
311
+
312
+
313
  def predict(image_path: str | None, gsd: float, tta: bool = False):
314
  if not image_path:
315
  raise gr.Error("Upload an image first.")
 
316
  try:
317
  from dwdata.preprocess import read_scene
 
318
 
319
+ # CPU work, deliberately outside the reservation
320
+ _, spec, _ = _load_model_once()
321
  rgb, scene_meta = read_scene(
322
  image_path,
323
  user_gsd_m=float(gsd or 0),
324
  assumed_gsd_m=spec.canonical_gsd_m,
325
  max_side=1600,
326
  )
327
+
328
+ runner = _run_model_gpu_tta if tta else _run_model_gpu
329
+ height = runner(rgb, scene_meta.gsd_m, bool(tta))
 
 
 
 
 
 
330
 
331
  height = _sanitise(height)
332
  low, high = _height_bounds(height)
 
387
  (str(out_dir / "hillshade.png"), "Hillshade (315° / 45°)"),
388
  ]
389
  return gallery, height16, viewer_html, _bundle(out_dir, files), info
390
+ except gr.Error:
391
+ raise
392
  except Exception as exc:
393
+ text = str(exc).lower()
394
+ if "quota" in text or "gpu task aborted" in text or "gpu time" in text:
395
+ # not a model failure: the caller's ZeroGPU allowance ran out
396
+ raise gr.Error(
397
+ "Your ZeroGPU quota for today is used up. Sign in to Hugging "
398
+ "Face for a larger allowance, turn TTA off (it asks for a "
399
+ f"{_GPU_DURATION_TTA} s window instead of {_GPU_DURATION} s), "
400
+ "or retry after the quota resets."
401
+ ) from exc
402
  raise gr.Error(f"Inference failed: {exc}") from exc
403
 
404
 
 
446
  )
447
 
448
 
449
+ # Build the model on the CPU while the Space boots, so the first visitor's GPU
450
+ # reservation pays for the forward pass only and not for the checkpoint read
451
+ # and the encoder download. A failure here (no bucket, no HF_TOKEN) is
452
+ # reported per-request as before, so it must not take the Space down.
453
+ try:
454
+ _load_model_once()
455
+ except Exception as _warm_exc: # pragma: no cover - startup convenience only
456
+ print(f"[app] model warm-up deferred: {_warm_exc}")
457
+
458
+
459
  if __name__ == "__main__":
460
  demo.launch()