Spaces:
Running on Zero
Running on Zero
Commit ·
f30b43a
1
Parent(s): 9fcf338
Add per-phase timing to pipeline call and text_encoder offload
Browse filesThe 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 +3 -0
- qwenimage/pipeline_qwenimage_edit_plus.py +23 -0
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,)
|