someone-in-the-world Claude Sonnet 5 commited on
Commit
6e3ec3e
·
unverified ·
1 Parent(s): 98269eb

Introduce a two-stage develop/main deploy flow (#27)

Browse files

* Set up develop/main two-stage deploy: retarget dev-en to develop, add prod-en workflow

develop -> dev-en Space (staging), main -> prod-en Space (release).
See #26 for the release flow design.

* Centralize print_*() logging functions in logging_utils.py (#29)

Consolidates the named print_*() functions that #23 extracted in
model_loading.py, inference.py, examples_ui.py, image_codec.py, and the
qwenimage attention/pipeline modules into logging_utils.py, alongside the
print_log_*() functions already there, so all logging lives in one file
instead of being scattered per-module.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* Rename dev-en's repo variable from HF_SPACE_ID to HF_SPACE_ID_DEV_EN (#30)

Matches the <ENV>_<LANG> naming already used by HF_SPACE_ID_PROD_EN and
HF_SPACE_ID_PROD_JA — the bare HF_SPACE_ID for dev-en was the odd one out.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* Extract _InferTimer into a generic Timer in timing.py (#32)

print_timings() hardcoded a rows table of inference.py-specific mark
names, coupling an otherwise-reusable checkpoint timer to one call
site. Timer.print_report() now takes the row schema (and optional
total pair) as arguments instead of owning it.

Also drops the dead "image_load"/load_start/load_end row: nothing
ever marked those checkpoints, so that row and the overhead/total
lines never printed. The total now uses pipe_start/pipe_end, which
are actually marked.

Fixes #31

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

.github/workflows/deploy-dev-en.yml CHANGED
@@ -2,7 +2,7 @@ name: Deploy to dev-en Space
2
 
3
  on:
4
  push:
5
- branches: [main]
6
  workflow_dispatch: {}
7
 
8
  concurrency:
@@ -25,14 +25,14 @@ jobs:
25
  - name: Push to Hugging Face Space (dev-en)
26
  env:
27
  HF_TOKEN: ${{ secrets.HF_TOKEN }}
28
- HF_SPACE_ID: ${{ vars.HF_SPACE_ID }}
29
  run: |
30
  if [ -z "$HF_TOKEN" ]; then
31
  echo "::error::HF_TOKEN secret is not set on this repo (Settings > Secrets and variables > Actions)."
32
  exit 1
33
  fi
34
  if [ -z "$HF_SPACE_ID" ]; then
35
- echo "::error::HF_SPACE_ID variable is not set on this repo (Settings > Secrets and variables > Actions > Variables), format: <owner>/<space-name>."
36
  exit 1
37
  fi
38
  git remote add space "https://user:${HF_TOKEN}@huggingface.co/spaces/${HF_SPACE_ID}"
 
2
 
3
  on:
4
  push:
5
+ branches: [develop]
6
  workflow_dispatch: {}
7
 
8
  concurrency:
 
25
  - name: Push to Hugging Face Space (dev-en)
26
  env:
27
  HF_TOKEN: ${{ secrets.HF_TOKEN }}
28
+ HF_SPACE_ID: ${{ vars.HF_SPACE_ID_DEV_EN }}
29
  run: |
30
  if [ -z "$HF_TOKEN" ]; then
31
  echo "::error::HF_TOKEN secret is not set on this repo (Settings > Secrets and variables > Actions)."
32
  exit 1
33
  fi
34
  if [ -z "$HF_SPACE_ID" ]; then
35
+ echo "::error::HF_SPACE_ID_DEV_EN variable is not set on this repo (Settings > Secrets and variables > Actions > Variables), format: <owner>/<space-name>."
36
  exit 1
37
  fi
38
  git remote add space "https://user:${HF_TOKEN}@huggingface.co/spaces/${HF_SPACE_ID}"
.github/workflows/deploy-prod-en.yml ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Deploy to prod-en Space
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ workflow_dispatch: {}
7
+
8
+ concurrency:
9
+ group: deploy-prod-en
10
+ cancel-in-progress: false
11
+
12
+ permissions:
13
+ contents: read
14
+
15
+ jobs:
16
+ deploy:
17
+ runs-on: ubuntu-latest
18
+ steps:
19
+ - name: Checkout
20
+ uses: actions/checkout@v4
21
+ with:
22
+ fetch-depth: 0
23
+ lfs: true
24
+
25
+ - name: Push to Hugging Face Space (prod-en)
26
+ env:
27
+ HF_TOKEN: ${{ secrets.HF_TOKEN }}
28
+ HF_SPACE_ID: ${{ vars.HF_SPACE_ID_PROD_EN }}
29
+ run: |
30
+ if [ -z "$HF_TOKEN" ]; then
31
+ echo "::error::HF_TOKEN secret is not set on this repo (Settings > Secrets and variables > Actions)."
32
+ exit 1
33
+ fi
34
+ if [ -z "$HF_SPACE_ID" ]; then
35
+ echo "::error::HF_SPACE_ID_PROD_EN variable is not set on this repo (Settings > Secrets and variables > Actions > Variables), format: <owner>/<space-name>."
36
+ exit 1
37
+ fi
38
+ git remote add space "https://user:${HF_TOKEN}@huggingface.co/spaces/${HF_SPACE_ID}"
39
+ # No --force: a non-fast-forward push means the Space diverged from GitHub
40
+ # (e.g. edited directly in the HF UI) and needs a human to reconcile it,
41
+ # rather than silently overwriting that history.
42
+ git push space HEAD:main
.github/workflows/typecheck.yml CHANGED
@@ -2,7 +2,7 @@ name: Type check
2
 
3
  on:
4
  push:
5
- branches: [main]
6
  pull_request: {}
7
 
8
  permissions:
 
2
 
3
  on:
4
  push:
5
+ branches: [main, develop]
6
  pull_request: {}
7
 
8
  permissions:
examples_ui.py CHANGED
@@ -6,6 +6,7 @@ import os
6
  from typing import Any
7
 
8
  from image_codec import encode_full_image, make_thumb_b64
 
9
 
10
  with open("examples.json") as _f:
11
  EXAMPLES_CONFIG: list[dict[str, Any]] = json.load(_f)
@@ -73,18 +74,6 @@ def load_example_data(idx_str: str) -> str:
73
  return json.dumps({"images": b64_list, "prompt": ex["prompt"], "names": names, "status": "ok"})
74
 
75
 
76
- def print_building_example_thumbnails() -> None:
77
- print("Building example thumbnails...")
78
-
79
-
80
- def print_built_example_cards(n: int) -> None:
81
- print(f"Built {n} example cards.")
82
-
83
-
84
- def print_built_suggestion_chips(n: int) -> None:
85
- print(f"Built {n} suggestion chips.")
86
-
87
-
88
  print_building_example_thumbnails()
89
  EXAMPLE_CARDS_HTML = build_example_cards_html()
90
  print_built_example_cards(len(EXAMPLES_CONFIG))
 
6
  from typing import Any
7
 
8
  from image_codec import encode_full_image, make_thumb_b64
9
+ from logging_utils import print_built_example_cards, print_built_suggestion_chips, print_building_example_thumbnails
10
 
11
  with open("examples.json") as _f:
12
  EXAMPLES_CONFIG: list[dict[str, Any]] = json.load(_f)
 
74
  return json.dumps({"images": b64_list, "prompt": ex["prompt"], "names": names, "status": "ok"})
75
 
76
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  print_building_example_thumbnails()
78
  EXAMPLE_CARDS_HTML = build_example_cards_html()
79
  print_built_example_cards(len(EXAMPLES_CONFIG))
image_codec.py CHANGED
@@ -7,19 +7,9 @@ from io import BytesIO
7
  from PIL import Image
8
  from PIL.Image import Image as PILImage
9
 
10
- LANCZOS = getattr(Image, "Resampling", Image).LANCZOS
11
-
12
-
13
- def print_thumbnail_error(path: str, e: Exception) -> None:
14
- print(f"Thumbnail error for {path}: {e}")
15
-
16
 
17
- def print_encode_error(path: str, e: Exception) -> None:
18
- print(f"Encode error for {path}: {e}")
19
-
20
-
21
- def print_decode_error(e: Exception) -> None:
22
- print(f"Error decoding image: {e}")
23
 
24
 
25
  def make_thumb_b64(path: str, max_dim: int = 220) -> str:
 
7
  from PIL import Image
8
  from PIL.Image import Image as PILImage
9
 
10
+ from logging_utils import print_decode_error, print_encode_error, print_thumbnail_error
 
 
 
 
 
11
 
12
+ LANCZOS = getattr(Image, "Resampling", Image).LANCZOS
 
 
 
 
 
13
 
14
 
15
  def make_thumb_b64(path: str, max_dim: int = 220) -> str:
inference.py CHANGED
@@ -18,9 +18,36 @@ from transformers import Qwen2_5_VLForConditionalGeneration
18
 
19
  from dimensions import compute_output_dimensions
20
  from image_codec import b64_to_pil_list
21
- from logging_utils import LogUploader
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  from mode import Mode
23
  from model_loading import device, pipe
 
24
 
25
  MAX_SEED = np.iinfo(np.int32).max
26
 
@@ -35,65 +62,13 @@ _log_uploader = LogUploader(
35
  )
36
 
37
 
38
- def print_timing_divider() -> None:
39
- print("[timing] ─────────────────────────────────────")
40
-
41
-
42
- def print_timing_lines(lines: list[str]) -> None:
43
- print("\n".join(lines))
44
-
45
-
46
- class _InferTimer:
47
- def __init__(self, cuda_ok: bool) -> None:
48
- self._cuda_ok = cuda_ok
49
- self._marks: dict[str, tuple[torch.cuda.Event | None, float]] = {}
50
-
51
- def mark(self, name: str) -> None:
52
- ev: torch.cuda.Event | None = None
53
- if self._cuda_ok:
54
- ev = torch.cuda.Event(enable_timing=True) # type: ignore[no-untyped-call]
55
- ev.record()
56
- self._marks[name] = (ev, time.perf_counter())
57
-
58
- def elapsed_ms(self, a: str, b: str) -> float:
59
- ev_a, t_a = self._marks[a]
60
- ev_b, t_b = self._marks[b]
61
- if ev_a and ev_b:
62
- return float(ev_a.elapsed_time(ev_b)) # true GPU-timeline ms
63
- return (t_b - t_a) * 1000.0
64
-
65
- def wall_start(self, name: str) -> float:
66
- return self._marks[name][1]
67
-
68
- def __contains__(self, name: str) -> bool:
69
- return name in self._marks
70
-
71
- def print_timings(self) -> None:
72
- if self._cuda_ok:
73
- try:
74
- torch.cuda.synchronize()
75
- except Exception:
76
- pass
77
- rows = [
78
- ("image_load", "load_start", "load_end"),
79
- ("preprocess", "pipe_start", "first_step"),
80
- ("inference", "first_step", "last_step"),
81
- ("vae_decode", "last_step", "pipe_end"),
82
- ]
83
- total_ms = 0.0
84
- lines = []
85
- for label, a, b in rows:
86
- if a in self._marks and b in self._marks:
87
- ms = self.elapsed_ms(a, b)
88
- total_ms += ms
89
- lines.append(f"[timing] {label:<14} {ms:8.1f} ms")
90
- if "load_start" in self._marks and "pipe_end" in self._marks:
91
- overall_ms = self.elapsed_ms("load_start", "pipe_end")
92
- lines.append(f"[timing] {'overhead':<14} {overall_ms - total_ms:8.1f} ms")
93
- lines.append(f"[timing] {'── total ──':<14} {overall_ms:8.1f} ms")
94
- print_timing_divider()
95
- print_timing_lines(lines)
96
- print_timing_divider()
97
 
98
 
99
  def _gpu_mem_str(cuda_ok: bool, sync: bool = False) -> str:
@@ -169,26 +144,6 @@ def infer(images_b64_json: str, prompt: str, seed: int, randomize_seed: bool, gu
169
  raise
170
 
171
 
172
- def print_infer_exception(e: Exception) -> None:
173
- print(f"[infer] EXCEPTION type={type(e).__module__}.{type(e).__qualname__} repr={e!r}")
174
-
175
-
176
- def print_infer_start_header() -> None:
177
- print("[infer] ===== START =====")
178
-
179
-
180
- def print_infer_params(steps: int, guidance_scale: float, seed: int, gpu_duration: int, mode: Mode) -> None:
181
- print(f"[infer] steps={steps}, guidance={guidance_scale}, seed={seed}, gpu_duration={gpu_duration}s, mode={mode.value}")
182
-
183
-
184
- def print_infer_prompt(prompt: str) -> None:
185
- print(f"[infer] prompt={repr(prompt[:120])}")
186
-
187
-
188
- def print_infer_gpu_properties(p: torch.cuda._CudaDeviceProperties) -> None:
189
- print(f"[infer] GPU: {p.name}, total={p.total_memory/1024**3:.1f}GB, cap={p.major}.{p.minor}")
190
-
191
-
192
  def _log_infer_start(prompt: str, steps: int, guidance_scale: float, seed: int, gpu_duration: int, mode: Mode) -> None:
193
  print_infer_start_header()
194
  print_infer_params(steps, guidance_scale, seed, gpu_duration, mode)
@@ -210,22 +165,6 @@ def _log_gpu_properties(cuda_ok: bool) -> torch.cuda._CudaDeviceProperties | Non
210
  _TEXT_ENCODER_INT8_REPO = os.environ.get("TEXT_ENCODER_INT8_REPO")
211
 
212
 
213
- def print_pre_int8_load(mem_str: str, elapsed: float) -> None:
214
- print(f"[infer] pre-int8-load — {mem_str} | t={elapsed:.1f}s")
215
-
216
-
217
- def print_int8_text_encoder_loaded(repo: str, load_ms: float, elapsed: float, mem_str: str) -> None:
218
- print(
219
- f"[infer] loaded int8 text_encoder from {repo} — "
220
- f"{load_ms:.0f}ms | t={elapsed:.1f}s | "
221
- f"{mem_str}"
222
- )
223
-
224
-
225
- def print_int8_text_encoder_load_failed(e: Exception) -> None:
226
- print(f"[infer] WARNING: int8 text_encoder load failed, keeping bf16: {type(e).__name__}: {e}")
227
-
228
-
229
  def _ensure_int8_text_encoder(cuda_ok: bool, t0: float) -> None:
230
  # bitsandbytes 8bit modules can't be moved between devices with `.to()` (transformers
231
  # raises unconditionally for 8bit, unlike the version-gated allowance for 4bit), so this
@@ -252,10 +191,6 @@ def _ensure_int8_text_encoder(cuda_ok: bool, t0: float) -> None:
252
  print_int8_text_encoder_load_failed(e)
253
 
254
 
255
- def print_bf16_text_encoder_moved(device: torch.device, move_ms: float, elapsed: float) -> None:
256
- print(f"[infer] moved bf16 text_encoder to {device} — {move_ms:.0f}ms | t={elapsed:.1f}s")
257
-
258
-
259
  def _ensure_bf16_text_encoder_on_device(cuda_ok: bool, t0: float) -> None:
260
  # Fallback for when int8 quantization is unavailable/unset/failed: model_loading.py
261
  # deliberately leaves the bf16 text_encoder on CPU at startup (see the comment in
@@ -271,10 +206,6 @@ def _ensure_bf16_text_encoder_on_device(cuda_ok: bool, t0: float) -> None:
271
  print_bf16_text_encoder_moved(device, (time.perf_counter() - _t0) * 1000, time.perf_counter() - t0)
272
 
273
 
274
- def print_first_call_into_module(name: str, mem_str: str, elapsed: float) -> None:
275
- print(f"[infer] first call into {name} — {mem_str} | t={elapsed:.1f}s")
276
-
277
-
278
  def _instrument_first_touch(modules_with_names: list[tuple[torch.nn.Module, str]], t0: float) -> None:
279
  """Install self-removing forward-pre-hooks that log the moment each module is first entered."""
280
  def _make_hook(name: str, handle_box: dict[str, torch.utils.hooks.RemovableHandle]) -> Any:
@@ -287,27 +218,7 @@ def _instrument_first_touch(modules_with_names: list[tuple[torch.nn.Module, str]
287
  handle_box["h"] = module.register_forward_pre_hook(_make_hook(name, handle_box))
288
 
289
 
290
- def print_step_done(step_idx: int, steps: int, delta_ms: float, tag: str, elapsed: float) -> None:
291
- print(f"[infer] step {step_idx+1}/{steps} done — {delta_ms:.0f}ms{tag} | t={elapsed:.1f}s")
292
-
293
-
294
- def print_text_encoder_offloaded(offload_ms: float, elapsed: float) -> None:
295
- print(f"[infer] text_encoder offload to cpu — {offload_ms:.0f}ms | t={elapsed:.1f}s")
296
-
297
-
298
- def print_text_encoder_offload_skipped_int8() -> None:
299
- print("[infer] skipping text_encoder offload (int8, .to() unsupported / smaller footprint)")
300
-
301
-
302
- def print_text_encoder_offload_skipped_mode(mode: Mode) -> None:
303
- print(f"[infer] skipping text_encoder offload for mode={mode.value} (ample headroom at this resolution)")
304
-
305
-
306
- def print_pre_vae_decode(mem_str: str, elapsed: float) -> None:
307
- print(f"[infer] pre-VAE-decode — {mem_str} | t={elapsed:.1f}s")
308
-
309
-
310
- def _make_step_callback(steps: int, timer: _InferTimer, t0: float, mode: Mode, cuda_ok: bool = False) -> Any:
311
  """Build the diffusers step callback that logs per-step timing and marks timer checkpoints."""
312
  step_times: list[float] = []
313
  def _step_cb(pipeline: Any, step_idx: int, timestep: Any, cb_kwargs: dict[str, Any]) -> dict[str, Any]:
@@ -344,26 +255,14 @@ def _make_step_callback(steps: int, timer: _InferTimer, t0: float, mode: Mode, c
344
  return _step_cb
345
 
346
 
347
- def print_infer_error(e: Exception, elapsed: float) -> None:
348
- print(f"[infer] ERROR: {type(e).__name__}: {e} | t={elapsed:.1f}s")
349
-
350
-
351
- def print_infer_traceback() -> None:
352
- print(traceback.format_exc())
353
-
354
-
355
- def print_cuda_sync_after_error(cuda_err: Exception) -> None:
356
- print(f"[infer] CUDA synchronize after error: {cuda_err}")
357
-
358
-
359
- def _log_infer_error(e: Exception, t0: float, timer: _InferTimer) -> None:
360
  print_infer_error(e, time.perf_counter() - t0)
361
  print_infer_traceback()
362
  try:
363
  torch.cuda.synchronize()
364
  except Exception as cuda_err:
365
  print_cuda_sync_after_error(cuda_err)
366
- timer.print_timings()
367
 
368
 
369
  # Every @spaces.GPU call lands on a fresh worker, so _ensure_int8_text_encoder's reload is
@@ -379,36 +278,11 @@ _COLD_START_BUFFER_S = 45
379
  _MAX_GPU_DURATION_S = 120 # matches the gpu_duration slider's max in the UI
380
 
381
 
382
- def print_gpu_mem_status(mem_str: str, elapsed: float) -> None:
383
- print(f"[infer] {mem_str} — t={elapsed:.1f}s")
384
-
385
-
386
- def print_images_predecoded(n: int, width: int, height: int, seed: int) -> None:
387
- print(f"[infer] {n} image(s) pre-decoded, output={width}x{height}, seed={seed}")
388
-
389
-
390
- def print_vae_tiling_activation(will_tile: bool, height: int, width: int) -> None:
391
- print(f"[infer] VAE tiling will {'activate' if will_tile else 'NOT activate'} "
392
- f"(threshold={height}x{width}px)")
393
-
394
-
395
- def print_calling_pipe(elapsed: float) -> None:
396
- print(f"[infer] calling pipe... t={elapsed:.1f}s")
397
-
398
-
399
- def print_vae_decode_done(mem_str: str, elapsed: float) -> None:
400
- print(f"[infer] VAE decode + postprocess done — {mem_str} | t={elapsed:.1f}s")
401
-
402
-
403
- def print_infer_end(elapsed: float) -> None:
404
- print(f"[infer] ===== END t={elapsed:.1f}s =====")
405
-
406
-
407
  @spaces.GPU(duration=lambda *a, **kw: min(int(a[8]) + _COLD_START_BUFFER_S, _MAX_GPU_DURATION_S) if len(a) > 8 else 60) # type: ignore[untyped-decorator]
408
  def _infer_gpu(pil_images: list[PILImage], prompt: str, seed: int, guidance_scale: float, steps: int,
409
  width: int, height: int, mode: Mode, gpu_duration: int = 20) -> tuple[PILImage, int, float]:
410
  _cuda_ok = torch.cuda.is_available()
411
- timer = _InferTimer(_cuda_ok)
412
  t0 = time.perf_counter()
413
 
414
  _log_infer_start(prompt, steps, guidance_scale, seed, gpu_duration, mode)
@@ -446,7 +320,7 @@ def _infer_gpu(pil_images: list[PILImage], prompt: str, seed: int, guidance_scal
446
  ).images[0]
447
  timer.mark("pipe_end")
448
  print_vae_decode_done(_gpu_mem_str(_cuda_ok, sync=True), time.perf_counter() - t0)
449
- timer.print_timings()
450
  duration = timer.elapsed_ms("pipe_start", "pipe_end") / 1000.0
451
  return result_image, seed, duration
452
  except Exception as e:
 
18
 
19
  from dimensions import compute_output_dimensions
20
  from image_codec import b64_to_pil_list
21
+ from logging_utils import (
22
+ LogUploader,
23
+ print_bf16_text_encoder_moved,
24
+ print_calling_pipe,
25
+ print_cuda_sync_after_error,
26
+ print_first_call_into_module,
27
+ print_gpu_mem_status,
28
+ print_images_predecoded,
29
+ print_infer_end,
30
+ print_infer_error,
31
+ print_infer_exception,
32
+ print_infer_gpu_properties,
33
+ print_infer_params,
34
+ print_infer_prompt,
35
+ print_infer_start_header,
36
+ print_infer_traceback,
37
+ print_int8_text_encoder_load_failed,
38
+ print_int8_text_encoder_loaded,
39
+ print_pre_int8_load,
40
+ print_pre_vae_decode,
41
+ print_step_done,
42
+ print_text_encoder_offload_skipped_int8,
43
+ print_text_encoder_offload_skipped_mode,
44
+ print_text_encoder_offloaded,
45
+ print_vae_decode_done,
46
+ print_vae_tiling_activation,
47
+ )
48
  from mode import Mode
49
  from model_loading import device, pipe
50
+ from timing import Timer
51
 
52
  MAX_SEED = np.iinfo(np.int32).max
53
 
 
62
  )
63
 
64
 
65
+ # (label, start_mark, end_mark) rows for Timer.print_report(), matching the marks
66
+ # _infer_gpu/_make_step_callback record: pipe_start, first_step, last_step, pipe_end.
67
+ _TIMING_ROWS: list[tuple[str, str, str]] = [
68
+ ("preprocess", "pipe_start", "first_step"),
69
+ ("inference", "first_step", "last_step"),
70
+ ("vae_decode", "last_step", "pipe_end"),
71
+ ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
 
73
 
74
  def _gpu_mem_str(cuda_ok: bool, sync: bool = False) -> str:
 
144
  raise
145
 
146
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
  def _log_infer_start(prompt: str, steps: int, guidance_scale: float, seed: int, gpu_duration: int, mode: Mode) -> None:
148
  print_infer_start_header()
149
  print_infer_params(steps, guidance_scale, seed, gpu_duration, mode)
 
165
  _TEXT_ENCODER_INT8_REPO = os.environ.get("TEXT_ENCODER_INT8_REPO")
166
 
167
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
  def _ensure_int8_text_encoder(cuda_ok: bool, t0: float) -> None:
169
  # bitsandbytes 8bit modules can't be moved between devices with `.to()` (transformers
170
  # raises unconditionally for 8bit, unlike the version-gated allowance for 4bit), so this
 
191
  print_int8_text_encoder_load_failed(e)
192
 
193
 
 
 
 
 
194
  def _ensure_bf16_text_encoder_on_device(cuda_ok: bool, t0: float) -> None:
195
  # Fallback for when int8 quantization is unavailable/unset/failed: model_loading.py
196
  # deliberately leaves the bf16 text_encoder on CPU at startup (see the comment in
 
206
  print_bf16_text_encoder_moved(device, (time.perf_counter() - _t0) * 1000, time.perf_counter() - t0)
207
 
208
 
 
 
 
 
209
  def _instrument_first_touch(modules_with_names: list[tuple[torch.nn.Module, str]], t0: float) -> None:
210
  """Install self-removing forward-pre-hooks that log the moment each module is first entered."""
211
  def _make_hook(name: str, handle_box: dict[str, torch.utils.hooks.RemovableHandle]) -> Any:
 
218
  handle_box["h"] = module.register_forward_pre_hook(_make_hook(name, handle_box))
219
 
220
 
221
+ def _make_step_callback(steps: int, timer: Timer, t0: float, mode: Mode, cuda_ok: bool = False) -> Any:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
222
  """Build the diffusers step callback that logs per-step timing and marks timer checkpoints."""
223
  step_times: list[float] = []
224
  def _step_cb(pipeline: Any, step_idx: int, timestep: Any, cb_kwargs: dict[str, Any]) -> dict[str, Any]:
 
255
  return _step_cb
256
 
257
 
258
+ def _log_infer_error(e: Exception, t0: float, timer: Timer) -> None:
 
 
 
 
 
 
 
 
 
 
 
 
259
  print_infer_error(e, time.perf_counter() - t0)
260
  print_infer_traceback()
261
  try:
262
  torch.cuda.synchronize()
263
  except Exception as cuda_err:
264
  print_cuda_sync_after_error(cuda_err)
265
+ timer.print_report(_TIMING_ROWS, total=("pipe_start", "pipe_end"))
266
 
267
 
268
  # Every @spaces.GPU call lands on a fresh worker, so _ensure_int8_text_encoder's reload is
 
278
  _MAX_GPU_DURATION_S = 120 # matches the gpu_duration slider's max in the UI
279
 
280
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
281
  @spaces.GPU(duration=lambda *a, **kw: min(int(a[8]) + _COLD_START_BUFFER_S, _MAX_GPU_DURATION_S) if len(a) > 8 else 60) # type: ignore[untyped-decorator]
282
  def _infer_gpu(pil_images: list[PILImage], prompt: str, seed: int, guidance_scale: float, steps: int,
283
  width: int, height: int, mode: Mode, gpu_duration: int = 20) -> tuple[PILImage, int, float]:
284
  _cuda_ok = torch.cuda.is_available()
285
+ timer = Timer(_cuda_ok)
286
  t0 = time.perf_counter()
287
 
288
  _log_infer_start(prompt, steps, guidance_scale, seed, gpu_duration, mode)
 
320
  ).images[0]
321
  timer.mark("pipe_end")
322
  print_vae_decode_done(_gpu_mem_str(_cuda_ok, sync=True), time.perf_counter() - t0)
323
+ timer.print_report(_TIMING_ROWS, total=("pipe_start", "pipe_end"))
324
  duration = timer.elapsed_ms("pipe_start", "pipe_end") / 1000.0
325
  return result_image, seed, duration
326
  except Exception as e:
logging_utils.py CHANGED
@@ -5,13 +5,258 @@ import time as _time
5
  from io import BytesIO
6
  from datetime import datetime, timezone
7
  from typing import TYPE_CHECKING, Any
 
8
  from huggingface_hub import hf_hub_download, CommitOperationAdd, CommitOperationDelete
9
  from PIL.Image import Image as PILImage
10
 
 
 
11
  if TYPE_CHECKING:
12
  from huggingface_hub import HfApi
13
 
14
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  def _img_to_jpeg(img: PILImage | None, quality: int = 85) -> bytes | None:
16
  if img is None:
17
  return None
 
5
  from io import BytesIO
6
  from datetime import datetime, timezone
7
  from typing import TYPE_CHECKING, Any
8
+ import torch
9
  from huggingface_hub import hf_hub_download, CommitOperationAdd, CommitOperationDelete
10
  from PIL.Image import Image as PILImage
11
 
12
+ from mode import Mode
13
+
14
  if TYPE_CHECKING:
15
  from huggingface_hub import HfApi
16
 
17
 
18
+ def print_cuda_visible_devices() -> None:
19
+ print("CUDA_VISIBLE_DEVICES=", os.environ.get("CUDA_VISIBLE_DEVICES"), flush=True)
20
+
21
+
22
+ def print_torch_version() -> None:
23
+ print("torch.__version__ =", torch.__version__, flush=True)
24
+
25
+
26
+ def print_using_device(device: torch.device) -> None:
27
+ print("Using device:", device, flush=True)
28
+
29
+
30
+ def print_cuda_device_count() -> None:
31
+ print(f"CUDA device_count={torch.cuda.device_count()}, is_available={torch.cuda.is_available()}", flush=True)
32
+
33
+
34
+ def print_env_gpu(p: torch.cuda._CudaDeviceProperties) -> None:
35
+ print(f"[env] GPU: {p.name}, VRAM={p.total_memory/1024**3:.1f}GB, cap={p.major}.{p.minor}", flush=True)
36
+
37
+
38
+ def print_env_cuda_version() -> None:
39
+ print(f"[env] CUDA (torch build): {torch.version.cuda}", flush=True)
40
+
41
+
42
+ def print_env_cudnn_version() -> None:
43
+ print(f"[env] cuDNN: {torch.backends.cudnn.version()}", flush=True) # type: ignore[no-untyped-call]
44
+
45
+
46
+ def print_env_package_version(pkg: str, version: str) -> None:
47
+ print(f"[env] {pkg}=={version}", flush=True)
48
+
49
+
50
+ def print_env_package_version_unavailable(pkg: str, error: Exception) -> None:
51
+ print(f"[env] {pkg}==? ({error})", flush=True)
52
+
53
+
54
+ def print_env_ram(total_gb: float, avail_gb: float) -> None:
55
+ print(f"[env] RAM: {total_gb:.0f}GB total, {avail_gb:.0f}GB available", flush=True)
56
+
57
+
58
+ def print_env_ram_unavailable(error: Exception) -> None:
59
+ print(f"[env] RAM: unavailable ({error})", flush=True)
60
+
61
+
62
+ def print_tf32_enabled() -> None:
63
+ print("[startup] TF32 enabled", flush=True)
64
+
65
+
66
+ def print_heartbeat(label: str, elapsed: float) -> None:
67
+ print(f"[startup] {label} still loading... ({elapsed:.0f}s)", flush=True)
68
+
69
+
70
+ def print_unpatched_fp8_param(name: str, pname: str, module_type: str) -> None:
71
+ print(
72
+ f"[startup] WARNING: unpatched fp8 parameter {name}.{pname} "
73
+ f"({module_type}) — will likely error at inference",
74
+ flush=True,
75
+ )
76
+
77
+
78
+ def print_loading_transformer() -> None:
79
+ print("[startup] loading transformer from_pretrained (prithivMLmods/Qwen-Image-Edit-Rapid-AIO-V23)...", flush=True)
80
+
81
+
82
+ def print_transformer_loaded(elapsed: float) -> None:
83
+ print(f"[startup] transformer loaded in {elapsed:.1f}s", flush=True)
84
+
85
+
86
+ def print_transformer_patched(n_patched: int) -> None:
87
+ print(f"[startup] patched {n_patched} fp8-resident nn.Linear/RMSNorm modules for just-in-time upcast", flush=True)
88
+
89
+
90
+ def print_transformer_memory_footprint(gb: float) -> None:
91
+ print(f"[startup] transformer memory footprint: {gb:.2f}GB", flush=True)
92
+
93
+
94
+ def print_transformer_memory_footprint_unavailable(error: Exception) -> None:
95
+ print(f"[startup] transformer memory footprint: unavailable ({error})", flush=True)
96
+
97
+
98
+ def print_loading_pipeline() -> None:
99
+ print("[startup] loading pipeline from_pretrained (FireRedTeam/FireRed-Image-Edit-1.1)...", flush=True)
100
+
101
+
102
+ def print_vae_tiling(height: int, width: int, use_tiling: bool) -> None:
103
+ print(f"[startup] VAE tiling: threshold={height}x{width}px use_tiling={use_tiling}", flush=True)
104
+
105
+
106
+ def print_pipeline_loaded(elapsed: float) -> None:
107
+ print(f"[startup] pipeline loaded in {elapsed:.1f}s", flush=True)
108
+
109
+
110
+ def print_setting_attn_processor() -> None:
111
+ print("[startup] setting cuDNN SDPA attention processor...", flush=True)
112
+
113
+
114
+ def print_attn_processor_set() -> None:
115
+ print("[startup] cuDNN SDPA attention processor set.", flush=True)
116
+
117
+
118
+ def print_timing_divider() -> None:
119
+ print("[timing] ─────────────────────────────────────")
120
+
121
+
122
+ def print_timing_lines(lines: list[str]) -> None:
123
+ print("\n".join(lines))
124
+
125
+
126
+ def print_infer_exception(e: Exception) -> None:
127
+ print(f"[infer] EXCEPTION type={type(e).__module__}.{type(e).__qualname__} repr={e!r}")
128
+
129
+
130
+ def print_infer_start_header() -> None:
131
+ print("[infer] ===== START =====")
132
+
133
+
134
+ def print_infer_params(steps: int, guidance_scale: float, seed: int, gpu_duration: int, mode: Mode) -> None:
135
+ print(f"[infer] steps={steps}, guidance={guidance_scale}, seed={seed}, gpu_duration={gpu_duration}s, mode={mode.value}")
136
+
137
+
138
+ def print_infer_prompt(prompt: str) -> None:
139
+ print(f"[infer] prompt={repr(prompt[:120])}")
140
+
141
+
142
+ def print_infer_gpu_properties(p: torch.cuda._CudaDeviceProperties) -> None:
143
+ print(f"[infer] GPU: {p.name}, total={p.total_memory/1024**3:.1f}GB, cap={p.major}.{p.minor}")
144
+
145
+
146
+ def print_pre_int8_load(mem_str: str, elapsed: float) -> None:
147
+ print(f"[infer] pre-int8-load — {mem_str} | t={elapsed:.1f}s")
148
+
149
+
150
+ def print_int8_text_encoder_loaded(repo: str, load_ms: float, elapsed: float, mem_str: str) -> None:
151
+ print(
152
+ f"[infer] loaded int8 text_encoder from {repo} — "
153
+ f"{load_ms:.0f}ms | t={elapsed:.1f}s | "
154
+ f"{mem_str}"
155
+ )
156
+
157
+
158
+ def print_int8_text_encoder_load_failed(e: Exception) -> None:
159
+ print(f"[infer] WARNING: int8 text_encoder load failed, keeping bf16: {type(e).__name__}: {e}")
160
+
161
+
162
+ def print_bf16_text_encoder_moved(device: torch.device, move_ms: float, elapsed: float) -> None:
163
+ print(f"[infer] moved bf16 text_encoder to {device} — {move_ms:.0f}ms | t={elapsed:.1f}s")
164
+
165
+
166
+ def print_first_call_into_module(name: str, mem_str: str, elapsed: float) -> None:
167
+ print(f"[infer] first call into {name} — {mem_str} | t={elapsed:.1f}s")
168
+
169
+
170
+ def print_step_done(step_idx: int, steps: int, delta_ms: float, tag: str, elapsed: float) -> None:
171
+ print(f"[infer] step {step_idx+1}/{steps} done — {delta_ms:.0f}ms{tag} | t={elapsed:.1f}s")
172
+
173
+
174
+ def print_text_encoder_offloaded(offload_ms: float, elapsed: float) -> None:
175
+ print(f"[infer] text_encoder offload to cpu — {offload_ms:.0f}ms | t={elapsed:.1f}s")
176
+
177
+
178
+ def print_text_encoder_offload_skipped_int8() -> None:
179
+ print("[infer] skipping text_encoder offload (int8, .to() unsupported / smaller footprint)")
180
+
181
+
182
+ def print_text_encoder_offload_skipped_mode(mode: Mode) -> None:
183
+ print(f"[infer] skipping text_encoder offload for mode={mode.value} (ample headroom at this resolution)")
184
+
185
+
186
+ def print_pre_vae_decode(mem_str: str, elapsed: float) -> None:
187
+ print(f"[infer] pre-VAE-decode — {mem_str} | t={elapsed:.1f}s")
188
+
189
+
190
+ def print_infer_error(e: Exception, elapsed: float) -> None:
191
+ print(f"[infer] ERROR: {type(e).__name__}: {e} | t={elapsed:.1f}s")
192
+
193
+
194
+ def print_infer_traceback() -> None:
195
+ import traceback
196
+ print(traceback.format_exc())
197
+
198
+
199
+ def print_cuda_sync_after_error(cuda_err: Exception) -> None:
200
+ print(f"[infer] CUDA synchronize after error: {cuda_err}")
201
+
202
+
203
+ def print_gpu_mem_status(mem_str: str, elapsed: float) -> None:
204
+ print(f"[infer] {mem_str} — t={elapsed:.1f}s")
205
+
206
+
207
+ def print_images_predecoded(n: int, width: int, height: int, seed: int) -> None:
208
+ print(f"[infer] {n} image(s) pre-decoded, output={width}x{height}, seed={seed}")
209
+
210
+
211
+ def print_vae_tiling_activation(will_tile: bool, height: int, width: int) -> None:
212
+ print(f"[infer] VAE tiling will {'activate' if will_tile else 'NOT activate'} "
213
+ f"(threshold={height}x{width}px)")
214
+
215
+
216
+ def print_calling_pipe(elapsed: float) -> None:
217
+ print(f"[infer] calling pipe... t={elapsed:.1f}s")
218
+
219
+
220
+ def print_vae_decode_done(mem_str: str, elapsed: float) -> None:
221
+ print(f"[infer] VAE decode + postprocess done — {mem_str} | t={elapsed:.1f}s")
222
+
223
+
224
+ def print_infer_end(elapsed: float) -> None:
225
+ print(f"[infer] ===== END t={elapsed:.1f}s =====")
226
+
227
+
228
+ def print_building_example_thumbnails() -> None:
229
+ print("Building example thumbnails...")
230
+
231
+
232
+ def print_built_example_cards(n: int) -> None:
233
+ print(f"Built {n} example cards.")
234
+
235
+
236
+ def print_built_suggestion_chips(n: int) -> None:
237
+ print(f"Built {n} suggestion chips.")
238
+
239
+
240
+ def print_thumbnail_error(path: str, e: Exception) -> None:
241
+ print(f"Thumbnail error for {path}: {e}")
242
+
243
+
244
+ def print_encode_error(path: str, e: Exception) -> None:
245
+ print(f"Encode error for {path}: {e}")
246
+
247
+
248
+ def print_decode_error(e: Exception) -> None:
249
+ print(f"Error decoding image: {e}")
250
+
251
+
252
+ def print_cudnn_sdpa_fallback(e: RuntimeError) -> None:
253
+ print(f"[attn] cuDNN SDPA backend unavailable ({e}), falling back to default", flush=True)
254
+
255
+
256
+ def print_pipe_phase_timing(label: str, delta_ms: float, elapsed_s: float) -> None:
257
+ print(f"[pipe] {label} — {delta_ms:.0f}ms | t={elapsed_s:.1f}s", flush=True)
258
+
259
+
260
  def _img_to_jpeg(img: PILImage | None, quality: int = 85) -> bytes | None:
261
  if img is None:
262
  return None
model_loading.py CHANGED
@@ -15,6 +15,32 @@ import spaces # noqa: F401 (must be imported before any CUDA-touching code bel
15
  import torch
16
  from diffusers.models.normalization import RMSNorm
17
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  from mode import Mode
19
  from qwenimage.pipeline_qwenimage_edit_plus import QwenImageEditPlusPipeline
20
  from qwenimage.qwen_fa3_processor import QwenDoubleStreamAttnProcessorFA3
@@ -23,110 +49,9 @@ from qwenimage.transformer_qwenimage import QwenImageTransformer2DModel
23
  device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
24
  dtype = torch.bfloat16
25
 
26
-
27
- def print_cuda_visible_devices() -> None:
28
- print("CUDA_VISIBLE_DEVICES=", os.environ.get("CUDA_VISIBLE_DEVICES"), flush=True)
29
-
30
-
31
- def print_torch_version() -> None:
32
- print("torch.__version__ =", torch.__version__, flush=True)
33
-
34
-
35
- def print_using_device() -> None:
36
- print("Using device:", device, flush=True)
37
-
38
-
39
- def print_cuda_device_count() -> None:
40
- print(f"CUDA device_count={torch.cuda.device_count()}, is_available={torch.cuda.is_available()}", flush=True)
41
-
42
-
43
- def print_env_gpu(p: torch.cuda._CudaDeviceProperties) -> None:
44
- print(f"[env] GPU: {p.name}, VRAM={p.total_memory/1024**3:.1f}GB, cap={p.major}.{p.minor}", flush=True)
45
-
46
-
47
- def print_env_cuda_version() -> None:
48
- print(f"[env] CUDA (torch build): {torch.version.cuda}", flush=True)
49
-
50
-
51
- def print_env_cudnn_version() -> None:
52
- print(f"[env] cuDNN: {torch.backends.cudnn.version()}", flush=True) # type: ignore[no-untyped-call]
53
-
54
-
55
- def print_env_package_version(pkg: str, version: str) -> None:
56
- print(f"[env] {pkg}=={version}", flush=True)
57
-
58
-
59
- def print_env_package_version_unavailable(pkg: str, error: Exception) -> None:
60
- print(f"[env] {pkg}==? ({error})", flush=True)
61
-
62
-
63
- def print_env_ram(total_gb: float, avail_gb: float) -> None:
64
- print(f"[env] RAM: {total_gb:.0f}GB total, {avail_gb:.0f}GB available", flush=True)
65
-
66
-
67
- def print_env_ram_unavailable(error: Exception) -> None:
68
- print(f"[env] RAM: unavailable ({error})", flush=True)
69
-
70
-
71
- def print_tf32_enabled() -> None:
72
- print("[startup] TF32 enabled", flush=True)
73
-
74
-
75
- def print_heartbeat(label: str, elapsed: float) -> None:
76
- print(f"[startup] {label} still loading... ({elapsed:.0f}s)", flush=True)
77
-
78
-
79
- def print_unpatched_fp8_param(name: str, pname: str, module_type: str) -> None:
80
- print(
81
- f"[startup] WARNING: unpatched fp8 parameter {name}.{pname} "
82
- f"({module_type}) — will likely error at inference",
83
- flush=True,
84
- )
85
-
86
-
87
- def print_loading_transformer() -> None:
88
- print("[startup] loading transformer from_pretrained (prithivMLmods/Qwen-Image-Edit-Rapid-AIO-V23)...", flush=True)
89
-
90
-
91
- def print_transformer_loaded(elapsed: float) -> None:
92
- print(f"[startup] transformer loaded in {elapsed:.1f}s", flush=True)
93
-
94
-
95
- def print_transformer_patched(n_patched: int) -> None:
96
- print(f"[startup] patched {n_patched} fp8-resident nn.Linear/RMSNorm modules for just-in-time upcast", flush=True)
97
-
98
-
99
- def print_transformer_memory_footprint(gb: float) -> None:
100
- print(f"[startup] transformer memory footprint: {gb:.2f}GB", flush=True)
101
-
102
-
103
- def print_transformer_memory_footprint_unavailable(error: Exception) -> None:
104
- print(f"[startup] transformer memory footprint: unavailable ({error})", flush=True)
105
-
106
-
107
- def print_loading_pipeline() -> None:
108
- print("[startup] loading pipeline from_pretrained (FireRedTeam/FireRed-Image-Edit-1.1)...", flush=True)
109
-
110
-
111
- def print_vae_tiling(height: int, width: int, use_tiling: bool) -> None:
112
- print(f"[startup] VAE tiling: threshold={height}x{width}px use_tiling={use_tiling}", flush=True)
113
-
114
-
115
- def print_pipeline_loaded(elapsed: float) -> None:
116
- print(f"[startup] pipeline loaded in {elapsed:.1f}s", flush=True)
117
-
118
-
119
- def print_setting_attn_processor() -> None:
120
- print("[startup] setting cuDNN SDPA attention processor...", flush=True)
121
-
122
-
123
- def print_attn_processor_set() -> None:
124
- print("[startup] cuDNN SDPA attention processor set.", flush=True)
125
-
126
-
127
  print_cuda_visible_devices()
128
  print_torch_version()
129
- print_using_device()
130
  print_cuda_device_count()
131
 
132
 
 
15
  import torch
16
  from diffusers.models.normalization import RMSNorm
17
 
18
+ from logging_utils import (
19
+ print_attn_processor_set,
20
+ print_cuda_device_count,
21
+ print_cuda_visible_devices,
22
+ print_env_cuda_version,
23
+ print_env_cudnn_version,
24
+ print_env_gpu,
25
+ print_env_package_version,
26
+ print_env_package_version_unavailable,
27
+ print_env_ram,
28
+ print_env_ram_unavailable,
29
+ print_heartbeat,
30
+ print_loading_pipeline,
31
+ print_loading_transformer,
32
+ print_pipeline_loaded,
33
+ print_setting_attn_processor,
34
+ print_tf32_enabled,
35
+ print_torch_version,
36
+ print_transformer_loaded,
37
+ print_transformer_memory_footprint,
38
+ print_transformer_memory_footprint_unavailable,
39
+ print_transformer_patched,
40
+ print_unpatched_fp8_param,
41
+ print_using_device,
42
+ print_vae_tiling,
43
+ )
44
  from mode import Mode
45
  from qwenimage.pipeline_qwenimage_edit_plus import QwenImageEditPlusPipeline
46
  from qwenimage.qwen_fa3_processor import QwenDoubleStreamAttnProcessorFA3
 
49
  device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
50
  dtype = torch.bfloat16
51
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  print_cuda_visible_devices()
53
  print_torch_version()
54
+ print_using_device(device)
55
  print_cuda_device_count()
56
 
57
 
qwenimage/pipeline_qwenimage_edit_plus.py CHANGED
@@ -30,6 +30,8 @@ from diffusers.utils.torch_utils import randn_tensor
30
  from diffusers.pipelines.pipeline_utils import DiffusionPipeline
31
  from diffusers.pipelines.qwenimage.pipeline_output import QwenImagePipelineOutput
32
 
 
 
33
 
34
  if is_torch_xla_available():
35
  import torch_xla.core.xla_model as xm
@@ -156,10 +158,6 @@ def retrieve_latents(
156
  raise AttributeError("Could not access latents of provided encoder_output")
157
 
158
 
159
- def print_pipe_phase_timing(label, delta_ms, elapsed_s):
160
- print(f"[pipe] {label} — {delta_ms:.0f}ms | t={elapsed_s:.1f}s", flush=True)
161
-
162
-
163
  def calculate_dimensions(target_area, ratio):
164
  width = math.sqrt(target_area * ratio)
165
  height = width / ratio
 
30
  from diffusers.pipelines.pipeline_utils import DiffusionPipeline
31
  from diffusers.pipelines.qwenimage.pipeline_output import QwenImagePipelineOutput
32
 
33
+ from logging_utils import print_pipe_phase_timing
34
+
35
 
36
  if is_torch_xla_available():
37
  import torch_xla.core.xla_model as xm
 
158
  raise AttributeError("Could not access latents of provided encoder_output")
159
 
160
 
 
 
 
 
161
  def calculate_dimensions(target_area, ratio):
162
  width = math.sqrt(target_area * ratio)
163
  height = width / ratio
qwenimage/qwen_fa3_processor.py CHANGED
@@ -4,9 +4,7 @@ from typing import Optional, Tuple
4
  from torch.nn.attention import SDPBackend, sdpa_kernel
5
  from diffusers.models.transformers.transformer_qwenimage import apply_rotary_emb_qwen
6
 
7
-
8
- def print_cudnn_sdpa_fallback(e: RuntimeError) -> None:
9
- print(f"[attn] cuDNN SDPA backend unavailable ({e}), falling back to default", flush=True)
10
 
11
 
12
  class QwenDoubleStreamAttnProcessorFA3:
 
4
  from torch.nn.attention import SDPBackend, sdpa_kernel
5
  from diffusers.models.transformers.transformer_qwenimage import apply_rotary_emb_qwen
6
 
7
+ from logging_utils import print_cudnn_sdpa_fallback
 
 
8
 
9
 
10
  class QwenDoubleStreamAttnProcessorFA3:
timing.py ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Generic checkpoint timer for instrumenting wall-clock/GPU-timeline durations."""
2
+ import time
3
+
4
+ import torch
5
+
6
+ from logging_utils import print_timing_divider, print_timing_lines
7
+
8
+
9
+ class Timer:
10
+ """Records named marks and reports elapsed time between them.
11
+
12
+ On CUDA, each mark also captures a CUDA event so elapsed_ms() reports true
13
+ GPU-timeline duration instead of wall-clock time that includes async queuing.
14
+ """
15
+
16
+ def __init__(self, cuda_ok: bool) -> None:
17
+ self._cuda_ok = cuda_ok
18
+ self._marks: dict[str, tuple[torch.cuda.Event | None, float]] = {}
19
+
20
+ def mark(self, name: str) -> None:
21
+ ev: torch.cuda.Event | None = None
22
+ if self._cuda_ok:
23
+ ev = torch.cuda.Event(enable_timing=True) # type: ignore[no-untyped-call]
24
+ ev.record()
25
+ self._marks[name] = (ev, time.perf_counter())
26
+
27
+ def elapsed_ms(self, a: str, b: str) -> float:
28
+ ev_a, t_a = self._marks[a]
29
+ ev_b, t_b = self._marks[b]
30
+ if ev_a and ev_b:
31
+ return float(ev_a.elapsed_time(ev_b)) # true GPU-timeline ms
32
+ return (t_b - t_a) * 1000.0
33
+
34
+ def wall_start(self, name: str) -> float:
35
+ return self._marks[name][1]
36
+
37
+ def __contains__(self, name: str) -> bool:
38
+ return name in self._marks
39
+
40
+ def print_report(self, rows: list[tuple[str, str, str]], total: tuple[str, str] | None = None) -> None:
41
+ """Print elapsed_ms() for each (label, start_mark, end_mark) row that has both marks recorded.
42
+
43
+ If `total` is given as (start_mark, end_mark) and both are recorded, also prints an
44
+ "overhead" line (total minus the sum of the printed rows) and a grand total line.
45
+ """
46
+ if self._cuda_ok:
47
+ try:
48
+ torch.cuda.synchronize()
49
+ except Exception:
50
+ pass
51
+ total_ms = 0.0
52
+ lines = []
53
+ for label, a, b in rows:
54
+ if a in self._marks and b in self._marks:
55
+ ms = self.elapsed_ms(a, b)
56
+ total_ms += ms
57
+ lines.append(f"[timing] {label:<14} {ms:8.1f} ms")
58
+ if total and total[0] in self._marks and total[1] in self._marks:
59
+ overall_ms = self.elapsed_ms(*total)
60
+ lines.append(f"[timing] {'overhead':<14} {overall_ms - total_ms:8.1f} ms")
61
+ lines.append(f"[timing] {'── total ──':<14} {overall_ms:8.1f} ms")
62
+ print_timing_divider()
63
+ print_timing_lines(lines)
64
+ print_timing_divider()