dima0987654321 commited on
Commit
bb72cfb
Β·
verified Β·
1 Parent(s): c9a6d72

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +0 -32
app.py CHANGED
@@ -109,7 +109,6 @@ PIPE = None
109
  LOAD_ERROR: str | None = None
110
  LOADED_IN: float | None = None
111
 
112
-
113
  def snap_frames(seconds: float) -> int:
114
  """The frame count MiniMax-H3's video VAE can decode: the next ``17 * n + 5`` at 24 fps."""
115
  frames = max(1, round(float(seconds) * FPS))
@@ -117,14 +116,12 @@ def snap_frames(seconds: float) -> int:
117
  frames += 1
118
  return frames
119
 
120
-
121
  def lower_duration_floor(seconds: float = MIN_UI_DURATION) -> None:
122
  """Let the pipeline generate below its 5 s floor."""
123
  from diffusers.modular_pipelines.minimax_h3.modular_pipeline import MiniMaxH3ModularPipeline
124
 
125
  MiniMaxH3ModularPipeline.min_duration = property(lambda self: float(seconds))
126
 
127
-
128
  def shifted_schedule(shift: float) -> list[float]:
129
  """``BASE_SCHEDULE`` pushed through H3's exponential sigma shift ``s*Οƒ / (1 + (s - 1)*Οƒ)``.
130
 
@@ -142,7 +139,6 @@ def shifted_schedule(shift: float) -> list[float]:
142
  """
143
  return [shift * sigma / (1 + (shift - 1) * sigma) for sigma in BASE_SCHEDULE]
144
 
145
-
146
  def pin_distilled_schedule(scheduler) -> None:
147
  """Pin the shifted ``BASE_SCHEDULE`` onto a scheduler so *every* ``set_timesteps`` call lands on it.
148
 
@@ -173,7 +169,6 @@ def pin_distilled_schedule(scheduler) -> None:
173
  scheduler.set_timesteps = set_timesteps
174
  scheduler._flashgen_pinned = True
175
 
176
-
177
  # ── LoRA loading (weight folding) ─────────────────────────────────────────────
178
  #
179
  # This mirrors ``merge_lora_ckpt.py``, the merge script the LoRA repo itself ships, rather than a
@@ -201,7 +196,6 @@ def pin_distilled_schedule(scheduler) -> None:
201
  # contiguous thirds directly β€” the naive reading β€” scatters every head's q/k/v across all three
202
  # projections, which turns the adapter into structured noise on all 52 attention blocks.
203
 
204
-
205
  def _reorder_interleaved_qkv(weight, num_heads: int, head_dim: int):
206
  """De-interleave per-head fused-QKV rows into ``[q_all; k_all; v_all]``.
207
 
@@ -215,7 +209,6 @@ def _reorder_interleaved_qkv(weight, num_heads: int, head_dim: int):
215
  [part.reshape(num_heads * head_dim, *weight.shape[1:]) for part in (query, key, value)], dim=0
216
  )
217
 
218
-
219
  def _lora_target_name(source_name: str) -> str:
220
  """Map an original-checkpoint base name (without ``.lora_A/B.*``) to the diffusers parameter path."""
221
  if source_name.startswith("token_refiner.blocks."):
@@ -226,7 +219,6 @@ def _lora_target_name(source_name: str) -> str:
226
  target = source_name
227
  return target.replace("final_layer.adaln_proj.linear", "norm_out.linear")
228
 
229
-
230
  def _lora_targets(name: str, b_weight, num_heads: int, head_dim: int):
231
  """Yield ``(diffusers_param_key, row_transformed_B)`` for one original LoRA base name."""
232
  target = _lora_target_name(name)
@@ -250,7 +242,6 @@ def _lora_targets(name: str, b_weight, num_heads: int, head_dim: int):
250
  # adaln_proj.linear (block-level) and norm_out.linear: identical row layout.
251
  yield target + ".weight", b_weight
252
 
253
-
254
  def load_and_apply_lora(transformer) -> str:
255
  """Download the FlashGen LoRA, remap keys, and fold ``scale * (B @ A)`` into the base weights."""
256
  import torch
@@ -317,7 +308,6 @@ def load_and_apply_lora(transformer) -> str:
317
  f"-> {folded} weight deltas Β· {LORA_FILE}"
318
  )
319
 
320
-
321
  # ── Additional LoRAs ─────────────────────────────────────────────────────────
322
  #
323
  # These are folded after FlashGen, so the original 4-step FlashGen schedule and
@@ -422,7 +412,6 @@ def _apply_extra_lora(
422
  f"{folded} weight deltas Β· scale {float(scale):g}"
423
  )
424
 
425
-
426
  def load_and_apply_extra_loras(transformer) -> list[str]:
427
  return [
428
  _apply_extra_lora(
@@ -441,10 +430,8 @@ def load_and_apply_extra_loras(transformer) -> list[str]:
441
  ),
442
  ]
443
 
444
-
445
  # ── Model loading ───────────────────────────────────────────────────────────
446
 
447
-
448
  def load_models() -> str | None:
449
  """Load the denoising half at startup: transformer + VAEs + schedulers, then fold the LoRA in."""
450
  global PIPE, LOAD_ERROR, LOADED_IN
@@ -504,17 +491,14 @@ def load_models() -> str | None:
504
  LOAD_ERROR = f"**Loading failed** after {time.time() - started:.0f}s: `{type(error).__name__}: {error}`"
505
  return LOAD_ERROR
506
 
507
-
508
  # ── Remote conditioning ──────────────────────────────────────────────────────
509
 
510
-
511
  @cache
512
  def conditioner():
513
  from gradio_client import Client
514
 
515
  return Client(CONDITIONER_SPACE)
516
 
517
-
518
  def encode_remote(prompt, image_path, last_image_path, canvas, num_frames, ip_token=None, rewrite_prompt=False):
519
  """Call the conditioner Space to get ``prompt_embeds`` + ``text_token_tags`` + resolved geometry.
520
 
@@ -550,7 +534,6 @@ def encode_remote(prompt, image_path, last_image_path, canvas, num_frames, ip_to
550
  metadata = handle.metadata()
551
  return handle.get_tensor("prompt_embeds"), handle.get_tensor("text_token_tags"), metadata, plan
552
 
553
-
554
  def conditioner_client(ip_token):
555
  if not ip_token:
556
  return conditioner()
@@ -558,10 +541,8 @@ def conditioner_client(ip_token):
558
 
559
  return Client(CONDITIONER_SPACE, headers={"x-ip-token": ip_token})
560
 
561
-
562
  # ── Geometry helpers ────────────────────────────────────────────────────────
563
 
564
-
565
  def _snap_canvas(aspect: float, current_canvas: str) -> str:
566
  """The supported canvas whose aspect ratio is closest to ``aspect``.
567
 
@@ -582,7 +563,6 @@ def _snap_canvas(aspect: float, current_canvas: str) -> str:
582
  return current_canvas
583
  return fastest[ratio][0]
584
 
585
-
586
  def _cover_crop(image_path: str, canvas_label: str) -> str:
587
  """Center cover-crop the file at ``image_path`` to ``canvas_label``'s aspect ratio, in place.
588
 
@@ -607,7 +587,6 @@ def _cover_crop(image_path: str, canvas_label: str) -> str:
607
  img.save(image_path)
608
  return image_path
609
 
610
-
611
  def _fit_keyframe(image_path, current_canvas):
612
  """Snap the canvas to a keyframe's aspect ratio, then cover-crop the keyframe to that canvas."""
613
  from PIL import Image as _Image
@@ -617,7 +596,6 @@ def _fit_keyframe(image_path, current_canvas):
617
  label = _snap_canvas(aspect, current_canvas)
618
  return _cover_crop(image_path, label), label
619
 
620
-
621
  def _fit_keyframe_ui(image_path, current_canvas):
622
  """``image.upload`` handler β€” the reference Space's behaviour, ported.
623
 
@@ -631,7 +609,6 @@ def _fit_keyframe_ui(image_path, current_canvas):
631
  cropped, label = _fit_keyframe(path, current_canvas)
632
  return gr.update(value=cropped), gr.update(value=label)
633
 
634
-
635
  def _fit_last_keyframe_ui(last_image_path, image_path, current_canvas):
636
  """``last_image.upload`` handler. The first frame owns the canvas when there is one, so a last
637
  frame uploaded on top of it is only cover-cropped to the canvas already chosen."""
@@ -642,20 +619,17 @@ def _fit_last_keyframe_ui(last_image_path, image_path, current_canvas):
642
  return gr.update(value=_cover_crop(path, current_canvas)), gr.update()
643
  return _fit_keyframe_ui(path, current_canvas)
644
 
645
-
646
  def _as_path(value):
647
  if isinstance(value, dict):
648
  value = value.get("path") or (value.get("url") or "").removeprefix("/gradio_api/file=")
649
  return value or None
650
 
651
-
652
  # ── GPU duration estimation ─────────────────────────────────────────────────
653
 
654
  _DUR_B, _DUR_C = 1.1745e-4, 3.8396e-9
655
  _DECODE_BASE, _DECODE_PER_DEFAULT_CANVAS, _DEFAULT_CANVAS_PIXELS = 15, 15, 960 * 544 * 124
656
  _PLACEMENT_ALLOWANCE, _PAD = 12, 10
657
 
658
-
659
  def get_duration(prompt_embeds, text_token_tags, image, last_image, height, width, num_frames, seed, *a, **k):
660
  """Estimate GPU duration for the ``_generate`` call. Steps is fixed at NUM_STEPS (4)."""
661
  height, width, num_frames = int(height), int(width), int(num_frames)
@@ -667,10 +641,8 @@ def get_duration(prompt_embeds, text_token_tags, image, last_image, height, widt
667
  decode = _DECODE_BASE + _DECODE_PER_DEFAULT_CANVAS * (height * width * num_frames) / _DEFAULT_CANVAS_PIXELS
668
  return max(60, int(denoise + decode) + _PLACEMENT_ALLOWANCE + _PAD)
669
 
670
-
671
  # ── Inference ───────────────────────────────────────────────────────────────
672
 
673
-
674
  @spaces.GPU(duration=get_duration, size=GPU_SIZE)
675
  def _generate(prompt_embeds, text_token_tags, image, last_image, height, width, num_frames, seed):
676
  """Denoise + decode on GPU time. The LoRA is already folded at startup; 4 steps with custom sigmas."""
@@ -713,14 +685,12 @@ def _generate(prompt_embeds, text_token_tags, image, last_image, height, width,
713
  sigmas_used,
714
  )
715
 
716
-
717
  def _caller_ip_token() -> str | None:
718
  from gradio.context import LocalContext
719
 
720
  request = LocalContext.request.get()
721
  return request.headers.get("x-ip-token") if request is not None else None
722
 
723
-
724
  def generate(
725
  prompt,
726
  image_path=None,
@@ -820,10 +790,8 @@ def generate(
820
  )
821
  return path, report, enhanced, gr.update(visible=bool(enhanced))
822
 
823
-
824
  # ── UI ──────────────────────────────────────────────────────────────────────
825
 
826
-
827
  INTRO = """# MiniMax-H3 4-Step FlashGen LoRA
828
 
829
  <div align="center">
 
109
  LOAD_ERROR: str | None = None
110
  LOADED_IN: float | None = None
111
 
 
112
  def snap_frames(seconds: float) -> int:
113
  """The frame count MiniMax-H3's video VAE can decode: the next ``17 * n + 5`` at 24 fps."""
114
  frames = max(1, round(float(seconds) * FPS))
 
116
  frames += 1
117
  return frames
118
 
 
119
  def lower_duration_floor(seconds: float = MIN_UI_DURATION) -> None:
120
  """Let the pipeline generate below its 5 s floor."""
121
  from diffusers.modular_pipelines.minimax_h3.modular_pipeline import MiniMaxH3ModularPipeline
122
 
123
  MiniMaxH3ModularPipeline.min_duration = property(lambda self: float(seconds))
124
 
 
125
  def shifted_schedule(shift: float) -> list[float]:
126
  """``BASE_SCHEDULE`` pushed through H3's exponential sigma shift ``s*Οƒ / (1 + (s - 1)*Οƒ)``.
127
 
 
139
  """
140
  return [shift * sigma / (1 + (shift - 1) * sigma) for sigma in BASE_SCHEDULE]
141
 
 
142
  def pin_distilled_schedule(scheduler) -> None:
143
  """Pin the shifted ``BASE_SCHEDULE`` onto a scheduler so *every* ``set_timesteps`` call lands on it.
144
 
 
169
  scheduler.set_timesteps = set_timesteps
170
  scheduler._flashgen_pinned = True
171
 
 
172
  # ── LoRA loading (weight folding) ─────────────────────────────────────────────
173
  #
174
  # This mirrors ``merge_lora_ckpt.py``, the merge script the LoRA repo itself ships, rather than a
 
196
  # contiguous thirds directly β€” the naive reading β€” scatters every head's q/k/v across all three
197
  # projections, which turns the adapter into structured noise on all 52 attention blocks.
198
 
 
199
  def _reorder_interleaved_qkv(weight, num_heads: int, head_dim: int):
200
  """De-interleave per-head fused-QKV rows into ``[q_all; k_all; v_all]``.
201
 
 
209
  [part.reshape(num_heads * head_dim, *weight.shape[1:]) for part in (query, key, value)], dim=0
210
  )
211
 
 
212
  def _lora_target_name(source_name: str) -> str:
213
  """Map an original-checkpoint base name (without ``.lora_A/B.*``) to the diffusers parameter path."""
214
  if source_name.startswith("token_refiner.blocks."):
 
219
  target = source_name
220
  return target.replace("final_layer.adaln_proj.linear", "norm_out.linear")
221
 
 
222
  def _lora_targets(name: str, b_weight, num_heads: int, head_dim: int):
223
  """Yield ``(diffusers_param_key, row_transformed_B)`` for one original LoRA base name."""
224
  target = _lora_target_name(name)
 
242
  # adaln_proj.linear (block-level) and norm_out.linear: identical row layout.
243
  yield target + ".weight", b_weight
244
 
 
245
  def load_and_apply_lora(transformer) -> str:
246
  """Download the FlashGen LoRA, remap keys, and fold ``scale * (B @ A)`` into the base weights."""
247
  import torch
 
308
  f"-> {folded} weight deltas Β· {LORA_FILE}"
309
  )
310
 
 
311
  # ── Additional LoRAs ─────────────────────────────────────────────────────────
312
  #
313
  # These are folded after FlashGen, so the original 4-step FlashGen schedule and
 
412
  f"{folded} weight deltas Β· scale {float(scale):g}"
413
  )
414
 
 
415
  def load_and_apply_extra_loras(transformer) -> list[str]:
416
  return [
417
  _apply_extra_lora(
 
430
  ),
431
  ]
432
 
 
433
  # ── Model loading ───────────────────────────────────────────────────────────
434
 
 
435
  def load_models() -> str | None:
436
  """Load the denoising half at startup: transformer + VAEs + schedulers, then fold the LoRA in."""
437
  global PIPE, LOAD_ERROR, LOADED_IN
 
491
  LOAD_ERROR = f"**Loading failed** after {time.time() - started:.0f}s: `{type(error).__name__}: {error}`"
492
  return LOAD_ERROR
493
 
 
494
  # ── Remote conditioning ──────────────────────────────────────────────────────
495
 
 
496
  @cache
497
  def conditioner():
498
  from gradio_client import Client
499
 
500
  return Client(CONDITIONER_SPACE)
501
 
 
502
  def encode_remote(prompt, image_path, last_image_path, canvas, num_frames, ip_token=None, rewrite_prompt=False):
503
  """Call the conditioner Space to get ``prompt_embeds`` + ``text_token_tags`` + resolved geometry.
504
 
 
534
  metadata = handle.metadata()
535
  return handle.get_tensor("prompt_embeds"), handle.get_tensor("text_token_tags"), metadata, plan
536
 
 
537
  def conditioner_client(ip_token):
538
  if not ip_token:
539
  return conditioner()
 
541
 
542
  return Client(CONDITIONER_SPACE, headers={"x-ip-token": ip_token})
543
 
 
544
  # ── Geometry helpers ────────────────────────────────────────────────────────
545
 
 
546
  def _snap_canvas(aspect: float, current_canvas: str) -> str:
547
  """The supported canvas whose aspect ratio is closest to ``aspect``.
548
 
 
563
  return current_canvas
564
  return fastest[ratio][0]
565
 
 
566
  def _cover_crop(image_path: str, canvas_label: str) -> str:
567
  """Center cover-crop the file at ``image_path`` to ``canvas_label``'s aspect ratio, in place.
568
 
 
587
  img.save(image_path)
588
  return image_path
589
 
 
590
  def _fit_keyframe(image_path, current_canvas):
591
  """Snap the canvas to a keyframe's aspect ratio, then cover-crop the keyframe to that canvas."""
592
  from PIL import Image as _Image
 
596
  label = _snap_canvas(aspect, current_canvas)
597
  return _cover_crop(image_path, label), label
598
 
 
599
  def _fit_keyframe_ui(image_path, current_canvas):
600
  """``image.upload`` handler β€” the reference Space's behaviour, ported.
601
 
 
609
  cropped, label = _fit_keyframe(path, current_canvas)
610
  return gr.update(value=cropped), gr.update(value=label)
611
 
 
612
  def _fit_last_keyframe_ui(last_image_path, image_path, current_canvas):
613
  """``last_image.upload`` handler. The first frame owns the canvas when there is one, so a last
614
  frame uploaded on top of it is only cover-cropped to the canvas already chosen."""
 
619
  return gr.update(value=_cover_crop(path, current_canvas)), gr.update()
620
  return _fit_keyframe_ui(path, current_canvas)
621
 
 
622
  def _as_path(value):
623
  if isinstance(value, dict):
624
  value = value.get("path") or (value.get("url") or "").removeprefix("/gradio_api/file=")
625
  return value or None
626
 
 
627
  # ── GPU duration estimation ─────────────────────────────────────────────────
628
 
629
  _DUR_B, _DUR_C = 1.1745e-4, 3.8396e-9
630
  _DECODE_BASE, _DECODE_PER_DEFAULT_CANVAS, _DEFAULT_CANVAS_PIXELS = 15, 15, 960 * 544 * 124
631
  _PLACEMENT_ALLOWANCE, _PAD = 12, 10
632
 
 
633
  def get_duration(prompt_embeds, text_token_tags, image, last_image, height, width, num_frames, seed, *a, **k):
634
  """Estimate GPU duration for the ``_generate`` call. Steps is fixed at NUM_STEPS (4)."""
635
  height, width, num_frames = int(height), int(width), int(num_frames)
 
641
  decode = _DECODE_BASE + _DECODE_PER_DEFAULT_CANVAS * (height * width * num_frames) / _DEFAULT_CANVAS_PIXELS
642
  return max(60, int(denoise + decode) + _PLACEMENT_ALLOWANCE + _PAD)
643
 
 
644
  # ── Inference ───────────────────────────────────────────────────────────────
645
 
 
646
  @spaces.GPU(duration=get_duration, size=GPU_SIZE)
647
  def _generate(prompt_embeds, text_token_tags, image, last_image, height, width, num_frames, seed):
648
  """Denoise + decode on GPU time. The LoRA is already folded at startup; 4 steps with custom sigmas."""
 
685
  sigmas_used,
686
  )
687
 
 
688
  def _caller_ip_token() -> str | None:
689
  from gradio.context import LocalContext
690
 
691
  request = LocalContext.request.get()
692
  return request.headers.get("x-ip-token") if request is not None else None
693
 
 
694
  def generate(
695
  prompt,
696
  image_path=None,
 
790
  )
791
  return path, report, enhanced, gr.update(visible=bool(enhanced))
792
 
 
793
  # ── UI ──────────────────────────────────────────────────────────────────────
794
 
 
795
  INTRO = """# MiniMax-H3 4-Step FlashGen LoRA
796
 
797
  <div align="center">