Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions src/diffusers/modular_pipelines/minimax_h3/before_denoise.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
MiniMaxH3ModularPipeline,
align_num_frames,
audio_latent_num_frames,
frame_bounds,
resolve_canvas_size,
video_latent_num_frames,
)
Expand Down Expand Up @@ -395,15 +396,17 @@ def __call__(self, components: MiniMaxH3ModularPipeline, state: PipelineState) -
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:
# The bound is a frame count on the VAE's own grid, so the declared envelope is carried onto that grid at BOTH
# ends by the same upward snap the request gets. Comparing the aligned count against a ceiling left in seconds
# is what refuses 362 frames on a model documented to reach 15 seconds.
min_frames, max_frames = frame_bounds(
components.min_duration, components.max_duration, components.fps, frames_per_chunk, latents_per_chunk
)
if not min_frames <= aligned_num_frames <= max_frames:
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"encode, must be between {min_frames} and {max_frames}, got {block_state.num_frames} (rounded up to "
f"{aligned_num_frames})."
)
if aligned_num_frames != block_state.num_frames:
Expand Down
19 changes: 12 additions & 7 deletions src/diffusers/modular_pipelines/minimax_h3/before_encoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from .modular_pipeline import (
MiniMaxH3ModularPipeline,
align_num_frames,
frame_bounds,
resolve_canvas_size,
)
from .references import (
Expand Down Expand Up @@ -417,23 +418,27 @@ def __call__(self, components: MiniMaxH3ModularPipeline, state: PipelineState) -
"on its own."
)

# 2. Resolve the canvas and the frame count. The duration the request generates is the one of the *aligned*
# frame count, so that is what the ceiling holds for: 346 frames would otherwise pass the check and then be
# rounded up to 362, i.e. 15.083 seconds.
# 2. Resolve the canvas and the frame count. The bound is a frame count on the VAE's own grid, so the declared
# envelope is carried onto that grid at BOTH ends by the same upward snap the request gets.
if block_state.height is None:
block_state.height, block_state.width = resolve_canvas_size(
16, 9, multiple, components.config.canvas_short_edge, components.config.canvas_max_pixels
)
aligned_num_frames = align_num_frames(
block_state.num_frames, components.vae_frames_per_chunk, components.vae_latents_per_chunk
)
duration = aligned_num_frames / components.fps
if not components.min_duration <= duration <= components.max_duration:
min_frames, max_frames = frame_bounds(
components.min_duration,
components.max_duration,
components.fps,
components.vae_frames_per_chunk,
components.vae_latents_per_chunk,
)
if not min_frames <= aligned_num_frames <= max_frames:
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"encode, must be between {min_frames} and {max_frames}, got {block_state.num_frames} (rounded up to "
f"{aligned_num_frames})."
)
if aligned_num_frames != block_state.num_frames:
Expand Down
26 changes: 26 additions & 0 deletions src/diffusers/modular_pipelines/minimax_h3/modular_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,32 @@ def align_num_frames(num_frames: int, frames_per_chunk: int, latents_per_chunk:
return num_frames


def frame_bounds(min_duration: float, max_duration: float, fps: int, frames_per_chunk: int, latents_per_chunk: int):
r"""
Carry a duration envelope onto the video VAE's `frames_per_chunk * n + latents_per_chunk` grid.

Both bounds are snapped the same way a request's `num_frames` is, because the grid — not the clock — decides what
the VAE can encode. Snapping only the floor and holding the ceiling in seconds makes the two ends inconsistent:
at 24 fps a 5.0 s floor admits 124 frames (5.167 s, above the floor it is checked against) while a 15.0 s ceiling
refuses 362 frames (15.083 s), and since the grid has no point at `15.0 * 24 = 360` that leaves a model documented
as generating up to 15 seconds unable to generate 15 seconds at all.

Args:
min_duration (`float`): Shortest duration the model generates, in seconds.
max_duration (`float`): Longest duration the model generates, in seconds.
fps (`int`): The model's frame rate.
frames_per_chunk (`int`): Pixel frames the video VAE encodes per chunk, its `clip_length`.
latents_per_chunk (`int`): Latent frames a chunk keeps, the VAE's `tokens_chunk_size`.

Returns:
`tuple[int, int]`: The smallest and largest frame counts on the grid.
"""
return (
align_num_frames(int(min_duration * fps), frames_per_chunk, latents_per_chunk),
align_num_frames(int(max_duration * fps), frames_per_chunk, latents_per_chunk),
)


def video_latent_num_frames(num_frames: int, frames_per_chunk: int, latents_per_chunk: int) -> int:
r"""
The number of latent frames the video VAE produces for a `17 * n + 5` frame count.
Expand Down
Loading