Spaces:
Running on Zero
Running on Zero
Commit ·
c2cea37
1
Parent(s): 777c30b
minor chnages in app
Browse files
app.py
CHANGED
|
@@ -55,12 +55,21 @@ _VIEWER_MAX_SIDE = 1024
|
|
| 55 |
_PAYLOAD_SLOT = "__DW_EMBED_JSON__"
|
| 56 |
|
| 57 |
|
| 58 |
-
def _load_model_once(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 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 |
-
#
|
| 259 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
| 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 |
-
|
| 276 |
-
|
| 277 |
-
|
| 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()
|