Spaces:
Paused
Paused
| # Copyright 2026 The MiniMax and HuggingFace Teams. All rights reserved. | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| r""" | |
| Packed-sequence and conditioning machinery of the MiniMax-H3 blocks. | |
| This module holds no block of its own: it is the checkpoint's geometry and its constants, imported by every block | |
| of `modular_pipelines.minimax_h3` that has to place a row, so that none of them reimplements it. | |
| MiniMax-H3 runs its transformer over a single packed 1-D sequence that holds every modality at once. For the | |
| text/keyframe tasks the row order is | |
| ``` | |
| [ text (L) | keyframe conditions (C) | target audio (A) | target video (V) ] | |
| ``` | |
| and every piece of geometry in this module exists to place a row in that sequence and to give it its `(t, h, w)` | |
| rotary coordinate. The coordinates are built in float64 because video and audio share one 40-units-per-second | |
| rotary clock — video advances `5/3` rotary units per pixel frame at 24 fps, audio advances one unit per latent at | |
| 40 latents/s — and that shared clock *is* the audio/video alignment. | |
| The reference implementation pads the packed sequence up to a multiple of 64 and keeps the padding tail as a | |
| separate attention document. Padding therefore cannot influence a live row, and this module builds the sequence | |
| without it: `MiniMaxH3Transformer3DModel` then needs no attention mask, which keeps the unmasked attention | |
| backends available. | |
| """ | |
| from dataclasses import dataclass | |
| import numpy as np | |
| import torch | |
| from PIL import Image | |
| from ...utils.torch_utils import randn_tensor | |
| # Per-row modality tags. They index the transformer's AdaLN table, so the values are a checkpoint contract. | |
| MINIMAX_H3_VIDEO_TAG = 0 | |
| MINIMAX_H3_TEXT_TAG = 1 | |
| MINIMAX_H3_AUDIO_TAG = 2 | |
| # MiniMax-H3 generates at a fixed 24 fps and was released for a 768 pixel short edge only, with a soft area cap of | |
| # 768x1344 and both axes rounded to a multiple of 32. | |
| MINIMAX_H3_FPS = 24 | |
| MINIMAX_H3_SHORT_EDGE = 768 | |
| MINIMAX_H3_MAX_PIXELS = 768 * 1344 | |
| MINIMAX_H3_CANVAS_MULTIPLE = 32 | |
| MINIMAX_H3_MIN_ASPECT_RATIO = 1 / 4 | |
| MINIMAX_H3_MAX_ASPECT_RATIO = 4 | |
| MINIMAX_H3_MIN_DURATION = 2.0 # released contract says 5.0; lowered for short-clip support, snaps to 56 frames minimum | |
| MINIMAX_H3_MAX_DURATION = 15.0 | |
| # The video VAE encodes 17 pixel frames per chunk and drops the 3 trailing latent frames of every chunk, so | |
| # `17 * n + 5` pixel frames map to `5 * n + 2` latent frames. | |
| MINIMAX_H3_FRAMES_PER_CHUNK = 17 | |
| MINIMAX_H3_LATENTS_PER_CHUNK = 5 | |
| # The pixel convention of the video VAE: ImageNet-normalized RGB over a `[0, 1]` base range. | |
| MINIMAX_H3_PIXEL_MEAN = (0.485, 0.456, 0.406) | |
| MINIMAX_H3_PIXEL_STD = (0.229, 0.224, 0.225) | |
| # MiniMax-H3 conditions on the *unnormalized* hidden state its Qwen3-VL conditioner produces after the 50th of its 64 | |
| # decoder layers, i.e. `hidden_states[50]` (`hidden_states[0]` being the embedding output). | |
| MINIMAX_H3_TEXT_ENCODER_LAYER = 50 | |
| # The audio VAE hops 800 samples at 32 kHz, i.e. 40 latents per second. Stereo is carried as two channel-major | |
| # blocks of audio rows (and as two batch items at the audio VAE boundary, which is mono). | |
| MINIMAX_H3_AUDIO_LATENTS_PER_SECOND = 40 | |
| MINIMAX_H3_AUDIO_CHANNELS = 2 | |
| # Conditioning rows are not fully clean: the released model noises keyframe latents to `t = 0.999` and runs them at | |
| # that timestep for every denoising step. | |
| MINIMAX_H3_KEYFRAME_NOISE_AUG = 0.999 | |
| # The seeded posterior sample of the keyframe VAE encode. Fixed at 42 independently of the request seed. | |
| MINIMAX_H3_KEYFRAME_ENCODE_SEED = 42 | |
| # Rotary-time constants. One latent frame spans `5/3 * frames_per_latent` rotary units, where the pattern | |
| # `(1, 4, 4, 4, 4)` mirrors the VAE's 17-pixel-frames-to-5-latent-frames grouping; the spatial axes are normalized | |
| # by the square root of the latent area and scaled by 32. | |
| _ROPE_FRAME_RESCALE = 5.0 / 3.0 | |
| _ROPE_FRAMES_PER_LATENT = (1, 4, 4, 4, 4) | |
| _ROPE_SPATIAL_SCALE = 32 | |
| class MiniMaxH3PackedSequence: | |
| r""" | |
| The structural description of one packed MiniMax-H3 sequence. | |
| Attributes: | |
| sequence_length (`int`): | |
| Total number of rows, `L + C + A + V`. | |
| position_ids (`torch.Tensor` of shape `(sequence_length, 3)`, float64): | |
| The `(t, h, w)` rotary coordinate of every row. | |
| token_tags (`torch.Tensor` of shape `(sequence_length,)`): | |
| The modality tag of every row. | |
| video_indices (`torch.Tensor`): | |
| Sequence positions of the video rows: the keyframe conditioning rows first, then the target rows. | |
| audio_indices (`torch.Tensor`): | |
| Sequence positions of the audio rows: reference rows first (`ref2va` only), then the target rows. | |
| text_indices (`torch.Tensor`): | |
| Sequence positions of the text rows. | |
| num_condition_video_rows (`int`): | |
| How many leading entries of `video_indices` are conditioning rows rather than generated rows. | |
| num_condition_audio_rows (`int`): | |
| How many leading entries of `audio_indices` are reference rows rather than generated rows. | |
| """ | |
| sequence_length: int | |
| position_ids: torch.Tensor | |
| token_tags: torch.Tensor | |
| video_indices: torch.Tensor | |
| audio_indices: torch.Tensor | |
| text_indices: torch.Tensor | |
| num_condition_video_rows: int | |
| num_condition_audio_rows: int | |
| def resolve_canvas_size(aspect_width: float, aspect_height: float) -> tuple[int, int]: | |
| r""" | |
| Resolve a display aspect ratio into a MiniMax-H3 canvas. | |
| The short edge starts at 768, the area is capped at `768 * 1344` and both axes are then rounded to the nearest | |
| multiple of 32 — so the final area may end up slightly above the pre-rounding budget. Only the ratio of the two | |
| arguments matters; pass either the aspect ratio (`16, 9`) or the source dimensions of a keyframe. | |
| Args: | |
| aspect_width (`float`): Width of the target ratio. | |
| aspect_height (`float`): Height of the target ratio. | |
| Returns: | |
| `tuple[int, int]`: the `(height, width)` of the canvas. | |
| """ | |
| if aspect_width <= 0 or aspect_height <= 0: | |
| raise ValueError(f"The aspect ratio must be positive, got {aspect_width}:{aspect_height}.") | |
| ratio = aspect_width / aspect_height | |
| if not MINIMAX_H3_MIN_ASPECT_RATIO <= ratio <= MINIMAX_H3_MAX_ASPECT_RATIO: | |
| raise ValueError( | |
| f"MiniMax-H3 supports aspect ratios from 1:4 to 4:1, got {aspect_width}:{aspect_height} ({ratio:g})." | |
| ) | |
| if ratio >= 1.0: | |
| width, height = MINIMAX_H3_SHORT_EDGE * ratio, float(MINIMAX_H3_SHORT_EDGE) | |
| else: | |
| width, height = float(MINIMAX_H3_SHORT_EDGE), MINIMAX_H3_SHORT_EDGE / ratio | |
| area = width * height | |
| if area > MINIMAX_H3_MAX_PIXELS: | |
| scale = (MINIMAX_H3_MAX_PIXELS / area) ** 0.5 | |
| width, height = width * scale, height * scale | |
| multiple = MINIMAX_H3_CANVAS_MULTIPLE | |
| return max(multiple, round(height / multiple) * multiple), max(multiple, round(width / multiple) * multiple) | |
| def align_num_frames(num_frames: int) -> int: | |
| r""" | |
| Snap a frame count up to the next `17 * n + 5` the video VAE can encode. | |
| Args: | |
| num_frames (`int`): The requested number of frames. | |
| Returns: | |
| `int`: The aligned number of frames. | |
| """ | |
| if num_frames < 1: | |
| raise ValueError(f"`num_frames` must be positive, got {num_frames}.") | |
| while num_frames % MINIMAX_H3_FRAMES_PER_CHUNK != MINIMAX_H3_LATENTS_PER_CHUNK: | |
| num_frames += 1 | |
| return num_frames | |
| def video_latent_num_frames(num_frames: int) -> int: | |
| r""" | |
| The number of latent frames the video VAE produces for a `17 * n + 5` frame count. | |
| Args: | |
| num_frames (`int`): An aligned number of frames. | |
| Returns: | |
| `int`: The number of latent frames, `5 * n + 2`. | |
| """ | |
| if num_frames % MINIMAX_H3_FRAMES_PER_CHUNK != MINIMAX_H3_LATENTS_PER_CHUNK: | |
| raise ValueError(f"`num_frames` must be of the form 17 * n + 5, got {num_frames}.") | |
| return ( | |
| num_frames - MINIMAX_H3_LATENTS_PER_CHUNK | |
| ) // MINIMAX_H3_FRAMES_PER_CHUNK * MINIMAX_H3_LATENTS_PER_CHUNK + 2 | |
| def audio_latent_num_frames(num_frames: int) -> int: | |
| r""" | |
| The number of audio latents that covers a video of `num_frames` frames at 24 fps. | |
| Args: | |
| num_frames (`int`): The number of video frames. | |
| Returns: | |
| `int`: The number of audio latents, rounded at the 40 Hz latent grid. | |
| """ | |
| return int(round(num_frames / MINIMAX_H3_FPS * MINIMAX_H3_AUDIO_LATENTS_PER_SECOND)) | |
| def prepare_keyframe_image(image, height: int, width: int, stretch: bool): | |
| r""" | |
| Put a keyframe onto the target canvas. | |
| The first keyframe of a request is the geometry anchor and is *stretched* onto the canvas, while a second | |
| keyframe follows that canvas and is cover-cropped (aspect-preserving max-scale LANCZOS resize plus a centre | |
| crop). An image that already is the canvas is returned untouched, without a resampling pass. | |
| Args: | |
| image (`PIL.Image.Image`): The keyframe, in RGB and already EXIF-transposed. | |
| height (`int`): Canvas height. | |
| width (`int`): Canvas width. | |
| stretch (`bool`): Whether to stretch (geometry anchor) instead of cover-cropping (follower). | |
| Returns: | |
| `PIL.Image.Image`: The prepared keyframe. | |
| """ | |
| if image.size == (width, height): | |
| return image | |
| if stretch: | |
| return image.resize((width, height), Image.Resampling.LANCZOS) | |
| scale = max(width / image.size[0], height / image.size[1]) | |
| resized_size = (max(width, round(image.size[0] * scale)), max(height, round(image.size[1] * scale))) | |
| left = max(0, (resized_size[0] - width) // 2) | |
| top = max(0, (resized_size[1] - height) // 2) | |
| resized = image.resize(resized_size, Image.Resampling.LANCZOS) | |
| return resized.crop((left, top, left + width, top + height)) | |
| def patchify_video_latents(latents: torch.Tensor, patch_size: tuple[int, int, int]) -> torch.Tensor: | |
| r""" | |
| Pack video latents into transformer rows. | |
| Args: | |
| latents (`torch.Tensor` of shape `(batch_size, channels, num_frames, height, width)`): | |
| The latents to pack. | |
| patch_size (`tuple[int, int, int]`): The `(t, h, w)` patch. | |
| Returns: | |
| `torch.Tensor` of shape `(batch_size * num_patches, channels * prod(patch_size))`: The packed rows, ordered | |
| frame-major then row-major. | |
| """ | |
| patch_t, patch_h, patch_w = patch_size | |
| batch_size, channels, num_frames, height, width = latents.shape | |
| if num_frames % patch_t or height % patch_h or width % patch_w: | |
| raise ValueError(f"Latents of shape {tuple(latents.shape)} are not divisible by the patch {patch_size}.") | |
| latents = latents.reshape( | |
| batch_size, | |
| channels, | |
| num_frames // patch_t, | |
| patch_t, | |
| height // patch_h, | |
| patch_h, | |
| width // patch_w, | |
| patch_w, | |
| ) | |
| latents = latents.permute(0, 2, 4, 6, 1, 3, 5, 7) | |
| return latents.reshape(-1, channels * patch_t * patch_h * patch_w).contiguous() | |
| def unpatchify_video_tokens( | |
| rows: torch.Tensor, | |
| num_latent_frames: int, | |
| latent_height: int, | |
| latent_width: int, | |
| channels: int, | |
| patch_size: tuple[int, int, int], | |
| ) -> torch.Tensor: | |
| r""" | |
| Unpack transformer rows back into video latents. The inverse of [`patchify_video_latents`]. | |
| Args: | |
| rows (`torch.Tensor` of shape `(num_patches, channels * prod(patch_size))`): The packed rows. | |
| num_latent_frames (`int`): Number of latent frames. | |
| latent_height (`int`): Latent height. | |
| latent_width (`int`): Latent width. | |
| channels (`int`): Number of latent channels. | |
| patch_size (`tuple[int, int, int]`): The `(t, h, w)` patch. | |
| Returns: | |
| `torch.Tensor` of shape `(batch_size, channels, num_latent_frames, latent_height, latent_width)`. | |
| """ | |
| patch_t, patch_h, patch_w = patch_size | |
| rows = rows.reshape( | |
| -1, | |
| num_latent_frames // patch_t, | |
| latent_height // patch_h, | |
| latent_width // patch_w, | |
| channels, | |
| patch_t, | |
| patch_h, | |
| patch_w, | |
| ) | |
| rows = rows.permute(0, 4, 1, 5, 2, 6, 3, 7) | |
| return rows.reshape(-1, channels, num_latent_frames, latent_height, latent_width).contiguous() | |
| def unpack_audio_tokens(rows: torch.Tensor, num_audio_latents: int) -> torch.Tensor: | |
| r""" | |
| Unpack the channel-major audio rows into audio VAE latents. | |
| Args: | |
| rows (`torch.Tensor` of shape `(num_audio_latents * 2, latent_channels)`): The packed audio rows. | |
| num_audio_latents (`int`): Number of audio latents per channel. | |
| Returns: | |
| `torch.Tensor` of shape `(2, latent_channels, num_audio_latents)`: One batch item per stereo channel, which | |
| is what the mono audio VAE consumes. | |
| """ | |
| rows = rows.reshape(MINIMAX_H3_AUDIO_CHANNELS, num_audio_latents, rows.shape[-1]) | |
| return rows.permute(0, 2, 1).contiguous() | |
| def _spatial_position_grid(dim: int, patch: int, sqrt_area: float) -> torch.Tensor: | |
| r""" | |
| One aspect-normalized spatial rotary axis: `dim // patch` coordinates centred on the unit interval, scaled up by | |
| 32. The right endpoint is excluded, so a square canvas spans `[0, 32)`. | |
| """ | |
| ratio = dim / sqrt_area | |
| left = (1.0 - ratio) / 2.0 | |
| # Built with numpy: `np.linspace(..., endpoint=False)` is `start + arange(num) * (stop - start) / num`, which is | |
| # not what `torch.linspace` computes, and the float64 grid has to be reproduced exactly. | |
| grid = np.linspace(left, left + ratio, dim // patch, endpoint=False) * _ROPE_SPATIAL_SCALE | |
| return torch.from_numpy(grid).to(torch.float64) | |
| def _temporal_position_grid(num_latent_frames: int, origin: float) -> torch.Tensor: | |
| r"""The rotary time of every latent frame, starting at `origin`. Spacing is non-uniform: `5/3 * (1, 4, 4, 4, 4)`.""" | |
| spans = torch.tensor( | |
| [ | |
| _ROPE_FRAME_RESCALE * _ROPE_FRAMES_PER_LATENT[index % len(_ROPE_FRAMES_PER_LATENT)] | |
| for index in range(num_latent_frames) | |
| ], | |
| dtype=torch.float64, | |
| ) | |
| return origin + torch.cat([torch.zeros(1, dtype=torch.float64), spans[:-1].cumsum(0)]) | |
| def _temporal_position_span(num_latent_frames: int) -> float: | |
| r""" | |
| The rotary time spanned by `num_latent_frames` latent frames. | |
| Summed by numpy (pairwise summation) rather than sequentially: the reference computes the keyframe anchor this | |
| way and the two summation orders differ in the last ulp from 16 latent frames onwards. | |
| """ | |
| spans = np.ones(num_latent_frames, dtype=np.float64) * _ROPE_FRAME_RESCALE | |
| for index in range(len(_ROPE_FRAMES_PER_LATENT)): | |
| spans[index :: len(_ROPE_FRAMES_PER_LATENT)] *= _ROPE_FRAMES_PER_LATENT[index] | |
| return float(spans.sum()) | |
| def build_packed_sequence( | |
| text_token_tags: torch.Tensor, | |
| num_latent_frames: int, | |
| latent_height: int, | |
| latent_width: int, | |
| num_audio_latents: int, | |
| patch_size: tuple[int, int, int], | |
| keyframe_anchors: tuple[str, ...] = (), | |
| ) -> MiniMaxH3PackedSequence: | |
| r""" | |
| Build the `[text | keyframe conditions | target audio | target video]` layout used by the `t2va` and `fl2va` | |
| tasks. | |
| Args: | |
| text_token_tags (`torch.Tensor` of shape `(num_text_tokens,)`): | |
| The modality tag of every text row. Text is tagged `1`, except for the rows of a keyframe's vision block, | |
| which MiniMax-H3 tags `0` (video). | |
| num_latent_frames (`int`): Number of target latent frames. | |
| latent_height (`int`): Target latent height. | |
| latent_width (`int`): Target latent width. | |
| num_audio_latents (`int`): Number of target audio latents per channel. | |
| patch_size (`tuple[int, int, int]`): The transformer's `(t, h, w)` patch. | |
| keyframe_anchors (`tuple[str, ...]`): | |
| One entry per keyframe conditioning block, in packed order: `"first"` anchors the block at the first | |
| latent frame, `"last"` at the last one. | |
| Returns: | |
| [`MiniMaxH3PackedSequence`] | |
| """ | |
| _, patch_h, patch_w = patch_size | |
| rows_per_frame = (latent_height // patch_h) * (latent_width // patch_w) | |
| num_text_tokens = text_token_tags.shape[0] | |
| num_condition_rows = len(keyframe_anchors) * rows_per_frame | |
| num_audio_rows = num_audio_latents * MINIMAX_H3_AUDIO_CHANNELS | |
| num_video_rows = num_latent_frames * rows_per_frame | |
| sequence_length = num_text_tokens + num_condition_rows + num_audio_rows + num_video_rows | |
| condition_start = num_text_tokens | |
| audio_start = condition_start + num_condition_rows | |
| video_start = audio_start + num_audio_rows | |
| # 1. The (t, h, w) grid. Text rows sit on the time axis at their row index, and the media rows continue the time | |
| # axis from there, so text length shifts the whole media clock. | |
| position_ids = torch.zeros(sequence_length, 3, dtype=torch.float64) | |
| position_ids[:num_text_tokens, 0] = torch.arange(num_text_tokens, dtype=torch.float64) | |
| sqrt_area = np.sqrt(latent_height * latent_width) | |
| height_grid = _spatial_position_grid(latent_height, patch_h, sqrt_area) | |
| width_grid = _spatial_position_grid(latent_width, patch_w, sqrt_area) | |
| frame_grid = torch.stack([grid.reshape(-1) for grid in torch.meshgrid(height_grid, width_grid, indexing="ij")], -1) | |
| for index, anchor in enumerate(keyframe_anchors): | |
| if anchor == "first": | |
| anchor_time = float(num_text_tokens) | |
| elif anchor == "last": | |
| anchor_time = float(num_text_tokens) + _temporal_position_span(num_latent_frames) - _ROPE_FRAME_RESCALE | |
| else: | |
| raise ValueError(f"A keyframe anchor must be 'first' or 'last', got {anchor!r}.") | |
| rows = slice(condition_start + index * rows_per_frame, condition_start + (index + 1) * rows_per_frame) | |
| position_ids[rows, 0] = anchor_time | |
| position_ids[rows, 1:] = frame_grid | |
| # Audio rows are channel-major and share the video's rotary clock: one unit per latent at 40 latents/s equals | |
| # 24 fps * 5/3. They carry no height coordinate and are pinned to the two extremes of the width grid. | |
| audio_time = float(num_text_tokens) + torch.arange(num_audio_latents, dtype=torch.float64) | |
| position_ids[audio_start:video_start, 0] = audio_time.repeat(MINIMAX_H3_AUDIO_CHANNELS) | |
| position_ids[audio_start:video_start, 2] = torch.cat( | |
| [ | |
| torch.full((num_audio_latents,), float(width_grid[0]), dtype=torch.float64), | |
| torch.full((num_audio_rows - num_audio_latents,), float(width_grid[-1]), dtype=torch.float64), | |
| ] | |
| ) | |
| video_position_ids = torch.empty(num_latent_frames, rows_per_frame, 3, dtype=torch.float64) | |
| video_position_ids[:, :, 0] = _temporal_position_grid(num_latent_frames, float(num_text_tokens))[:, None] | |
| video_position_ids[:, :, 1:] = frame_grid[None] | |
| position_ids[video_start:] = video_position_ids.reshape(-1, 3) | |
| # 2. Row indices and modality tags. | |
| video_indices = torch.cat([torch.arange(condition_start, audio_start), torch.arange(video_start, sequence_length)]) | |
| 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] = MINIMAX_H3_AUDIO_TAG | |
| token_tags[video_indices] = MINIMAX_H3_VIDEO_TAG | |
| return MiniMaxH3PackedSequence( | |
| sequence_length=sequence_length, | |
| position_ids=position_ids, | |
| token_tags=token_tags, | |
| video_indices=video_indices, | |
| audio_indices=audio_indices, | |
| text_indices=text_indices, | |
| num_condition_video_rows=num_condition_rows, | |
| num_condition_audio_rows=0, | |
| ) | |
| def build_row_timesteps( | |
| layout: MiniMaxH3PackedSequence, | |
| video_timestep: float, | |
| audio_timestep: float, | |
| condition_video_timestep: float, | |
| condition_audio_timestep: float, | |
| ) -> tuple[torch.Tensor, torch.Tensor]: | |
| r""" | |
| Assign a timestep to every row of the packed sequence and reduce it to the transformer's `(timestep, | |
| timestep_indices)` pair. | |
| One forward serves rows at different noise levels: the generated video and audio rows step down their own | |
| schedules while the conditioning rows stay pinned at their noise-augmentation level. Text rows never reach an | |
| output head and inherit the video timestep. | |
| Args: | |
| layout ([`MiniMaxH3PackedSequence`]): The packed layout. | |
| video_timestep (`float`): Timestep of the generated video rows. | |
| audio_timestep (`float`): Timestep of the generated audio rows. | |
| condition_video_timestep (`float`): Timestep of the video conditioning rows. | |
| condition_audio_timestep (`float`): Timestep of the audio reference rows. | |
| Returns: | |
| `tuple[torch.Tensor, torch.Tensor]`: the distinct timesteps, sorted, and the index of every row into them. | |
| """ | |
| row_timesteps = torch.full((layout.sequence_length,), video_timestep, dtype=torch.float32) | |
| row_timesteps[layout.video_indices[: layout.num_condition_video_rows]] = condition_video_timestep | |
| row_timesteps[layout.audio_indices[layout.num_condition_audio_rows :]] = audio_timestep | |
| row_timesteps[layout.audio_indices[: layout.num_condition_audio_rows]] = condition_audio_timestep | |
| return torch.unique(row_timesteps, sorted=True, return_inverse=True) | |
| def keyframe_condition_noise( | |
| condition_latent_shapes: tuple[tuple[int, int, int], ...], | |
| patch_size: tuple[int, int, int], | |
| latent_channels: int, | |
| generator: torch.Generator | list[torch.Generator] | None = None, | |
| device: torch.device | None = None, | |
| dtype: torch.dtype = torch.float32, | |
| ) -> torch.Tensor: | |
| r""" | |
| Draw the noise that the keyframe (or reference) conditioning rows are mixed with. | |
| One draw per condition, in packed order, off the request's generator. The conditioning rows are prepared before | |
| the target rows, so these are the *first* draws of a request, ahead of the video and audio noise of | |
| [`~MiniMaxH3PrepareLatentsStep.prepare_latents`] — the order is part of what a generator reproduces. | |
| Args: | |
| condition_latent_shapes (`tuple[tuple[int, int, int], ...]`): | |
| The `(num_latent_frames, latent_height, latent_width)` of every condition, in packed order. | |
| patch_size (`tuple[int, int, int]`): The transformer's `(t, h, w)` patch. | |
| latent_channels (`int`): Number of video latent channels. | |
| generator (`torch.Generator`, *optional*): The generator of the request. | |
| device (`torch.device`, *optional*): The device the noise is drawn on. | |
| dtype (`torch.dtype`, defaults to `torch.float32`): The dtype of the noise. | |
| Returns: | |
| `torch.Tensor` of shape `(num_condition_rows, latent_channels * prod(patch_size))`: the noise rows, | |
| concatenated in packed order. | |
| """ | |
| rows = [] | |
| for num_latent_frames, latent_height, latent_width in condition_latent_shapes: | |
| noise = randn_tensor( | |
| (1, latent_channels, num_latent_frames, latent_height, latent_width), | |
| generator=generator, | |
| device=device, | |
| dtype=dtype, | |
| ) | |
| rows.append(patchify_video_latents(noise, patch_size)) | |
| return torch.cat(rows) | |