Spaces:
Running on Zero
Running on Zero
Update app.py
Browse files
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(
|
|
|
|
|
|
|
|
|
|
| 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
|
|
|
|
|
|
|
|
|
|
| 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 {
|
| 783 |
call = (
|
| 784 |
prompt_embeds,
|
| 785 |
text_token_tags,
|
|
@@ -788,7 +857,7 @@ def generate(
|
|
| 788 |
height,
|
| 789 |
width,
|
| 790 |
num_frames,
|
| 791 |
-
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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__":
|