multimodalart's picture
multimodalart HF Staff
Sync the split MiniMax-H3 Spaces (part 2)
cd458ae verified
Raw
History Blame Contribute Delete
17.5 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 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, InputParam, OutputParam
from .modular_pipeline import MiniMaxH3ModularPipeline, MiniMaxH3Ref2VAModularPipeline
from .packing import (
MINIMAX_H3_AUDIO_CHANNELS,
MINIMAX_H3_KEYFRAME_NOISE_AUG,
MiniMaxH3PackedSequence,
build_packed_sequence,
build_row_timesteps,
patchify_video_latents,
)
from .packing_ref2va import MiniMaxH3PreparedReference, build_ref2va_packed_sequence
logger = logging.get_logger(__name__) # pylint: disable=invalid-name
def _layout_inputs() -> list[InputParam]:
r"""What both packed layouts are built from, beyond the conditioning of the task itself."""
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="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.",
),
]
def _layout_outputs() -> list[OutputParam]:
r"""The row layout of the packed sequence, shared by the two tasks."""
return [
OutputParam(
"layout",
type_hint=MiniMaxH3PackedSequence,
description="The structural description of the packed sequence.",
),
OutputParam(
"position_ids",
type_hint=torch.Tensor,
description="The `(t, h, w)` rotary coordinate of every row, in float64.",
),
OutputParam("token_tags", type_hint=torch.Tensor, description="The modality tag of every row."),
OutputParam(
"video_indices",
type_hint=torch.Tensor,
description="Sequence positions of the video rows, conditioning rows first.",
),
OutputParam(
"audio_indices",
type_hint=torch.Tensor,
description="Sequence positions of the audio rows, reference rows first.",
),
OutputParam("text_indices", type_hint=torch.Tensor, 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 _set_layout_state(block_state, layout: MiniMaxH3PackedSequence, device: torch.device) -> None:
block_state.layout = layout
block_state.position_ids = layout.position_ids.to(device)
block_state.token_tags = layout.token_tags.to(device)
block_state.video_indices = layout.video_indices.to(device)
block_state.audio_indices = layout.audio_indices.to(device)
block_state.text_indices = layout.text_indices.to(device)
block_state.num_condition_video_rows = layout.num_condition_video_rows
block_state.num_condition_audio_rows = layout.num_condition_audio_rows
class MiniMaxH3PrepareLayoutStep(ModularPipelineBlocks):
model_name = "minimax-h3"
@property
def description(self) -> str:
return (
"Builds the packed layout of a `t2va` / `fl2va` request — `[text | keyframe conditions | target audio | "
"target video]` — and 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."
)
@property
def inputs(self) -> list[InputParam]:
return [
*_layout_inputs(),
InputParam(
name="keyframe_anchors",
type_hint=tuple,
default=(),
description="Which end of the video every keyframe is anchored to, in packed order.",
),
]
@property
def intermediate_outputs(self) -> list[OutputParam]:
return _layout_outputs()
@torch.no_grad()
def __call__(self, components: MiniMaxH3ModularPipeline, state: PipelineState) -> PipelineState:
block_state = self.get_block_state(state)
layout = 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,
block_state.keyframe_anchors,
)
_set_layout_state(block_state, layout, components._execution_device)
self.set_block_state(state, block_state)
return components, state
class MiniMaxH3Ref2VAPrepareLayoutStep(ModularPipelineBlocks):
model_name = "minimax-h3-ref2va"
@property
def description(self) -> str:
return (
"Builds the packed layout of a `ref2va` request — `[text | reference blocks | target audio | target "
"video]` — and 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."
)
@property
def inputs(self) -> list[InputParam]:
return [
*_layout_inputs(),
InputParam(
name="prepared_references",
type_hint=list[MiniMaxH3PreparedReference],
required=True,
description="The prepared references, in packed order, with their latent geometry filled in.",
),
]
@property
def intermediate_outputs(self) -> list[OutputParam]:
return _layout_outputs()
@torch.no_grad()
def __call__(self, components: MiniMaxH3Ref2VAModularPipeline, state: PipelineState) -> PipelineState:
block_state = self.get_block_state(state)
layout = build_ref2va_packed_sequence(
block_state.text_token_tags,
block_state.prepared_references,
block_state.num_latent_frames,
block_state.latent_height,
block_state.latent_width,
block_state.num_audio_latents,
components.patch_size,
)
_set_layout_state(block_state, layout, components._execution_device)
self.set_block_state(state, block_state)
return components, state
class MiniMaxH3PrepareLatentsStep(ModularPipelineBlocks):
model_name = "minimax-h3"
@property
def description(self) -> str:
return (
"Draws the initial noise of the generated rows and prepends the conditioning rows. MiniMax-H3 draws the "
"video noise as a latent tensor and patchifies it afterwards, then the audio noise directly in row "
"layout — both off the request's generator, after the conditioning noise of the encoder step."
)
@property
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)`.",
),
InputParam(
name="condition_latents",
type_hint=torch.Tensor,
description="The video conditioning rows to prepend, or None for a request that has none.",
),
InputParam(
name="audio_condition_latents",
type_hint=torch.Tensor,
description="The audio conditioning rows to prepend, or None for a request that has none.",
),
]
@property
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.",
),
]
@staticmethod
def prepare_latents(
components,
num_latent_frames: int,
latent_height: int,
latent_width: int,
num_audio_latents: int,
device: torch.device,
generator: torch.Generator | list[torch.Generator] | None = None,
latents: torch.Tensor | None = None,
audio_latents: torch.Tensor | None = None,
) -> tuple[torch.Tensor, torch.Tensor]:
r"""
Draw the initial noise of both modalities and pack it into transformer rows.
A request draws every stream from the one generator it is given, and the order is part of what that generator
reproduces: the conditioning noise of the keyframes or references first (one draw per condition, in
[`~modular_pipelines.minimax_h3.packing.keyframe_condition_noise`]), then the video noise here, as a latent tensor
that is patchified afterwards, then the audio noise, directly in row layout. Passing `latents` or
`audio_latents` skips its draw and shifts the ones after it.
Args:
num_latent_frames (`int`): Number of video latent frames.
latent_height (`int`): Latent height.
latent_width (`int`): Latent width.
num_audio_latents (`int`): Number of audio latents per channel.
device (`torch.device`): The device the rows are drawn on.
generator (`torch.Generator`, *optional*): The generator of the request.
latents (`torch.Tensor`, *optional*):
Pre-generated video noise of shape `(1, latent_channels, num_latent_frames, latent_height,
latent_width)`, used instead of the draw.
audio_latents (`torch.Tensor`, *optional*):
Pre-generated audio noise of shape `(2, audio_latent_channels, num_audio_latents)`.
Returns:
`tuple[torch.Tensor, torch.Tensor]`: the video rows and the channel-major audio rows.
"""
if latents is None:
latents = randn_tensor(
(1, components.vae_latent_channels, num_latent_frames, latent_height, latent_width),
generator=generator,
device=device,
dtype=torch.float32,
)
video_rows = patchify_video_latents(latents.to(torch.float32), components.patch_size)
if audio_latents is None:
audio_rows = randn_tensor(
(num_audio_latents * MINIMAX_H3_AUDIO_CHANNELS, components.audio_latent_channels),
generator=generator,
device=device,
dtype=torch.float32,
)
else:
audio_rows = audio_latents.to(torch.float32).permute(0, 2, 1).reshape(-1, components.audio_latent_channels)
return video_rows.to(device), audio_rows.to(device)
@torch.no_grad()
def __call__(self, components: MiniMaxH3ModularPipeline, state: PipelineState) -> PipelineState:
block_state = self.get_block_state(state)
latents, audio_latents = self.prepare_latents(
components,
block_state.num_latent_frames,
block_state.latent_height,
block_state.latent_width,
block_state.num_audio_latents,
components._execution_device,
block_state.generator,
block_state.latents,
block_state.audio_latents,
)
if block_state.condition_latents is not None:
latents = torch.cat([block_state.condition_latents, latents])
if block_state.audio_condition_latents is not None:
audio_latents = torch.cat([block_state.audio_condition_latents, audio_latents])
block_state.latents, block_state.audio_latents = latents, audio_latents
self.set_block_state(state, block_state)
return components, state
class MiniMaxH3SetTimestepsStep(ModularPipelineBlocks):
model_name = "minimax-h3"
@property
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."
)
@property
def expected_components(self) -> list[ComponentSpec]:
return [
ComponentSpec("scheduler", MiniMaxH3Scheduler),
ComponentSpec("audio_scheduler", MiniMaxH3Scheduler),
]
@property
def inputs(self) -> list[InputParam]:
return [
InputParam.template("num_inference_steps", required=True),
InputParam(
name="layout",
type_hint=MiniMaxH3PackedSequence,
required=True,
description="The structural description of the packed sequence.",
),
]
@property
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."
),
),
]
@torch.no_grad()
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 build_row_timesteps(
block_state.layout,
float(timestep),
float(audio_timestep),
max(float(timestep), MINIMAX_H3_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