someone-in-the-world Claude Sonnet 4.6 commited on
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>

Files changed (1) hide show
  1. app.py +15 -18
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}, randomize={randomize_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
- timer.mark("load_start")
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 iteration; final value = end of last step
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} | {_gpu_mem_str(_cuda_ok)} | t={time.perf_counter()-t0:.1f}s")
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")