{"record":{"id":"d15a7f05e158f894","repo":"invoke-ai/InvokeAI","slug":"expected-latent-dim-packed-channels-got-channe","errorCode":null,"errorMessage":"expected {LATENT_DIM} packed channels, got {channels}","messagePattern":"expected (.+?) packed channels, got (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"invokeai/backend/ideogram4/sampling_utils.py","lineNumber":120,"sourceCode":"    \"\"\"\n    batch_size = z.shape[0]\n    z = z.reshape(batch_size, grid_h, grid_w, LATENT_DIM)\n    return z.permute(0, 3, 1, 2).contiguous()\n\n\ndef unpatchify_and_denormalize(\n    packed: torch.Tensor,\n    latent_shift: torch.Tensor,\n    latent_scale: torch.Tensor,\n) -> torch.Tensor:\n    \"\"\"Convert a packed latent ``(1, LATENT_DIM, grid_h, grid_w)`` to a VAE latent ``(1, 32, H/8, W/8)``.\n\n    Applies the per-channel latent denormalization (``z * scale + shift``) in the\n    packed space, then unpatchifies, exactly as ``Ideogram4Pipeline._decode`` does.\n    \"\"\"\n    batch_size, channels, grid_h, grid_w = packed.shape\n    if channels != LATENT_DIM:\n        raise ValueError(f\"expected {LATENT_DIM} packed channels, got {channels}\")\n\n    # (B, grid_h, grid_w, LATENT_DIM)\n    z = packed.permute(0, 2, 3, 1)\n    z = z * latent_scale.to(z.device, z.dtype) + latent_shift.to(z.device, z.dtype)\n\n    ae_channels = LATENT_DIM // (PATCH_SIZE * PATCH_SIZE)  # 32\n    z = z.reshape(batch_size, grid_h, grid_w, PATCH_SIZE, PATCH_SIZE, ae_channels)\n    z = z.permute(0, 5, 1, 3, 2, 4).contiguous()\n    z = z.reshape(batch_size, ae_channels, grid_h * PATCH_SIZE, grid_w * PATCH_SIZE)\n    return z\n","sourceCodeStart":102,"sourceCodeEnd":131,"githubUrl":"https://github.com/invoke-ai/InvokeAI/blob/0b6a024f2ff6a86bfb953dcdb9cc504ef7397a06/invokeai/backend/ideogram4/sampling_utils.py#L102-L131","documentation":"unpatchify_and_denormalize expects the packed latent tensor to have exactly LATENT_DIM channels; any other channel count cannot be unpatchified back into the 32-channel autoencoder latent space.","triggerScenarios":"Passing a raw VAE latent (e.g. 16 or 32 channels) or a mis-patched tensor to unpatchify_and_denormalize from step_callback or invoke instead of the LATENT_DIM-channel packed diffusion tensor.","commonSituations":"Wiring the decoded image callback to the wrong tensor, using latents from another model's VAE, forgetting the patchify step, transposing shape incorrectly so channels dimension is wrong.","solutions":["Ensure the tensor passed in is the LATENT_DIM-channel packed tensor produced by patchify/pipeline denoise steps","Check tensor.permute/reshape so channels are in dim 1","If starting from a VAE latent, patchify it (pack LATENT_DIM = ae_channels * PATCH_SIZE^2) first"],"exampleFix":"// before\nunpatchify_and_denormalize(vae_latent, scale, shift)  # 32 channels\n// after\npacked = patchify(vae_latent)  # -> (B, LATENT_DIM, gh, gw)\nunpatchify_and_denormalize(packed, scale, shift)","handlingStrategy":"type-guard","validationCode":"assert packed.ndim == 4, f\"expected (B,C,gh,gw), got {packed.shape}\"\nassert packed.shape[1] == LATENT_DIM, f\"need {LATENT_DIM} channels, got {packed.shape[1]}\"","typeGuard":"def is_packed_latent(t: torch.Tensor) -> bool:\n    return t.ndim == 4 and t.shape[1] == LATENT_DIM","tryCatchPattern":"try:\n    image = unpatchify_and_denormalize(packed, scale, shift)\nexcept ValueError as e:\n    if \"packed channels\" in str(e):\n        packed = patchify(packed)  # or fix permute\n        image = unpatchify_and_denormalize(packed, scale, shift)\n    else:\n        raise","preventionTips":["Only feed tensors produced by the pipeline's patchify step into unpatchify","Check channel count in callbacks before decoding","Keep a single helper for pack/unpack to avoid shape drift"],"tags":["validation","tensor-shape","latent"],"backgroundTag":"tensor-shape-mismatch","analyzedSha":"0b6a024f2ff6a86bfb953dcdb9cc504ef7397a06","analyzedAt":"2026-08-29T04:46:49.967Z","schemaVersion":2},"datasetVersion":"2026-08-29T07:17:48.351Z"}