Spaces:
Running on Zero
Running on Zero
Download diffusers/modular_pipelines/minimax_h3/before_denoise.py from diffusers-modular/flux3-image-klein-bbox: direct link, hf CLI and curl.
- Browser
- Download file 59 kB
-
https://huggingface.co/spaces/diffusers-modular/flux3-image-klein-bbox/resolve/main/diffusers/modular_pipelines/minimax_h3/before_denoise.py
- Command line
-
hf download hf://spaces/diffusers-modular/flux3-image-klein-bbox/diffusers/modular_pipelines/minimax_h3/before_denoise.py
-
curl -L -o before_denoise.py https://huggingface.co/spaces/diffusers-modular/flux3-image-klein-bbox/resolve/main/diffusers/modular_pipelines/minimax_h3/before_denoise.py
59 kB
| # 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. | |
| import numpy as np | |
| import torch | |
| from ...schedulers import MiniMaxH3Scheduler | |
| from ...utils import logging | |
| from ...utils.torch_utils import randn_tensor | |
| from ..modular_pipeline import ModularPipelineBlocks, PipelineState | |
| from ..modular_pipeline_utils import ComponentSpec, ConfigSpec, InputParam, OutputParam | |
| from .modular_pipeline import ( | |
| MiniMaxH3ModularPipeline, | |
| align_num_frames, | |
| audio_latent_num_frames, | |
| resolve_canvas_size, | |
| video_latent_num_frames, | |
| ) | |
| from .references import MiniMaxH3Reference | |
| logger = logging.get_logger(__name__) # pylint: disable=invalid-name | |
| # 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 | |
| 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 _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 _frame_position_grid( | |
| latent_height: int, latent_width: int, patch_h: int, patch_w: int | |
| ) -> tuple[torch.Tensor, torch.Tensor]: | |
| r"""The `(h, w)` rotary coordinates of one latent frame, and the width axis they were built from.""" | |
| 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) | |
| grids = torch.meshgrid(height_grid, width_grid, indexing="ij") | |
| return torch.stack([grid.reshape(-1) for grid in grids], dim=-1), width_grid | |
| def _fill_audio_positions( | |
| position_ids: torch.Tensor, | |
| rows: slice, | |
| num_audio_latents: int, | |
| rotary_time: float, | |
| width_grid: torch.Tensor, | |
| audio_channels: int, | |
| ) -> None: | |
| r""" | |
| Place one channel-major audio block. | |
| Audio rows carry no height coordinate and are pinned to the two extremes of the width grid of *their own* block β | |
| the target grid for a standalone audio reference, the video's grid for a soundtrack. | |
| """ | |
| time = rotary_time + torch.arange(num_audio_latents, dtype=torch.float64) | |
| position_ids[rows, 0] = time.repeat(audio_channels) | |
| position_ids[rows, 2] = torch.cat( | |
| [ | |
| torch.full((num_audio_latents,), float(width_grid[0]), dtype=torch.float64), | |
| torch.full((num_audio_latents,), float(width_grid[-1]), dtype=torch.float64), | |
| ] | |
| ) | |
| class MiniMaxH3NoKeyframeAnchorsStep(ModularPipelineBlocks): | |
| model_name = "minimax-h3" | |
| def description(self) -> str: | |
| return ( | |
| "Declares that a `t2va` request anchors no keyframes. The layout step is the same block for `t2va` and " | |
| "`fl2va` and reads the anchors the request resolved; a text-only one resolves none, and saying so here " | |
| "keeps `keyframe_anchors` off the `t2va` signature." | |
| ) | |
| def intermediate_outputs(self) -> list[OutputParam]: | |
| return [ | |
| OutputParam( | |
| "keyframe_anchors", | |
| type_hint=tuple, | |
| description="Which end of the video every keyframe is anchored to β empty, since there are none.", | |
| ), | |
| ] | |
| def __call__(self, components: MiniMaxH3ModularPipeline, state: PipelineState) -> PipelineState: | |
| block_state = self.get_block_state(state) | |
| block_state.keyframe_anchors = () | |
| self.set_block_state(state, block_state) | |
| return components, state | |
| class MiniMaxH3PrepareLayoutStep(ModularPipelineBlocks): | |
| model_name = "minimax-h3" | |
| def description(self) -> str: | |
| return ( | |
| "Resolves the geometry of a `t2va` / `fl2va` request β the canvas, the `17 * n + 5` frame count the video " | |
| "VAE can decode and the latent shapes every later block keys off β and builds the packed layout from it: " | |
| "`[text | keyframe conditions | target audio | target video]` plus its fp64 rotary grid. MiniMax-H3 runs " | |
| "full self-attention over this one sequence, so the layout is what every later block addresses rows " | |
| "through." | |
| ) | |
| def expected_configs(self) -> list[ConfigSpec]: | |
| # The canvas MiniMax-H3 was released for, which a request without keyframes generates on at 16:9. | |
| return [ConfigSpec("canvas_short_edge", 768), ConfigSpec("canvas_max_pixels", 768 * 1344)] | |
| def inputs(self) -> list[InputParam]: | |
| return [ | |
| InputParam( | |
| name="text_token_tags", | |
| type_hint=torch.Tensor, | |
| required=True, | |
| description="The per-row modality tag of every row of `prompt_embeds`.", | |
| ), | |
| InputParam.template("height", description="Height of the generated video in pixels, a multiple of 32."), | |
| InputParam.template("width", description="Width of the generated video in pixels, a multiple of 32."), | |
| InputParam( | |
| name="num_frames", | |
| type_hint=int, | |
| default=124, | |
| description=( | |
| "Number of frames to generate, at the fixed 24 fps. Snapped up to the next `17 * n + 5` the video " | |
| "VAE can decode; the resulting duration must stay between 5 and 15 seconds." | |
| ), | |
| ), | |
| InputParam( | |
| name="keyframe_anchors", | |
| type_hint=tuple, | |
| default=(), | |
| description="Which end of the video every keyframe is anchored to, in packed order.", | |
| ), | |
| ] | |
| def intermediate_outputs(self) -> list[OutputParam]: | |
| return [ | |
| OutputParam("height", type_hint=int, description="Resolved height of the generated video in pixels."), | |
| OutputParam("width", type_hint=int, description="Resolved width of the generated video in pixels."), | |
| OutputParam("num_frames", type_hint=int, description="Resolved number of frames, of the form 17 * n + 5."), | |
| OutputParam("num_latent_frames", type_hint=int, description="Number of generated video latent frames."), | |
| OutputParam("latent_height", type_hint=int, description="Height of the generated video latents."), | |
| OutputParam("latent_width", type_hint=int, description="Width of the generated video latents."), | |
| OutputParam( | |
| "num_audio_latents", type_hint=int, description="Number of generated audio latents per channel." | |
| ), | |
| OutputParam( | |
| "position_ids", | |
| type_hint=torch.Tensor, | |
| kwargs_type="denoiser_input_fields", | |
| description="The `(t, h, w)` rotary coordinate of every row, in float64.", | |
| ), | |
| OutputParam( | |
| "token_tags", | |
| type_hint=torch.Tensor, | |
| kwargs_type="denoiser_input_fields", | |
| description="The modality tag of every row.", | |
| ), | |
| OutputParam( | |
| "video_indices", | |
| type_hint=torch.Tensor, | |
| kwargs_type="denoiser_input_fields", | |
| description="Sequence positions of the video rows, conditioning rows first.", | |
| ), | |
| OutputParam( | |
| "audio_indices", | |
| type_hint=torch.Tensor, | |
| kwargs_type="denoiser_input_fields", | |
| description="Sequence positions of the audio rows, reference rows first.", | |
| ), | |
| OutputParam( | |
| "text_indices", | |
| type_hint=torch.Tensor, | |
| kwargs_type="denoiser_input_fields", | |
| description="Sequence positions of the text rows.", | |
| ), | |
| OutputParam( | |
| "num_condition_video_rows", | |
| type_hint=int, | |
| description="How many leading video rows are conditioning rows rather than generated rows.", | |
| ), | |
| OutputParam( | |
| "num_condition_audio_rows", | |
| type_hint=int, | |
| description="How many leading audio rows are reference rows rather than generated rows.", | |
| ), | |
| ] | |
| 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], | |
| audio_channels: int, | |
| audio_tag: int, | |
| video_tag: int, | |
| keyframe_anchors: tuple[str, ...] = (), | |
| ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, int, int]: | |
| 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. | |
| audio_channels (`int`): Channels the soundtrack is packed channel-major over, i.e. | |
| `components.audio_channels`. | |
| audio_tag (`int`), video_tag (`int`): | |
| The modality tags an audio and a video row carry, i.e. `components.audio_tag` and | |
| `components.video_tag`. | |
| 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: | |
| `tuple`: `position_ids`, `token_tags`, `video_indices`, `audio_indices`, `text_indices`, and the number of | |
| leading video and audio rows that are conditioning rather than generated. | |
| """ | |
| _, 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 * 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) | |
| frame_grid, width_grid = _frame_position_grid(latent_height, latent_width, patch_h, patch_w) | |
| for index, anchor in enumerate(keyframe_anchors): | |
| if anchor == "first": | |
| anchor_time = float(num_text_tokens) | |
| elif anchor == "last": | |
| # The rotary time the generated frames span, summed by numpy's pairwise summation because that is | |
| # how the reference computes this anchor. The soundtrack span in the `ref2va` layout sums the same | |
| # series sequentially, and the two orders differ in the last ulp from 16 latent frames onwards, so | |
| # the port keeps both β one per call site. | |
| 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] | |
| anchor_time = float(num_text_tokens) + float(spans.sum()) - _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(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] = audio_tag | |
| token_tags[video_indices] = video_tag | |
| return position_ids, token_tags, video_indices, audio_indices, text_indices, num_condition_rows, 0 | |
| def __call__(self, components: MiniMaxH3ModularPipeline, state: PipelineState) -> PipelineState: | |
| block_state = self.get_block_state(state) | |
| device = components._execution_device | |
| if (block_state.height is None) != (block_state.width is None): | |
| raise ValueError("`height` and `width` have to be passed together, or neither of them.") | |
| # Without a keyframe to take the aspect ratio from, MiniMax-H3 generates on its own 16:9 canvas. | |
| if block_state.height is None: | |
| block_state.height, block_state.width = resolve_canvas_size( | |
| 16, | |
| 9, | |
| components.canvas_multiple, | |
| components.config.canvas_short_edge, | |
| components.config.canvas_max_pixels, | |
| ) | |
| if block_state.height % components.canvas_multiple or block_state.width % components.canvas_multiple: | |
| raise ValueError( | |
| f"`height` and `width` must be multiples of {components.canvas_multiple}, got " | |
| f"{block_state.height}x{block_state.width}." | |
| ) | |
| frames_per_chunk = components.vae_frames_per_chunk | |
| latents_per_chunk = components.vae_latents_per_chunk | |
| aligned_num_frames = align_num_frames(block_state.num_frames, frames_per_chunk, latents_per_chunk) | |
| # The duration the request generates is the one of the *aligned* frame count, so that is what the ceiling has | |
| # to hold for: 346 frames would otherwise pass the check and then be rounded up to 362, i.e. 15.083 seconds. | |
| duration = aligned_num_frames / components.fps | |
| if not components.min_duration <= duration <= components.max_duration: | |
| raise ValueError( | |
| f"MiniMax-H3 generates between {components.min_duration} and {components.max_duration} seconds at " | |
| f"{components.fps} fps, so `num_frames`, rounded up to the next `17 * n + 5` the video VAE can " | |
| f"encode, must be between {int(components.min_duration * components.fps)} and " | |
| f"{int(components.max_duration * components.fps)}, got {block_state.num_frames} (rounded up to " | |
| f"{aligned_num_frames})." | |
| ) | |
| if aligned_num_frames != block_state.num_frames: | |
| logger.warning( | |
| f"`num_frames` has to be of the form 17 * n + 5 for the video VAE; rounding {block_state.num_frames} " | |
| f"up to {aligned_num_frames}." | |
| ) | |
| block_state.num_frames = aligned_num_frames | |
| ratio = components.vae_spatial_compression_ratio | |
| block_state.num_latent_frames = video_latent_num_frames( | |
| block_state.num_frames, frames_per_chunk, latents_per_chunk | |
| ) | |
| block_state.latent_height = block_state.height // ratio | |
| block_state.latent_width = block_state.width // ratio | |
| block_state.num_audio_latents = audio_latent_num_frames(block_state.num_frames) | |
| ( | |
| position_ids, | |
| token_tags, | |
| video_indices, | |
| audio_indices, | |
| text_indices, | |
| block_state.num_condition_video_rows, | |
| block_state.num_condition_audio_rows, | |
| ) = self.build_packed_sequence( | |
| block_state.text_token_tags, | |
| block_state.num_latent_frames, | |
| block_state.latent_height, | |
| block_state.latent_width, | |
| block_state.num_audio_latents, | |
| components.patch_size, | |
| components.audio_channels, | |
| components.audio_tag, | |
| components.video_tag, | |
| block_state.keyframe_anchors, | |
| ) | |
| block_state.position_ids = position_ids.to(device) | |
| block_state.token_tags = token_tags.to(device) | |
| block_state.video_indices = video_indices.to(device) | |
| block_state.audio_indices = audio_indices.to(device) | |
| block_state.text_indices = text_indices.to(device) | |
| self.set_block_state(state, block_state) | |
| return components, state | |
| class MiniMaxH3Ref2VAPrepareLayoutStep(ModularPipelineBlocks): | |
| model_name = "minimax-h3" | |
| def description(self) -> str: | |
| return ( | |
| "Resolves the latent shapes of a `ref2va` request and builds its packed layout β `[text | reference " | |
| "blocks | target audio | target video]` β plus its fp64 rotary grid. The reference order advances the " | |
| "shared audio/video rotary clock, so it is part of the layout rather than a detail of the presentation." | |
| ) | |
| def inputs(self) -> list[InputParam]: | |
| return [ | |
| InputParam( | |
| name="text_token_tags", | |
| type_hint=torch.Tensor, | |
| required=True, | |
| description="The per-row modality tag of every row of `prompt_embeds`.", | |
| ), | |
| InputParam( | |
| name="normalized_references", | |
| type_hint=list[MiniMaxH3Reference], | |
| required=True, | |
| description="The references normalized by the setup step, in packed order.", | |
| ), | |
| InputParam( | |
| name="condition_latents", | |
| type_hint=list[torch.Tensor], | |
| required=True, | |
| description=( | |
| "The encoded video conditioning latents, one per image and video reference in packed order. " | |
| "Their shape is where every reference block's geometry comes from." | |
| ), | |
| ), | |
| InputParam( | |
| name="audio_condition_latents", | |
| type_hint=list[torch.Tensor], | |
| required=True, | |
| description="The encoded audio conditioning rows, one per audio-bearing reference in packed order.", | |
| ), | |
| InputParam.template("height", required=True, description="Height of the generated video in pixels."), | |
| InputParam.template("width", required=True, description="Width of the generated video in pixels."), | |
| InputParam( | |
| name="num_frames", | |
| type_hint=int, | |
| required=True, | |
| description="Resolved number of frames, of the form 17 * n + 5.", | |
| ), | |
| ] | |
| def intermediate_outputs(self) -> list[OutputParam]: | |
| return [ | |
| OutputParam("height", type_hint=int, description="Resolved height of the generated video in pixels."), | |
| OutputParam("width", type_hint=int, description="Resolved width of the generated video in pixels."), | |
| OutputParam("num_frames", type_hint=int, description="Resolved number of frames, of the form 17 * n + 5."), | |
| OutputParam("num_latent_frames", type_hint=int, description="Number of generated video latent frames."), | |
| OutputParam("latent_height", type_hint=int, description="Height of the generated video latents."), | |
| OutputParam("latent_width", type_hint=int, description="Width of the generated video latents."), | |
| OutputParam( | |
| "num_audio_latents", type_hint=int, description="Number of generated audio latents per channel." | |
| ), | |
| OutputParam( | |
| "position_ids", | |
| type_hint=torch.Tensor, | |
| kwargs_type="denoiser_input_fields", | |
| description="The `(t, h, w)` rotary coordinate of every row, in float64.", | |
| ), | |
| OutputParam( | |
| "token_tags", | |
| type_hint=torch.Tensor, | |
| kwargs_type="denoiser_input_fields", | |
| description="The modality tag of every row.", | |
| ), | |
| OutputParam( | |
| "video_indices", | |
| type_hint=torch.Tensor, | |
| kwargs_type="denoiser_input_fields", | |
| description="Sequence positions of the video rows, conditioning rows first.", | |
| ), | |
| OutputParam( | |
| "audio_indices", | |
| type_hint=torch.Tensor, | |
| kwargs_type="denoiser_input_fields", | |
| description="Sequence positions of the audio rows, reference rows first.", | |
| ), | |
| OutputParam( | |
| "text_indices", | |
| type_hint=torch.Tensor, | |
| kwargs_type="denoiser_input_fields", | |
| description="Sequence positions of the text rows.", | |
| ), | |
| OutputParam( | |
| "num_condition_video_rows", | |
| type_hint=int, | |
| description="How many leading video rows are conditioning rows rather than generated rows.", | |
| ), | |
| OutputParam( | |
| "num_condition_audio_rows", | |
| type_hint=int, | |
| description="How many leading audio rows are reference rows rather than generated rows.", | |
| ), | |
| ] | |
| def build_ref2va_packed_sequence( | |
| text_token_tags: torch.Tensor, | |
| references: list[MiniMaxH3Reference], | |
| 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, | |
| ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, int, int]: | |
| r""" | |
| Build the `[text | reference blocks | target audio | target video]` layout of the `ref2va` task. | |
| 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 reference's vision | |
| block, which MiniMax-H3 tags `0` (video). | |
| references (`list[MiniMaxH3Reference]`): | |
| The references, in packed order. Only their modality is read here; the geometry comes from the latents. | |
| condition_latents (`list[torch.Tensor]`): | |
| One `(1, channels, num_latent_frames, latent_height, latent_width)` tensor per image and video | |
| reference, in packed order, as [`MiniMaxH3Ref2VAReferenceEncoderStep`] produced them. | |
| audio_condition_latents (`list[torch.Tensor]`): | |
| One `(num_audio_latents * 2, audio_latent_channels)` tensor per audio-bearing reference, in packed | |
| order. | |
| 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. | |
| audio_channels (`int`): Channels the soundtrack is packed channel-major over, i.e. | |
| `components.audio_channels`. | |
| audio_tag (`int`), video_tag (`int`): | |
| The modality tags an audio and a video row carry, i.e. `components.audio_tag` and | |
| `components.video_tag`. | |
| Returns: | |
| `tuple`: `position_ids`, `token_tags`, `video_indices`, `audio_indices`, `text_indices`, and the number of | |
| leading video and audio rows that are references rather than generated. | |
| """ | |
| _, patch_h, patch_w = patch_size | |
| num_text_tokens = text_token_tags.shape[0] | |
| num_target_video_rows = num_latent_frames * (latent_height // patch_h) * (latent_width // patch_w) | |
| num_target_audio_rows = num_audio_latents * audio_channels | |
| # The geometry of every reference block is the shape of what the encoder produced for it, so the two can never | |
| # disagree. Both lists are in packed order but skip the references they do not apply to, so they are consumed as | |
| # iterators alongside the reference list rather than indexed by it. | |
| visual_geometry = iter(tuple(latents.shape[2:5]) for latents in condition_latents) | |
| audio_row_counts = iter(rows.shape[0] for rows in audio_condition_latents) | |
| 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 condition_latents) | |
| ) | |
| num_reference_audio_rows = sum(rows.shape[0] for rows in audio_condition_latents) | |
| sequence_length = ( | |
| num_text_tokens | |
| + 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) | |
| # Reference blocks, in request order. `rotary_time` is the shared audio/video clock: it starts where the text | |
| # rows end and every block pushes it forward by the time that block occupies. | |
| video_indices, audio_indices = [], [] | |
| cursor = num_text_tokens | |
| rotary_time = float(num_text_tokens) | |
| for reference in references: | |
| if reference.kind == "image": | |
| num_latent_frames_, reference_height, reference_width = next(visual_geometry) | |
| num_video_rows = num_latent_frames_ * (reference_height // patch_h) * (reference_width // patch_w) | |
| rows = slice(cursor, cursor + num_video_rows) | |
| cursor = rows.stop | |
| video_indices.append(torch.arange(rows.start, rows.stop)) | |
| frame_grid, _ = _frame_position_grid(reference_height, reference_width, patch_h, patch_w) | |
| position_ids[rows, 0] = rotary_time | |
| position_ids[rows, 1:] = frame_grid | |
| # An image is a single frame and takes a single integer rotary slot, not a latent frame's 5/3 units. | |
| rotary_time += 1.0 | |
| elif reference.kind == "audio": | |
| num_audio_rows = next(audio_row_counts) | |
| reference_audio_latents = num_audio_rows // audio_channels | |
| rows = slice(cursor, cursor + num_audio_rows) | |
| cursor = rows.stop | |
| audio_indices.append(torch.arange(rows.start, rows.stop)) | |
| _fill_audio_positions( | |
| position_ids, rows, reference_audio_latents, rotary_time, target_width_grid, audio_channels | |
| ) | |
| rotary_time += float(reference_audio_latents) | |
| elif reference.kind == "video": | |
| # A video reference's soundtrack rows are packed immediately before its video rows and share their | |
| # origin, so the two are rotary-aligned exactly as the generated audio and video are. | |
| num_audio_rows = next(audio_row_counts) if reference.has_audio else 0 | |
| reference_audio_latents = num_audio_rows // audio_channels | |
| num_latent_frames_, reference_height, reference_width = next(visual_geometry) | |
| num_video_rows = num_latent_frames_ * (reference_height // patch_h) * (reference_width // patch_w) | |
| audio_rows = slice(cursor, cursor + num_audio_rows) | |
| video_rows = slice(audio_rows.stop, audio_rows.stop + num_video_rows) | |
| 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(reference_height, reference_width, patch_h, patch_w) | |
| _fill_audio_positions( | |
| position_ids, audio_rows, reference_audio_latents, rotary_time, width_grid, audio_channels | |
| ) | |
| frame_time = _temporal_position_grid(num_latent_frames_, rotary_time) | |
| position_ids[video_rows, 0] = frame_time.repeat_interleave(frame_grid.shape[0]) | |
| position_ids[video_rows, 1:] = frame_grid.repeat(num_latent_frames_, 1) | |
| # The rotary time this reference advances the clock by, summed sequentially in float64. That is *not* | |
| # how `_temporal_position_span_pairwise` sums the same series β it reproduces a numpy pairwise sum, | |
| # and the two orders differ in the last ulp from 16 latent frames onwards. The reference | |
| # implementation keeps both, one per call site, so the port has to as well. | |
| video_span = sum( | |
| _ROPE_FRAME_RESCALE * _ROPE_FRAMES_PER_LATENT[index % len(_ROPE_FRAMES_PER_LATENT)] | |
| for index in range(num_latent_frames_) | |
| ) | |
| rotary_time += max(float(reference_audio_latents), video_span) | |
| else: | |
| raise ValueError(f"A reference must be an 'image', a 'video' or an 'audio', got {reference.kind!r}.") | |
| # The generated rows. Target audio and target video share the origin the reference blocks left behind. | |
| audio_start = cursor | |
| video_start = audio_start + num_target_audio_rows | |
| _fill_audio_positions( | |
| position_ids, | |
| slice(audio_start, video_start), | |
| num_audio_latents, | |
| rotary_time, | |
| target_width_grid, | |
| audio_channels, | |
| ) | |
| frame_time = _temporal_position_grid(num_latent_frames, rotary_time) | |
| 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_reference_video_rows, | |
| num_reference_audio_rows, | |
| ) | |
| def __call__(self, components: MiniMaxH3ModularPipeline, state: PipelineState) -> PipelineState: | |
| block_state = self.get_block_state(state) | |
| device = components._execution_device | |
| # The canvas and the frame count are settled by the setup step, but in a hand-assembled chain this block | |
| # sees user input directly β and a canvas off the multiple floor-divides into a broken decode. | |
| multiple = components.canvas_multiple | |
| if block_state.height % multiple or block_state.width % multiple: | |
| raise ValueError( | |
| f"`height` and `width` must be multiples of {multiple}, got {block_state.height}x{block_state.width}." | |
| ) | |
| # A `ref2va` soundtrack is truncated to the generated duration as the references are prepared, so | |
| # `num_frames` has to be final before the setup step runs. | |
| ratio = components.vae_spatial_compression_ratio | |
| block_state.num_latent_frames = video_latent_num_frames( | |
| block_state.num_frames, components.vae_frames_per_chunk, components.vae_latents_per_chunk | |
| ) | |
| block_state.latent_height = block_state.height // ratio | |
| block_state.latent_width = block_state.width // ratio | |
| block_state.num_audio_latents = audio_latent_num_frames(block_state.num_frames) | |
| ( | |
| position_ids, | |
| token_tags, | |
| video_indices, | |
| audio_indices, | |
| text_indices, | |
| block_state.num_condition_video_rows, | |
| block_state.num_condition_audio_rows, | |
| ) = self.build_ref2va_packed_sequence( | |
| block_state.text_token_tags, | |
| block_state.normalized_references, | |
| block_state.condition_latents, | |
| block_state.audio_condition_latents, | |
| block_state.num_latent_frames, | |
| block_state.latent_height, | |
| block_state.latent_width, | |
| block_state.num_audio_latents, | |
| components.patch_size, | |
| components.audio_channels, | |
| components.audio_tag, | |
| components.video_tag, | |
| ) | |
| block_state.position_ids = position_ids.to(device) | |
| block_state.token_tags = token_tags.to(device) | |
| block_state.video_indices = video_indices.to(device) | |
| block_state.audio_indices = audio_indices.to(device) | |
| block_state.text_indices = text_indices.to(device) | |
| self.set_block_state(state, block_state) | |
| return components, state | |
| class MiniMaxH3PrepareLatentsStep(ModularPipelineBlocks): | |
| model_name = "minimax-h3" | |
| def description(self) -> str: | |
| return ( | |
| "Draws the noise of the generated rows and packs them: the video noise as a latent tensor first, then " | |
| "the audio noise directly in row layout, both off the request's generator, in that order. Every " | |
| "workflow generates the same way, so this is the same block for all of them; a request that conditions " | |
| "on something noises it *before* this block β the draw order is part of what its generator reproduces β " | |
| "and puts it in front of these rows after." | |
| ) | |
| def inputs(self) -> list[InputParam]: | |
| return [ | |
| InputParam( | |
| name="num_latent_frames", type_hint=int, required=True, description="Number of video latent frames." | |
| ), | |
| InputParam(name="latent_height", type_hint=int, required=True, description="Height of the video latents."), | |
| InputParam(name="latent_width", type_hint=int, required=True, description="Width of the video latents."), | |
| InputParam( | |
| name="num_audio_latents", | |
| type_hint=int, | |
| required=True, | |
| description="Number of audio latents per channel.", | |
| ), | |
| InputParam.template( | |
| "generator", | |
| description=( | |
| "The generator of the request. The video noise is drawn from it first, then the audio noise." | |
| ), | |
| ), | |
| InputParam( | |
| name="latents", | |
| type_hint=torch.Tensor, | |
| description=( | |
| "Pre-generated video noise of shape `(1, 24, num_latent_frames, latent_height, latent_width)`, " | |
| "used instead of the draw." | |
| ), | |
| ), | |
| InputParam( | |
| name="audio_latents", | |
| type_hint=torch.Tensor, | |
| description="Pre-generated audio noise of shape `(2, 32, num_audio_latents)`.", | |
| ), | |
| ] | |
| def intermediate_outputs(self) -> list[OutputParam]: | |
| return [ | |
| OutputParam( | |
| "latents", | |
| type_hint=torch.Tensor, | |
| description="The generated video rows of the packed sequence.", | |
| ), | |
| OutputParam( | |
| "audio_latents", | |
| type_hint=torch.Tensor, | |
| description="The generated audio rows of the packed sequence, channel-major.", | |
| ), | |
| ] | |
| def __call__(self, components: MiniMaxH3ModularPipeline, state: PipelineState) -> PipelineState: | |
| block_state = self.get_block_state(state) | |
| device = components._execution_device | |
| patch_size = components.patch_size | |
| # A request draws every stream from the one generator it is given, and the order is part of what that | |
| # generator reproduces: any conditioning noise first (drawn before this block), then the video noise as a | |
| # latent tensor, then the audio noise directly in row layout. Passing `latents` or `audio_latents` skips its | |
| # draw and shifts the ones after it. | |
| latents = block_state.latents | |
| if latents is None: | |
| latents = randn_tensor( | |
| ( | |
| 1, | |
| components.vae_latent_channels, | |
| block_state.num_latent_frames, | |
| block_state.latent_height, | |
| block_state.latent_width, | |
| ), | |
| generator=block_state.generator, | |
| device=device, | |
| dtype=torch.float32, | |
| ) | |
| video_rows = patchify_video_latents(latents.to(device, torch.float32), patch_size) | |
| if block_state.audio_latents is None: | |
| audio_rows = randn_tensor( | |
| (block_state.num_audio_latents * components.audio_channels, components.audio_latent_channels), | |
| generator=block_state.generator, | |
| device=device, | |
| dtype=torch.float32, | |
| ) | |
| else: | |
| audio_rows = ( | |
| block_state.audio_latents.to(device, torch.float32) | |
| .permute(0, 2, 1) | |
| .reshape(-1, components.audio_latent_channels) | |
| ) | |
| block_state.latents, block_state.audio_latents = video_rows, audio_rows | |
| self.set_block_state(state, block_state) | |
| return components, state | |
| class MiniMaxH3PrepareConditionLatentsStep(ModularPipelineBlocks): | |
| model_name = "minimax-h3" | |
| def description(self) -> str: | |
| return ( | |
| "Noises the encoded visual conditioning to MiniMax-H3's conditioning level and packs it into rows β the " | |
| "`fl2va` keyframes or the `ref2va` image and video references, the same recipe either way. It runs " | |
| "*before* the noise of the generated rows is drawn, because a request draws one condition at a time " | |
| "first and that order is part of what its generator reproduces." | |
| ) | |
| def expected_components(self) -> list[ComponentSpec]: | |
| return [ComponentSpec("scheduler", MiniMaxH3Scheduler)] | |
| def inputs(self) -> list[InputParam]: | |
| return [ | |
| InputParam.template( | |
| "generator", | |
| description=( | |
| "The generator of the request. The conditioning noise is drawn from it first, one draw per " | |
| "condition, before the noise of the generated rows." | |
| ), | |
| ), | |
| InputParam( | |
| name="num_condition_video_rows", | |
| type_hint=int, | |
| default=0, | |
| description="How many conditioning rows the layout reserved, which the packed conditioning must match.", | |
| ), | |
| InputParam( | |
| name="condition_latents", | |
| type_hint=list[torch.Tensor], | |
| required=True, | |
| description=( | |
| "The encoded video conditioning latents, one `(1, latent_channels, num_latent_frames, " | |
| "latent_height, latent_width)` tensor per condition in packed order." | |
| ), | |
| ), | |
| ] | |
| def intermediate_outputs(self) -> list[OutputParam]: | |
| return [ | |
| OutputParam( | |
| "condition_rows", | |
| type_hint=torch.Tensor, | |
| description="The noised conditioning, packed into the leading video rows of the sequence.", | |
| ), | |
| ] | |
| def __call__(self, components: MiniMaxH3ModularPipeline, state: PipelineState) -> PipelineState: | |
| block_state = self.get_block_state(state) | |
| device = components._execution_device | |
| patch_size = components.patch_size | |
| # One draw per condition, in packed order. Each is packed on its own because `ref2va` references are encoded | |
| # at their own resolutions, so their latents do not share a shape. | |
| packed = [] | |
| for condition in block_state.condition_latents: | |
| noise = randn_tensor(condition.shape, generator=block_state.generator, device=device, dtype=torch.float32) | |
| # The anchors are not fully clean: the released model noises them to `t = 0.999` and holds them there | |
| # for every step. Mixing before the patchify is the same arithmetic, since patchify only permutes. | |
| noised = components.scheduler.scale_noise(condition.to(device), components.keyframe_noise_aug, noise) | |
| packed.append(patchify_video_latents(noised, patch_size)) | |
| block_state.condition_rows = torch.cat(packed) | |
| # In a hand-assembled chain the canvas reaching the layout is user input, so it can disagree with the | |
| # conditioning that was actually encoded. Left alone the mismatch first surfaces as an `index_copy` shape | |
| # error inside the transformer, 50 layers deep. | |
| if block_state.condition_rows.shape[0] != block_state.num_condition_video_rows: | |
| raise ValueError( | |
| f"The layout reserved {block_state.num_condition_video_rows} conditioning rows but the encoded " | |
| f"conditioning latents pack into {block_state.condition_rows.shape[0]}. The canvas the layout was " | |
| "built from and the one the conditioning was encoded at do not agree." | |
| ) | |
| self.set_block_state(state, block_state) | |
| return components, state | |
| class MiniMaxH3FL2VAPrepareLatentsStep(ModularPipelineBlocks): | |
| model_name = "minimax-h3" | |
| def description(self) -> str: | |
| return ( | |
| "Finishes the video rows of a `fl2va` request by putting its noised keyframe conditioning in front of " | |
| "the generated rows, which is where the layout reserved it. The denoising loop only ever writes the " | |
| "generated rows, so the conditioning rides through every step unchanged." | |
| ) | |
| def inputs(self) -> list[InputParam]: | |
| return [ | |
| InputParam( | |
| name="condition_rows", | |
| type_hint=torch.Tensor, | |
| required=True, | |
| description="The noised keyframe conditioning, packed into rows.", | |
| ), | |
| InputParam( | |
| name="latents", | |
| type_hint=torch.Tensor, | |
| required=True, | |
| description="The generated video rows of the packed sequence.", | |
| ), | |
| ] | |
| def intermediate_outputs(self) -> list[OutputParam]: | |
| return [ | |
| OutputParam( | |
| "latents", | |
| type_hint=torch.Tensor, | |
| description="The video rows of the packed sequence, conditioning rows first.", | |
| ), | |
| ] | |
| def __call__(self, components: MiniMaxH3ModularPipeline, state: PipelineState) -> PipelineState: | |
| block_state = self.get_block_state(state) | |
| block_state.latents = torch.cat([block_state.condition_rows, block_state.latents]) | |
| self.set_block_state(state, block_state) | |
| return components, state | |
| class MiniMaxH3Ref2VAPrepareLatentsStep(ModularPipelineBlocks): | |
| model_name = "minimax-h3" | |
| def description(self) -> str: | |
| return ( | |
| "Finishes both streams of a `ref2va` request by putting its conditioning in front of the generated " | |
| "rows, which is where the layout reserved it: the noised image and video references on the video side, " | |
| "and the reference soundtracks on the audio side. The soundtracks are never noised β a reference " | |
| "soundtrack conditions at `t = 0` β and the denoising loop only ever writes the generated rows, so all " | |
| "of it rides through every step unchanged." | |
| ) | |
| def inputs(self) -> list[InputParam]: | |
| return [ | |
| InputParam( | |
| name="condition_rows", | |
| type_hint=torch.Tensor, | |
| required=True, | |
| description="The noised conditioning of the image and video references, packed into rows.", | |
| ), | |
| InputParam( | |
| name="latents", | |
| type_hint=torch.Tensor, | |
| required=True, | |
| description="The generated video rows of the packed sequence.", | |
| ), | |
| InputParam( | |
| name="audio_condition_latents", | |
| type_hint=list[torch.Tensor], | |
| required=True, | |
| description=( | |
| "The audio conditioning rows to prepend, one tensor per audio-bearing reference in packed " | |
| "order. Empty for a request that has none." | |
| ), | |
| ), | |
| InputParam( | |
| name="num_condition_audio_rows", | |
| type_hint=int, | |
| default=0, | |
| description="How many reference audio rows the layout reserved, which the packed rows must match.", | |
| ), | |
| InputParam( | |
| name="audio_latents", | |
| type_hint=torch.Tensor, | |
| required=True, | |
| description="The generated audio rows of the packed sequence, channel-major.", | |
| ), | |
| ] | |
| def intermediate_outputs(self) -> list[OutputParam]: | |
| return [ | |
| OutputParam( | |
| "latents", | |
| type_hint=torch.Tensor, | |
| description="The video rows of the packed sequence, conditioning rows first.", | |
| ), | |
| OutputParam( | |
| "audio_latents", | |
| type_hint=torch.Tensor, | |
| description="The channel-major audio rows of the packed sequence, reference rows first.", | |
| ), | |
| ] | |
| def __call__(self, components: MiniMaxH3ModularPipeline, state: PipelineState) -> PipelineState: | |
| block_state = self.get_block_state(state) | |
| device = components._execution_device | |
| block_state.latents = torch.cat([block_state.condition_rows, block_state.latents]) | |
| if block_state.audio_condition_latents: | |
| num_reference_audio_rows = sum(rows.shape[0] for rows in block_state.audio_condition_latents) | |
| if num_reference_audio_rows != block_state.num_condition_audio_rows: | |
| raise ValueError( | |
| f"The layout reserved {block_state.num_condition_audio_rows} reference audio rows but the encoded " | |
| f"soundtracks pack into {num_reference_audio_rows}. The references the layout was built from and " | |
| "the ones the audio conditioning was encoded from do not agree." | |
| ) | |
| block_state.audio_latents = torch.cat( | |
| [rows.to(device) for rows in block_state.audio_condition_latents] + [block_state.audio_latents] | |
| ) | |
| self.set_block_state(state, block_state) | |
| return components, state | |
| class MiniMaxH3SetTimestepsStep(ModularPipelineBlocks): | |
| model_name = "minimax-h3" | |
| def description(self) -> str: | |
| return ( | |
| "Initializes the two schedules β `shift = 12.0` for video, `shift = 3.0` for audio β and stages the " | |
| "row-to-timestep plan of every step. One forward serves every modality and every noise level at once: " | |
| "the generated rows step down their own schedule while the conditioning rows stay pinned at their " | |
| "noise-augmentation level, and that assignment is static per step." | |
| ) | |
| def expected_components(self) -> list[ComponentSpec]: | |
| return [ | |
| ComponentSpec("scheduler", MiniMaxH3Scheduler), | |
| ComponentSpec("audio_scheduler", MiniMaxH3Scheduler), | |
| ] | |
| def inputs(self) -> list[InputParam]: | |
| return [ | |
| InputParam.template("num_inference_steps", required=True), | |
| InputParam( | |
| name="video_indices", | |
| type_hint=torch.Tensor, | |
| required=True, | |
| description="Sequence positions of the video rows, conditioning rows first.", | |
| ), | |
| InputParam( | |
| name="audio_indices", | |
| type_hint=torch.Tensor, | |
| required=True, | |
| description="Sequence positions of the audio rows, reference rows first.", | |
| ), | |
| InputParam( | |
| name="text_indices", | |
| type_hint=torch.Tensor, | |
| required=True, | |
| description="Sequence positions of the text rows.", | |
| ), | |
| InputParam( | |
| name="num_condition_video_rows", | |
| type_hint=int, | |
| default=0, | |
| description="How many leading video rows are conditioning rows.", | |
| ), | |
| InputParam( | |
| name="num_condition_audio_rows", | |
| type_hint=int, | |
| default=0, | |
| description="How many leading audio rows are reference rows.", | |
| ), | |
| ] | |
| def intermediate_outputs(self) -> list[OutputParam]: | |
| return [ | |
| OutputParam("timesteps", type_hint=torch.Tensor, description="Timesteps of the video schedule."), | |
| OutputParam("audio_timesteps", type_hint=torch.Tensor, description="Timesteps of the audio schedule."), | |
| OutputParam( | |
| "row_timestep_plan", | |
| type_hint=list, | |
| description=( | |
| "One `(timestep, timestep_indices)` pair per step: the distinct timesteps of the sequence and the " | |
| "index of every row into them." | |
| ), | |
| ), | |
| ] | |
| def build_row_timesteps( | |
| video_indices: torch.Tensor, | |
| audio_indices: torch.Tensor, | |
| num_condition_video_rows: int, | |
| num_condition_audio_rows: int, | |
| num_text_tokens: int, | |
| 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: | |
| video_indices (`torch.Tensor`): Sequence positions of the video rows, conditioning rows first. | |
| audio_indices (`torch.Tensor`): Sequence positions of the audio rows, reference rows first. | |
| num_condition_video_rows (`int`): How many leading video rows are conditioning rows. | |
| num_condition_audio_rows (`int`): How many leading audio rows are reference rows. | |
| num_text_tokens (`int`): Number of text rows, which never reach an output head. | |
| 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. | |
| """ | |
| sequence_length = int(video_indices.numel() + audio_indices.numel() + num_text_tokens) | |
| row_timesteps = torch.full((sequence_length,), video_timestep, dtype=torch.float32) | |
| row_timesteps[video_indices[:num_condition_video_rows]] = condition_video_timestep | |
| row_timesteps[audio_indices[num_condition_audio_rows:]] = audio_timestep | |
| row_timesteps[audio_indices[:num_condition_audio_rows]] = condition_audio_timestep | |
| return torch.unique(row_timesteps, sorted=True, return_inverse=True) | |
| def __call__(self, components: MiniMaxH3ModularPipeline, state: PipelineState) -> PipelineState: | |
| block_state = self.get_block_state(state) | |
| device = components._execution_device | |
| components.scheduler.set_timesteps(block_state.num_inference_steps, device=device) | |
| components.audio_scheduler.set_timesteps(block_state.num_inference_steps, device=device) | |
| block_state.timesteps = components.scheduler.timesteps | |
| block_state.audio_timesteps = components.audio_scheduler.timesteps | |
| block_state.row_timestep_plan = [ | |
| tuple( | |
| tensor.to(device) | |
| for tensor in self.build_row_timesteps( | |
| block_state.video_indices, | |
| block_state.audio_indices, | |
| block_state.num_condition_video_rows, | |
| block_state.num_condition_audio_rows, | |
| block_state.text_indices.numel(), | |
| float(timestep), | |
| float(audio_timestep), | |
| max(float(timestep), components.keyframe_noise_aug), | |
| 1.0, | |
| ) | |
| ) | |
| for timestep, audio_timestep in zip(block_state.timesteps, block_state.audio_timesteps) | |
| ] | |
| self.set_block_state(state, block_state) | |
| return components, state | |