dagloop5 commited on
Commit
101e02b
·
verified ·
1 Parent(s): abeea88

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +118 -6
app.py CHANGED
@@ -11,6 +11,7 @@ transformer and the two autoencoders. `prompt_embeds` + `text_token_tags` is the
11
 
12
  from __future__ import annotations
13
 
 
14
  import os
15
  import tempfile
16
  import time
@@ -154,6 +155,9 @@ INTERPOLATION = {"off · 24 fps": 1, "2x · 48 fps (PlagueKind)": 2, "4x · 96 f
154
  DEFAULT_INTERPOLATION = "2x · 48 fps (PlagueKind)"
155
  DEFAULT_SHARPEN = 0.3
156
  DEFAULT_STEPS = 15
 
 
 
157
 
158
 
159
  def snap_frames(seconds: float) -> int:
@@ -615,6 +619,10 @@ def _generate(
615
  video_shift,
616
  audio_shift,
617
  sampler,
 
 
 
 
618
  ):
619
  """The only thing on GPU time: the denoise loop, the two decoders and the workflow's post chain.
620
  The mp4 is muxed here rather than in the caller: a `@spaces.GPU` return crosses a process boundary by pickling,
@@ -661,10 +669,22 @@ def _generate(
661
  requested_steps = steps if custom_schedule else steps + 1
662
 
663
  started = time.time()
664
- with pk.use_schedule(PIPE, steps, schedule, video_shift, audio_shift, sampler_name=sampler, seed=int(seed)):
 
 
 
665
  with use_dpmpp_2s_ancestral(PIPE, int(seed), enabled=(sampler == "dpmpp_2s_ancestral")):
666
  with use_dpmpp_sde_gpu(PIPE, int(seed), enabled=(sampler == "dpmpp_sde_gpu")):
667
  with use_seeds_2(PIPE, int(seed), enabled=(sampler == "seeds_2")):
 
 
 
 
 
 
 
 
 
668
  state = PIPE(
669
  prompt_embeds=prompt_embeds.to("cuda"),
670
  text_token_tags=text_token_tags,
@@ -676,6 +696,7 @@ def _generate(
676
  num_inference_steps=requested_steps,
677
  output_type="pt",
678
  generator=torch.Generator("cpu").manual_seed(int(seed)),
 
679
  )
680
 
681
  denoised = time.time() - started
@@ -683,6 +704,10 @@ def _generate(
683
  video = state.get("videos")[0] # (frames, 3, H, W), float in [0, 1], on the card
684
  audio = state.get("audio")[0].cpu()
685
  sampling_rate = state.get("sampling_rate")
 
 
 
 
686
  del state
687
  # The post chain runs on the allocator the denoise loop just left fragmented (78.5 GiB at the full canvas), and
688
  # RCAS and FILM both want a few contiguous gigabytes.
@@ -707,7 +732,10 @@ def _generate(
707
  encode_video(frames, fps=fps, output_path=path, audio=audio, audio_sample_rate=sampling_rate)
708
 
709
  # `booked` to here is what `get_duration` had to predict, so it is what the report prints it against.
710
- return path, denoised, post_seconds, time.time() - booked, int(frames.shape[0]), fps, multiplier
 
 
 
711
 
712
 
713
  def generate(
@@ -736,10 +764,23 @@ def generate(
736
  video_shift=DEFAULT_VIDEO_SHIFT,
737
  audio_shift=DEFAULT_AUDIO_SHIFT,
738
  sampler=DEFAULT_SAMPLER,
 
 
 
739
  progress=gr.Progress(track_tqdm=True),
 
 
740
  ):
741
  """One request through the PlagueKind graph. Every parameter but the prompt carries the default its UI
742
- component carries, so an example that fills only `prompt` (and `canvas`) behaves exactly like the button."""
 
 
 
 
 
 
 
 
743
  if LOAD_ERROR:
744
  raise gr.Error(LOAD_ERROR)
745
  if PIPE is None:
@@ -754,6 +795,26 @@ def generate(
754
  multiplier = INTERPOLATION.get(interpolation, 2)
755
  num_frames = snap_frames(duration)
756
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
757
  progress(
758
  0.0,
759
  desc=(
@@ -770,6 +831,14 @@ def generate(
770
  height, width, num_frames = (int(metadata[key]) for key in ("height", "width", "num_frames"))
771
  refined = plan.get("refined_prompt") or ""
772
 
 
 
 
 
 
 
 
 
773
  def keyframe(path):
774
  # The conditioning latents encoded here have to be of the image the conditioner looked at, which it
775
  # prepares exactly this way.
@@ -779,7 +848,7 @@ def generate(
779
  # needs wiring in; `_generate`, `set_adapters`, and the report line below are all keyed off this dict.
780
  lora_strengths = {"lora1": float(lora_1_strength), "lorah": float(lora_h_strength), "lorai": float(lora_i_strength), "loraa": float(lora_a_strength), "lorab": float(lora_b_strength), "lorac": float(lora_c_strength), "lorad": float(lora_d_strength), "lorae": float(lora_e_strength), "loraf": float(lora_f_strength), "lorag": float(lora_g_strength)}
781
 
782
- progress(0.1, desc=f"Denoising {int(steps)} steps at {width}x{height}, {num_frames} frames ...")
783
  call = (
784
  prompt_embeds,
785
  text_token_tags,
@@ -788,7 +857,7 @@ def generate(
788
  height,
789
  width,
790
  num_frames,
791
- int(steps),
792
  schedule_key,
793
  float(sharpen),
794
  multiplier,
@@ -798,6 +867,10 @@ def generate(
798
  float(video_shift),
799
  float(audio_shift),
800
  SAMPLERS.get(sampler, "euler"),
 
 
 
 
801
  )
802
  # The same call `spaces` will book the worker with, so the report can show the fit against the measurement.
803
  booked_seconds = get_duration(*call)
@@ -1112,6 +1185,27 @@ with gr.Blocks(title="PlagueKind · MiniMax-H3") as demo:
1112
  value=DEFAULT_LORA_G_STRENGTH,
1113
  )
1114
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1115
  first_frame.upload(_fit_keyframe, [first_frame, canvas], [first_frame, canvas])
1116
  last_frame.upload(_fit_keyframe, [last_frame, canvas], [last_frame, canvas])
1117
 
@@ -1126,6 +1220,13 @@ with gr.Blocks(title="PlagueKind · MiniMax-H3") as demo:
1126
  last_frame_timestamp.change(
1127
  _extract_frame, [video, last_frame_timestamp], last_frame, show_progress="hidden"
1128
  )
 
 
 
 
 
 
 
1129
 
1130
  controls = [
1131
  prompt,
@@ -1153,9 +1254,20 @@ with gr.Blocks(title="PlagueKind · MiniMax-H3") as demo:
1153
  video_shift,
1154
  audio_shift,
1155
  sampler,
 
 
 
1156
  ]
1157
 
1158
- run.click(generate, controls, [video, report], api_name="generate")
 
 
 
 
 
 
 
 
1159
 
1160
 
1161
  if __name__ == "__main__":
 
11
 
12
  from __future__ import annotations
13
 
14
+ import functools
15
  import os
16
  import tempfile
17
  import time
 
155
  DEFAULT_INTERPOLATION = "2x · 48 fps (PlagueKind)"
156
  DEFAULT_SHARPEN = 0.3
157
  DEFAULT_STEPS = 15
158
+ # Staged Denoising: an arbitrary, adjustable starting point for the "Target total steps" slider — the total the
159
+ # fixed schedule is built at, walked across however many "Advance" presses it takes at "Steps" steps per press.
160
+ DEFAULT_TARGET_STEPS = 25
161
 
162
 
163
  def snap_frames(seconds: float) -> int:
 
619
  video_shift,
620
  audio_shift,
621
  sampler,
622
+ total_steps,
623
+ stage_from,
624
+ resume_video_latents,
625
+ resume_audio_latents,
626
  ):
627
  """The only thing on GPU time: the denoise loop, the two decoders and the workflow's post chain.
628
  The mp4 is muxed here rather than in the caller: a `@spaces.GPU` return crosses a process boundary by pickling,
 
669
  requested_steps = steps if custom_schedule else steps + 1
670
 
671
  started = time.time()
672
+ with pk.use_schedule(
673
+ PIPE, steps, schedule, video_shift, audio_shift, sampler_name=sampler, seed=int(seed),
674
+ total_steps=total_steps, stage_from=stage_from,
675
+ ):
676
  with use_dpmpp_2s_ancestral(PIPE, int(seed), enabled=(sampler == "dpmpp_2s_ancestral")):
677
  with use_dpmpp_sde_gpu(PIPE, int(seed), enabled=(sampler == "dpmpp_sde_gpu")):
678
  with use_seeds_2(PIPE, int(seed), enabled=(sampler == "seeds_2")):
679
+ # Staged Denoising: resuming hands the pipeline the previous stage's own latents instead of
680
+ # letting `PrepareLatentsStep` draw fresh noise — both are declared-optional inputs on that
681
+ # step precisely for this ("used instead of the draw"), so nothing else about the call
682
+ # changes. `resume_video_latents is None` is exactly the unstaged, fresh-start case.
683
+ resume_kwargs = (
684
+ {"latents": resume_video_latents.to("cuda"), "audio_latents": resume_audio_latents.to("cuda")}
685
+ if resume_video_latents is not None
686
+ else {}
687
+ )
688
  state = PIPE(
689
  prompt_embeds=prompt_embeds.to("cuda"),
690
  text_token_tags=text_token_tags,
 
696
  num_inference_steps=requested_steps,
697
  output_type="pt",
698
  generator=torch.Generator("cpu").manual_seed(int(seed)),
699
+ **resume_kwargs,
700
  )
701
 
702
  denoised = time.time() - started
 
704
  video = state.get("videos")[0] # (frames, 3, H, W), float in [0, 1], on the card
705
  audio = state.get("audio")[0].cpu()
706
  sampling_rate = state.get("sampling_rate")
707
+ # Staged Denoising: this stage's own final latents, ahead of decode — the state a later "Advance" press
708
+ # resumes from. Computed unconditionally; harmless and cheap when staging isn't in use.
709
+ stage_video_latents = state.get("latents").cpu()
710
+ stage_audio_latents = state.get("audio_latents").cpu()
711
  del state
712
  # The post chain runs on the allocator the denoise loop just left fragmented (78.5 GiB at the full canvas), and
713
  # RCAS and FILM both want a few contiguous gigabytes.
 
732
  encode_video(frames, fps=fps, output_path=path, audio=audio, audio_sample_rate=sampling_rate)
733
 
734
  # `booked` to here is what `get_duration` had to predict, so it is what the report prints it against.
735
+ return (
736
+ path, denoised, post_seconds, time.time() - booked, int(frames.shape[0]), fps, multiplier,
737
+ stage_video_latents, stage_audio_latents,
738
+ )
739
 
740
 
741
  def generate(
 
764
  video_shift=DEFAULT_VIDEO_SHIFT,
765
  audio_shift=DEFAULT_AUDIO_SHIFT,
766
  sampler=DEFAULT_SAMPLER,
767
+ stage_enabled=False,
768
+ target_steps=DEFAULT_TARGET_STEPS,
769
+ stage_state=None,
770
  progress=gr.Progress(track_tqdm=True),
771
+ *,
772
+ advance: bool = False,
773
  ):
774
  """One request through the PlagueKind graph. Every parameter but the prompt carries the default its UI
775
+ component carries, so an example that fills only `prompt` (and `canvas`) behaves exactly like the button.
776
+
777
+ `advance` isn't a UI control — it's bound per-button via `functools.partial` (`False` for "Generate", `True`
778
+ for "Advance") so the two share this one function rather than duplicating the conditioning/report logic.
779
+ Staged Denoising, debugging-only, unlocked: nothing here stops the prompt, canvas, sampler, schedule, or
780
+ shift from changing between an "Advance" press and the stage before it — the only samplers actually reasoned
781
+ through for exact-vs-different-but-equal-quality resume behavior are `euler`, `euler_ancestral`, `seeds_2`,
782
+ and `dpmpp_2s_ancestral`; the SDE-family samplers are untested here and not recommended.
783
+ """
784
  if LOAD_ERROR:
785
  raise gr.Error(LOAD_ERROR)
786
  if PIPE is None:
 
795
  multiplier = INTERPOLATION.get(interpolation, 2)
796
  num_frames = snap_frames(duration)
797
 
798
+ if stage_enabled and schedule_key == "native":
799
+ raise gr.Error(
800
+ "Staged Denoising needs a named sigma schedule, not `native` — the stage boundary is a slice of a "
801
+ "schedule this Space builds itself, and the pipeline's own default schedule isn't one this Space "
802
+ "controls the construction of."
803
+ )
804
+ if advance and stage_state is None:
805
+ raise gr.Error("Press Generate with Staged Denoising enabled first, to start a staged sequence.")
806
+ steps_done = int(stage_state["steps_done"]) if (advance and stage_state) else 0
807
+ if advance:
808
+ remaining = int(target_steps) - steps_done
809
+ if remaining <= 0:
810
+ raise gr.Error(
811
+ f"Already at or past the target step count ({steps_done}/{int(target_steps)}). Raise "
812
+ f"'Target total steps' to continue."
813
+ )
814
+ this_stage_steps = min(int(steps), remaining)
815
+ else:
816
+ this_stage_steps = int(steps)
817
+
818
  progress(
819
  0.0,
820
  desc=(
 
831
  height, width, num_frames = (int(metadata[key]) for key in ("height", "width", "num_frames"))
832
  refined = plan.get("refined_prompt") or ""
833
 
834
+ if advance and (height, width, num_frames) != (
835
+ int(stage_state["height"]), int(stage_state["width"]), int(stage_state["num_frames"])
836
+ ):
837
+ raise gr.Error(
838
+ "Canvas or duration resolved differently than the staged sequence's first stage — both have to stay "
839
+ "fixed across a staged sequence, since they determine the saved latents' shape."
840
+ )
841
+
842
  def keyframe(path):
843
  # The conditioning latents encoded here have to be of the image the conditioner looked at, which it
844
  # prepares exactly this way.
 
848
  # needs wiring in; `_generate`, `set_adapters`, and the report line below are all keyed off this dict.
849
  lora_strengths = {"lora1": float(lora_1_strength), "lorah": float(lora_h_strength), "lorai": float(lora_i_strength), "loraa": float(lora_a_strength), "lorab": float(lora_b_strength), "lorac": float(lora_c_strength), "lorad": float(lora_d_strength), "lorae": float(lora_e_strength), "loraf": float(lora_f_strength), "lorag": float(lora_g_strength)}
850
 
851
+ progress(0.1, desc=f"Denoising {this_stage_steps} steps at {width}x{height}, {num_frames} frames ...")
852
  call = (
853
  prompt_embeds,
854
  text_token_tags,
 
857
  height,
858
  width,
859
  num_frames,
860
+ this_stage_steps,
861
  schedule_key,
862
  float(sharpen),
863
  multiplier,
 
867
  float(video_shift),
868
  float(audio_shift),
869
  SAMPLERS.get(sampler, "euler"),
870
+ int(target_steps) if stage_enabled else None,
871
+ steps_done if advance else 0,
872
+ stage_state["video_latents"] if advance else None,
873
+ stage_state["audio_latents"] if advance else None,
874
  )
875
  # The same call `spaces` will book the worker with, so the report can show the fit against the measurement.
876
  booked_seconds = get_duration(*call)
 
1185
  value=DEFAULT_LORA_G_STRENGTH,
1186
  )
1187
 
1188
+ with gr.Accordion("Staged Denoising", open=False):
1189
+ gr.Markdown(
1190
+ "**Debugging feature — not for the SDE-family samplers** (`dpmpp_2m_sde_gpu`, "
1191
+ "`dpmpp_3m_sde_gpu`, `dpmpp_sde_gpu`). Splits one long denoise into several cheaper requests: "
1192
+ "run the first stage with **Generate**, then **Advance** to keep denoising the same latents "
1193
+ "further, as many times as needed to reach the target."
1194
+ )
1195
+ stage_enabled = gr.Checkbox(label="Enable staged denoising", value=False)
1196
+ target_steps = gr.Slider(
1197
+ label="Target total steps",
1198
+ minimum=4,
1199
+ maximum=100,
1200
+ step=1,
1201
+ value=DEFAULT_TARGET_STEPS,
1202
+ visible=False,
1203
+ info="The fixed schedule's total length — 'Steps' above is how many of these one press runs.",
1204
+ )
1205
+ advance_btn = gr.Button("Advance", variant="secondary", visible=False)
1206
+
1207
+ stage_state = gr.State(None)
1208
+
1209
  first_frame.upload(_fit_keyframe, [first_frame, canvas], [first_frame, canvas])
1210
  last_frame.upload(_fit_keyframe, [last_frame, canvas], [last_frame, canvas])
1211
 
 
1220
  last_frame_timestamp.change(
1221
  _extract_frame, [video, last_frame_timestamp], last_frame, show_progress="hidden"
1222
  )
1223
+
1224
+ stage_enabled.change(
1225
+ lambda enabled: (gr.update(visible=enabled), gr.update(visible=enabled)),
1226
+ stage_enabled,
1227
+ [target_steps, advance_btn],
1228
+ api_name=False,
1229
+ )
1230
 
1231
  controls = [
1232
  prompt,
 
1254
  video_shift,
1255
  audio_shift,
1256
  sampler,
1257
+ stage_enabled,
1258
+ target_steps,
1259
+ stage_state,
1260
  ]
1261
 
1262
+ # `functools.partial` binds `advance` by keyword regardless of its position in `generate`'s signature — the
1263
+ # two buttons share every other line of conditioning/report logic and differ only in this one flag.
1264
+ run.click(
1265
+ functools.partial(generate, advance=False), controls, [video, report, stage_state], api_name="generate"
1266
+ )
1267
+ advance_btn.click(
1268
+ functools.partial(generate, advance=True), controls, [video, report, stage_state],
1269
+ api_name="generate_advance",
1270
+ )
1271
 
1272
 
1273
  if __name__ == "__main__":