Spaces:
Running on Zero
Running on Zero
File size: 13,877 Bytes
34ea185 | 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 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 | """Per-keyframe injection frame and strength for the `fl2va` branch of the staged-denoising Space.
Two independent knobs per keyframe, both optional (defaults reproduce the stock pipeline exactly):
* **Injection frame.** A keyframe's conditioning rows carry one rotary time, `anchor_time`. Stock supports exactly
two values: "first" = `T` and "last" = `T + 5/3 * (N - 1)`, where `T` is the text-token count and `N` the number of
pixel frames the latent grid spans. The rotary clock runs at 5/3 units per *pixel* frame, so injecting at pixel
frame `p` is just `anchor_time = T + 5/3 * p`. Nothing else about the keyframe changes.
NOTE: the released model was only trained with the two stock positions. Other positions are untested.
* **Strength.** A keyframe is held at `t = 0.999` ("just short of clean": `x_t = t*x0 + (1-t)*noise`). Strength `s`
in `[0, 1]` maps to that `t`: `s = 1` is stock (0.999), `s = 0` is pure noise (`t = 0`, the keyframe carries no
information). The same `t` is what the rows are presented to the transformer at, so the label stays honest.
Blocks:
MiniMaxH3KeyframePlacementStep - rewrites the keyframe rows' rotary time after `prepare_layout`
MiniMaxH3KeyframeConditionStep - noises each keyframe to its own level instead of the shared 0.999
(build_row_timesteps patch) - presents each keyframe's rows at its own level, in every sampler
The patch of `MiniMaxH3SetTimestepsStep.build_row_timesteps` is deliberate: the dpmpp_2s / SDE / seeds_2 samplers
build their own second-evaluation row timesteps through that same static method, so patching it keeps all of them
consistent with the plan without touching their files. It is a no-op for anything with no conditioning rows
(`t2va`, the momentum pipeline) and whenever no per-keyframe levels were set for the request.
"""
import torch
from diffusers.modular_pipelines.minimax_h3.before_denoise import (
_ROPE_FRAME_RESCALE,
_ROPE_FRAMES_PER_LATENT,
MiniMaxH3FL2VAPrepareLatentsStep,
MiniMaxH3PrepareConditionLatentsStep,
MiniMaxH3PrepareLayoutStep,
MiniMaxH3SetTimestepsStep,
patchify_video_latents,
)
from diffusers.modular_pipelines.minimax_h3.before_denoise import MiniMaxH3PrepareLatentsStep
from diffusers.modular_pipelines.minimax_h3.decoders import MiniMaxH3AfterDenoiseStep
from diffusers.modular_pipelines.minimax_h3.denoise import MiniMaxH3DenoiseStep
from diffusers.modular_pipelines.minimax_h3.modular_blocks_minimax_h3 import (
MiniMaxH3CoreDenoiseStep,
MiniMaxH3DecodeStep,
)
from diffusers.modular_pipelines.minimax_h3.modular_pipeline import MiniMaxH3ModularPipeline
from diffusers.modular_pipelines.modular_pipeline import (
ConditionalPipelineBlocks,
ModularPipelineBlocks,
PipelineState,
SequentialPipelineBlocks,
)
from diffusers.modular_pipelines.modular_pipeline_utils import InputParam, OutputParam
from diffusers.utils.torch_utils import randn_tensor
from h3_split_blocks import MiniMaxH3SplitBeforeEncodeStep, MiniMaxH3SplitVaeEncoderStep
# ---------------------------------------------------------------------------------------------------------------
# Per-request state shared with build_row_timesteps. Set by the condition step, read by the patched static method.
# ---------------------------------------------------------------------------------------------------------------
_LEVELS: tuple[float, ...] | None = None
_AUG: float = 0.999
def strength_to_level(strength: float, aug: float) -> float:
"""Slider strength in [0, 1] -> the keyframe's `t`. 1 (or anything above `aug`) is stock."""
return max(0.0, min(float(strength), float(aug)))
_original_build_row_timesteps = MiniMaxH3SetTimestepsStep.build_row_timesteps
def _build_row_timesteps(
video_indices,
audio_indices,
num_condition_video_rows,
num_condition_audio_rows,
num_text_tokens,
video_timestep,
audio_timestep,
condition_video_timestep,
condition_audio_timestep,
):
levels = _LEVELS
n = len(levels) if levels else 0
if n == 0 or num_condition_video_rows <= 0 or num_condition_video_rows % n != 0:
return _original_build_row_timesteps(
video_indices, audio_indices, num_condition_video_rows, num_condition_audio_rows, num_text_tokens,
video_timestep, audio_timestep, condition_video_timestep, condition_audio_timestep,
)
sequence_length = int(video_indices.numel() + audio_indices.numel() + num_text_tokens)
row_timesteps = torch.full((sequence_length,), video_timestep, dtype=torch.float32)
rows_per_keyframe = num_condition_video_rows // n
for i, level in enumerate(levels):
block = video_indices[i * rows_per_keyframe : (i + 1) * rows_per_keyframe]
# At or above the stock level the keyframe is presented exactly as stock does (max with the current step's
# timestep). Below it, the keyframe really is that noisy and stays labelled that way for every step.
row_timesteps[block] = condition_video_timestep if level >= _AUG else level
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)
MiniMaxH3SetTimestepsStep.build_row_timesteps = staticmethod(_build_row_timesteps)
# ---------------------------------------------------------------------------------------------------------------
# Blocks
# ---------------------------------------------------------------------------------------------------------------
class MiniMaxH3KeyframePlacementStep(ModularPipelineBlocks):
model_name = "minimax-h3"
@property
def description(self) -> str:
return (
"Moves each keyframe's conditioning rows to a chosen pixel frame on the rotary time axis. The stock "
"layout only knows 'first' (frame 0) and 'last' (the final frame); `anchor_time = T + 5/3 * frame`."
)
@property
def inputs(self) -> list[InputParam]:
return [
InputParam(name="position_ids", type_hint=torch.Tensor, required=True),
InputParam(name="text_indices", type_hint=torch.Tensor, required=True),
InputParam(name="num_latent_frames", type_hint=int, required=True),
InputParam(name="latent_height", type_hint=int, required=True),
InputParam(name="latent_width", type_hint=int, required=True),
InputParam(name="keyframe_anchors", type_hint=tuple, default=()),
InputParam(
name="keyframe_frames",
type_hint=tuple,
default=None,
description="Requested pixel frame per keyframe, in packed order. None / missing entries keep stock.",
),
]
@property
def intermediate_outputs(self) -> list[OutputParam]:
return [OutputParam("position_ids", type_hint=torch.Tensor)]
@torch.no_grad()
def __call__(self, components: MiniMaxH3ModularPipeline, state: PipelineState):
block_state = self.get_block_state(state)
anchors = tuple(block_state.keyframe_anchors or ())
frames = block_state.keyframe_frames
if anchors and frames is not None:
_, patch_h, patch_w = components.patch_size
rows_per_frame = (block_state.latent_height // patch_h) * (block_state.latent_width // patch_w)
num_text = int(block_state.text_indices.numel())
pattern = _ROPE_FRAMES_PER_LATENT
total_pixel_frames = sum(pattern[i % len(pattern)] for i in range(block_state.num_latent_frames))
last_frame = total_pixel_frames - 1
position_ids = block_state.position_ids
for index, (anchor, requested) in enumerate(zip(anchors, frames)):
if requested is None:
continue
frame = max(0, min(int(requested), last_frame))
# Defaults reproduce the stock anchors bit for bit: leave those rows untouched.
if (anchor == "first" and frame <= 0) or (anchor == "last" and frame >= last_frame):
continue
rows = slice(num_text + index * rows_per_frame, num_text + (index + 1) * rows_per_frame)
position_ids[rows, 0] = float(num_text) + _ROPE_FRAME_RESCALE * frame
block_state.position_ids = position_ids
self.set_block_state(state, block_state)
return components, state
class MiniMaxH3KeyframeConditionStep(MiniMaxH3PrepareConditionLatentsStep):
model_name = "minimax-h3"
@property
def description(self) -> str:
return (
"`MiniMaxH3PrepareConditionLatentsStep` with a noise level per keyframe: `x_t = t*x0 + (1-t)*noise`, "
"`t = strength` capped at the stock 0.999. Draw order is unchanged (one draw per condition)."
)
@property
def inputs(self) -> list[InputParam]:
return super().inputs + [
InputParam(
name="keyframe_strengths",
type_hint=tuple,
default=None,
description="Strength in [0, 1] per keyframe, in packed order. None keeps the stock 0.999.",
)
]
@property
def intermediate_outputs(self) -> list[OutputParam]:
return super().intermediate_outputs
@torch.no_grad()
def __call__(self, components: MiniMaxH3ModularPipeline, state: PipelineState):
global _LEVELS, _AUG
block_state = self.get_block_state(state)
device = components._execution_device
patch_size = components.patch_size
aug = float(components.keyframe_noise_aug)
strengths = block_state.keyframe_strengths
levels = []
packed = []
for index, condition in enumerate(block_state.condition_latents):
noise = randn_tensor(condition.shape, generator=block_state.generator, device=device, dtype=torch.float32)
strength = 1.0 if strengths is None or index >= len(strengths) or strengths[index] is None else strengths[index]
level = strength_to_level(strength, aug)
levels.append(level)
noised = components.scheduler.scale_noise(condition.to(device), level, noise)
packed.append(patchify_video_latents(noised, patch_size))
block_state.condition_rows = torch.cat(packed)
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]}."
)
_AUG = aug
_LEVELS = tuple(levels) if any(level < aug for level in levels) else None
self.set_block_state(state, block_state)
return components, state
class MiniMaxH3KeyframeControlCoreDenoiseStep(SequentialPipelineBlocks):
"""`MiniMaxH3FL2VACoreDenoiseStep` with the placement step after the layout and the per-keyframe condition step."""
model_name = "minimax-h3"
block_classes = [
MiniMaxH3PrepareLayoutStep,
MiniMaxH3KeyframePlacementStep,
MiniMaxH3KeyframeConditionStep,
MiniMaxH3PrepareLatentsStep,
MiniMaxH3FL2VAPrepareLatentsStep,
MiniMaxH3SetTimestepsStep,
MiniMaxH3DenoiseStep,
MiniMaxH3AfterDenoiseStep,
]
block_names = [
"prepare_layout",
"keyframe_placement",
"prepare_condition_latents",
"prepare_latents",
"prepare_latents_fl2va",
"set_timesteps",
"denoise",
"after_denoise",
]
@property
def description(self) -> str:
return "Core denoising workflow for `fl2va` with per-keyframe injection frame and strength."
@property
def outputs(self):
return [
OutputParam("latents", type_hint=torch.Tensor, description="The generated video latents."),
OutputParam("audio_latents", type_hint=torch.Tensor, description="The generated audio latents."),
]
class MiniMaxH3KeyframeControlDenoiseStep(ConditionalPipelineBlocks):
"""Split-deployment denoise selector with the keyframe-control `fl2va` core in place of the stock one."""
model_name = "minimax-h3"
block_classes = [MiniMaxH3KeyframeControlCoreDenoiseStep, MiniMaxH3CoreDenoiseStep]
block_names = ["fl2va", "t2va"]
block_trigger_inputs = ["image", "last_image"]
default_block_name = "t2va"
def select_block(self, **kwargs) -> str | None:
if kwargs.get("image") is not None or kwargs.get("last_image") is not None:
return "fl2va"
return None
@property
def description(self):
return "`fl2va` (keyframe control) when a keyframe is provided, `t2va` otherwise."
class MiniMaxH3KeyframeGeneratorBlocks(SequentialPipelineBlocks):
"""Drop-in for `h3_split_blocks.MiniMaxH3GeneratorBlocks`, with keyframe injection frame / strength."""
model_name = "minimax-h3"
block_classes = [
MiniMaxH3SplitBeforeEncodeStep,
MiniMaxH3SplitVaeEncoderStep,
MiniMaxH3KeyframeControlDenoiseStep,
MiniMaxH3DecodeStep,
]
block_names = ["resize", "vae_encoder", "denoise", "decode"]
@property
def description(self):
return "Denoising half of the split deployment with per-keyframe injection frame and strength."
@property
def outputs(self):
return [
OutputParam.template("videos", description="The generated video."),
OutputParam("audio", type_hint=torch.Tensor, description="The soundtrack, `(1, 2, num_samples)`."),
OutputParam("sampling_rate", type_hint=int, description="Sample rate of the soundtrack in Hz."),
]
|