Spaces:
Running on Zero
Running on Zero
Commit Β·
961feed
1
Parent(s): 2837b03
Add torch.cuda.Event section timing to infer()
Browse filesReplaces time.time() with perf_counter() and torch.cuda.Event-based
timing so GPU-timeline durations (preprocess, inference, vae_decode)
are measured accurately via elapsed_time() rather than wall-clock.
Prints a per-section table after each run.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
app.py
CHANGED
|
@@ -161,29 +161,73 @@ def update_dimensions_on_upload(image):
|
|
| 161 |
@spaces.GPU
|
| 162 |
def infer(images_b64_json, prompt, seed, randomize_seed, guidance_scale, steps, progress=gr.Progress(track_tqdm=True)):
|
| 163 |
import time, traceback
|
| 164 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 165 |
|
| 166 |
def _t():
|
| 167 |
-
return f"t={time.
|
| 168 |
|
| 169 |
def _mem(sync=False):
|
| 170 |
-
if not
|
| 171 |
return "CUDA not available"
|
| 172 |
if sync:
|
| 173 |
try:
|
| 174 |
torch.cuda.synchronize()
|
| 175 |
except Exception as se:
|
| 176 |
return f"CUDA sync failed: {se}"
|
| 177 |
-
alloc
|
| 178 |
-
reserved= torch.cuda.memory_reserved()
|
| 179 |
-
peak
|
| 180 |
return f"alloc={alloc:.2f}GB reserved={reserved:.2f}GB peak={peak:.2f}GB"
|
| 181 |
|
| 182 |
print(f"[infer] ===== START =====")
|
| 183 |
print(f"[infer] steps={steps}, guidance={guidance_scale}, seed={seed}, randomize={randomize_seed}")
|
| 184 |
print(f"[infer] prompt={repr(prompt[:120])}")
|
| 185 |
|
| 186 |
-
if
|
| 187 |
p = torch.cuda.get_device_properties(0)
|
| 188 |
print(f"[infer] GPU: {p.name}, total={p.total_memory/1024**3:.1f}GB, cap={p.major}.{p.minor}")
|
| 189 |
torch.cuda.reset_peak_memory_stats()
|
|
@@ -194,8 +238,10 @@ def infer(images_b64_json, prompt, seed, randomize_seed, guidance_scale, steps,
|
|
| 194 |
torch.cuda.empty_cache()
|
| 195 |
print(f"[infer] cache cleared β {_mem()}")
|
| 196 |
|
|
|
|
| 197 |
pil_images = b64_to_pil_list(images_b64_json)
|
| 198 |
-
|
|
|
|
| 199 |
if not pil_images:
|
| 200 |
raise gr.Error("Please upload at least one image to edit.")
|
| 201 |
if not prompt or prompt.strip() == "":
|
|
@@ -207,20 +253,23 @@ def infer(images_b64_json, prompt, seed, randomize_seed, guidance_scale, steps,
|
|
| 207 |
width, height = update_dimensions_on_upload(pil_images[0])
|
| 208 |
print(f"[infer] input={pil_images[0].size}, output={width}x{height}, seed={seed}")
|
| 209 |
|
| 210 |
-
# Per-step callback:
|
| 211 |
-
#
|
| 212 |
-
#
|
| 213 |
_step_t = []
|
| 214 |
def _step_cb(pipeline, step_idx, timestep, cb_kwargs):
|
| 215 |
torch.cuda.synchronize()
|
| 216 |
-
now = time.
|
| 217 |
_step_t.append(now)
|
| 218 |
-
|
|
|
|
|
|
|
|
|
|
| 219 |
tag = " β includes compile" if step_idx == 0 else ""
|
| 220 |
-
print(f"[infer] step {step_idx+1}/{steps} done β {
|
| 221 |
return cb_kwargs
|
| 222 |
|
| 223 |
-
|
| 224 |
print(f"[infer] calling pipe... {_t()}")
|
| 225 |
try:
|
| 226 |
result_image = pipe(
|
|
@@ -230,8 +279,10 @@ def infer(images_b64_json, prompt, seed, randomize_seed, guidance_scale, steps,
|
|
| 230 |
callback_on_step_end=_step_cb,
|
| 231 |
callback_on_step_end_tensor_inputs=["latents"],
|
| 232 |
).images[0]
|
|
|
|
| 233 |
print(f"[infer] VAE decode + postprocess done β {_mem(sync=True)} | {_t()}")
|
| 234 |
-
|
|
|
|
| 235 |
threading.Thread(
|
| 236 |
target=log_inference,
|
| 237 |
args=(pil_images, result_image, prompt, seed, steps, guidance_scale,
|
|
@@ -246,7 +297,8 @@ def infer(images_b64_json, prompt, seed, randomize_seed, guidance_scale, steps,
|
|
| 246 |
torch.cuda.synchronize()
|
| 247 |
except Exception as cuda_err:
|
| 248 |
print(f"[infer] CUDA synchronize after error: {cuda_err}")
|
| 249 |
-
|
|
|
|
| 250 |
threading.Thread(
|
| 251 |
target=log_inference,
|
| 252 |
args=(pil_images, None, prompt, seed, steps, guidance_scale,
|
|
|
|
| 161 |
@spaces.GPU
|
| 162 |
def infer(images_b64_json, prompt, seed, randomize_seed, guidance_scale, steps, progress=gr.Progress(track_tqdm=True)):
|
| 163 |
import time, traceback
|
| 164 |
+
_cuda_ok = torch.cuda.is_available()
|
| 165 |
+
_marks: dict = {} # name -> (cuda_event | None, perf_counter_float)
|
| 166 |
+
|
| 167 |
+
def _mark(name: str) -> None:
|
| 168 |
+
ev = None
|
| 169 |
+
if _cuda_ok:
|
| 170 |
+
ev = torch.cuda.Event(enable_timing=True)
|
| 171 |
+
ev.record()
|
| 172 |
+
_marks[name] = (ev, time.perf_counter())
|
| 173 |
+
|
| 174 |
+
def _elapsed_ms(a: str, b: str) -> float:
|
| 175 |
+
ev_a, t_a = _marks[a]
|
| 176 |
+
ev_b, t_b = _marks[b]
|
| 177 |
+
if ev_a and ev_b:
|
| 178 |
+
return ev_a.elapsed_time(ev_b) # true GPU-timeline ms
|
| 179 |
+
return (t_b - t_a) * 1000.0
|
| 180 |
+
|
| 181 |
+
def _print_timings() -> None:
|
| 182 |
+
if _cuda_ok:
|
| 183 |
+
try:
|
| 184 |
+
torch.cuda.synchronize()
|
| 185 |
+
except Exception:
|
| 186 |
+
pass
|
| 187 |
+
rows = [
|
| 188 |
+
("image_load", "load_start", "load_end"),
|
| 189 |
+
("preprocess", "pipe_start", "first_step"),
|
| 190 |
+
("inference", "first_step", "last_step"),
|
| 191 |
+
("vae_decode", "last_step", "pipe_end"),
|
| 192 |
+
]
|
| 193 |
+
total_ms = 0.0
|
| 194 |
+
lines = []
|
| 195 |
+
for label, a, b in rows:
|
| 196 |
+
if a in _marks and b in _marks:
|
| 197 |
+
ms = _elapsed_ms(a, b)
|
| 198 |
+
total_ms += ms
|
| 199 |
+
lines.append(f"[timing] {label:<14} {ms:8.1f} ms")
|
| 200 |
+
if "load_start" in _marks and "pipe_end" in _marks:
|
| 201 |
+
overall_ms = _elapsed_ms("load_start", "pipe_end")
|
| 202 |
+
lines.append(f"[timing] {'overhead':<14} {overall_ms - total_ms:8.1f} ms")
|
| 203 |
+
lines.append(f"[timing] {'ββ total ββ':<14} {overall_ms:8.1f} ms")
|
| 204 |
+
print("[timing] βββββββββββββββββββββββββββββββββββββ")
|
| 205 |
+
print("\n".join(lines))
|
| 206 |
+
print("[timing] βββββββββββββββββββββββββββββββββββββ")
|
| 207 |
+
|
| 208 |
+
t0 = time.perf_counter()
|
| 209 |
|
| 210 |
def _t():
|
| 211 |
+
return f"t={time.perf_counter()-t0:.1f}s"
|
| 212 |
|
| 213 |
def _mem(sync=False):
|
| 214 |
+
if not _cuda_ok:
|
| 215 |
return "CUDA not available"
|
| 216 |
if sync:
|
| 217 |
try:
|
| 218 |
torch.cuda.synchronize()
|
| 219 |
except Exception as se:
|
| 220 |
return f"CUDA sync failed: {se}"
|
| 221 |
+
alloc = torch.cuda.memory_allocated() / 1024**3
|
| 222 |
+
reserved = torch.cuda.memory_reserved() / 1024**3
|
| 223 |
+
peak = torch.cuda.max_memory_allocated()/ 1024**3
|
| 224 |
return f"alloc={alloc:.2f}GB reserved={reserved:.2f}GB peak={peak:.2f}GB"
|
| 225 |
|
| 226 |
print(f"[infer] ===== START =====")
|
| 227 |
print(f"[infer] steps={steps}, guidance={guidance_scale}, seed={seed}, randomize={randomize_seed}")
|
| 228 |
print(f"[infer] prompt={repr(prompt[:120])}")
|
| 229 |
|
| 230 |
+
if _cuda_ok:
|
| 231 |
p = torch.cuda.get_device_properties(0)
|
| 232 |
print(f"[infer] GPU: {p.name}, total={p.total_memory/1024**3:.1f}GB, cap={p.major}.{p.minor}")
|
| 233 |
torch.cuda.reset_peak_memory_stats()
|
|
|
|
| 238 |
torch.cuda.empty_cache()
|
| 239 |
print(f"[infer] cache cleared β {_mem()}")
|
| 240 |
|
| 241 |
+
_mark("load_start")
|
| 242 |
pil_images = b64_to_pil_list(images_b64_json)
|
| 243 |
+
_mark("load_end")
|
| 244 |
+
print(f"[infer] decoded {len(pil_images)} image(s) β {_elapsed_ms('load_start', 'load_end'):.0f}ms")
|
| 245 |
if not pil_images:
|
| 246 |
raise gr.Error("Please upload at least one image to edit.")
|
| 247 |
if not prompt or prompt.strip() == "":
|
|
|
|
| 253 |
width, height = update_dimensions_on_upload(pil_images[0])
|
| 254 |
print(f"[infer] input={pil_images[0].size}, output={width}x{height}, seed={seed}")
|
| 255 |
|
| 256 |
+
# Per-step callback: syncs the GPU then records a CUDA event so elapsed_time()
|
| 257 |
+
# gives true GPU-timeline durations for preprocess / inference / vae_decode.
|
| 258 |
+
# Step 1 time includes any torch.compile Triton kernel compilation.
|
| 259 |
_step_t = []
|
| 260 |
def _step_cb(pipeline, step_idx, timestep, cb_kwargs):
|
| 261 |
torch.cuda.synchronize()
|
| 262 |
+
now = time.perf_counter()
|
| 263 |
_step_t.append(now)
|
| 264 |
+
if step_idx == 0:
|
| 265 |
+
_mark("first_step")
|
| 266 |
+
_mark("last_step") # overwritten each iteration; final value = end of last step
|
| 267 |
+
delta_ms = (now - (_step_t[-2] if len(_step_t) > 1 else t0)) * 1000
|
| 268 |
tag = " β includes compile" if step_idx == 0 else ""
|
| 269 |
+
print(f"[infer] step {step_idx+1}/{steps} done β {delta_ms:.0f}ms{tag} | {_mem()} | {_t()}")
|
| 270 |
return cb_kwargs
|
| 271 |
|
| 272 |
+
_mark("pipe_start")
|
| 273 |
print(f"[infer] calling pipe... {_t()}")
|
| 274 |
try:
|
| 275 |
result_image = pipe(
|
|
|
|
| 279 |
callback_on_step_end=_step_cb,
|
| 280 |
callback_on_step_end_tensor_inputs=["latents"],
|
| 281 |
).images[0]
|
| 282 |
+
_mark("pipe_end")
|
| 283 |
print(f"[infer] VAE decode + postprocess done β {_mem(sync=True)} | {_t()}")
|
| 284 |
+
_print_timings()
|
| 285 |
+
duration = _elapsed_ms("pipe_start", "pipe_end") / 1000.0
|
| 286 |
threading.Thread(
|
| 287 |
target=log_inference,
|
| 288 |
args=(pil_images, result_image, prompt, seed, steps, guidance_scale,
|
|
|
|
| 297 |
torch.cuda.synchronize()
|
| 298 |
except Exception as cuda_err:
|
| 299 |
print(f"[infer] CUDA synchronize after error: {cuda_err}")
|
| 300 |
+
_print_timings()
|
| 301 |
+
duration = time.perf_counter() - _marks["pipe_start"][1]
|
| 302 |
threading.Thread(
|
| 303 |
target=log_inference,
|
| 304 |
args=(pil_images, None, prompt, seed, steps, guidance_scale,
|