Spaces:
Running on Zero
Running on Zero
Update app.py
Browse files
app.py
CHANGED
|
@@ -91,6 +91,9 @@ SCHEDULES = {
|
|
| 91 |
DEFAULT_SCHEDULE = "linear_quadratic · PlagueKind"
|
| 92 |
DEFAULT_VIDEO_SHIFT = 12.0
|
| 93 |
DEFAULT_AUDIO_SHIFT = 3.0
|
|
|
|
|
|
|
|
|
|
| 94 |
|
| 95 |
FPS, FRAMES_PER_CHUNK, LATENTS_PER_CHUNK = 24, 17, 5
|
| 96 |
# It is the *snapped* frame count the ceiling has to hold for: 15 s is 360 frames, which rounds up to 362, i.e.
|
|
@@ -113,7 +116,12 @@ STEP_LINEAR, STEP_QUADRATIC, SAFETY = 1.1745e-4, 3.8396e-9, 1.3
|
|
| 113 |
PLACEMENT_ALLOWANCE = int(os.environ.get("H3_PLACEMENT_ALLOWANCE", "8"))
|
| 114 |
AUDIO_LATENTS_PER_SECOND, AUDIO_CHANNELS = 40, 2
|
| 115 |
REFERENCE_IMAGE_SHORT_EDGE, CANVAS_MULTIPLE = 2048, 32
|
| 116 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
|
| 118 |
|
| 119 |
def snap_frames(seconds: float) -> int:
|
|
@@ -187,7 +195,8 @@ def reference_rows(references: list[tuple[str, str]], num_frames: int) -> int:
|
|
| 187 |
|
| 188 |
|
| 189 |
def get_duration(
|
| 190 |
-
prompt_embeds, text_token_tags, references, height, width, num_frames, steps, seed,
|
|
|
|
| 191 |
):
|
| 192 |
"""Seconds of GPU to reserve for one request. Takes the arguments of the `@spaces.GPU` function it decorates, and
|
| 193 |
tolerates the `gr.Progress` `spaces` injects."""
|
|
@@ -199,7 +208,16 @@ def get_duration(
|
|
| 199 |
# they are handed rather than with the step count.
|
| 200 |
encode = 5 + reference_rows(references, num_frames) * 1e-3
|
| 201 |
decode = DECODE_BASE + DECODE_PER_DEFAULT_CANVAS * (height * width * num_frames) / DEFAULT_CANVAS_PIXELS
|
| 202 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 203 |
duration = max(MIN_GPU_DURATION, min(MAX_GPU_DURATION, int(total)))
|
| 204 |
print(f"[ref2va] S={sequence} -> reserving {duration}s ({denoise:.0f}s of denoise at {steps} steps)", flush=True)
|
| 205 |
return duration
|
|
@@ -463,29 +481,62 @@ def encode_remote(prompt, references, canvas, num_frames, rewrite_prompt=False):
|
|
| 463 |
|
| 464 |
|
| 465 |
@spaces.GPU(duration=get_duration, size=GPU_SIZE)
|
| 466 |
-
def _generate(
|
| 467 |
-
|
| 468 |
-
|
| 469 |
-
|
| 470 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 471 |
"""
|
| 472 |
import torch
|
|
|
|
|
|
|
|
|
|
| 473 |
if PLACEMENT == "lazy":
|
| 474 |
PIPE.to("cuda")
|
| 475 |
elif PLACEMENT == "pack":
|
| 476 |
PIPE.vae.to("cuda")
|
| 477 |
PIPE.audio_vae.to("cuda")
|
| 478 |
-
|
| 479 |
-
|
| 480 |
-
|
| 481 |
-
|
| 482 |
-
|
| 483 |
-
|
| 484 |
-
|
| 485 |
-
|
| 486 |
-
|
| 487 |
-
|
| 488 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 489 |
|
| 490 |
|
| 491 |
def generate(
|
|
@@ -507,6 +558,12 @@ def generate(
|
|
| 507 |
steps=28,
|
| 508 |
seed=42,
|
| 509 |
upsample=False,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 510 |
progress=gr.Progress(track_tqdm=True),
|
| 511 |
):
|
| 512 |
"""One request."""
|
|
@@ -525,6 +582,9 @@ def generate(
|
|
| 525 |
# soundtrack. The conditioner resolves it either way and this Space pins whatever comes back.
|
| 526 |
derivable = len(audio_bearing(references)) == 1
|
| 527 |
requested = 0 if (match and derivable) else snap_frames(duration)
|
|
|
|
|
|
|
|
|
|
| 528 |
|
| 529 |
progress(0.0, desc="Upsampling the prompt ..." if upsample else "Reading the prompt and references ...")
|
| 530 |
conditioned = time.time()
|
|
@@ -548,19 +608,21 @@ def generate(
|
|
| 548 |
|
| 549 |
progress(0.1, desc=f"Generating {num_frames / FPS:.1f} s at {width}x{height} ...")
|
| 550 |
started = time.time()
|
| 551 |
-
|
| 552 |
-
prompt_embeds, text_token_tags, references, height, width, num_frames, steps, seed
|
|
|
|
| 553 |
)
|
| 554 |
generate_seconds = time.time() - started
|
| 555 |
|
| 556 |
directory = os.path.join(tempfile.gettempdir(), "h3-outputs")
|
| 557 |
os.makedirs(directory, exist_ok=True)
|
| 558 |
path = os.path.join(directory, f"h3-ref2va-{int(time.time() * 1000)}.mp4")
|
| 559 |
-
encode_video(
|
| 560 |
|
| 561 |
print(
|
| 562 |
f"[ref2va] {[kind for kind, _ in references]} · `{width}x{height}`, {num_frames} frames "
|
| 563 |
-
f"({num_frames / FPS:.3f} s), {int(steps)} steps ·
|
|
|
|
| 564 |
f"({plan['num_text_tokens']} tokens{', upsampled' if refined else ''}) · "
|
| 565 |
f"denoise + decode {generate_seconds:.0f}s "
|
| 566 |
f"({generate_seconds / int(steps):.1f} s/step) · seed {int(seed)}",
|
|
@@ -648,6 +710,20 @@ with gr.Blocks(title="MiniMax-H3 Reference Custom Lora") as demo:
|
|
| 648 |
audio_shift = gr.Slider(
|
| 649 |
label="Audio shift", minimum=0.5, maximum=20.0, step=0.5, value=DEFAULT_AUDIO_SHIFT
|
| 650 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 651 |
seed = gr.Number(label="Seed", value=42, precision=0)
|
| 652 |
|
| 653 |
with gr.Column():
|
|
@@ -679,7 +755,8 @@ with gr.Blocks(title="MiniMax-H3 Reference Custom Lora") as demo:
|
|
| 679 |
video.change(auto_canvas, video, canvas, show_progress="hidden", api_name=False)
|
| 680 |
|
| 681 |
request = [
|
| 682 |
-
prompt, images[0], audio, video, canvas, *images[1:], match, duration, steps, seed, upsample
|
|
|
|
| 683 |
]
|
| 684 |
run.click(generate, request, [result, upsampled, upsampled_panel], api_name="generate")
|
| 685 |
|
|
|
|
| 91 |
DEFAULT_SCHEDULE = "linear_quadratic · PlagueKind"
|
| 92 |
DEFAULT_VIDEO_SHIFT = 12.0
|
| 93 |
DEFAULT_AUDIO_SHIFT = 3.0
|
| 94 |
+
DEFAULT_SHARPEN = 0.3
|
| 95 |
+
INTERPOLATION = {"off · 24 fps": 1, "2x · 48 fps (PlagueKind)": 2, "4x · 96 fps": 4}
|
| 96 |
+
DEFAULT_INTERPOLATION = "2x · 48 fps (PlagueKind)"
|
| 97 |
|
| 98 |
FPS, FRAMES_PER_CHUNK, LATENTS_PER_CHUNK = 24, 17, 5
|
| 99 |
# It is the *snapped* frame count the ceiling has to hold for: 15 s is 360 frames, which rounds up to 362, i.e.
|
|
|
|
| 116 |
PLACEMENT_ALLOWANCE = int(os.environ.get("H3_PLACEMENT_ALLOWANCE", "8"))
|
| 117 |
AUDIO_LATENTS_PER_SECOND, AUDIO_CHANNELS = 40, 2
|
| 118 |
REFERENCE_IMAGE_SHORT_EDGE, CANVAS_MULTIPLE = 2048, 32
|
| 119 |
+
DDECODE_BASE, DECODE_PER_DEFAULT_CANVAS, DEFAULT_CANVAS_PIXELS = 15, 25, 960 * 544 * 124
|
| 120 |
+
# The workflow's post chain. RCAS is a handful of elementwise passes over the clip; FILM is per *emitted*
|
| 121 |
+
# intermediate frame; the h264 mux is per frame actually written. Ported from the fl2va Space's fitted constants
|
| 122 |
+
# as a starting point, same caveat as `PLACEMENT_ALLOWANCE` above — worth checking booked-vs-actual here
|
| 123 |
+
# specifically once this is testable.
|
| 124 |
+
_POST_BASE, _FILM_PER_FRAME, _MUX_PER_FRAME = 2.0, 0.025, 0.02
|
| 125 |
|
| 126 |
|
| 127 |
def snap_frames(seconds: float) -> int:
|
|
|
|
| 195 |
|
| 196 |
|
| 197 |
def get_duration(
|
| 198 |
+
prompt_embeds, text_token_tags, references, height, width, num_frames, steps, seed,
|
| 199 |
+
sampler, schedule, video_shift, audio_shift, sharpen, multiplier, **_
|
| 200 |
):
|
| 201 |
"""Seconds of GPU to reserve for one request. Takes the arguments of the `@spaces.GPU` function it decorates, and
|
| 202 |
tolerates the `gr.Progress` `spaces` injects."""
|
|
|
|
| 208 |
# they are handed rather than with the step count.
|
| 209 |
encode = 5 + reference_rows(references, num_frames) * 1e-3
|
| 210 |
decode = DECODE_BASE + DECODE_PER_DEFAULT_CANVAS * (height * width * num_frames) / DEFAULT_CANVAS_PIXELS
|
| 211 |
+
|
| 212 |
+
multiplier = max(1, int(multiplier))
|
| 213 |
+
if multiplier > 1 and FILM is None:
|
| 214 |
+
multiplier = 1
|
| 215 |
+
pixel_ratio = (height * width) / (960 * 544)
|
| 216 |
+
out_frames = (num_frames - 1) * multiplier + 1 if multiplier > 1 else num_frames
|
| 217 |
+
film = (num_frames - 1) * (multiplier - 1) * _FILM_PER_FRAME * pixel_ratio
|
| 218 |
+
post = _POST_BASE + film + out_frames * _MUX_PER_FRAME * pixel_ratio
|
| 219 |
+
|
| 220 |
+
total = PLACEMENT_ALLOWANCE + encode + denoise + decode + post
|
| 221 |
duration = max(MIN_GPU_DURATION, min(MAX_GPU_DURATION, int(total)))
|
| 222 |
print(f"[ref2va] S={sequence} -> reserving {duration}s ({denoise:.0f}s of denoise at {steps} steps)", flush=True)
|
| 223 |
return duration
|
|
|
|
| 481 |
|
| 482 |
|
| 483 |
@spaces.GPU(duration=get_duration, size=GPU_SIZE)
|
| 484 |
+
def _generate(
|
| 485 |
+
prompt_embeds, text_token_tags, references, height, width, num_frames, steps, seed,
|
| 486 |
+
sampler, schedule, video_shift, audio_shift, sharpen, multiplier,
|
| 487 |
+
):
|
| 488 |
+
"""The only thing on GPU time: the two reference encoders, the packed-sequence denoise loop, the decoders, and
|
| 489 |
+
the RCAS + FILM post chain. References cross as paths and are decoded here; only the generated outputs come
|
| 490 |
+
back. A `@spaces.GPU` argument crosses a process boundary by pickling, a 5 s 1344x768 reference video is
|
| 491 |
+
370 MB of expanded frames, and the full `PipelineState` still holds the packed latents and the rotary grid on
|
| 492 |
+
the card.
|
| 493 |
"""
|
| 494 |
import torch
|
| 495 |
+
|
| 496 |
+
global FILM
|
| 497 |
+
|
| 498 |
if PLACEMENT == "lazy":
|
| 499 |
PIPE.to("cuda")
|
| 500 |
elif PLACEMENT == "pack":
|
| 501 |
PIPE.vae.to("cuda")
|
| 502 |
PIPE.audio_vae.to("cuda")
|
| 503 |
+
|
| 504 |
+
custom_schedule = schedule != "native"
|
| 505 |
+
# Any custom schedule hands `set_timesteps` a finished `steps + 1` sigma grid, so it runs `steps` forwards.
|
| 506 |
+
# The native grid counts its terminal zero as one of `num_inference_steps`, so it needs one more to match.
|
| 507 |
+
requested_steps = int(steps) if custom_schedule else int(steps) + 1
|
| 508 |
+
|
| 509 |
+
with pk.use_schedule(PIPE, int(steps), schedule, video_shift, audio_shift, sampler_name=sampler, seed=int(seed)):
|
| 510 |
+
state = PIPE(
|
| 511 |
+
prompt_embeds=prompt_embeds.to("cuda"),
|
| 512 |
+
text_token_tags=text_token_tags,
|
| 513 |
+
references=build_references(references),
|
| 514 |
+
height=height,
|
| 515 |
+
width=width,
|
| 516 |
+
num_frames=num_frames,
|
| 517 |
+
num_inference_steps=requested_steps,
|
| 518 |
+
generator=torch.Generator("cpu").manual_seed(int(seed)),
|
| 519 |
+
)
|
| 520 |
+
|
| 521 |
+
video = state.get("videos")[0] # (frames, 3, H, W), float in [0, 1], on the card
|
| 522 |
+
audio = state.get("audio")[0].cpu()
|
| 523 |
+
sampling_rate = state.get("sampling_rate")
|
| 524 |
+
del state
|
| 525 |
+
# The post chain runs on the allocator the denoise loop just left fragmented, and RCAS and FILM both want a
|
| 526 |
+
# few contiguous gigabytes.
|
| 527 |
+
torch.cuda.empty_cache()
|
| 528 |
+
|
| 529 |
+
video = pk.rcas(video, float(sharpen))
|
| 530 |
+
multiplier = max(1, int(multiplier))
|
| 531 |
+
if multiplier > 1:
|
| 532 |
+
if FILM is None:
|
| 533 |
+
multiplier = 1
|
| 534 |
+
else:
|
| 535 |
+
FILM = FILM.to("cuda")
|
| 536 |
+
video = pk.interpolate(FILM, video, multiplier)
|
| 537 |
+
fps = FPS * multiplier
|
| 538 |
+
|
| 539 |
+
return video, audio, sampling_rate, fps, multiplier
|
| 540 |
|
| 541 |
|
| 542 |
def generate(
|
|
|
|
| 558 |
steps=28,
|
| 559 |
seed=42,
|
| 560 |
upsample=False,
|
| 561 |
+
sampler=DEFAULT_SAMPLER,
|
| 562 |
+
schedule=DEFAULT_SCHEDULE,
|
| 563 |
+
video_shift=DEFAULT_VIDEO_SHIFT,
|
| 564 |
+
audio_shift=DEFAULT_AUDIO_SHIFT,
|
| 565 |
+
sharpen=DEFAULT_SHARPEN,
|
| 566 |
+
interpolation=DEFAULT_INTERPOLATION,
|
| 567 |
progress=gr.Progress(track_tqdm=True),
|
| 568 |
):
|
| 569 |
"""One request."""
|
|
|
|
| 582 |
# soundtrack. The conditioner resolves it either way and this Space pins whatever comes back.
|
| 583 |
derivable = len(audio_bearing(references)) == 1
|
| 584 |
requested = 0 if (match and derivable) else snap_frames(duration)
|
| 585 |
+
schedule_key = SCHEDULES.get(schedule, "linear_quadratic")
|
| 586 |
+
sampler_key = SAMPLERS.get(sampler, "euler")
|
| 587 |
+
multiplier = INTERPOLATION.get(interpolation, 2)
|
| 588 |
|
| 589 |
progress(0.0, desc="Upsampling the prompt ..." if upsample else "Reading the prompt and references ...")
|
| 590 |
conditioned = time.time()
|
|
|
|
| 608 |
|
| 609 |
progress(0.1, desc=f"Generating {num_frames / FPS:.1f} s at {width}x{height} ...")
|
| 610 |
started = time.time()
|
| 611 |
+
video, audio, sampling_rate, fps, multiplier = _generate(
|
| 612 |
+
prompt_embeds, text_token_tags, references, height, width, num_frames, steps, seed,
|
| 613 |
+
sampler_key, schedule_key, float(video_shift), float(audio_shift), float(sharpen), multiplier,
|
| 614 |
)
|
| 615 |
generate_seconds = time.time() - started
|
| 616 |
|
| 617 |
directory = os.path.join(tempfile.gettempdir(), "h3-outputs")
|
| 618 |
os.makedirs(directory, exist_ok=True)
|
| 619 |
path = os.path.join(directory, f"h3-ref2va-{int(time.time() * 1000)}.mp4")
|
| 620 |
+
encode_video(video, fps=fps, output_path=path, audio=audio, audio_sample_rate=sampling_rate)
|
| 621 |
|
| 622 |
print(
|
| 623 |
f"[ref2va] {[kind for kind, _ in references]} · `{width}x{height}`, {num_frames} frames "
|
| 624 |
+
f"({num_frames / FPS:.3f} s), {int(steps)} steps of `{schedule_key}` · sampler `{sampler_key}` · "
|
| 625 |
+
f"shift {float(video_shift):.1f}/{float(audio_shift):.1f} · conditioner {condition_seconds:.0f}s "
|
| 626 |
f"({plan['num_text_tokens']} tokens{', upsampled' if refined else ''}) · "
|
| 627 |
f"denoise + decode {generate_seconds:.0f}s "
|
| 628 |
f"({generate_seconds / int(steps):.1f} s/step) · seed {int(seed)}",
|
|
|
|
| 710 |
audio_shift = gr.Slider(
|
| 711 |
label="Audio shift", minimum=0.5, maximum=20.0, step=0.5, value=DEFAULT_AUDIO_SHIFT
|
| 712 |
)
|
| 713 |
+
sharpen = gr.Slider(
|
| 714 |
+
label="RCAS sharpening",
|
| 715 |
+
minimum=0.0,
|
| 716 |
+
maximum=1.0,
|
| 717 |
+
step=0.05,
|
| 718 |
+
value=DEFAULT_SHARPEN,
|
| 719 |
+
info="FidelityFX Robust Contrast Adaptive Sharpening. PlagueKind: 0.3 is very natural.",
|
| 720 |
+
)
|
| 721 |
+
interpolation = gr.Dropdown(
|
| 722 |
+
label="FILM frame interpolation",
|
| 723 |
+
choices=list(INTERPOLATION),
|
| 724 |
+
value=DEFAULT_INTERPOLATION,
|
| 725 |
+
info="MiniMax-H3 generates 24 fps; FILM synthesizes the frames in between.",
|
| 726 |
+
)
|
| 727 |
seed = gr.Number(label="Seed", value=42, precision=0)
|
| 728 |
|
| 729 |
with gr.Column():
|
|
|
|
| 755 |
video.change(auto_canvas, video, canvas, show_progress="hidden", api_name=False)
|
| 756 |
|
| 757 |
request = [
|
| 758 |
+
prompt, images[0], audio, video, canvas, *images[1:], match, duration, steps, seed, upsample,
|
| 759 |
+
sampler, schedule, video_shift, audio_shift, sharpen, interpolation,
|
| 760 |
]
|
| 761 |
run.click(generate, request, [result, upsampled, upsampled_panel], api_name="generate")
|
| 762 |
|