Comfy-Org/ComfyUI · 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

Flux ControlNet's __call__ requires img (latent image tokens) and txt (text tokens) to be rank-3 tensors of shape (batch, sequence, channel). Anything else raises ValueError immediately. This contract matches the packed-token format produced by Flux latents and T5 text encodings before entering the controlnet.

Source

Thrown at comfy/ldm/flux/controlnet.py:122

                    operations.Conv2d(16, 16, 3, padding=1, stride=2, dtype=dtype, device=device),
                    nn.SiLU(),
                    operations.Conv2d(16, 16, 3, padding=1, dtype=dtype, device=device)
                )

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

        if y is None:
            y = torch.zeros((img.shape[0], self.params.vec_in_dim), device=img.device, dtype=img.dtype)
        else:
            y = y[:, :self.params.vec_in_dim]

        # running on sequences img
        img = self.img_in(img)

        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:
            vec = vec + self.guidance_in(timestep_embedding(guidance, 256))
        vec = vec + self.vector_in(y)
        txt = self.txt_in(txt)

        if self.controlnet_mode_embedder is not None and len(control_type) > 0:

View on GitHub (pinned to 1c6d8d45b3)

Solutions

  1. Feed the same packed (B, seq, dim) img/txt tensors that the main Flux model consumes (from Flux latent conditioning, not raw VAE latents).
  2. If your tensor is (B,C,H,W), apply the Flux patchify step first to reach rank 3.
  3. Use the stock Flux controlnet ComfyUI nodes, which build correctly shaped inputs, instead of calling the module directly.

Example fix

# before (raw VAE latent, rank 4)
ctrl = controlnet(img_latent_bchw, img_ids, cond, txt_btd, txt_ids, t, y)

# after (packed sequence, rank 3)
img_seq = pack_latents(img_latent_bchw)  # -> (B, H/2*W/2, C*4)
ctrl = controlnet(img_seq, img_ids, cond, txt_btd, txt_ids, t, y)
Defensive patterns

Strategy: validation

Validate before calling

assert img.ndim == 3 and txt.ndim == 3, f"img/txt must be (B,seq,dim); got img {tuple(img.shape)}, txt {tuple(txt.shape)}"

Type guard

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

Prevention

When it happens

Trigger: Passing a 4D image tensor (B,C,H,W) that was not patchified/packed into (B,seq,dim); passing text embeddings with an extra batch dim; reusing a controlnet wrapper with tensors formatted for a different conditioning path.

Common situations: Custom nodes calling the Flux controlnet forward directly with VAE latents instead of Flux sequence latents; importing code from a diffusers-style pipeline where tensor layouts differ; empty or scalar edge-case tensors.

Related errors


AI-assisted analysis of Comfy-Org/ComfyUI@1c6d8d45b3 (2026-08-14). Data as JSON: /api/errors/1642b759c96e2c04. Report an issue: GitHub.