multimodalart HF Staff commited on
Commit
79e1481
·
verified ·
1 Parent(s): cf77190

Images/audio/video tab order, expandable image slots, native attention for the VAEs

Browse files
Files changed (1) hide show
  1. app.py +76 -23
app.py CHANGED
@@ -66,6 +66,9 @@ MAX_UI_DURATION = 14
66
  MIN_DURATION = 5
67
  # A reference video shorter than 2 s gives the model almost no motion to read, and 15 s is the checkpoint's ceiling.
68
  MIN_REFERENCE_VIDEO, MAX_REFERENCE_VIDEO = 2.0, 15.0
 
 
 
69
 
70
 
71
  def snap_frames(seconds: float) -> int:
@@ -121,6 +124,23 @@ def load_models() -> str | None:
121
  print(f"[ref2va] loading {[c.name for c in blocks.expected_components]} from {MODEL_REPO} ...", flush=True)
122
  pipe = blocks.init_pipeline(MODEL_REPO, components_manager=manager, collection="h3")
123
  pipe.load_components(dtype=torch.bfloat16, token=token)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
  pipe.transformer_ref.set_attention_backend(ATTENTION)
125
 
126
  # Still startup, still free: an AoTI package carries no weights and opens its compiled archive lazily inside
@@ -195,18 +215,19 @@ def probe(path: str) -> tuple[float | None, float | None]:
195
  return video, audio
196
 
197
 
198
- def collect(video_path, first_image_path, second_image_path, audio_path) -> list[tuple[str, str]]:
199
  """The `(kind, path)` references of a request, **in the order the model reads them**.
200
 
201
  That order is semantic rather than cosmetic: it numbers the labels of MiniMax-H3's prompt presentation and it
202
  advances the shared audio/video rotary clock, so the same references in a different order are a different
203
- request. Video first, then images, then a standalone audio clip which is also the order the packed sequence
204
- lays them out in.
205
  """
206
- ordered = [("video", video_path)] if video_path else []
207
- ordered += [("image", path) for path in (first_image_path, second_image_path) if path]
208
  if audio_path:
209
  ordered.append(("audio", audio_path))
 
 
210
  return ordered
211
 
212
 
@@ -222,10 +243,13 @@ def audio_bearing(references: list[tuple[str, str]]) -> list[tuple[str, float]]:
222
  return carried
223
 
224
 
225
- def duration_controls(video_path, first_image_path, second_image_path, audio_path, match: bool):
226
- """Show the duration slider unless a single soundtrack can set it, which is when MiniMax-H3 lets it be left out."""
 
 
 
227
  try:
228
- carried = audio_bearing(collect(video_path, first_image_path, second_image_path, audio_path))
229
  except Exception:
230
  carried = []
231
  # Exactly one soundtrack, and one long enough to be a duration MiniMax-H3 generates. Anything else and the
@@ -312,10 +336,17 @@ def _generate(prompt_embeds, text_token_tags, references, height, width, num_fra
312
 
313
  def generate(
314
  prompt,
315
- video_path=None,
316
- first_image_path=None,
317
- second_image_path=None,
 
 
 
 
 
 
318
  audio_path=None,
 
319
  canvas=DEFAULT_CANVAS,
320
  match=True,
321
  duration=5,
@@ -330,7 +361,8 @@ def generate(
330
 
331
  from diffusers.utils import encode_video
332
 
333
- references = collect(video_path, first_image_path, second_image_path, audio_path)
 
334
  check(prompt, references)
335
 
336
  # `0` is "leave it to the references" over the wire, which MiniMax-H3 accepts when exactly one of them carries a
@@ -382,8 +414,8 @@ INTRO = """# MiniMax-H3
382
  </div>
383
 
384
  **MiniMax-H3** is a 33B parameter state of the art video generation model that produces video and a
385
- fully synchronized soundtrack (ambience, foley, speech). Bring your own subject, camera move or voice as a
386
- reference — they are read **in tab order**, video then images then audio.
387
  """
388
 
389
  CSS = """
@@ -391,7 +423,7 @@ CSS = """
391
  .dark .gradio-container { color: var(--body-text-color); }
392
  """
393
 
394
- with gr.Blocks(title="MiniMax-H3 references") as demo:
395
  gr.Markdown(INTRO)
396
 
397
  with gr.Row():
@@ -404,14 +436,24 @@ with gr.Blocks(title="MiniMax-H3 references") as demo:
404
  # One tab per modality, in the order the model reads them. A reference left in a tab that is not the
405
  # open one is still part of the request — the tabs lay the slots out, they do not choose between them.
406
  with gr.Tabs():
407
- with gr.Tab("Video"):
408
- video = gr.Video(label="Motion & camera, 2–15 s. Its soundtrack comes along.")
409
  with gr.Tab("Images"):
 
 
410
  with gr.Row():
411
- first_image = gr.Image(label="Subject, style or scene", type="filepath")
412
- second_image = gr.Image(label="Subject, style or scene", type="filepath")
 
 
 
 
 
 
 
 
413
  with gr.Tab("Audio"):
414
  audio = gr.Audio(label="A voice or a piece of music", type="filepath")
 
 
415
  run = gr.Button("Generate", variant="primary")
416
  with gr.Accordion("Advanced options", open=False):
417
  canvas = gr.Dropdown(label="Canvas", choices=list(CANVASES), value=DEFAULT_CANVAS)
@@ -425,15 +467,26 @@ with gr.Blocks(title="MiniMax-H3 references") as demo:
425
  with gr.Column():
426
  result = gr.Video(label="Video + soundtrack")
427
 
428
- slots = [video, first_image, second_image, audio]
429
- for control in [*slots, match]:
 
 
 
 
 
 
 
 
 
 
 
430
  control.change(
431
- duration_controls, [*slots, match], [match, duration], show_progress="hidden", api_name=False
432
  )
433
 
434
  run.click(
435
  generate,
436
- [prompt, *slots, canvas, match, duration, steps, seed],
437
  result,
438
  api_name="generate",
439
  )
 
66
  MIN_DURATION = 5
67
  # A reference video shorter than 2 s gives the model almost no motion to read, and 15 s is the checkpoint's ceiling.
68
  MIN_REFERENCE_VIDEO, MAX_REFERENCE_VIDEO = 2.0, 15.0
69
+ # `MINIMAX_H3_MAX_REFERENCE_IMAGES`, hardcoded so the UI renders before `diffusers` is importable. The slots are all
70
+ # built up front and revealed one at a time, because a demo asking for two subjects should not open with nine boxes.
71
+ MAX_IMAGE_SLOTS, OPEN_IMAGE_SLOTS = 9, 2
72
 
73
 
74
  def snap_frames(seconds: float) -> int:
 
124
  print(f"[ref2va] loading {[c.name for c in blocks.expected_components]} from {MODEL_REPO} ...", flush=True)
125
  pipe = blocks.init_pipeline(MODEL_REPO, components_manager=manager, collection="h3")
126
  pipe.load_components(dtype=torch.bfloat16, token=token)
127
+
128
+ # Pin the two autoencoders to torch SDPA *before* the transformer takes cuDNN, and in that order.
129
+ #
130
+ # `set_attention_backend` does two things: it stamps the backend onto every attention processor of the model
131
+ # it is called on, and it sets the registry's **global** active backend, which every processor that was not
132
+ # stamped then falls through to. Both VAEs carry `AttentionModuleMixin` attention with `_attention_backend =
133
+ # None`, so stamping only the transformer leaves them inheriting cuDNN — and they are float32, for which
134
+ # cuDNN has no kernel:
135
+ #
136
+ # RuntimeError: No available kernel. Aborting execution. # audio_vae pre_block, is_causal=True
137
+ #
138
+ # It is `ref2va` that exposes this. The keyframe half only ever *decodes* audio, and the audio VAE's
139
+ # attention is on its encoder side, so nothing reached it until a reference brought a soundtrack along.
140
+ # Stamping the VAEs first leaves both explicitly on `native`; the transformer then stamps itself and takes
141
+ # the global with it, which no longer matters to anyone.
142
+ pipe.vae.set_attention_backend("native")
143
+ pipe.audio_vae.set_attention_backend("native")
144
  pipe.transformer_ref.set_attention_backend(ATTENTION)
145
 
146
  # Still startup, still free: an AoTI package carries no weights and opens its compiled archive lazily inside
 
215
  return video, audio
216
 
217
 
218
+ def collect(image_paths, audio_path, video_path) -> list[tuple[str, str]]:
219
  """The `(kind, path)` references of a request, **in the order the model reads them**.
220
 
221
  That order is semantic rather than cosmetic: it numbers the labels of MiniMax-H3's prompt presentation and it
222
  advances the shared audio/video rotary clock, so the same references in a different order are a different
223
+ request. Images first, then a standalone audio clip, then the video the order the tabs are laid out in, so
224
+ what the UI shows is what the model is handed.
225
  """
226
+ ordered = [("image", path) for path in image_paths if path]
 
227
  if audio_path:
228
  ordered.append(("audio", audio_path))
229
+ if video_path:
230
+ ordered.append(("video", video_path))
231
  return ordered
232
 
233
 
 
243
  return carried
244
 
245
 
246
+ def duration_controls(audio_path, video_path, match: bool):
247
+ """Show the duration slider unless a single soundtrack can set it, which is when MiniMax-H3 lets it be left out.
248
+
249
+ Only the audio and video slots matter here: an image reference never carries a waveform.
250
+ """
251
  try:
252
+ carried = audio_bearing(collect([], audio_path, video_path))
253
  except Exception:
254
  carried = []
255
  # Exactly one soundtrack, and one long enough to be a duration MiniMax-H3 generates. Anything else and the
 
336
 
337
  def generate(
338
  prompt,
339
+ image_1=None,
340
+ image_2=None,
341
+ image_3=None,
342
+ image_4=None,
343
+ image_5=None,
344
+ image_6=None,
345
+ image_7=None,
346
+ image_8=None,
347
+ image_9=None,
348
  audio_path=None,
349
+ video_path=None,
350
  canvas=DEFAULT_CANVAS,
351
  match=True,
352
  duration=5,
 
361
 
362
  from diffusers.utils import encode_video
363
 
364
+ images = [image_1, image_2, image_3, image_4, image_5, image_6, image_7, image_8, image_9]
365
+ references = collect(images, audio_path, video_path)
366
  check(prompt, references)
367
 
368
  # `0` is "leave it to the references" over the wire, which MiniMax-H3 accepts when exactly one of them carries a
 
414
  </div>
415
 
416
  **MiniMax-H3** is a 33B parameter state of the art video generation model that produces video and a
417
+ fully synchronized soundtrack (ambience, foley, speech). Bring your own subject, voice or camera move as a
418
+ reference — they are read **in tab order**, images then audio then video.
419
  """
420
 
421
  CSS = """
 
423
  .dark .gradio-container { color: var(--body-text-color); }
424
  """
425
 
426
+ with gr.Blocks(title="MiniMax-H3 Reference") as demo:
427
  gr.Markdown(INTRO)
428
 
429
  with gr.Row():
 
436
  # One tab per modality, in the order the model reads them. A reference left in a tab that is not the
437
  # open one is still part of the request — the tabs lay the slots out, they do not choose between them.
438
  with gr.Tabs():
 
 
439
  with gr.Tab("Images"):
440
+ # One `gr.Row`, so gradio splits the width evenly and wraps once the slots hit `min_width`
441
+ # rather than leaving a hole where a hidden slot used to be.
442
  with gr.Row():
443
+ images = [
444
+ gr.Image(
445
+ label="Subject, style or scene",
446
+ type="filepath",
447
+ min_width=180,
448
+ visible=index < OPEN_IMAGE_SLOTS,
449
+ )
450
+ for index in range(MAX_IMAGE_SLOTS)
451
+ ]
452
+ add_image = gr.Button("+ Add another image", size="sm", variant="secondary")
453
  with gr.Tab("Audio"):
454
  audio = gr.Audio(label="A voice or a piece of music", type="filepath")
455
+ with gr.Tab("Video"):
456
+ video = gr.Video(label="Motion & camera, 2–15 s. Its soundtrack comes along.")
457
  run = gr.Button("Generate", variant="primary")
458
  with gr.Accordion("Advanced options", open=False):
459
  canvas = gr.Dropdown(label="Canvas", choices=list(CANVASES), value=DEFAULT_CANVAS)
 
467
  with gr.Column():
468
  result = gr.Video(label="Video + soundtrack")
469
 
470
+ open_slots = gr.State(OPEN_IMAGE_SLOTS)
471
+
472
+ def reveal_image_slot(open_count):
473
+ open_count = min(open_count + 1, MAX_IMAGE_SLOTS)
474
+ return [
475
+ open_count,
476
+ *[gr.update(visible=index < open_count) for index in range(MAX_IMAGE_SLOTS)],
477
+ gr.update(visible=open_count < MAX_IMAGE_SLOTS),
478
+ ]
479
+
480
+ add_image.click(reveal_image_slot, open_slots, [open_slots, *images, add_image], api_name=False)
481
+
482
+ for control in (audio, video, match):
483
  control.change(
484
+ duration_controls, [audio, video, match], [match, duration], show_progress="hidden", api_name=False
485
  )
486
 
487
  run.click(
488
  generate,
489
+ [prompt, *images, audio, video, canvas, match, duration, steps, seed],
490
  result,
491
  api_name="generate",
492
  )