invoke-ai/InvokeAI · error · ValueError

Input img and txt tensors must have 3 dimensions.

Error message

Input img and txt tensors must have 3 dimensions.

What it means

XLabsControlNetFlux.forward expects sequence-form tokens: img (packed latent tokens) and txt (T5 text tokens) must both be 3D (batch, seq_len, channels). The controlnet_cond image goes through input_hint_block separately, so passing img/txt as 2D or 4D tensors breaks the token-stream contract and raises this ValueError.

Source

Thrown at invokeai/backend/flux/controlnet/xlabs_controlnet_flux.py:100

            torch.nn.SiLU(),
            torch.nn.Conv2d(16, 16, 3, padding=1, stride=2),
            torch.nn.SiLU(),
            zero_module(torch.nn.Conv2d(16, 16, 3, padding=1)),
        )

    def forward(
        self,
        img: torch.Tensor,
        img_ids: torch.Tensor,
        controlnet_cond: torch.Tensor,
        txt: torch.Tensor,
        txt_ids: torch.Tensor,
        timesteps: torch.Tensor,
        y: torch.Tensor,
        guidance: torch.Tensor | None = None,
    ) -> XLabsControlNetFluxOutput:
        if img.ndim != 3 or txt.ndim != 3:
            raise ValueError("Input img and txt tensors must have 3 dimensions.")

        # running on sequences img
        img = self.img_in(img)
        controlnet_cond = self.input_hint_block(controlnet_cond)
        controlnet_cond = rearrange(controlnet_cond, "b c (h ph) (w pw) -> b (h w) (c ph pw)", ph=2, pw=2)
        controlnet_cond = self.pos_embed_input(controlnet_cond)
        img = img + controlnet_cond
        vec = self.time_in(timestep_embedding(timesteps, 256))
        if self.params.guidance_embed:
            if guidance is None:
                raise ValueError("Didn't get guidance strength for guidance distilled model.")
            vec = vec + self.guidance_in(timestep_embedding(guidance, 256))
        vec = vec + self.vector_in(y)
        txt = self.txt_in(txt)

        ids = torch.cat((txt_ids, img_ids), dim=1)
        pe = self.pe_embedder(ids)

View on GitHub (pinned to 0b6a024f2f)

Solutions

  1. Pack img to (batch, seq_len, hidden) using the standard 2x2 patchify rearrange before calling forward.
  2. Keep txt as (batch, t5_seq_len, 4096); avoid squeeze() that removes the seq dim.
  3. Add assert img.ndim == 3 and txt.ndim == 3 in your caller to catch regressions.
  4. Reuse the model's pipeline helpers for latent preparation instead of manual reshaping.

Example fix

// before
controlnet(img=latents, txt=t5_emb[:, 0, :], ...)  # 4D img, 2D txt
// after
img_tokens = rearrange(latents, "b c (h ph) (w pw) -> b (h w) (c ph pw)", ph=2, pw=2)
controlnet(img=img_tokens, txt=t5_emb, ...)
Defensive patterns

Strategy: validation

Validate before calling

assert img.ndim == 3 and txt.ndim == 3, (
    f"expected (B, seq, dim): img={tuple(img.shape)}, txt={tuple(txt.shape)}")

Type guard

def is_token_stream(t: torch.Tensor) -> bool:
    return t.ndim == 3

Try / catch

try:
    out = xlabs_controlnet(img=img, txt=txt, ...)
except ValueError as e:
    if "must have 3 dimensions" in str(e):
        img = pack_latents(img)  # (B,C,H,W) -> (B, seq, dim)
        out = xlabs_controlnet(img=img, txt=txt, ...)
    else:
        raise

Prevention

When it happens

Trigger: Calling forward() with img not patchified to (B, seq, dim) — e.g. a raw (B,C,H,W) latent — or txt squeezed to (B, C) or still 4D.

Common situations: Bypassing the pipeline's latent-packing step; removing the batch/sequence dim with .squeeze() when batch size is 1; mixing tensor layouts from diffusers-style code with this InvokeAI implementation.

Related errors


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