No jobs
Core 10 min 3 of 4

What a 4-channel latent is

Enough spatial correspondence to be useful, and enough channel opacity to be misleading if you read it as a small image.

What this repairs

Reading latent channels as colour channels, and expecting latent-space edits to behave like pixel-space edits.

A latent is [4,64,64][4, 64, 64] from an image that was [3,512,512][3, 512, 512] . The spatial stride is 88 in each direction, and it is not a hyperparameter someone picked — it is the count of downsampling stages in the encoder, 241=82^{4-1} = 8 for the four-level codec SD uses. That is worth knowing because it means the stride and the depth of the codec are the same fact, and neither moves without retraining.

The temptation is to read the result as a small image with a spare channel. The spatial half of that reading is roughly right and useful. The channel half is wrong in a way that survives inspection, because a latent channel rendered with a colourmap looks exactly like a meaningful map.

Where a latent cell actually reaches

  1. z=E(x),zR4×64×64,xR3×512×512z = E(x), \qquad z \in \mathbb{R}^{4 \times 64 \times 64}, \quad x \in \mathbb{R}^{3 \times 512 \times 512}

    The map, with its shapes. The encoder is fully convolutional — no flattening, no positional embedding, no global pooling — which is the property everything below rests on.

  2. E(T8kx)=TkE(x)E(T_{8k}\,x) = T_{k}\,E(x)

    A fully convolutional encoder is translation-equivariant at its stride: shift the image by a multiple of 8 pixels and the latent shifts by that many cells. This is why a latent has a spatial layout at all and why overlaying it on the image is meaningful.

  3. (i,j)    [8i,8i+8)×[8j,8j+8)(i, j) \;\longmapsto\; \left[8i,\, 8i+8\right) \times \left[8j,\, 8j+8\right)

    The nominal correspondence: one latent cell per 8×8 pixel block. This is the map every latent-space mask is built from, and about position it is correct.

  4. reff8pixelsr_{\text{eff}} \gg 8 \quad \text{pixels}

    About extent it is wrong. The encoder is many convolutions deep, so each cell’s receptive field is far wider than its stride — a cell summarises its 8×8 block in the context of a neighbourhood many times that size, and the decoder’s reach is symmetric.

  5. Δzij0    ΔD(z) supported well outside 8×8\Delta z_{ij} \neq 0 \;\Longrightarrow\; \Delta D(z) \text{ supported well outside } 8 \times 8

    So a single-cell edit does not produce a single-block change. This is the reason latent inpainting bleeds past its mask, why latent masks need feathering, and why "edit the 2×2 cells under this 16×16 region" is a statement about position rather than about extent.

Check yourself

You mask a 16×16 pixel region, edit the corresponding 2×2 latent cells, and decode. The change in the output is clearly wider than 16×16. Is the mask arithmetic wrong?

Show answer

No — the arithmetic is right and the expectation is wrong. 16/8=216/8 = 2 , and those two cells are the ones whose nominal blocks cover the region.

What spilled is the decoder's receptive field. Each latent cell participates in many convolutions on the way back up, so the pixels it influences extend well past its own 8×88 \times 8 block; the influence falls off with distance rather than stopping at a boundary. Nothing in the encoder or decoder ever promised a hard block boundary — the 8×8\times figure is a stride, and a stride is about sampling rate, not about support.

The practical fix is the one every latent-space editing pipeline arrives at: feather the latent mask, composite in pixel space after decoding, or both. And the practical warning is that the spill is largest exactly where it is most visible, at high-contrast edges.

Euler View - ML Experiment Monitor