Nodes/ComfyUI-latent-ops/LatentOperationRoll
ComfyUI Node

LatentOperationRoll

Shift your latent around and wrap it back like a scrolling marquee

By hnmr293·Created about a year ago·Updated about a year ago· 2
LatentOperationRoll
    • op
    shift0
    axis-1

    LatentOperationRoll shifts the values of a latent along an axis and wraps them around - what falls off one edge comes back in on the other, exactly like a scrolling marquee. It's torch.roll(shift, dims=axis) under the hood, and it's the least glamorous but most genuinely useful structural node in this pack for one particular job: making content that tiles seamlessly.

    The mechanism

    def roll(latent, **kwargs):
        return latent.roll(shift, dims=axis)
    

    Two inputs, both plain integers:

    • shift (INT, default 0) - "Number of positions to shift." Positive moves one way along the axis, negative the other.
    • axis (INT, default -1) - "Axis to roll along." On a 4D [batch, channels, height, width] latent: -1/3 is width, 2 is height, 1 is channels, 0 is batch.

    Because it wraps rather than drops, a roll of half the width is exactly how you test for tiling: shift by half, subtract the original, and if the difference is near zero across the seam you've got a seamless latent. That's the classic trick - people do the same dance with pixel-space images for tileable textures, and doing it in latent space (as this pack encourages, in the spirit of the author's llul - his local latent upscaler) skips a decode/encode round trip. Rolls are also handy for mechanical batch juggling, like rotating which item in a batch lands in the front.

    The trap: it's a shift, not a resize

    Roll doesn't drop data or create gaps - it's purely circular, so the total content is preserved, just relocated. That's the whole point and the whole limitation: you can't use roll to crop (that's LatentOperationSlice) or to discard edges. And since it operates on the latent's spatial axes, rolling by a non-multiple-of-8 amount on a latent that will be decoded by an 8×-downsampling VAE can interact oddly with the decoder's receptive field - if the goal is seamless tiling, stick to shifts that align with the VAE's compression ratio (multiples of 8 for SD-family latents; the KB's concepts notes VAE downsampling is typically 8× per side).

    The op output

    As with every node in hnmr293/ComfyUI-latent-ops, the output is op of type LATENT_OPERATION - a deferred closure, not an applied result. The pack builds operations and ships no apply node, so you need a consumer of LATENT_OPERATION (Sonar's SonarApplyLatentOperationCFG is the one that exists in the wild) or your own apply node. Directly wiring op into a VAE Decode yields a type mismatch - the pack's universal first trap, by design.

    Install

    The usual. ComfyUI Manager → search ComfyUI-latent-ops, or:

    cd ComfyUI/custom_nodes
    git clone https://github.com/hnmr293/ComfyUI-latent-ops
    

    Restart. No requirements.txt, no model downloads - pure PyTorch. This is the workbench of hnmr293 (the sd-webui-cutoff / llul author); the pack is near-invisible to the community, so latent_ops/ops.py is your real documentation. Everything registers under hnmr/latent_ops.

    Categoryhnmr/latent_ops

    Inputs (2)

    NameTypeDefaultDescription
    shiftINT0-10000–10000Number of positions to shift.
    axisINT-1-10000–10000Axis to roll along.

    Outputs (1)

    NameTypeDescription
    opLATENT_OPERATION