Image-Text-to-Video
Diffusers
Safetensors
MiniMax H3
modular-diffusers
ref2va
fl2va
Merge
synchronized-audio-video
experimental
Instructions to use diffusers-modular/MiniMax-H3-Pruned-Ref-Delta-Fused-r1024 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Diffusers
How to use diffusers-modular/MiniMax-H3-Pruned-Ref-Delta-Fused-r1024 with Diffusers:
pip install -U diffusers transformers accelerate
import torch from diffusers import DiffusionPipeline # switch to "mps" for apple devices pipe = DiffusionPipeline.from_pretrained("diffusers-modular/MiniMax-H3-Pruned-Ref-Delta-Fused-r1024", dtype=torch.bfloat16, device_map="cuda") prompt = "Astronaut in a jungle, cold color palette, muted colors, detailed, 8k" image = pipe(prompt).images[0] - Notebooks
- Google Colab
- Kaggle
File size: 12,524 Bytes
d9863e2 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 | """Packed layout for a MiniMax-H3 request carrying BOTH keyframes and references.
Shipped with the checkpoint so `ModularPipeline.from_pretrained(..., trust_remote_code=True)` can serve a request
that has keyframes *and* references, which the stock blocks cannot express.
diffusers ships two builders and dispatches either/or: `references` wins and keyframes are dropped. ComfyUI packs
both (`PackedLayout(..., keyframes=..., refs=...)` in `comfy/ldm/minimax/model.py`), so the layout exists — this is
a port of it, not an invention.
Row order: [ text | keyframe cond | reference blocks | target audio | target video ]
The coupling that makes naive composition wrong: references pack between the text and the targets, so the *target
timeline* starts after their spans, and the keyframe anchors — which live on the target timeline — shift by exactly
that amount. ComfyUI does this with a pre-pass (`cursor = text_len + sum(_ref_t_span(blk))`); so does this.
Correctness is pinned by `validate_against_stock()`: with no keyframes this must reproduce diffusers' `ref2va`
layout bit for bit, and with no references its `fl2va` layout, both including `position_ids` in float64.
"""
from __future__ import annotations
import numpy as np
import torch
from diffusers.modular_pipelines.minimax_h3.before_denoise import (
_ROPE_FRAME_RESCALE,
_ROPE_FRAMES_PER_LATENT,
_fill_audio_positions,
_frame_position_grid,
_temporal_position_grid,
MiniMaxH3PrepareLayoutStep,
MiniMaxH3Ref2VAPrepareLayoutStep,
)
def _target_span_sum(num_latent_frames: int) -> float:
"""Rotary time the generated frames span, by numpy pairwise summation — the order the keyframe anchor uses."""
spans = np.ones(num_latent_frames, dtype=np.float64) * _ROPE_FRAME_RESCALE
for offset in range(len(_ROPE_FRAMES_PER_LATENT)):
spans[offset :: len(_ROPE_FRAMES_PER_LATENT)] *= _ROPE_FRAMES_PER_LATENT[offset]
return float(spans.sum())
def _video_span_sequential(num_latent_frames: int) -> float:
"""The same series summed sequentially — the order a reference block advances the clock by. The two differ in
the last ulp from 16 latent frames on, and the reference implementation keeps both, one per call site."""
return sum(
_ROPE_FRAME_RESCALE * _ROPE_FRAMES_PER_LATENT[index % len(_ROPE_FRAMES_PER_LATENT)]
for index in range(num_latent_frames)
)
def _reference_span(reference, visual_geometry, audio_row_counts, audio_channels) -> float:
"""`_ref_t_span` from ComfyUI: the time axis a reference block occupies ahead of the target streams."""
if reference.kind == "image":
next(visual_geometry)
return 1.0
if reference.kind == "audio":
return float(next(audio_row_counts) // audio_channels)
if reference.kind == "video":
audio_latents = (next(audio_row_counts) // audio_channels) if reference.has_audio else 0
frames, _, _ = next(visual_geometry)
return max(float(audio_latents), _video_span_sequential(frames))
raise ValueError(f"A reference must be an 'image', a 'video' or an 'audio', got {reference.kind!r}.")
def build_combined_packed_sequence(
text_token_tags: torch.Tensor,
references: list,
condition_latents: list[torch.Tensor],
audio_condition_latents: list[torch.Tensor],
num_latent_frames: int,
latent_height: int,
latent_width: int,
num_audio_latents: int,
patch_size: tuple[int, int, int],
audio_channels: int,
audio_tag: int,
video_tag: int,
keyframe_anchors: tuple[str, ...] = (),
):
"""`condition_latents` is keyframe latents first (one per `keyframe_anchors` entry), then the reference latents."""
_, patch_h, patch_w = patch_size
num_keyframes = len(keyframe_anchors)
keyframe_latents, reference_latents = condition_latents[:num_keyframes], condition_latents[num_keyframes:]
num_text_tokens = text_token_tags.shape[0]
rows_per_frame = (latent_height // patch_h) * (latent_width // patch_w)
num_keyframe_rows = num_keyframes * rows_per_frame
num_reference_video_rows = sum(
frames * (height // patch_h) * (width // patch_w)
for frames, height, width in (tuple(latents.shape[2:5]) for latents in reference_latents)
)
num_reference_audio_rows = sum(rows.shape[0] for rows in audio_condition_latents)
num_target_video_rows = num_latent_frames * rows_per_frame
num_target_audio_rows = num_audio_latents * audio_channels
sequence_length = (
num_text_tokens
+ num_keyframe_rows
+ num_reference_video_rows
+ num_reference_audio_rows
+ num_target_audio_rows
+ num_target_video_rows
)
position_ids = torch.zeros(sequence_length, 3, dtype=torch.float64)
position_ids[:num_text_tokens, 0] = torch.arange(num_text_tokens, dtype=torch.float64)
target_frame_grid, target_width_grid = _frame_position_grid(latent_height, latent_width, patch_h, patch_w)
# Pre-pass: how far the references push the target timeline out. The keyframe anchors ride on that timeline.
span_geometry = iter(tuple(latents.shape[2:5]) for latents in reference_latents)
span_audio = iter(rows.shape[0] for rows in audio_condition_latents)
reference_span = sum(
_reference_span(reference, span_geometry, span_audio, audio_channels) for reference in references
)
target_origin = float(num_text_tokens) + reference_span
video_indices, audio_indices = [], []
# 1. Keyframe conditioning rows, immediately after the text, on the target spatial grid.
cursor = num_text_tokens
for index, anchor in enumerate(keyframe_anchors):
if anchor == "first":
anchor_time = target_origin
elif anchor == "last":
anchor_time = target_origin + _target_span_sum(num_latent_frames) - _ROPE_FRAME_RESCALE
else:
raise ValueError(f"A keyframe anchor must be 'first' or 'last', got {anchor!r}.")
frames = keyframe_latents[index].shape[2]
rows = slice(cursor, cursor + frames * rows_per_frame)
cursor = rows.stop
video_indices.append(torch.arange(rows.start, rows.stop))
position_ids[rows, 0] = anchor_time
position_ids[rows, 1:] = target_frame_grid.repeat(frames, 1)
# 2. Reference blocks, on their own clock starting where the text ends — exactly the stock `ref2va` walk.
visual_geometry = iter(tuple(latents.shape[2:5]) for latents in reference_latents)
audio_row_counts = iter(rows.shape[0] for rows in audio_condition_latents)
rotary_time = float(num_text_tokens)
for reference in references:
if reference.kind == "image":
frames, height, width = next(visual_geometry)
rows = slice(cursor, cursor + frames * (height // patch_h) * (width // patch_w))
cursor = rows.stop
video_indices.append(torch.arange(rows.start, rows.stop))
frame_grid, _ = _frame_position_grid(height, width, patch_h, patch_w)
position_ids[rows, 0] = rotary_time
position_ids[rows, 1:] = frame_grid
rotary_time += 1.0
elif reference.kind == "audio":
num_rows = next(audio_row_counts)
latents = num_rows // audio_channels
rows = slice(cursor, cursor + num_rows)
cursor = rows.stop
audio_indices.append(torch.arange(rows.start, rows.stop))
_fill_audio_positions(position_ids, rows, latents, rotary_time, target_width_grid, audio_channels)
rotary_time += float(latents)
elif reference.kind == "video":
num_rows = next(audio_row_counts) if reference.has_audio else 0
latents = num_rows // audio_channels
frames, height, width = next(visual_geometry)
audio_rows = slice(cursor, cursor + num_rows)
video_rows = slice(audio_rows.stop, audio_rows.stop + frames * (height // patch_h) * (width // patch_w))
cursor = video_rows.stop
audio_indices.append(torch.arange(audio_rows.start, audio_rows.stop))
video_indices.append(torch.arange(video_rows.start, video_rows.stop))
frame_grid, width_grid = _frame_position_grid(height, width, patch_h, patch_w)
_fill_audio_positions(position_ids, audio_rows, latents, rotary_time, width_grid, audio_channels)
frame_time = _temporal_position_grid(frames, rotary_time)
position_ids[video_rows, 0] = frame_time.repeat_interleave(frame_grid.shape[0])
position_ids[video_rows, 1:] = frame_grid.repeat(frames, 1)
rotary_time += max(float(latents), _video_span_sequential(frames))
else:
raise ValueError(f"A reference must be an 'image', a 'video' or an 'audio', got {reference.kind!r}.")
# 3. The generated rows, on the timeline the references left behind — the same origin the keyframes anchored to.
audio_start = cursor
video_start = audio_start + num_target_audio_rows
_fill_audio_positions(
position_ids,
slice(audio_start, video_start),
num_audio_latents,
target_origin,
target_width_grid,
audio_channels,
)
frame_time = _temporal_position_grid(num_latent_frames, target_origin)
position_ids[video_start:, 0] = frame_time.repeat_interleave(target_frame_grid.shape[0])
position_ids[video_start:, 1:] = target_frame_grid.repeat(num_latent_frames, 1)
video_indices = torch.cat(video_indices + [torch.arange(video_start, sequence_length)])
audio_indices = torch.cat(audio_indices + [torch.arange(audio_start, video_start)])
text_indices = torch.arange(num_text_tokens)
token_tags = torch.empty(sequence_length, dtype=torch.long)
token_tags[text_indices] = text_token_tags.to(torch.long)
token_tags[audio_indices] = audio_tag
token_tags[video_indices] = video_tag
return (
position_ids,
token_tags,
video_indices,
audio_indices,
text_indices,
num_keyframe_rows + num_reference_video_rows,
num_reference_audio_rows,
)
def validate_against_stock(verbose: bool = True) -> dict:
"""Both degenerate cases must reproduce the shipped builders exactly."""
class _Ref:
def __init__(self, kind, has_audio=False):
self.kind, self.has_audio = kind, has_audio
geometry = dict(num_latent_frames=8, latent_height=34, latent_width=60, num_audio_latents=200,
patch_size=(1, 2, 2), audio_channels=2, audio_tag=2, video_tag=0)
tags = torch.randint(0, 2, (57,))
report = {}
# (a) references only -> the stock ref2va layout
refs = [_Ref("image"), _Ref("video", has_audio=True), _Ref("audio")]
ref_latents = [torch.zeros(1, 16, 1, 32, 32), torch.zeros(1, 16, 3, 34, 60)]
ref_audio = [torch.zeros(60, 8), torch.zeros(40, 8)]
mine = build_combined_packed_sequence(tags, refs, ref_latents, ref_audio, **geometry)
stock = MiniMaxH3Ref2VAPrepareLayoutStep.build_ref2va_packed_sequence(
tags, refs, ref_latents, ref_audio, **geometry)
report["refs_only"] = _compare(mine, stock)
# (b) keyframes only -> the stock fl2va layout
anchors = ("first", "last")
kf_latents = [torch.zeros(1, 16, 1, 34, 60), torch.zeros(1, 16, 1, 34, 60)]
mine = build_combined_packed_sequence(tags, [], kf_latents, [], keyframe_anchors=anchors, **geometry)
stock = MiniMaxH3PrepareLayoutStep.build_packed_sequence(tags, keyframe_anchors=anchors, **geometry)
report["keyframes_only"] = _compare(mine, stock)
if verbose:
for case, result in report.items():
print(f"{case}: {result}")
return report
def _compare(mine, stock) -> dict:
names = ["position_ids", "token_tags", "video_indices", "audio_indices", "text_indices",
"num_condition_video_rows", "num_condition_audio_rows"]
out = {}
for name, a, b in zip(names, mine, stock):
if isinstance(a, torch.Tensor):
out[name] = "identical" if a.shape == b.shape and torch.equal(a, b) else f"DIFFERS {tuple(a.shape)} vs {tuple(b.shape)}"
else:
out[name] = "identical" if a == b else f"DIFFERS {a} vs {b}"
return out
if __name__ == "__main__":
report = validate_against_stock()
bad = {c: {k: v for k, v in r.items() if v != "identical"} for c, r in report.items()}
bad = {c: v for c, v in bad.items() if v}
print("\nVALIDATION", "PASSED" if not bad else f"FAILED: {bad}")
|