invoke-ai/InvokeAI · error · ValueError

num_frames must satisfy (num_frames - 1) %% 4 == 0 for the W

Error message

num_frames must satisfy (num_frames - 1) %% 4 == 0 for the Wan VAE's temporal compression (got {self.num_frames}). Try 5, 9, 13, ..., 81, 85, ...

What it means

The Wan Video Denoise invocation enforces the same Wan VAE temporal-compression constraint as the encoder: (num_frames - 1) must be divisible by 4 so latents line up on the time axis. The check runs at the start of _run_diffusion so users fail fast with a clear message instead of a shape mismatch during denoising or decode.

Source

Thrown at invokeai/app/invocations/wan_video_denoise.py:158

    def invoke(self, context: InvocationContext) -> LatentsOutput:
        latents = self._run_diffusion(context)
        # Keep the 5D shape (B, C, T, H, W) — wan_latents_to_video expects it.
        latents = latents.detach().to("cpu")
        name = context.tensors.save(tensor=latents)
        # LatentsOutput.build uses latents.size()[3] / [2] for width / height.
        # For 5D the spatial dims are at indices 4 / 3 instead of 3 / 2, so we
        # call the constructor directly with the actual H/W from the inputs.
        from invokeai.app.invocations.fields import LatentsField

        return LatentsOutput(
            latents=LatentsField(latents_name=name, seed=self.seed),
            width=self.width,
            height=self.height,
        )

    def _run_diffusion(self, context: InvocationContext) -> torch.Tensor:
        if (self.num_frames - 1) % 4 != 0:
            raise ValueError(
                f"num_frames must satisfy (num_frames - 1) %% 4 == 0 for the Wan VAE's temporal "
                f"compression (got {self.num_frames}). Try 5, 9, 13, ..., 81, 85, ..."
            )

        device = TorchDevice.choose_torch_device()
        inference_dtype = TorchDevice.choose_bfloat16_safe_dtype(device)

        variant = _resolve_variant(context, self.transformer)
        _validate_spatial_dimensions(variant, self.width, self.height)
        spatial_scale = get_spatial_scale_factor(variant)

        # Reuse the image denoise's scheduler construction so we pick up whatever
        # scheduler the variant ships with (UniPCMultistepScheduler with the
        # variant's flow_shift, or whatever an on-disk config specifies).
        scheduler_builder = WanDenoiseInvocation._build_scheduler  # bound on instance below
        # Bind a minimal instance to call _build_scheduler — it only reads
        # self.transformer, which is shape-compatible.
        proxy = WanDenoiseInvocation.model_construct(

View on GitHub (pinned to 0b6a024f2f)

Solutions

  1. Set num_frames to a 4n+1 value (5, 9, 13, ..., 81, 85).
  2. Adjust duration math: e.g. ~2s at 24fps -> 49 frames instead of 48.
  3. Keep num_frames = 1 only for single-image workflows; the check passes for 1 ((1-1)%4==0).

Example fix

// before
num_frames = 48  # invalid
// after
num_frames = 49  # 4n+1
Defensive patterns

Strategy: validation

Validate before calling

def snap_to_wan_frames(n: int) -> int:
    return 1 if n <= 1 else max(5, ((n - 1) // 4) * 4 + 1)
num_frames = snap_to_wan_frames(num_frames)

Try / catch

try:
    tensor = denoise._run_diffusion(context)
except ValueError as e:
    if "num_frames must satisfy" in str(e):
        denoise.num_frames = ((denoise.num_frames - 1) // 4) * 4 + 1
    else:
        raise

Prevention

When it happens

Trigger: Setting num_frames on the 'Wan Video Denoise' invocation to any value > 1 that is not of the form 4n+1, e.g. 33, 48, 100.

Common situations: Choosing frame counts from fps/duration math (2s * 24fps = 48) or defaults copied from non-Wan video pipelines.

Related errors


AI-assisted analysis of invoke-ai/InvokeAI@0b6a024f2f (2026-08-29). Data as JSON: /api/errors/0d6b26c3aa57cb13. Report an issue: GitHub.