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
- Pack img to (batch, seq_len, hidden) using the standard 2x2 patchify rearrange before calling forward.
- Keep txt as (batch, t5_seq_len, 4096); avoid squeeze() that removes the seq dim.
- Add assert img.ndim == 3 and txt.ndim == 3 in your caller to catch regressions.
- 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
- Route all tensor prep through the pipeline's packing utilities rather than manual reshapes.
- Guard against batch-size-1 squeeze() collapsing the batch or seq dim.
- Keep controlnet_cond separate — only img/txt need the token-stream layout.
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
- Input img and txt tensors must have 3 dimensions.
- Unsupported cfg_scale type: {type(cfg_scale)}
- Invalid cfg_scale_start_step. Out of range: {cfg_scale_start
- Invalid cfg_scale_end_step. Out of range: {cfg_scale_end_ste
- cfg_scale_start_step ({cfg_scale_start_step}) must be before
AI-assisted analysis of invoke-ai/InvokeAI@0b6a024f2f (2026-08-29).
Data as JSON: /api/errors/be04dd0f6a98d407.
Report an issue: GitHub.