unslothai/unsloth · error · ValueError

num_frames must be positive, got {num_frames}.

Error message

num_frames must be positive, got {num_frames}.

What it means

Raised by h3_align_num_frames() when num_frames is less than 1. The function's job is to snap a frame count UP to the next value of the form 17*n + 5 that the MiniMax-H3 video VAE can encode, and that arithmetic is meaningless for zero or negative inputs. It guards a pure integer precondition before any alignment loop runs.

Source

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

# This is deliberately far below the 5 s floor MiniMax-H3 *generates* at, and the trade is
# explicit: the packed sequence is quadratic in its own length through the full self-attention,
# so at the released 768-short-edge canvas a 5 s clip is ~38k rows and a 22-frame clip is ~7k.
# Training at the native canvas on short clips keeps the SPATIAL statistics -- which is what a
# style LoRA learns -- exactly on distribution, and only shortens the temporal extent; training
# a 5 s clip at a canvas small enough to fit would put every spatial statistic off distribution
# instead. The temporal rotary grid of a 22-frame clip is a strict PREFIX of the grid of a
# generated one (``_temporal_position_grid`` starts at the same origin with the same spacing),
# so no row sits at a coordinate the model never visits.
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))

View on GitHub (pinned to 203007d190)

Solutions

  1. Pass a positive frame count; for H3 training use H3_TRAIN_NUM_FRAMES (22) or another 17*n+5 value.
  2. Trace where the zero/negative value originates (form default, subtraction, parse) and fix the producer.
  3. Validate with num_frames >= 1 at your config boundary before calling the alignment helper.

Example fix

# before
n = h3_align_num_frames(max_frames - trim)  # trim == max_frames -> 0

# after
n = h3_align_num_frames(max(1, max_frames - trim))
Defensive patterns

Strategy: validation

Validate before calling

def frames_alignable(num_frames: int) -> bool:
    return isinstance(num_frames, int) and num_frames >= 1

Prevention

When it happens

Trigger: Calling h3_align_num_frames(0) or with a negative count; deriving num_frames from user input, a clip probe, or a subtraction (e.g. num_frames - offset) that underflows to zero.

Common situations: Default-initializing num_frames to 0 in a form and passing it through unvalidated; slicing logic computing an empty window; a misparsed CLI argument.

Related errors


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