unslothai/unsloth · error · ValueError

num_frames must be of the form {H3_FRAMES_PER_CHUNK} * n + {

Error message

num_frames must be of the form {H3_FRAMES_PER_CHUNK} * n + {H3_LATENTS_PER_CHUNK}, got {num_frames}.

What it means

Raised by h3_video_latent_frames() when num_frames is not congruent to H3_LATENTS_PER_CHUNK (5) modulo H3_FRAMES_PER_CHUNK (17) — i.e. not of the form 17*n + 5. The video VAE only encodes aligned frame counts, so computing latent frames for any other count is undefined. Use h3_align_num_frames() first to snap the count up.

Source

Thrown at studio/backend/core/training/diffusion_h3_clips.py:99

H3_TRAIN_NUM_FRAMES = H3_FRAMES_PER_CHUNK + H3_LATENTS_PER_CHUNK

_VIDEO_EXTS = {".mp4", ".mov", ".mkv", ".webm", ".m4v", ".avi"}
_CAPTION_EXTS = (".txt", ".caption")


def h3_align_num_frames(num_frames: int) -> int:
    """Snap a frame count UP to the next ``17 * n + 5`` the video VAE can encode."""
    if num_frames < 1:
        raise ValueError(f"num_frames must be positive, got {num_frames}.")
    while num_frames % H3_FRAMES_PER_CHUNK != H3_LATENTS_PER_CHUNK:
        num_frames += 1
    return num_frames


def h3_video_latent_frames(num_frames: int) -> int:
    """Latent frames the video VAE produces for an aligned frame count: ``5 * n + 2``."""
    if num_frames % H3_FRAMES_PER_CHUNK != H3_LATENTS_PER_CHUNK:
        raise ValueError(
            f"num_frames must be of the form {H3_FRAMES_PER_CHUNK} * n + {H3_LATENTS_PER_CHUNK}, "
            f"got {num_frames}."
        )
    return (num_frames - H3_LATENTS_PER_CHUNK) // H3_FRAMES_PER_CHUNK * H3_LATENTS_PER_CHUNK + 2


def h3_audio_latent_count(num_frames: int) -> int:
    """Audio latents (per channel) covering ``num_frames`` frames at 24 fps / 40 latents per s."""
    return int(round(num_frames / H3_FPS * H3_AUDIO_LATENTS_PER_SECOND))


def h3_audio_sample_count(num_frames: int) -> int:
    """Waveform samples per channel the audio VAE must be handed for ``num_frames`` frames.

    The audio VAE hops 800 samples (32 kHz / 40 latents per second) and right-pads a short
    tail, so handing it exactly ``latents * hop`` samples produces exactly the latent count the
    packed layout reserves rows for -- no pad, no truncation."""
    hop = H3_AUDIO_SAMPLING_RATE // H3_AUDIO_LATENTS_PER_SECOND

View on GitHub (pinned to 203007d190)

Solutions

  1. Run the count through h3_align_num_frames() before h3_video_latent_frames().
  2. Use H3_TRAIN_NUM_FRAMES (the canonical 22-frame training clip) when in doubt.
  3. Add a modulo assertion at your call site to catch unaligned counts early.

Example fix

# before
latents = h3_video_latent_frames(requested_frames)  # e.g. 30 -> ValueError

# after
latents = h3_video_latent_frames(h3_align_num_frames(requested_frames))
Defensive patterns

Strategy: validation

Validate before calling

def frames_aligned(num_frames: int, per_chunk: int = 17, latents: int = 5) -> bool:
    return num_frames % per_chunk == latents

Prevention

When it happens

Trigger: Calling h3_video_latent_frames(30) or any count where num_frames % 17 != 5; passing a raw user-requested duration in frames that was never run through h3_align_num_frames().

Common situations: Computing memory/latent budgets from an arbitrary fps*seconds product; wiring a UI slider straight into latent math; copy-pasting a frame count from another model family (e.g. 24 or 25 fps clips).

Related errors


AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15). Data as JSON: /api/errors/cc77bc8803d7e440. Report an issue: GitHub.