Qwen-Image 2.1's VAE is a Wan-2.2-layout VAE with a 16x spatial downscale
and FOUR output channels (comfy/sd.py:841-847 — output_channels read from
the checkpoint tensor decoder.head.2.weight, pad_channel_value 1.0 for
opaque alpha). VAE.decode therefore returns [B, H, W, 4].
Every cascade node in this pack speaks the 3-channel convention, and the
damage is concrete rather than aesthetic: PixelRush's and FreeScale's
layout probes match on exactly 3 channels, so a 4-channel decode slips
through them channels-LAST into a channels-FIRST pipeline; HiFlow's
_sharpen probes shape[-1] == 3 for the same reason.
src/vae_channels.py adds the two pure helpers that normalize the boundary
— strip_alpha_channel on decode, pad_to_vae_channels on encode — wired
into all three adapters. Both gate on getattr(vae, "output_channels", 3),
so the other six architectures take a byte-identical path (pinned). The
encode side deliberately mirrors ComfyUI's own vae_encode_crop_pixels
(pad with pad_channel_value / truncate / leave alone when unset), which
makes it idempotent with core: it can never change what a real VAE
receives, it exists so three adapters share one decision. Alpha is not
preserved end to end — every consumer here is RGB — but the drop is
logged once per session, naming the node, rather than happening silently.
New node EmptyQwenImage21LatentImage: core ships no 2.1 t2i empty latent
(EmptyQwenImageLayeredLatentImage is the layered/edit latent — 16ch, //8,
5-D — and EmptyLatentImage is 4ch at //8), so the pack provides the 64ch
//16 counterpart TextEncodeQwenImage21 only emits as a by-product of
encoding a prompt. Width/height are first-class inputs, snapped to
multiples of 16 with a graph-time validate_inputs message.
Also shipped: example_workflows/DyPE-Qwen21-workflow.json (loaders ->
DyPE_FLUX with model_type qwen21 -> TextEncodeQwenImage21 ->
EmptyQwenImage21LatentImage -> KSampler -> decode), TextEncodeQwenImage21
and QwenImage21Cache added to the workflow validator's core allowlist,
and the structure guard's node-class/module lists extended (their two
tests were named "8_classes"/"8_node_extension" and are now named for
what they assert, since the literal count was the only thing keeping them
from breaking on every node addition).
Tests: tests/test_vae_channels.py (pure helpers, CPU, no ComfyUI) and
tests/test_cascade_qwen21.py (the three adapters end to end against a
fake RGBA VAE, the latent node's shape/validation/round-trip, plus pins
that read ComfyUI's real sd.py and latent_formats.py when COMFYUI_ROOT is
set — the 16x/RGBA/latent_dim==2 facts the whole design rests on).