Spaces:
Running on Zero
Running on Zero
Upload h3_keyframe_control.py
Browse files- h3_keyframe_control.py +305 -0
h3_keyframe_control.py
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Per-keyframe injection frame and strength for the `fl2va` branch of the staged-denoising Space.
|
| 2 |
+
|
| 3 |
+
Two independent knobs per keyframe, both optional (defaults reproduce the stock pipeline exactly):
|
| 4 |
+
|
| 5 |
+
* **Injection frame.** A keyframe's conditioning rows carry one rotary time, `anchor_time`. Stock supports exactly
|
| 6 |
+
two values: "first" = `T` and "last" = `T + 5/3 * (N - 1)`, where `T` is the text-token count and `N` the number of
|
| 7 |
+
pixel frames the latent grid spans. The rotary clock runs at 5/3 units per *pixel* frame, so injecting at pixel
|
| 8 |
+
frame `p` is just `anchor_time = T + 5/3 * p`. Nothing else about the keyframe changes.
|
| 9 |
+
NOTE: the released model was only trained with the two stock positions. Other positions are untested.
|
| 10 |
+
|
| 11 |
+
* **Strength.** A keyframe is held at `t = 0.999` ("just short of clean": `x_t = t*x0 + (1-t)*noise`). Strength `s`
|
| 12 |
+
in `[0, 1]` maps to that `t`: `s = 1` is stock (0.999), `s = 0` is pure noise (`t = 0`, the keyframe carries no
|
| 13 |
+
information). The same `t` is what the rows are presented to the transformer at, so the label stays honest.
|
| 14 |
+
|
| 15 |
+
Blocks:
|
| 16 |
+
MiniMaxH3KeyframePlacementStep - rewrites the keyframe rows' rotary time after `prepare_layout`
|
| 17 |
+
MiniMaxH3KeyframeConditionStep - noises each keyframe to its own level instead of the shared 0.999
|
| 18 |
+
(build_row_timesteps patch) - presents each keyframe's rows at its own level, in every sampler
|
| 19 |
+
|
| 20 |
+
The patch of `MiniMaxH3SetTimestepsStep.build_row_timesteps` is deliberate: the dpmpp_2s / SDE / seeds_2 samplers
|
| 21 |
+
build their own second-evaluation row timesteps through that same static method, so patching it keeps all of them
|
| 22 |
+
consistent with the plan without touching their files. It is a no-op for anything with no conditioning rows
|
| 23 |
+
(`t2va`, the momentum pipeline) and whenever no per-keyframe levels were set for the request.
|
| 24 |
+
"""
|
| 25 |
+
|
| 26 |
+
import torch
|
| 27 |
+
|
| 28 |
+
from diffusers.modular_pipelines.minimax_h3.before_denoise import (
|
| 29 |
+
_ROPE_FRAME_RESCALE,
|
| 30 |
+
_ROPE_FRAMES_PER_LATENT,
|
| 31 |
+
MiniMaxH3FL2VAPrepareLatentsStep,
|
| 32 |
+
MiniMaxH3PrepareConditionLatentsStep,
|
| 33 |
+
MiniMaxH3PrepareLayoutStep,
|
| 34 |
+
MiniMaxH3SetTimestepsStep,
|
| 35 |
+
patchify_video_latents,
|
| 36 |
+
)
|
| 37 |
+
from diffusers.modular_pipelines.minimax_h3.before_denoise import MiniMaxH3PrepareLatentsStep
|
| 38 |
+
from diffusers.modular_pipelines.minimax_h3.decoders import MiniMaxH3AfterDenoiseStep
|
| 39 |
+
from diffusers.modular_pipelines.minimax_h3.denoise import MiniMaxH3DenoiseStep
|
| 40 |
+
from diffusers.modular_pipelines.minimax_h3.modular_blocks_minimax_h3 import (
|
| 41 |
+
MiniMaxH3CoreDenoiseStep,
|
| 42 |
+
MiniMaxH3DecodeStep,
|
| 43 |
+
)
|
| 44 |
+
from diffusers.modular_pipelines.minimax_h3.modular_pipeline import MiniMaxH3ModularPipeline
|
| 45 |
+
from diffusers.modular_pipelines.modular_pipeline import (
|
| 46 |
+
ConditionalPipelineBlocks,
|
| 47 |
+
ModularPipelineBlocks,
|
| 48 |
+
PipelineState,
|
| 49 |
+
SequentialPipelineBlocks,
|
| 50 |
+
)
|
| 51 |
+
from diffusers.modular_pipelines.modular_pipeline_utils import InputParam, OutputParam
|
| 52 |
+
from diffusers.utils.torch_utils import randn_tensor
|
| 53 |
+
|
| 54 |
+
from h3_split_blocks import MiniMaxH3SplitBeforeEncodeStep, MiniMaxH3SplitVaeEncoderStep
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
# ---------------------------------------------------------------------------------------------------------------
|
| 58 |
+
# Per-request state shared with build_row_timesteps. Set by the condition step, read by the patched static method.
|
| 59 |
+
# ---------------------------------------------------------------------------------------------------------------
|
| 60 |
+
_LEVELS: tuple[float, ...] | None = None
|
| 61 |
+
_AUG: float = 0.999
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def strength_to_level(strength: float, aug: float) -> float:
|
| 65 |
+
"""Slider strength in [0, 1] -> the keyframe's `t`. 1 (or anything above `aug`) is stock."""
|
| 66 |
+
return max(0.0, min(float(strength), float(aug)))
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
_original_build_row_timesteps = MiniMaxH3SetTimestepsStep.build_row_timesteps
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def _build_row_timesteps(
|
| 73 |
+
video_indices,
|
| 74 |
+
audio_indices,
|
| 75 |
+
num_condition_video_rows,
|
| 76 |
+
num_condition_audio_rows,
|
| 77 |
+
num_text_tokens,
|
| 78 |
+
video_timestep,
|
| 79 |
+
audio_timestep,
|
| 80 |
+
condition_video_timestep,
|
| 81 |
+
condition_audio_timestep,
|
| 82 |
+
):
|
| 83 |
+
levels = _LEVELS
|
| 84 |
+
n = len(levels) if levels else 0
|
| 85 |
+
if n == 0 or num_condition_video_rows <= 0 or num_condition_video_rows % n != 0:
|
| 86 |
+
return _original_build_row_timesteps(
|
| 87 |
+
video_indices, audio_indices, num_condition_video_rows, num_condition_audio_rows, num_text_tokens,
|
| 88 |
+
video_timestep, audio_timestep, condition_video_timestep, condition_audio_timestep,
|
| 89 |
+
)
|
| 90 |
+
|
| 91 |
+
sequence_length = int(video_indices.numel() + audio_indices.numel() + num_text_tokens)
|
| 92 |
+
row_timesteps = torch.full((sequence_length,), video_timestep, dtype=torch.float32)
|
| 93 |
+
rows_per_keyframe = num_condition_video_rows // n
|
| 94 |
+
for i, level in enumerate(levels):
|
| 95 |
+
block = video_indices[i * rows_per_keyframe : (i + 1) * rows_per_keyframe]
|
| 96 |
+
# At or above the stock level the keyframe is presented exactly as stock does (max with the current step's
|
| 97 |
+
# timestep). Below it, the keyframe really is that noisy and stays labelled that way for every step.
|
| 98 |
+
row_timesteps[block] = condition_video_timestep if level >= _AUG else level
|
| 99 |
+
row_timesteps[audio_indices[num_condition_audio_rows:]] = audio_timestep
|
| 100 |
+
row_timesteps[audio_indices[:num_condition_audio_rows]] = condition_audio_timestep
|
| 101 |
+
return torch.unique(row_timesteps, sorted=True, return_inverse=True)
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
MiniMaxH3SetTimestepsStep.build_row_timesteps = staticmethod(_build_row_timesteps)
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
# ---------------------------------------------------------------------------------------------------------------
|
| 108 |
+
# Blocks
|
| 109 |
+
# ---------------------------------------------------------------------------------------------------------------
|
| 110 |
+
class MiniMaxH3KeyframePlacementStep(ModularPipelineBlocks):
|
| 111 |
+
model_name = "minimax-h3"
|
| 112 |
+
|
| 113 |
+
@property
|
| 114 |
+
def description(self) -> str:
|
| 115 |
+
return (
|
| 116 |
+
"Moves each keyframe's conditioning rows to a chosen pixel frame on the rotary time axis. The stock "
|
| 117 |
+
"layout only knows 'first' (frame 0) and 'last' (the final frame); `anchor_time = T + 5/3 * frame`."
|
| 118 |
+
)
|
| 119 |
+
|
| 120 |
+
@property
|
| 121 |
+
def inputs(self) -> list[InputParam]:
|
| 122 |
+
return [
|
| 123 |
+
InputParam(name="position_ids", type_hint=torch.Tensor, required=True),
|
| 124 |
+
InputParam(name="text_indices", type_hint=torch.Tensor, required=True),
|
| 125 |
+
InputParam(name="num_latent_frames", type_hint=int, required=True),
|
| 126 |
+
InputParam(name="latent_height", type_hint=int, required=True),
|
| 127 |
+
InputParam(name="latent_width", type_hint=int, required=True),
|
| 128 |
+
InputParam(name="keyframe_anchors", type_hint=tuple, default=()),
|
| 129 |
+
InputParam(
|
| 130 |
+
name="keyframe_frames",
|
| 131 |
+
type_hint=tuple,
|
| 132 |
+
default=None,
|
| 133 |
+
description="Requested pixel frame per keyframe, in packed order. None / missing entries keep stock.",
|
| 134 |
+
),
|
| 135 |
+
]
|
| 136 |
+
|
| 137 |
+
@property
|
| 138 |
+
def intermediate_outputs(self) -> list[OutputParam]:
|
| 139 |
+
return [OutputParam("position_ids", type_hint=torch.Tensor)]
|
| 140 |
+
|
| 141 |
+
@torch.no_grad()
|
| 142 |
+
def __call__(self, components: MiniMaxH3ModularPipeline, state: PipelineState):
|
| 143 |
+
block_state = self.get_block_state(state)
|
| 144 |
+
anchors = tuple(block_state.keyframe_anchors or ())
|
| 145 |
+
frames = block_state.keyframe_frames
|
| 146 |
+
if anchors and frames is not None:
|
| 147 |
+
_, patch_h, patch_w = components.patch_size
|
| 148 |
+
rows_per_frame = (block_state.latent_height // patch_h) * (block_state.latent_width // patch_w)
|
| 149 |
+
num_text = int(block_state.text_indices.numel())
|
| 150 |
+
pattern = _ROPE_FRAMES_PER_LATENT
|
| 151 |
+
total_pixel_frames = sum(pattern[i % len(pattern)] for i in range(block_state.num_latent_frames))
|
| 152 |
+
last_frame = total_pixel_frames - 1
|
| 153 |
+
position_ids = block_state.position_ids
|
| 154 |
+
for index, (anchor, requested) in enumerate(zip(anchors, frames)):
|
| 155 |
+
if requested is None:
|
| 156 |
+
continue
|
| 157 |
+
frame = max(0, min(int(requested), last_frame))
|
| 158 |
+
# Defaults reproduce the stock anchors bit for bit: leave those rows untouched.
|
| 159 |
+
if (anchor == "first" and frame <= 0) or (anchor == "last" and frame >= last_frame):
|
| 160 |
+
continue
|
| 161 |
+
rows = slice(num_text + index * rows_per_frame, num_text + (index + 1) * rows_per_frame)
|
| 162 |
+
position_ids[rows, 0] = float(num_text) + _ROPE_FRAME_RESCALE * frame
|
| 163 |
+
block_state.position_ids = position_ids
|
| 164 |
+
self.set_block_state(state, block_state)
|
| 165 |
+
return components, state
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
class MiniMaxH3KeyframeConditionStep(MiniMaxH3PrepareConditionLatentsStep):
|
| 169 |
+
model_name = "minimax-h3"
|
| 170 |
+
|
| 171 |
+
@property
|
| 172 |
+
def description(self) -> str:
|
| 173 |
+
return (
|
| 174 |
+
"`MiniMaxH3PrepareConditionLatentsStep` with a noise level per keyframe: `x_t = t*x0 + (1-t)*noise`, "
|
| 175 |
+
"`t = strength` capped at the stock 0.999. Draw order is unchanged (one draw per condition)."
|
| 176 |
+
)
|
| 177 |
+
|
| 178 |
+
@property
|
| 179 |
+
def inputs(self) -> list[InputParam]:
|
| 180 |
+
return super().inputs + [
|
| 181 |
+
InputParam(
|
| 182 |
+
name="keyframe_strengths",
|
| 183 |
+
type_hint=tuple,
|
| 184 |
+
default=None,
|
| 185 |
+
description="Strength in [0, 1] per keyframe, in packed order. None keeps the stock 0.999.",
|
| 186 |
+
)
|
| 187 |
+
]
|
| 188 |
+
|
| 189 |
+
@property
|
| 190 |
+
def intermediate_outputs(self) -> list[OutputParam]:
|
| 191 |
+
return super().intermediate_outputs
|
| 192 |
+
|
| 193 |
+
@torch.no_grad()
|
| 194 |
+
def __call__(self, components: MiniMaxH3ModularPipeline, state: PipelineState):
|
| 195 |
+
global _LEVELS, _AUG
|
| 196 |
+
|
| 197 |
+
block_state = self.get_block_state(state)
|
| 198 |
+
device = components._execution_device
|
| 199 |
+
patch_size = components.patch_size
|
| 200 |
+
aug = float(components.keyframe_noise_aug)
|
| 201 |
+
strengths = block_state.keyframe_strengths
|
| 202 |
+
|
| 203 |
+
levels = []
|
| 204 |
+
packed = []
|
| 205 |
+
for index, condition in enumerate(block_state.condition_latents):
|
| 206 |
+
noise = randn_tensor(condition.shape, generator=block_state.generator, device=device, dtype=torch.float32)
|
| 207 |
+
strength = 1.0 if strengths is None or index >= len(strengths) or strengths[index] is None else strengths[index]
|
| 208 |
+
level = strength_to_level(strength, aug)
|
| 209 |
+
levels.append(level)
|
| 210 |
+
noised = components.scheduler.scale_noise(condition.to(device), level, noise)
|
| 211 |
+
packed.append(patchify_video_latents(noised, patch_size))
|
| 212 |
+
block_state.condition_rows = torch.cat(packed)
|
| 213 |
+
|
| 214 |
+
if block_state.condition_rows.shape[0] != block_state.num_condition_video_rows:
|
| 215 |
+
raise ValueError(
|
| 216 |
+
f"The layout reserved {block_state.num_condition_video_rows} conditioning rows but the encoded "
|
| 217 |
+
f"conditioning latents pack into {block_state.condition_rows.shape[0]}."
|
| 218 |
+
)
|
| 219 |
+
|
| 220 |
+
_AUG = aug
|
| 221 |
+
_LEVELS = tuple(levels) if any(level < aug for level in levels) else None
|
| 222 |
+
|
| 223 |
+
self.set_block_state(state, block_state)
|
| 224 |
+
return components, state
|
| 225 |
+
|
| 226 |
+
|
| 227 |
+
class MiniMaxH3KeyframeControlCoreDenoiseStep(SequentialPipelineBlocks):
|
| 228 |
+
"""`MiniMaxH3FL2VACoreDenoiseStep` with the placement step after the layout and the per-keyframe condition step."""
|
| 229 |
+
|
| 230 |
+
model_name = "minimax-h3"
|
| 231 |
+
block_classes = [
|
| 232 |
+
MiniMaxH3PrepareLayoutStep,
|
| 233 |
+
MiniMaxH3KeyframePlacementStep,
|
| 234 |
+
MiniMaxH3KeyframeConditionStep,
|
| 235 |
+
MiniMaxH3PrepareLatentsStep,
|
| 236 |
+
MiniMaxH3FL2VAPrepareLatentsStep,
|
| 237 |
+
MiniMaxH3SetTimestepsStep,
|
| 238 |
+
MiniMaxH3DenoiseStep,
|
| 239 |
+
MiniMaxH3AfterDenoiseStep,
|
| 240 |
+
]
|
| 241 |
+
block_names = [
|
| 242 |
+
"prepare_layout",
|
| 243 |
+
"keyframe_placement",
|
| 244 |
+
"prepare_condition_latents",
|
| 245 |
+
"prepare_latents",
|
| 246 |
+
"prepare_latents_fl2va",
|
| 247 |
+
"set_timesteps",
|
| 248 |
+
"denoise",
|
| 249 |
+
"after_denoise",
|
| 250 |
+
]
|
| 251 |
+
|
| 252 |
+
@property
|
| 253 |
+
def description(self) -> str:
|
| 254 |
+
return "Core denoising workflow for `fl2va` with per-keyframe injection frame and strength."
|
| 255 |
+
|
| 256 |
+
@property
|
| 257 |
+
def outputs(self):
|
| 258 |
+
return [
|
| 259 |
+
OutputParam("latents", type_hint=torch.Tensor, description="The generated video latents."),
|
| 260 |
+
OutputParam("audio_latents", type_hint=torch.Tensor, description="The generated audio latents."),
|
| 261 |
+
]
|
| 262 |
+
|
| 263 |
+
|
| 264 |
+
class MiniMaxH3KeyframeControlDenoiseStep(ConditionalPipelineBlocks):
|
| 265 |
+
"""Split-deployment denoise selector with the keyframe-control `fl2va` core in place of the stock one."""
|
| 266 |
+
|
| 267 |
+
model_name = "minimax-h3"
|
| 268 |
+
block_classes = [MiniMaxH3KeyframeControlCoreDenoiseStep, MiniMaxH3CoreDenoiseStep]
|
| 269 |
+
block_names = ["fl2va", "t2va"]
|
| 270 |
+
block_trigger_inputs = ["image", "last_image"]
|
| 271 |
+
default_block_name = "t2va"
|
| 272 |
+
|
| 273 |
+
def select_block(self, **kwargs) -> str | None:
|
| 274 |
+
if kwargs.get("image") is not None or kwargs.get("last_image") is not None:
|
| 275 |
+
return "fl2va"
|
| 276 |
+
return None
|
| 277 |
+
|
| 278 |
+
@property
|
| 279 |
+
def description(self):
|
| 280 |
+
return "`fl2va` (keyframe control) when a keyframe is provided, `t2va` otherwise."
|
| 281 |
+
|
| 282 |
+
|
| 283 |
+
class MiniMaxH3KeyframeGeneratorBlocks(SequentialPipelineBlocks):
|
| 284 |
+
"""Drop-in for `h3_split_blocks.MiniMaxH3GeneratorBlocks`, with keyframe injection frame / strength."""
|
| 285 |
+
|
| 286 |
+
model_name = "minimax-h3"
|
| 287 |
+
block_classes = [
|
| 288 |
+
MiniMaxH3SplitBeforeEncodeStep,
|
| 289 |
+
MiniMaxH3SplitVaeEncoderStep,
|
| 290 |
+
MiniMaxH3KeyframeControlDenoiseStep,
|
| 291 |
+
MiniMaxH3DecodeStep,
|
| 292 |
+
]
|
| 293 |
+
block_names = ["resize", "vae_encoder", "denoise", "decode"]
|
| 294 |
+
|
| 295 |
+
@property
|
| 296 |
+
def description(self):
|
| 297 |
+
return "Denoising half of the split deployment with per-keyframe injection frame and strength."
|
| 298 |
+
|
| 299 |
+
@property
|
| 300 |
+
def outputs(self):
|
| 301 |
+
return [
|
| 302 |
+
OutputParam.template("videos", description="The generated video."),
|
| 303 |
+
OutputParam("audio", type_hint=torch.Tensor, description="The soundtrack, `(1, 2, num_samples)`."),
|
| 304 |
+
OutputParam("sampling_rate", type_hint=int, description="Sample rate of the soundtrack in Hz."),
|
| 305 |
+
]
|