dagloop5 commited on
Commit
1a8274b
·
verified ·
1 Parent(s): a1be217

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +10 -116
app.py CHANGED
@@ -421,23 +421,19 @@ def encode_remote(prompt, references, canvas, num_frames, rewrite_prompt=False):
421
 
422
 
423
  @spaces.GPU(duration=get_duration, size=GPU_SIZE)
 
424
  def _generate(prompt_embeds, text_token_tags, references, height, width, num_frames, steps, seed, loras=()):
425
  """The only thing on GPU time: the two reference encoders, the packed-sequence denoise loop and the decoders.
426
-
427
  References cross as paths and are decoded here; only the three generated outputs come back. A `@spaces.GPU`
428
  argument crosses a process boundary by pickling, a 5 s 1344x768 reference video is 370 MB of expanded frames, and
429
  the full `PipelineState` still holds the packed latents and the rotary grid on the card.
430
-
431
  The adapters are attached here rather than in the caller: `spaces` runs this body in its own worker, so the
432
  transformer the request sees is the one that has to carry them.
433
  """
434
  import torch
435
-
436
  if PLACEMENT == "lazy":
437
  PIPE.to("cuda")
438
-
439
- apply_loras(PIPE.transformer_ref, loras or ())
440
-
441
  state = PIPE(
442
  prompt_embeds=prompt_embeds.to("cuda"),
443
  text_token_tags=text_token_tags,
@@ -452,8 +448,6 @@ def _generate(prompt_embeds, text_token_tags, references, height, width, num_fra
452
 
453
 
454
  def generate(
455
- # Every parameter after `prompt` has a default, and the newest ones sit at the end, so a positional API client
456
- # written against an older signature keeps working.
457
  prompt,
458
  image_1=None,
459
  audio_path=None,
@@ -472,11 +466,9 @@ def generate(
472
  steps=28,
473
  seed=42,
474
  upsample=False,
475
- *lora_fields,
476
  progress=gr.Progress(track_tqdm=True),
477
  ):
478
- """One request. The LoRA fields are last and default to empty, so a positional API client that predates them is
479
- unaffected. `lora_fields` arrives as `reference, strength, reference, strength, ...`."""
480
  if LOAD_ERROR:
481
  raise gr.Error(LOAD_ERROR)
482
  if PIPE is None:
@@ -493,8 +485,6 @@ def generate(
493
  derivable = len(audio_bearing(references)) == 1
494
  requested = 0 if (match and derivable) else snap_frames(duration)
495
 
496
- loras, lora_labels = collect_loras(lora_fields, progress)
497
-
498
  progress(0.0, desc="Upsampling the prompt ..." if upsample else "Reading the prompt and references ...")
499
  conditioned = time.time()
500
  try:
@@ -518,7 +508,7 @@ def generate(
518
  progress(0.1, desc=f"Generating {num_frames / FPS:.1f} s at {width}x{height} ...")
519
  started = time.time()
520
  frames, audio, sampling_rate = _generate(
521
- prompt_embeds, text_token_tags, references, height, width, num_frames, steps, seed, loras
522
  )
523
  generate_seconds = time.time() - started
524
 
@@ -532,8 +522,7 @@ def generate(
532
  f"({num_frames / FPS:.3f} s), {int(steps)} steps · conditioner {condition_seconds:.0f}s "
533
  f"({plan['num_text_tokens']} tokens{', upsampled' if refined else ''}) · "
534
  f"denoise + decode {generate_seconds:.0f}s "
535
- f"({generate_seconds / int(steps):.1f} s/step) · seed {int(seed)}"
536
- f"{' · LoRA ' + ', '.join(lora_labels) if lora_labels else ''}",
537
  flush=True,
538
  )
539
  return path, refined, gr.update(visible=bool(refined))
@@ -547,11 +536,7 @@ def generate(
547
  # is gone by the next visit, so a saved path would restore as a dead file rather than as the image.
548
 
549
  SETTINGS_VERSION = 1
550
- SETTINGS_KEYS = (
551
- ["prompt", "upsample", "canvas", "match", "duration", "steps", "seed"]
552
- + [f"lora_{slot + 1}" for slot in range(LORA_SLOTS)]
553
- + [f"lora_{slot + 1}_scale" for slot in range(LORA_SLOTS)]
554
- )
555
 
556
 
557
  def save_settings(*values):
@@ -591,41 +576,9 @@ def load_settings(path):
591
  return updates
592
 
593
 
594
- def _fill_lora_slots(files, *current):
595
- """Drop `.safetensors` files on the uploader and their paths land in the first free slots, so a local adapter
596
- needs no typing at all."""
597
- slots = list(current)
598
- for path in files or []:
599
- for index, value in enumerate(slots):
600
- if not (value or "").strip():
601
- slots[index] = path
602
- break
603
- return [gr.update(value=value) for value in slots]
604
-
605
-
606
- def _add_preset_lora(preset, *current):
607
- """Fill the first free LoRA slot with a preset adapter, set its strength to 1.0 and move the steps slider to the
608
- preset's recommended count.
609
-
610
- The Turbo presets are tuned for a specific step range, so the steps slider is moved along with the slot — it is the
611
- one output beyond the LoRA fields. A slot already holding the same reference is a no-op, so the button can be
612
- pressed twice without duplicating, and a full set of slots is left untouched.
613
- """
614
- reference, steps, _ = LORA_PRESETS[preset]
615
- slots = list(current[:LORA_SLOTS])
616
- scales = list(current[LORA_SLOTS:])
617
- if reference not in [(value or "").strip() for value in slots]:
618
- for index, value in enumerate(slots):
619
- if not (value or "").strip():
620
- slots[index] = reference
621
- scales[index] = 1.0
622
- break
623
- return [*slots, *scales, steps]
624
-
625
-
626
  load_models()
627
 
628
- INTRO = """# MiniMax-H3 Reference Custom Lora
629
 
630
  <div align="center">
631
  <a href="https://huggingface.co/MiniMaxAI/MiniMax-H3" target="_blank" rel="noopener"><strong>[ model ]</strong></a> &nbsp;
@@ -636,28 +589,6 @@ INTRO = """# MiniMax-H3 Reference Custom Lora
636
  **MiniMax-H3** is a 33B parameter state of the art video generation model that produces video and a
637
  fully synchronized soundtrack (ambience, foley, speech). Bring your own subject, voice or camera move as a
638
  reference.
639
-
640
- This Space ships with **pre-wired Turbo LoRA presets** from
641
- [`larryvrh/MiniMax-H3-Turbo-Lora`](https://huggingface.co/larryvrh/MiniMax-H3-Turbo-Lora) that render joint video +
642
- soundtrack in **4–8 steps** instead of the usual ~20 (a ~5× speedup). It also accepts **ComfyUI-trained LoRAs** —
643
- adapters in the ComfyUI checkpoint naming are remapped to diffusers' module names on the fly, so a `.safetensors`
644
- trained against the ComfyUI base works without a separate conversion step.
645
- """
646
-
647
- LORA_HELP = """Each slot takes a Hugging Face repo (`owner/repo`), a file inside one
648
- (`owner/repo/name.safetensors`), a file URL, or a local path — or just drop the files below. A strength of `0`
649
- switches a slot off without clearing it. Adapters have to be trained against the `transformer_ref/` partition.
650
-
651
- **Turbo LoRA presets** — from
652
- [`larryvrh/MiniMax-H3-Turbo-Lora`](https://huggingface.co/larryvrh/MiniMax-H3-Turbo-Lora), a few-step distillation that
653
- renders joint video + soundtrack in **4–8 steps** instead of the usual ~20 (a ~5× speedup). Pick one from the dropdown
654
- and **Add to a free slot** to fill a slot, set its strength to `1.0` and move the steps slider to the recommended
655
- count. Keep strength at `1.0`; only nudge it if a specific clip misbehaves (smear → up, over-sharp → down).
656
- """
657
-
658
- SETTINGS_HELP = """Saves the prompt, the canvas, the sliders and the LoRA slots — everything typed rather than
659
- uploaded. Images, audio and video are not saved: gradio keeps them in a temporary folder that is gone by the next
660
- visit, so a saved path would come back as a dead file.
661
  """
662
 
663
  CSS = """
@@ -700,40 +631,6 @@ with gr.Blocks(title="MiniMax-H3 Reference Custom Lora") as demo:
700
  with gr.Tab("Video"):
701
  video = gr.Video(label="Motion & camera, 2–15 s. Its soundtrack comes along.")
702
  run = gr.Button("Generate", variant="primary")
703
-
704
- with gr.Accordion("LoRA", open=False):
705
- gr.Markdown(LORA_HELP)
706
- with gr.Row():
707
- lora_preset = gr.Dropdown(
708
- label="Turbo LoRA presets",
709
- choices=list(LORA_PRESETS),
710
- value=list(LORA_PRESETS)[0],
711
- scale=4,
712
- )
713
- lora_preset_add = gr.Button("Add to a free slot", size="sm", variant="secondary", scale=1)
714
- lora_references, lora_scales = [], []
715
- for slot in range(LORA_SLOTS):
716
- with gr.Row():
717
- lora_references.append(
718
- gr.Textbox(label=f"LoRA {slot + 1}", placeholder="owner/repo", scale=3)
719
- )
720
- lora_scales.append(
721
- gr.Slider(
722
- label="Strength",
723
- minimum=LORA_MIN_SCALE,
724
- maximum=LORA_MAX_SCALE,
725
- step=0.05,
726
- value=1.0,
727
- scale=2,
728
- )
729
- )
730
- lora_upload = gr.File(
731
- label="Drop .safetensors here to fill the slots",
732
- file_count="multiple",
733
- file_types=[".safetensors"],
734
- type="filepath",
735
- )
736
-
737
  with gr.Accordion("Advanced options", open=False):
738
  canvas = gr.Dropdown(label="Canvas", choices=list(CANVASES), value=DEFAULT_CANVAS)
739
  match = gr.Checkbox(label="Match the reference soundtrack", value=True, visible=False)
@@ -790,16 +687,13 @@ with gr.Blocks(title="MiniMax-H3 Reference Custom Lora") as demo:
790
  )
791
 
792
  # Same order as `SETTINGS_KEYS`.
793
- settings_fields = [prompt, upsample, canvas, match, duration, steps, seed, *lora_references, *lora_scales]
794
  save.click(save_settings, settings_fields, settings_download, api_name=False)
795
  settings_upload.upload(load_settings, settings_upload, settings_fields, api_name=False)
796
-
797
- # Same order as `generate`'s signature: the five leading columns first, then the remaining image slots, then the
798
- # LoRA fields the `*lora_fields` tail collects.
799
  request = [
800
- prompt, images[0], audio, video, canvas, *images[1:], match, duration, steps, seed, upsample, *lora_inputs
801
  ]
802
-
803
  run.click(generate, request, [result, upsampled, upsampled_panel], api_name="generate")
804
 
805
 
 
421
 
422
 
423
  @spaces.GPU(duration=get_duration, size=GPU_SIZE)
424
+ @spaces.GPU(duration=get_duration, size=GPU_SIZE)
425
  def _generate(prompt_embeds, text_token_tags, references, height, width, num_frames, steps, seed, loras=()):
426
  """The only thing on GPU time: the two reference encoders, the packed-sequence denoise loop and the decoders.
 
427
  References cross as paths and are decoded here; only the three generated outputs come back. A `@spaces.GPU`
428
  argument crosses a process boundary by pickling, a 5 s 1344x768 reference video is 370 MB of expanded frames, and
429
  the full `PipelineState` still holds the packed latents and the rotary grid on the card.
 
430
  The adapters are attached here rather than in the caller: `spaces` runs this body in its own worker, so the
431
  transformer the request sees is the one that has to carry them.
432
  """
433
  import torch
 
434
  if PLACEMENT == "lazy":
435
  PIPE.to("cuda")
436
+ apply_loras(PIPE.transformer_ref, loras or ())
 
 
437
  state = PIPE(
438
  prompt_embeds=prompt_embeds.to("cuda"),
439
  text_token_tags=text_token_tags,
 
448
 
449
 
450
  def generate(
 
 
451
  prompt,
452
  image_1=None,
453
  audio_path=None,
 
466
  steps=28,
467
  seed=42,
468
  upsample=False,
 
469
  progress=gr.Progress(track_tqdm=True),
470
  ):
471
+ """One request."""
 
472
  if LOAD_ERROR:
473
  raise gr.Error(LOAD_ERROR)
474
  if PIPE is None:
 
485
  derivable = len(audio_bearing(references)) == 1
486
  requested = 0 if (match and derivable) else snap_frames(duration)
487
 
 
 
488
  progress(0.0, desc="Upsampling the prompt ..." if upsample else "Reading the prompt and references ...")
489
  conditioned = time.time()
490
  try:
 
508
  progress(0.1, desc=f"Generating {num_frames / FPS:.1f} s at {width}x{height} ...")
509
  started = time.time()
510
  frames, audio, sampling_rate = _generate(
511
+ prompt_embeds, text_token_tags, references, height, width, num_frames, steps, seed
512
  )
513
  generate_seconds = time.time() - started
514
 
 
522
  f"({num_frames / FPS:.3f} s), {int(steps)} steps · conditioner {condition_seconds:.0f}s "
523
  f"({plan['num_text_tokens']} tokens{', upsampled' if refined else ''}) · "
524
  f"denoise + decode {generate_seconds:.0f}s "
525
+ f"({generate_seconds / int(steps):.1f} s/step) · seed {int(seed)}",
 
526
  flush=True,
527
  )
528
  return path, refined, gr.update(visible=bool(refined))
 
536
  # is gone by the next visit, so a saved path would restore as a dead file rather than as the image.
537
 
538
  SETTINGS_VERSION = 1
539
+ SETTINGS_KEYS = ["prompt", "upsample", "canvas", "match", "duration", "steps", "seed"]
 
 
 
 
540
 
541
 
542
  def save_settings(*values):
 
576
  return updates
577
 
578
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
579
  load_models()
580
 
581
+ INTRO = """# MiniMax-H3 Reference
582
 
583
  <div align="center">
584
  <a href="https://huggingface.co/MiniMaxAI/MiniMax-H3" target="_blank" rel="noopener"><strong>[ model ]</strong></a> &nbsp;
 
589
  **MiniMax-H3** is a 33B parameter state of the art video generation model that produces video and a
590
  fully synchronized soundtrack (ambience, foley, speech). Bring your own subject, voice or camera move as a
591
  reference.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
592
  """
593
 
594
  CSS = """
 
631
  with gr.Tab("Video"):
632
  video = gr.Video(label="Motion & camera, 2–15 s. Its soundtrack comes along.")
633
  run = gr.Button("Generate", variant="primary")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
634
  with gr.Accordion("Advanced options", open=False):
635
  canvas = gr.Dropdown(label="Canvas", choices=list(CANVASES), value=DEFAULT_CANVAS)
636
  match = gr.Checkbox(label="Match the reference soundtrack", value=True, visible=False)
 
687
  )
688
 
689
  # Same order as `SETTINGS_KEYS`.
690
+ settings_fields = [prompt, upsample, canvas, match, duration, steps, seed]
691
  save.click(save_settings, settings_fields, settings_download, api_name=False)
692
  settings_upload.upload(load_settings, settings_upload, settings_fields, api_name=False)
693
+ # Same order as `generate`'s signature: the five leading columns first, then the remaining image slots.
 
 
694
  request = [
695
+ prompt, images[0], audio, video, canvas, *images[1:], match, duration, steps, seed, upsample
696
  ]
 
697
  run.click(generate, request, [result, upsampled, upsampled_panel], api_name="generate")
698
 
699