Bind ZeroGPU handler directly and log SAM stages
Browse files
app.py
CHANGED
|
@@ -233,10 +233,17 @@ def _load_models_at_startup() -> None:
|
|
| 233 |
_load_models_at_startup()
|
| 234 |
|
| 235 |
|
| 236 |
-
def _segment(
|
|
|
|
|
|
|
| 237 |
import torch
|
| 238 |
|
| 239 |
model, processor = _load_sam3()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 240 |
inputs = processor(images=image, text=prompt, return_tensors="pt").to(model.device)
|
| 241 |
with torch.inference_mode():
|
| 242 |
output = model(**inputs)
|
|
@@ -248,10 +255,17 @@ def _segment(image: Image.Image, prompt: str) -> tuple[np.ndarray | None, int]:
|
|
| 248 |
)[0]
|
| 249 |
masks = result["masks"]
|
| 250 |
if len(masks) == 0:
|
| 251 |
-
|
|
|
|
| 252 |
scores = torch.as_tensor(result["scores"])
|
| 253 |
best = int(scores.argmax().item())
|
| 254 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 255 |
|
| 256 |
|
| 257 |
def _overlay(image: np.ndarray, mask: np.ndarray) -> np.ndarray:
|
|
@@ -282,9 +296,28 @@ def _request_log(request_id: str, event: str, detail: str = "") -> None:
|
|
| 282 |
|
| 283 |
|
| 284 |
@spaces.GPU(duration=360)
|
| 285 |
-
def
|
| 286 |
-
image: Image.Image
|
| 287 |
) -> tuple[Any, Any, Any, str]:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 288 |
started = time.monotonic()
|
| 289 |
rgb = image.convert("RGB")
|
| 290 |
image_array = np.asarray(rgb, dtype=np.uint8)
|
|
@@ -297,11 +330,11 @@ def _reconstruct_gpu(
|
|
| 297 |
try:
|
| 298 |
_request_log(request_id, "stage started", stage)
|
| 299 |
stage_started = time.monotonic()
|
| 300 |
-
mask, count = _segment(rgb, prompt)
|
| 301 |
_request_log(
|
| 302 |
request_id,
|
| 303 |
-
"stage completed",
|
| 304 |
-
f"{
|
| 305 |
)
|
| 306 |
if mask is None:
|
| 307 |
message = f"SAM 3 did not find '{prompt}' in this image."
|
|
@@ -356,64 +389,6 @@ def _reconstruct_gpu(
|
|
| 356 |
)
|
| 357 |
|
| 358 |
|
| 359 |
-
def reconstruct(image: Image.Image | None, prompt: str):
|
| 360 |
-
"""Stream a start message, then call the bounded GPU function."""
|
| 361 |
-
if image is None:
|
| 362 |
-
yield None, None, None, "Upload an image first."
|
| 363 |
-
return
|
| 364 |
-
prompt = (prompt or "").strip()
|
| 365 |
-
if not prompt:
|
| 366 |
-
yield None, None, None, "Enter a short object description, such as 'chair'."
|
| 367 |
-
return
|
| 368 |
-
if SETUP_ERROR:
|
| 369 |
-
yield None, None, None, f"Space setup is incomplete: {SETUP_ERROR}"
|
| 370 |
-
return
|
| 371 |
-
if MODEL_SETUP_ERROR:
|
| 372 |
-
yield None, None, None, f"Model setup failed: {MODEL_SETUP_ERROR}"
|
| 373 |
-
return
|
| 374 |
-
if SAM3 is None or SAM3D is None:
|
| 375 |
-
yield None, None, None, "Models are not ready; check the Space startup logs."
|
| 376 |
-
return
|
| 377 |
-
|
| 378 |
-
request_id = uuid.uuid4().hex[:8]
|
| 379 |
-
_request_log(
|
| 380 |
-
request_id,
|
| 381 |
-
"request accepted",
|
| 382 |
-
"submitting to ZeroGPU; maximum GPU time is 360s",
|
| 383 |
-
)
|
| 384 |
-
yield None, None, None, "Queued on ZeroGPU; maximum GPU time is 360 seconds. Progress is recorded in Space Container logs."
|
| 385 |
-
try:
|
| 386 |
-
result = _reconstruct_gpu(image, prompt, request_id)
|
| 387 |
-
except Exception as exc:
|
| 388 |
-
_request_log(
|
| 389 |
-
request_id,
|
| 390 |
-
"ZeroGPU call failed",
|
| 391 |
-
f"{type(exc).__name__}: {exc}",
|
| 392 |
-
)
|
| 393 |
-
traceback.print_exc()
|
| 394 |
-
error_text = str(exc)
|
| 395 |
-
error_lower = error_text.lower()
|
| 396 |
-
is_timeout = isinstance(exc, TimeoutError) or any(
|
| 397 |
-
word in error_lower for word in ("timeout", "timed out", "deadline", "time limit")
|
| 398 |
-
)
|
| 399 |
-
is_abort = any(word in error_lower for word in ("aborted", "killed", "worker lost"))
|
| 400 |
-
if is_timeout:
|
| 401 |
-
status_text = (
|
| 402 |
-
"ZeroGPU reported that the request hit its runtime limit (maximum 360 seconds). "
|
| 403 |
-
"Check Container logs for the last completed stage."
|
| 404 |
-
)
|
| 405 |
-
elif is_abort:
|
| 406 |
-
status_text = (
|
| 407 |
-
"ZeroGPU aborted or lost the request before it returned; that alone does not prove "
|
| 408 |
-
"a timeout. Check Container logs for the last completed stage."
|
| 409 |
-
)
|
| 410 |
-
else:
|
| 411 |
-
status_text = f"ZeroGPU request failed: {type(exc).__name__}: {error_text}"
|
| 412 |
-
yield None, None, None, status_text
|
| 413 |
-
return
|
| 414 |
-
yield result
|
| 415 |
-
|
| 416 |
-
|
| 417 |
def _readiness() -> str:
|
| 418 |
if SETUP_ERROR:
|
| 419 |
return f"Setup: {SETUP_ERROR}"
|
|
|
|
| 233 |
_load_models_at_startup()
|
| 234 |
|
| 235 |
|
| 236 |
+
def _segment(
|
| 237 |
+
image: Image.Image, prompt: str, request_id: str
|
| 238 |
+
) -> tuple[np.ndarray | None, int, float | None]:
|
| 239 |
import torch
|
| 240 |
|
| 241 |
model, processor = _load_sam3()
|
| 242 |
+
_request_log(
|
| 243 |
+
request_id,
|
| 244 |
+
"SAM 3 inference started",
|
| 245 |
+
f"prompt={prompt!r}; image={image.width}x{image.height}",
|
| 246 |
+
)
|
| 247 |
inputs = processor(images=image, text=prompt, return_tensors="pt").to(model.device)
|
| 248 |
with torch.inference_mode():
|
| 249 |
output = model(**inputs)
|
|
|
|
| 255 |
)[0]
|
| 256 |
masks = result["masks"]
|
| 257 |
if len(masks) == 0:
|
| 258 |
+
_request_log(request_id, "SAM 3 inference completed", "instances=0")
|
| 259 |
+
return None, 0, None
|
| 260 |
scores = torch.as_tensor(result["scores"])
|
| 261 |
best = int(scores.argmax().item())
|
| 262 |
+
best_score = float(scores[best].item())
|
| 263 |
+
_request_log(
|
| 264 |
+
request_id,
|
| 265 |
+
"SAM 3 inference completed",
|
| 266 |
+
f"instances={len(masks)}; selected_mask={best}; score={best_score:.4f}",
|
| 267 |
+
)
|
| 268 |
+
return masks[best].detach().cpu().numpy().astype(bool), len(masks), best_score
|
| 269 |
|
| 270 |
|
| 271 |
def _overlay(image: np.ndarray, mask: np.ndarray) -> np.ndarray:
|
|
|
|
| 296 |
|
| 297 |
|
| 298 |
@spaces.GPU(duration=360)
|
| 299 |
+
def reconstruct(
|
| 300 |
+
image: Image.Image | None, prompt: str
|
| 301 |
) -> tuple[Any, Any, Any, str]:
|
| 302 |
+
"""Gradio's direct ZeroGPU event handler; keep it directly decorated."""
|
| 303 |
+
if image is None:
|
| 304 |
+
return None, None, None, "Upload an image first."
|
| 305 |
+
prompt = (prompt or "").strip()
|
| 306 |
+
if not prompt:
|
| 307 |
+
return None, None, None, "Enter a short object description, such as 'chair'."
|
| 308 |
+
if SETUP_ERROR:
|
| 309 |
+
return None, None, None, f"Space setup is incomplete: {SETUP_ERROR}"
|
| 310 |
+
if MODEL_SETUP_ERROR:
|
| 311 |
+
return None, None, None, f"Model setup failed: {MODEL_SETUP_ERROR}"
|
| 312 |
+
if SAM3 is None or SAM3D is None:
|
| 313 |
+
return None, None, None, "Models are not ready; check the Space startup logs."
|
| 314 |
+
|
| 315 |
+
request_id = uuid.uuid4().hex[:8]
|
| 316 |
+
_request_log(
|
| 317 |
+
request_id,
|
| 318 |
+
"GPU handler entered",
|
| 319 |
+
"maximum_gpu_seconds=360",
|
| 320 |
+
)
|
| 321 |
started = time.monotonic()
|
| 322 |
rgb = image.convert("RGB")
|
| 323 |
image_array = np.asarray(rgb, dtype=np.uint8)
|
|
|
|
| 330 |
try:
|
| 331 |
_request_log(request_id, "stage started", stage)
|
| 332 |
stage_started = time.monotonic()
|
| 333 |
+
mask, count, score = _segment(rgb, prompt, request_id)
|
| 334 |
_request_log(
|
| 335 |
request_id,
|
| 336 |
+
"SAM 3 stage completed",
|
| 337 |
+
f"matching_instances={count}; selected_score={score}; stage_seconds={time.monotonic() - stage_started:.1f}; total_seconds={time.monotonic() - started:.1f}",
|
| 338 |
)
|
| 339 |
if mask is None:
|
| 340 |
message = f"SAM 3 did not find '{prompt}' in this image."
|
|
|
|
| 389 |
)
|
| 390 |
|
| 391 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 392 |
def _readiness() -> str:
|
| 393 |
if SETUP_ERROR:
|
| 394 |
return f"Setup: {SETUP_ERROR}"
|