ImageStudio Maintainer Claude Opus 4.8 (1M context) commited on
Commit
c29b387
·
1 Parent(s): 657da31

fix(progress): stream per-step progress for anima image models

Browse files

The default Anime/Realistic models use the anima family (Cosmos-Predict2
modular pipeline), whose __call__ accepts no callback_on_step_end — so the
diffusers-style step callback never fired and generate_image yielded no
per-step frames. The web progress bar sat at 0% until the final upload.

Wrap the modular pipeline's scheduler.step (called once per denoise step)
to drive the same callback the queue-fed generator already reads, via a
_anima_step_progress context manager that restores the original method in a
finally (no leak across requests) and no-ops if scheduler/callback is absent.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files changed (1) hide show
  1. app.py +63 -11
app.py CHANGED
@@ -1,3 +1,4 @@
 
1
  import inspect
2
  import io
3
  import json
@@ -933,6 +934,47 @@ def _supports_step_callback(pipe):
933
  return False
934
 
935
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
936
  @spaces.GPU
937
  def generate_image(
938
  model_name,
@@ -1134,17 +1176,27 @@ def _generate_image_inner(
1134
  generator = torch.Generator("cuda").manual_seed(seed)
1135
  prompt = _apply_prefix(prompt, entry.get("prefix", ""))
1136
  negative_prompt = _resolve_negative(entry, negative_prompt, use_negative_prompt, model_name)
1137
- images = pipe(
1138
- prompt=prompt,
1139
- negative_prompt=negative_prompt or None,
1140
- height=int(height),
1141
- width=int(width),
1142
- num_inference_steps=int(num_inference_steps),
1143
- num_images_per_prompt=1,
1144
- generator=generator,
1145
- output_type="pil",
1146
- output="images",
1147
- )
 
 
 
 
 
 
 
 
 
 
1148
  image = images[0] if isinstance(images, (list, tuple)) else images
1149
  return image, seed
1150
 
 
1
+ import contextlib
2
  import inspect
3
  import io
4
  import json
 
934
  return False
935
 
936
 
937
+ @contextlib.contextmanager
938
+ def _anima_step_progress(pipe, callback, total_steps):
939
+ """Emit per-step progress for the modular Anima pipeline by wrapping its
940
+ scheduler's ``step``.
941
+
942
+ The modular (Cosmos-Predict2) pipeline's ``__call__`` doesn't accept
943
+ ``callback_on_step_end``, so the diffusers-style ``callback`` the rest of the
944
+ generator relies on never fires for the anima family. The denoise loop calls
945
+ ``scheduler.step`` exactly once per inference step, so wrapping it gives a
946
+ reliable per-step tick. We forward into the existing ``callback`` (shape:
947
+ ``callback(pipe, step, timestep, callback_kwargs)`` — only the 0-based
948
+ ``step`` is used downstream). The original method is always restored, so a
949
+ no-op (missing scheduler / callback) or an exception can't leave the shared
950
+ pipeline patched across requests.
951
+ """
952
+ sched = getattr(pipe, "scheduler", None)
953
+ orig = getattr(sched, "step", None) if sched is not None else None
954
+ if callback is None or orig is None:
955
+ yield
956
+ return
957
+
958
+ state = {"i": 0}
959
+
960
+ def _counting_step(*args, **kwargs):
961
+ out = orig(*args, **kwargs)
962
+ i = state["i"]
963
+ if i < total_steps:
964
+ state["i"] = i + 1
965
+ try:
966
+ callback(pipe, i, None, {})
967
+ except Exception: # noqa: BLE001 - progress must never break sampling
968
+ pass
969
+ return out
970
+
971
+ sched.step = _counting_step
972
+ try:
973
+ yield
974
+ finally:
975
+ sched.step = orig
976
+
977
+
978
  @spaces.GPU
979
  def generate_image(
980
  model_name,
 
1176
  generator = torch.Generator("cuda").manual_seed(seed)
1177
  prompt = _apply_prefix(prompt, entry.get("prefix", ""))
1178
  negative_prompt = _resolve_negative(entry, negative_prompt, use_negative_prompt, model_name)
1179
+ # Per-step progress: the modular pipeline doesn't accept
1180
+ # `callback_on_step_end`, so we can't wire the callback the way the
1181
+ # illustrious / zimageturbo branches do. Instead wrap the scheduler's
1182
+ # `step` (called exactly once per denoise step) to drive the same
1183
+ # callback the queue-fed generator reads. Without this the anima family
1184
+ # — which backs the *default* Anime/Realistic models — emits no
1185
+ # sampling frames, so the web progress bar sits at 0% until the final
1186
+ # upload. Restored in a finally so a patched method never leaks across
1187
+ # requests sharing this pipeline.
1188
+ with _anima_step_progress(pipe, callback, int(num_inference_steps)):
1189
+ images = pipe(
1190
+ prompt=prompt,
1191
+ negative_prompt=negative_prompt or None,
1192
+ height=int(height),
1193
+ width=int(width),
1194
+ num_inference_steps=int(num_inference_steps),
1195
+ num_images_per_prompt=1,
1196
+ generator=generator,
1197
+ output_type="pil",
1198
+ output="images",
1199
+ )
1200
  image = images[0] if isinstance(images, (list, tuple)) else images
1201
  return image, seed
1202