Spaces:
Running on Zero
Running on Zero
Commit ·
639aead
1
Parent(s): 130538f
Reduce ZeroGPU usage by moving CPU work outside GPU scope
Browse files- Split infer() into a CPU-only wrapper and @spaces.GPU(duration=120) _infer_gpu()
so image decoding, validation, seed resolution, and dimension computation no
longer consume GPU quota
- Move gc.collect() before the GPU lease is acquired
- Remove torch.cuda.synchronize() from the per-step callback (was blocking once
per step just for wall-clock logging)
- Remove per-step GPU memory queries from the step callback
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
app.py
CHANGED
|
@@ -332,14 +332,24 @@ with open("templates/app.html") as _f:
|
|
| 332 |
|
| 333 |
# ── Gradio blocks ──────────────────────────────────────────────────────────────
|
| 334 |
|
| 335 |
-
@spaces.GPU
|
| 336 |
def infer(images_b64_json, prompt, seed, randomize_seed, guidance_scale, steps, mode="fast", progress=gr.Progress(track_tqdm=True)):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 337 |
_cuda_ok = torch.cuda.is_available()
|
| 338 |
timer = _InferTimer(_cuda_ok)
|
| 339 |
t0 = time.perf_counter()
|
| 340 |
|
| 341 |
print(f"[infer] ===== START =====")
|
| 342 |
-
print(f"[infer] steps={steps}, guidance={guidance_scale}, seed={seed}
|
| 343 |
print(f"[infer] prompt={repr(prompt[:120])}")
|
| 344 |
|
| 345 |
if _cuda_ok:
|
|
@@ -349,36 +359,23 @@ def infer(images_b64_json, prompt, seed, randomize_seed, guidance_scale, steps,
|
|
| 349 |
|
| 350 |
print(f"[infer] {_gpu_mem_str(_cuda_ok)} — t={time.perf_counter()-t0:.1f}s")
|
| 351 |
|
| 352 |
-
gc.collect()
|
| 353 |
torch.cuda.empty_cache()
|
| 354 |
print(f"[infer] cache cleared — {_gpu_mem_str(_cuda_ok)}")
|
| 355 |
|
| 356 |
-
|
| 357 |
-
pil_images = b64_to_pil_list(images_b64_json)
|
| 358 |
-
timer.mark("load_end")
|
| 359 |
-
print(f"[infer] decoded {len(pil_images)} image(s) — {timer.elapsed_ms('load_start', 'load_end'):.0f}ms")
|
| 360 |
-
|
| 361 |
-
_validate_infer_inputs(pil_images, prompt)
|
| 362 |
|
| 363 |
-
seed = _resolve_seed(seed, randomize_seed)
|
| 364 |
generator = torch.Generator(device=device).manual_seed(seed)
|
| 365 |
-
width, height = update_dimensions_on_upload(pil_images[0], max_dim_for_mode(mode))
|
| 366 |
-
print(f"[infer] input={pil_images[0].size}, output={width}x{height}, seed={seed}")
|
| 367 |
|
| 368 |
-
# Per-step callback: syncs the GPU then records a CUDA event so elapsed_time()
|
| 369 |
-
# gives true GPU-timeline durations for preprocess / inference / vae_decode.
|
| 370 |
-
# Step 1 time includes any torch.compile Triton kernel compilation.
|
| 371 |
_step_t = []
|
| 372 |
def _step_cb(pipeline, step_idx, timestep, cb_kwargs):
|
| 373 |
-
torch.cuda.synchronize()
|
| 374 |
now = time.perf_counter()
|
| 375 |
_step_t.append(now)
|
| 376 |
if step_idx == 0:
|
| 377 |
timer.mark("first_step")
|
| 378 |
-
timer.mark("last_step") # overwritten each
|
| 379 |
delta_ms = (now - (_step_t[-2] if len(_step_t) > 1 else t0)) * 1000
|
| 380 |
tag = " ← includes compile" if step_idx == 0 else ""
|
| 381 |
-
print(f"[infer] step {step_idx+1}/{steps} done — {delta_ms:.0f}ms{tag} |
|
| 382 |
return cb_kwargs
|
| 383 |
|
| 384 |
timer.mark("pipe_start")
|
|
|
|
| 332 |
|
| 333 |
# ── Gradio blocks ──────────────────────────────────────────────────────────────
|
| 334 |
|
|
|
|
| 335 |
def infer(images_b64_json, prompt, seed, randomize_seed, guidance_scale, steps, mode="fast", progress=gr.Progress(track_tqdm=True)):
|
| 336 |
+
# CPU-only preprocessing — GPU not yet allocated
|
| 337 |
+
gc.collect()
|
| 338 |
+
pil_images = b64_to_pil_list(images_b64_json)
|
| 339 |
+
_validate_infer_inputs(pil_images, prompt)
|
| 340 |
+
seed = _resolve_seed(seed, randomize_seed)
|
| 341 |
+
width, height = update_dimensions_on_upload(pil_images[0], max_dim_for_mode(mode))
|
| 342 |
+
return _infer_gpu(pil_images, prompt, seed, guidance_scale, steps, width, height)
|
| 343 |
+
|
| 344 |
+
|
| 345 |
+
@spaces.GPU(duration=120)
|
| 346 |
+
def _infer_gpu(pil_images, prompt, seed, guidance_scale, steps, width, height):
|
| 347 |
_cuda_ok = torch.cuda.is_available()
|
| 348 |
timer = _InferTimer(_cuda_ok)
|
| 349 |
t0 = time.perf_counter()
|
| 350 |
|
| 351 |
print(f"[infer] ===== START =====")
|
| 352 |
+
print(f"[infer] steps={steps}, guidance={guidance_scale}, seed={seed}")
|
| 353 |
print(f"[infer] prompt={repr(prompt[:120])}")
|
| 354 |
|
| 355 |
if _cuda_ok:
|
|
|
|
| 359 |
|
| 360 |
print(f"[infer] {_gpu_mem_str(_cuda_ok)} — t={time.perf_counter()-t0:.1f}s")
|
| 361 |
|
|
|
|
| 362 |
torch.cuda.empty_cache()
|
| 363 |
print(f"[infer] cache cleared — {_gpu_mem_str(_cuda_ok)}")
|
| 364 |
|
| 365 |
+
print(f"[infer] {len(pil_images)} image(s) pre-decoded, output={width}x{height}, seed={seed}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 366 |
|
|
|
|
| 367 |
generator = torch.Generator(device=device).manual_seed(seed)
|
|
|
|
|
|
|
| 368 |
|
|
|
|
|
|
|
|
|
|
| 369 |
_step_t = []
|
| 370 |
def _step_cb(pipeline, step_idx, timestep, cb_kwargs):
|
|
|
|
| 371 |
now = time.perf_counter()
|
| 372 |
_step_t.append(now)
|
| 373 |
if step_idx == 0:
|
| 374 |
timer.mark("first_step")
|
| 375 |
+
timer.mark("last_step") # overwritten each step; final value = end of last step
|
| 376 |
delta_ms = (now - (_step_t[-2] if len(_step_t) > 1 else t0)) * 1000
|
| 377 |
tag = " ← includes compile" if step_idx == 0 else ""
|
| 378 |
+
print(f"[infer] step {step_idx+1}/{steps} done — {delta_ms:.0f}ms{tag} | t={now-t0:.1f}s")
|
| 379 |
return cb_kwargs
|
| 380 |
|
| 381 |
timer.mark("pipe_start")
|