someone-in-the-world commited on
Commit
f30b43a
·
1 Parent(s): 9fcf338

Add per-phase timing to pipeline call and text_encoder offload

Browse files

The existing vae_decode timing bucket (last_step -> pipe_end) lumps
together the text_encoder CPU offload, latent unpacking, actual VAE
decode, and postprocessing, making it look like decode itself is slow.
Split each phase out with its own timestamped log line so the real
cost can be seen directly instead of inferred.

app.py CHANGED
@@ -545,8 +545,11 @@ def _make_step_callback(steps, timer, t0):
545
  # accelerate hooks are managing placement (offload-fallback path) to avoid
546
  # fighting their own device bookkeeping — see the finally block below.
547
  if step_idx == steps - 1 and getattr(pipeline.text_encoder, "_hf_hook", None) is None:
 
548
  pipeline.text_encoder.to("cpu")
549
  torch.cuda.empty_cache()
 
 
550
  delta_ms = (now - (step_times[-2] if len(step_times) > 1 else t0)) * 1000
551
  tag = " ← includes cold-start (offload hook install + first weight transfer)" if step_idx == 0 else ""
552
  print(f"[infer] step {step_idx+1}/{steps} done — {delta_ms:.0f}ms{tag} | t={now-t0:.1f}s")
 
545
  # accelerate hooks are managing placement (offload-fallback path) to avoid
546
  # fighting their own device bookkeeping — see the finally block below.
547
  if step_idx == steps - 1 and getattr(pipeline.text_encoder, "_hf_hook", None) is None:
548
+ _offload_t0 = time.perf_counter()
549
  pipeline.text_encoder.to("cpu")
550
  torch.cuda.empty_cache()
551
+ _offload_ms = (time.perf_counter() - _offload_t0) * 1000
552
+ print(f"[infer] text_encoder offload to cpu — {_offload_ms:.0f}ms | t={time.perf_counter()-t0:.1f}s")
553
  delta_ms = (now - (step_times[-2] if len(step_times) > 1 else t0)) * 1000
554
  tag = " ← includes cold-start (offload hook install + first weight transfer)" if step_idx == 0 else ""
555
  print(f"[infer] step {step_idx+1}/{steps} done — {delta_ms:.0f}ms{tag} | t={now-t0:.1f}s")
qwenimage/pipeline_qwenimage_edit_plus.py CHANGED
@@ -14,6 +14,7 @@
14
 
15
  import inspect
16
  import math
 
17
  from typing import Any, Callable, Dict, List, Optional, Union
18
 
19
  import numpy as np
@@ -664,6 +665,19 @@ class QwenImageEditPlusPipeline(DiffusionPipeline, QwenImageLoraLoaderMixin):
664
  batch_size = prompt_embeds.shape[0]
665
 
666
  device = self._execution_device
 
 
 
 
 
 
 
 
 
 
 
 
 
667
  # 3. Preprocess image
668
  if image is not None and not (isinstance(image, torch.Tensor) and image.size(1) == self.latent_channels):
669
  if not isinstance(image, list):
@@ -682,6 +696,7 @@ class QwenImageEditPlusPipeline(DiffusionPipeline, QwenImageLoraLoaderMixin):
682
  vae_image_sizes.append((vae_width, vae_height))
683
  condition_images.append(self.image_processor.resize(img, condition_height, condition_width))
684
  vae_images.append(self.image_processor.preprocess(img, vae_height, vae_width).unsqueeze(2))
 
685
 
686
  has_neg_prompt = negative_prompt is not None or (
687
  negative_prompt_embeds is not None and negative_prompt_embeds_mask is not None
@@ -716,6 +731,7 @@ class QwenImageEditPlusPipeline(DiffusionPipeline, QwenImageLoraLoaderMixin):
716
  num_images_per_prompt=num_images_per_prompt,
717
  max_sequence_length=max_sequence_length,
718
  )
 
719
 
720
  # 4. Prepare latent variables
721
  num_channels_latents = self.transformer.config.in_channels // 4
@@ -730,6 +746,7 @@ class QwenImageEditPlusPipeline(DiffusionPipeline, QwenImageLoraLoaderMixin):
730
  generator,
731
  latents,
732
  )
 
733
  img_shapes = [
734
  [
735
  (1, height // self.vae_scale_factor // 2, width // self.vae_scale_factor // 2),
@@ -791,6 +808,7 @@ class QwenImageEditPlusPipeline(DiffusionPipeline, QwenImageLoraLoaderMixin):
791
  )
792
  else:
793
  uncond_image_rotary_emb = None
 
794
 
795
  # 6. Denoising loop
796
  self.scheduler.set_begin_index(0)
@@ -863,6 +881,7 @@ class QwenImageEditPlusPipeline(DiffusionPipeline, QwenImageLoraLoaderMixin):
863
 
864
  if XLA_AVAILABLE:
865
  xm.mark_step()
 
866
 
867
  self._current_timestep = None
868
  if output_type == "latent":
@@ -879,11 +898,15 @@ class QwenImageEditPlusPipeline(DiffusionPipeline, QwenImageLoraLoaderMixin):
879
  latents.device, latents.dtype
880
  )
881
  latents = latents / latents_std + latents_mean
 
882
  image = self.vae.decode(latents, return_dict=False)[0][:, :, 0]
 
883
  image = self.image_processor.postprocess(image, output_type=output_type)
 
884
 
885
  # Offload all models
886
  self.maybe_free_model_hooks()
 
887
 
888
  if not return_dict:
889
  return (image,)
 
14
 
15
  import inspect
16
  import math
17
+ import time
18
  from typing import Any, Callable, Dict, List, Optional, Union
19
 
20
  import numpy as np
 
665
  batch_size = prompt_embeds.shape[0]
666
 
667
  device = self._execution_device
668
+
669
+ # Per-phase timing, independent of app.py's step-level instrumentation — isolates
670
+ # where GPU time actually goes inside a single pipeline call (image/text encoding,
671
+ # denoising loop, VAE decode, postprocess) instead of lumping everything after the
672
+ # last denoising step into one opaque "vae_decode" bucket.
673
+ _phase_t0 = time.perf_counter()
674
+ _phase_last = [_phase_t0]
675
+
676
+ def _mark(label):
677
+ now = time.perf_counter()
678
+ print(f"[pipe] {label} — {(now - _phase_last[0]) * 1000:.0f}ms | t={now - _phase_t0:.1f}s", flush=True)
679
+ _phase_last[0] = now
680
+
681
  # 3. Preprocess image
682
  if image is not None and not (isinstance(image, torch.Tensor) and image.size(1) == self.latent_channels):
683
  if not isinstance(image, list):
 
696
  vae_image_sizes.append((vae_width, vae_height))
697
  condition_images.append(self.image_processor.resize(img, condition_height, condition_width))
698
  vae_images.append(self.image_processor.preprocess(img, vae_height, vae_width).unsqueeze(2))
699
+ _mark("image_preprocess")
700
 
701
  has_neg_prompt = negative_prompt is not None or (
702
  negative_prompt_embeds is not None and negative_prompt_embeds_mask is not None
 
731
  num_images_per_prompt=num_images_per_prompt,
732
  max_sequence_length=max_sequence_length,
733
  )
734
+ _mark("encode_prompt")
735
 
736
  # 4. Prepare latent variables
737
  num_channels_latents = self.transformer.config.in_channels // 4
 
746
  generator,
747
  latents,
748
  )
749
+ _mark("prepare_latents (incl. condition-image VAE encode)")
750
  img_shapes = [
751
  [
752
  (1, height // self.vae_scale_factor // 2, width // self.vae_scale_factor // 2),
 
808
  )
809
  else:
810
  uncond_image_rotary_emb = None
811
+ _mark("timesteps_and_rope_setup")
812
 
813
  # 6. Denoising loop
814
  self.scheduler.set_begin_index(0)
 
881
 
882
  if XLA_AVAILABLE:
883
  xm.mark_step()
884
+ _mark("denoise_loop")
885
 
886
  self._current_timestep = None
887
  if output_type == "latent":
 
898
  latents.device, latents.dtype
899
  )
900
  latents = latents / latents_std + latents_mean
901
+ _mark("unpack_and_upcast_latents")
902
  image = self.vae.decode(latents, return_dict=False)[0][:, :, 0]
903
+ _mark("vae_decode")
904
  image = self.image_processor.postprocess(image, output_type=output_type)
905
+ _mark("postprocess")
906
 
907
  # Offload all models
908
  self.maybe_free_model_hooks()
909
+ _mark("free_model_hooks")
910
 
911
  if not return_dict:
912
  return (image,)