someone-in-the-world Claude Sonnet 4.6 commited on
Commit
a532114
Β·
1 Parent(s): 8152bef

Refactor app.py: extract helpers, consolidate imports

Browse files

- _InferTimer class, _gpu_mem_str, _validate_infer_inputs, _resolve_seed,
_spawn_log all extracted from infer()
- _example_thumbs_html, _example_card_html extracted from build_example_cards_html()
- _parse_example_idx extracted from load_example_data()
- Consolidate scattered imports (time, traceback) to module top
- Move negative_prompt load above infer() where it is used

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Files changed (1) hide show
  1. app.py +148 -121
app.py CHANGED
@@ -1,6 +1,8 @@
1
  import os
2
  import gc
 
3
  import threading
 
4
 
5
  import gradio as gr
6
  import numpy as np
@@ -44,24 +46,23 @@ print("[startup] all imports done", flush=True)
44
 
45
  dtype = torch.bfloat16
46
 
47
- import time as _time
48
- _t0_load = _time.perf_counter()
49
  print("[startup] loading transformer from_pretrained (prithivMLmods/Qwen-Image-Edit-Rapid-AIO-V23)...", flush=True)
50
  _transformer = QwenImageTransformer2DModel.from_pretrained(
51
  "prithivMLmods/Qwen-Image-Edit-Rapid-AIO-V23",
52
  torch_dtype=dtype,
53
  device_map="cuda",
54
  )
55
- print(f"[startup] transformer loaded in {_time.perf_counter()-_t0_load:.1f}s", flush=True)
56
 
57
- _t1_load = _time.perf_counter()
58
  print("[startup] loading pipeline from_pretrained (FireRedTeam/FireRed-Image-Edit-1.1)...", flush=True)
59
  pipe = QwenImageEditPlusPipeline.from_pretrained(
60
  "FireRedTeam/FireRed-Image-Edit-1.1",
61
  transformer=_transformer,
62
  torch_dtype=dtype,
63
  ).to(device)
64
- print(f"[startup] pipeline loaded in {_time.perf_counter()-_t1_load:.1f}s", flush=True)
65
 
66
  print("Using default attention processor (FA3 skipped for ZeroGPU GPU-arch compatibility).", flush=True)
67
 
@@ -99,34 +100,44 @@ def encode_full_image(path):
99
  return ""
100
 
101
 
102
- def build_example_cards_html():
103
- cards = ""
104
- for i, ex in enumerate(EXAMPLES_CONFIG):
105
- thumbs_html = ""
106
- for path in ex["images"]:
107
- thumb = make_thumb_b64(path)
108
- if thumb:
109
- thumbs_html += f'<img src="{thumb}" alt="">'
110
- else:
111
- thumbs_html += '<div class="example-thumb-placeholder">Preview</div>'
112
- n = len(ex["images"])
113
- badge = f'{n} image{"s" if n > 1 else ""}'
114
- prompt_short = html_lib.escape(ex["prompt"][:90])
115
- if len(ex["prompt"]) > 90:
116
- prompt_short += "..."
117
- cards += f'''<div class="example-card" data-idx="{i}">
 
 
 
118
  <div class="example-thumbs">{thumbs_html}</div>
119
  <div class="example-meta"><span class="example-badge">{badge}</span></div>
120
  <div class="example-prompt-text">{prompt_short}</div>
121
  </div>'''
122
- return cards
123
 
124
 
125
- def load_example_data(idx_str):
 
 
 
 
126
  try:
127
- idx = int(float(idx_str)) if idx_str and idx_str.strip() else -1
128
  except (ValueError, TypeError):
129
- idx = -1
 
 
 
 
130
  if idx < 0 or idx >= len(EXAMPLES_CONFIG):
131
  return json.dumps({"images": [], "prompt": "", "names": [], "status": "error"})
132
  ex = EXAMPLES_CONFIG[idx]
@@ -174,70 +185,123 @@ def update_dimensions_on_upload(image):
174
  return compute_output_dimensions(w, h)
175
 
176
 
177
- @spaces.GPU
178
- def infer(images_b64_json, prompt, seed, randomize_seed, guidance_scale, steps, progress=gr.Progress(track_tqdm=True)):
179
- import time, traceback
180
- _cuda_ok = torch.cuda.is_available()
181
- _marks: dict = {} # name -> (cuda_event | None, perf_counter_float)
182
 
183
- def _mark(name: str) -> None:
184
  ev = None
185
- if _cuda_ok:
186
  ev = torch.cuda.Event(enable_timing=True)
187
  ev.record()
188
- _marks[name] = (ev, time.perf_counter())
189
 
190
- def _elapsed_ms(a: str, b: str) -> float:
191
- ev_a, t_a = _marks[a]
192
- ev_b, t_b = _marks[b]
193
  if ev_a and ev_b:
194
  return ev_a.elapsed_time(ev_b) # true GPU-timeline ms
195
  return (t_b - t_a) * 1000.0
196
 
197
- def _print_timings() -> None:
198
- if _cuda_ok:
 
 
 
 
 
 
199
  try:
200
  torch.cuda.synchronize()
201
  except Exception:
202
  pass
203
  rows = [
204
- ("image_load", "load_start", "load_end"),
205
- ("preprocess", "pipe_start", "first_step"),
206
- ("inference", "first_step", "last_step"),
207
- ("vae_decode", "last_step", "pipe_end"),
208
  ]
209
  total_ms = 0.0
210
  lines = []
211
  for label, a, b in rows:
212
- if a in _marks and b in _marks:
213
- ms = _elapsed_ms(a, b)
214
  total_ms += ms
215
  lines.append(f"[timing] {label:<14} {ms:8.1f} ms")
216
- if "load_start" in _marks and "pipe_end" in _marks:
217
- overall_ms = _elapsed_ms("load_start", "pipe_end")
218
  lines.append(f"[timing] {'overhead':<14} {overall_ms - total_ms:8.1f} ms")
219
  lines.append(f"[timing] {'── total ──':<14} {overall_ms:8.1f} ms")
220
  print("[timing] ─────────────────────────────────────")
221
  print("\n".join(lines))
222
  print("[timing] ─────────────────────────────────────")
223
 
224
- t0 = time.perf_counter()
225
 
226
- def _t():
227
- return f"t={time.perf_counter()-t0:.1f}s"
 
 
 
 
 
 
 
 
 
 
228
 
229
- def _mem(sync=False):
230
- if not _cuda_ok:
231
- return "CUDA not available"
232
- if sync:
233
- try:
234
- torch.cuda.synchronize()
235
- except Exception as se:
236
- return f"CUDA sync failed: {se}"
237
- alloc = torch.cuda.memory_allocated() / 1024**3
238
- reserved = torch.cuda.memory_reserved() / 1024**3
239
- peak = torch.cuda.max_memory_allocated()/ 1024**3
240
- return f"alloc={alloc:.2f}GB reserved={reserved:.2f}GB peak={peak:.2f}GB"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
241
 
242
  print(f"[infer] ===== START =====")
243
  print(f"[infer] steps={steps}, guidance={guidance_scale}, seed={seed}, randomize={randomize_seed}")
@@ -248,23 +312,20 @@ def infer(images_b64_json, prompt, seed, randomize_seed, guidance_scale, steps,
248
  print(f"[infer] GPU: {p.name}, total={p.total_memory/1024**3:.1f}GB, cap={p.major}.{p.minor}")
249
  torch.cuda.reset_peak_memory_stats()
250
 
251
- print(f"[infer] {_mem()} β€” {_t()}")
252
 
253
  gc.collect()
254
  torch.cuda.empty_cache()
255
- print(f"[infer] cache cleared β€” {_mem()}")
256
 
257
- _mark("load_start")
258
  pil_images = b64_to_pil_list(images_b64_json)
259
- _mark("load_end")
260
- print(f"[infer] decoded {len(pil_images)} image(s) β€” {_elapsed_ms('load_start', 'load_end'):.0f}ms")
261
- if not pil_images:
262
- raise gr.Error("Please upload at least one image to edit.")
263
- if not prompt or prompt.strip() == "":
264
- raise gr.Error("Please enter an edit prompt.")
265
 
266
- if randomize_seed:
267
- seed = random.randint(0, MAX_SEED)
268
  generator = torch.Generator(device=device).manual_seed(seed)
269
  width, height = update_dimensions_on_upload(pil_images[0])
270
  print(f"[infer] input={pil_images[0].size}, output={width}x{height}, seed={seed}")
@@ -278,15 +339,15 @@ def infer(images_b64_json, prompt, seed, randomize_seed, guidance_scale, steps,
278
  now = time.perf_counter()
279
  _step_t.append(now)
280
  if step_idx == 0:
281
- _mark("first_step")
282
- _mark("last_step") # overwritten each iteration; final value = end of last step
283
  delta_ms = (now - (_step_t[-2] if len(_step_t) > 1 else t0)) * 1000
284
  tag = " ← includes compile" if step_idx == 0 else ""
285
- print(f"[infer] step {step_idx+1}/{steps} done β€” {delta_ms:.0f}ms{tag} | {_mem()} | {_t()}")
286
  return cb_kwargs
287
 
288
- _mark("pipe_start")
289
- print(f"[infer] calling pipe... {_t()}")
290
  try:
291
  result_image = pipe(
292
  image=pil_images, prompt=prompt, negative_prompt=negative_prompt,
@@ -295,62 +356,28 @@ def infer(images_b64_json, prompt, seed, randomize_seed, guidance_scale, steps,
295
  callback_on_step_end=_step_cb,
296
  callback_on_step_end_tensor_inputs=["latents"],
297
  ).images[0]
298
- _mark("pipe_end")
299
- print(f"[infer] VAE decode + postprocess done β€” {_mem(sync=True)} | {_t()}")
300
- _print_timings()
301
- duration = _elapsed_ms("pipe_start", "pipe_end") / 1000.0
302
- threading.Thread(
303
- target=log_inference,
304
- args=(pil_images, result_image, prompt, seed, steps, guidance_scale,
305
- width, height, duration, True, ""),
306
- daemon=True,
307
- ).start()
308
  return result_image, seed
309
  except Exception as e:
310
- print(f"[infer] ERROR: {type(e).__name__}: {e} | {_t()}")
311
  print(traceback.format_exc())
312
  try:
313
  torch.cuda.synchronize()
314
  except Exception as cuda_err:
315
  print(f"[infer] CUDA synchronize after error: {cuda_err}")
316
- _print_timings()
317
- duration = time.perf_counter() - _marks["pipe_start"][1]
318
- threading.Thread(
319
- target=log_inference,
320
- args=(pil_images, None, prompt, seed, steps, guidance_scale,
321
- width, height, duration, False, str(e)),
322
- daemon=True,
323
- ).start()
324
  raise e
325
  finally:
326
  gc.collect()
327
  torch.cuda.empty_cache()
328
- print(f"[infer] ===== END {_t()} =====")
329
-
330
-
331
- # ── static assets ─────────────────────────────────────────��───────────────────
332
 
333
- with open("static/app.css") as _f:
334
- css = _f.read()
335
-
336
- with open("static/gallery.js") as _f:
337
- gallery_js = _f.read()
338
-
339
- with open("static/wire_outputs.js") as _f:
340
- wire_outputs_js = _f.read()
341
-
342
- with open("static/run_preprocess.js") as _f:
343
- run_preprocess_js = _f.read()
344
-
345
- with open("static/negative_prompt.txt") as _f:
346
- negative_prompt = _f.read().strip()
347
-
348
- # ── HTML template ──────────────────────────────────────────────────────────────
349
-
350
- with open("templates/app.html") as _f:
351
- app_html = _f.read().format(example_cards_html=EXAMPLE_CARDS_HTML)
352
-
353
- # ── Gradio blocks ──────────────────────────────────────────────────────────────
354
 
355
  with gr.Blocks() as demo:
356
 
 
1
  import os
2
  import gc
3
+ import time
4
  import threading
5
+ import traceback
6
 
7
  import gradio as gr
8
  import numpy as np
 
46
 
47
  dtype = torch.bfloat16
48
 
49
+ _t0_load = time.perf_counter()
 
50
  print("[startup] loading transformer from_pretrained (prithivMLmods/Qwen-Image-Edit-Rapid-AIO-V23)...", flush=True)
51
  _transformer = QwenImageTransformer2DModel.from_pretrained(
52
  "prithivMLmods/Qwen-Image-Edit-Rapid-AIO-V23",
53
  torch_dtype=dtype,
54
  device_map="cuda",
55
  )
56
+ print(f"[startup] transformer loaded in {time.perf_counter()-_t0_load:.1f}s", flush=True)
57
 
58
+ _t1_load = time.perf_counter()
59
  print("[startup] loading pipeline from_pretrained (FireRedTeam/FireRed-Image-Edit-1.1)...", flush=True)
60
  pipe = QwenImageEditPlusPipeline.from_pretrained(
61
  "FireRedTeam/FireRed-Image-Edit-1.1",
62
  transformer=_transformer,
63
  torch_dtype=dtype,
64
  ).to(device)
65
+ print(f"[startup] pipeline loaded in {time.perf_counter()-_t1_load:.1f}s", flush=True)
66
 
67
  print("Using default attention processor (FA3 skipped for ZeroGPU GPU-arch compatibility).", flush=True)
68
 
 
100
  return ""
101
 
102
 
103
+ def _example_thumbs_html(images):
104
+ html = ""
105
+ for path in images:
106
+ thumb = make_thumb_b64(path)
107
+ if thumb:
108
+ html += f'<img src="{thumb}" alt="">'
109
+ else:
110
+ html += '<div class="example-thumb-placeholder">Preview</div>'
111
+ return html
112
+
113
+
114
+ def _example_card_html(idx, ex):
115
+ thumbs_html = _example_thumbs_html(ex["images"])
116
+ n = len(ex["images"])
117
+ badge = f'{n} image{"s" if n > 1 else ""}'
118
+ prompt_short = html_lib.escape(ex["prompt"][:90])
119
+ if len(ex["prompt"]) > 90:
120
+ prompt_short += "..."
121
+ return f'''<div class="example-card" data-idx="{idx}">
122
  <div class="example-thumbs">{thumbs_html}</div>
123
  <div class="example-meta"><span class="example-badge">{badge}</span></div>
124
  <div class="example-prompt-text">{prompt_short}</div>
125
  </div>'''
 
126
 
127
 
128
+ def build_example_cards_html():
129
+ return "".join(_example_card_html(i, ex) for i, ex in enumerate(EXAMPLES_CONFIG))
130
+
131
+
132
+ def _parse_example_idx(idx_str):
133
  try:
134
+ return int(float(idx_str)) if idx_str and idx_str.strip() else -1
135
  except (ValueError, TypeError):
136
+ return -1
137
+
138
+
139
+ def load_example_data(idx_str):
140
+ idx = _parse_example_idx(idx_str)
141
  if idx < 0 or idx >= len(EXAMPLES_CONFIG):
142
  return json.dumps({"images": [], "prompt": "", "names": [], "status": "error"})
143
  ex = EXAMPLES_CONFIG[idx]
 
185
  return compute_output_dimensions(w, h)
186
 
187
 
188
+ class _InferTimer:
189
+ def __init__(self, cuda_ok: bool) -> None:
190
+ self._cuda_ok = cuda_ok
191
+ self._marks: dict = {}
 
192
 
193
+ def mark(self, name: str) -> None:
194
  ev = None
195
+ if self._cuda_ok:
196
  ev = torch.cuda.Event(enable_timing=True)
197
  ev.record()
198
+ self._marks[name] = (ev, time.perf_counter())
199
 
200
+ def elapsed_ms(self, a: str, b: str) -> float:
201
+ ev_a, t_a = self._marks[a]
202
+ ev_b, t_b = self._marks[b]
203
  if ev_a and ev_b:
204
  return ev_a.elapsed_time(ev_b) # true GPU-timeline ms
205
  return (t_b - t_a) * 1000.0
206
 
207
+ def wall_start(self, name: str) -> float:
208
+ return self._marks[name][1]
209
+
210
+ def __contains__(self, name: str) -> bool:
211
+ return name in self._marks
212
+
213
+ def print_timings(self) -> None:
214
+ if self._cuda_ok:
215
  try:
216
  torch.cuda.synchronize()
217
  except Exception:
218
  pass
219
  rows = [
220
+ ("image_load", "load_start", "load_end"),
221
+ ("preprocess", "pipe_start", "first_step"),
222
+ ("inference", "first_step", "last_step"),
223
+ ("vae_decode", "last_step", "pipe_end"),
224
  ]
225
  total_ms = 0.0
226
  lines = []
227
  for label, a, b in rows:
228
+ if a in self._marks and b in self._marks:
229
+ ms = self.elapsed_ms(a, b)
230
  total_ms += ms
231
  lines.append(f"[timing] {label:<14} {ms:8.1f} ms")
232
+ if "load_start" in self._marks and "pipe_end" in self._marks:
233
+ overall_ms = self.elapsed_ms("load_start", "pipe_end")
234
  lines.append(f"[timing] {'overhead':<14} {overall_ms - total_ms:8.1f} ms")
235
  lines.append(f"[timing] {'── total ──':<14} {overall_ms:8.1f} ms")
236
  print("[timing] ─────────────────────────────────────")
237
  print("\n".join(lines))
238
  print("[timing] ─────────────────────────────────────")
239
 
 
240
 
241
+ def _gpu_mem_str(cuda_ok: bool, sync: bool = False) -> str:
242
+ if not cuda_ok:
243
+ return "CUDA not available"
244
+ if sync:
245
+ try:
246
+ torch.cuda.synchronize()
247
+ except Exception as se:
248
+ return f"CUDA sync failed: {se}"
249
+ alloc = torch.cuda.memory_allocated() / 1024**3
250
+ reserved = torch.cuda.memory_reserved() / 1024**3
251
+ peak = torch.cuda.max_memory_allocated() / 1024**3
252
+ return f"alloc={alloc:.2f}GB reserved={reserved:.2f}GB peak={peak:.2f}GB"
253
 
254
+
255
+ def _validate_infer_inputs(pil_images: list, prompt: str) -> None:
256
+ if not pil_images:
257
+ raise gr.Error("Please upload at least one image to edit.")
258
+ if not prompt or prompt.strip() == "":
259
+ raise gr.Error("Please enter an edit prompt.")
260
+
261
+
262
+ def _resolve_seed(seed: int, randomize_seed: bool) -> int:
263
+ return random.randint(0, MAX_SEED) if randomize_seed else seed
264
+
265
+
266
+ def _spawn_log(pil_images, result_image, prompt, seed, steps, guidance_scale,
267
+ width, height, duration, success, error=""):
268
+ threading.Thread(
269
+ target=log_inference,
270
+ args=(pil_images, result_image, prompt, seed, steps, guidance_scale,
271
+ width, height, duration, success, error),
272
+ daemon=True,
273
+ ).start()
274
+
275
+
276
+ # ── static assets ─────────────────────────────────────────────────────────────
277
+
278
+ with open("static/app.css") as _f:
279
+ css = _f.read()
280
+
281
+ with open("static/gallery.js") as _f:
282
+ gallery_js = _f.read()
283
+
284
+ with open("static/wire_outputs.js") as _f:
285
+ wire_outputs_js = _f.read()
286
+
287
+ with open("static/run_preprocess.js") as _f:
288
+ run_preprocess_js = _f.read()
289
+
290
+ with open("static/negative_prompt.txt") as _f:
291
+ negative_prompt = _f.read().strip()
292
+
293
+ # ── HTML template ──────────────────────────────────────────────────────────────
294
+
295
+ with open("templates/app.html") as _f:
296
+ app_html = _f.read().format(example_cards_html=EXAMPLE_CARDS_HTML)
297
+
298
+ # ── Gradio blocks ──────────────────────────────────────────────────────────────
299
+
300
+ @spaces.GPU
301
+ def infer(images_b64_json, prompt, seed, randomize_seed, guidance_scale, steps, progress=gr.Progress(track_tqdm=True)):
302
+ _cuda_ok = torch.cuda.is_available()
303
+ timer = _InferTimer(_cuda_ok)
304
+ t0 = time.perf_counter()
305
 
306
  print(f"[infer] ===== START =====")
307
  print(f"[infer] steps={steps}, guidance={guidance_scale}, seed={seed}, randomize={randomize_seed}")
 
312
  print(f"[infer] GPU: {p.name}, total={p.total_memory/1024**3:.1f}GB, cap={p.major}.{p.minor}")
313
  torch.cuda.reset_peak_memory_stats()
314
 
315
+ print(f"[infer] {_gpu_mem_str(_cuda_ok)} β€” t={time.perf_counter()-t0:.1f}s")
316
 
317
  gc.collect()
318
  torch.cuda.empty_cache()
319
+ print(f"[infer] cache cleared β€” {_gpu_mem_str(_cuda_ok)}")
320
 
321
+ timer.mark("load_start")
322
  pil_images = b64_to_pil_list(images_b64_json)
323
+ timer.mark("load_end")
324
+ print(f"[infer] decoded {len(pil_images)} image(s) β€” {timer.elapsed_ms('load_start', 'load_end'):.0f}ms")
325
+
326
+ _validate_infer_inputs(pil_images, prompt)
 
 
327
 
328
+ seed = _resolve_seed(seed, randomize_seed)
 
329
  generator = torch.Generator(device=device).manual_seed(seed)
330
  width, height = update_dimensions_on_upload(pil_images[0])
331
  print(f"[infer] input={pil_images[0].size}, output={width}x{height}, seed={seed}")
 
339
  now = time.perf_counter()
340
  _step_t.append(now)
341
  if step_idx == 0:
342
+ timer.mark("first_step")
343
+ timer.mark("last_step") # overwritten each iteration; final value = end of last step
344
  delta_ms = (now - (_step_t[-2] if len(_step_t) > 1 else t0)) * 1000
345
  tag = " ← includes compile" if step_idx == 0 else ""
346
+ 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")
347
  return cb_kwargs
348
 
349
+ timer.mark("pipe_start")
350
+ print(f"[infer] calling pipe... t={time.perf_counter()-t0:.1f}s")
351
  try:
352
  result_image = pipe(
353
  image=pil_images, prompt=prompt, negative_prompt=negative_prompt,
 
356
  callback_on_step_end=_step_cb,
357
  callback_on_step_end_tensor_inputs=["latents"],
358
  ).images[0]
359
+ timer.mark("pipe_end")
360
+ print(f"[infer] VAE decode + postprocess done β€” {_gpu_mem_str(_cuda_ok, sync=True)} | t={time.perf_counter()-t0:.1f}s")
361
+ timer.print_timings()
362
+ duration = timer.elapsed_ms("pipe_start", "pipe_end") / 1000.0
363
+ _spawn_log(pil_images, result_image, prompt, seed, steps, guidance_scale, width, height, duration, True)
 
 
 
 
 
364
  return result_image, seed
365
  except Exception as e:
366
+ print(f"[infer] ERROR: {type(e).__name__}: {e} | t={time.perf_counter()-t0:.1f}s")
367
  print(traceback.format_exc())
368
  try:
369
  torch.cuda.synchronize()
370
  except Exception as cuda_err:
371
  print(f"[infer] CUDA synchronize after error: {cuda_err}")
372
+ timer.print_timings()
373
+ duration = time.perf_counter() - timer.wall_start("pipe_start")
374
+ _spawn_log(pil_images, None, prompt, seed, steps, guidance_scale, width, height, duration, False, str(e))
 
 
 
 
 
375
  raise e
376
  finally:
377
  gc.collect()
378
  torch.cuda.empty_cache()
379
+ print(f"[infer] ===== END t={time.perf_counter()-t0:.1f}s =====")
 
 
 
380
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
381
 
382
  with gr.Blocks() as demo:
383