LatentOperationNormalize
One global z-score to pull a latent back to earth
- op
A latent that's drifted - mean shifted, variance ballooned, values sitting somewhere the sampler never expects - will decode into garbage or flat mush. LatentOperationNormalize is the blunt instrument for that: it computes the mean and standard deviation across the entire tensor and rescales so everything lands at zero mean, unit variance. No inputs, no axis, no knob. Just "fix my distribution, globally." It's the most direct member of the pack's normalization family, and sometimes brute force is exactly right.
The math, and the float32 trick
mean = latent.mean()
std = latent.std()
return (latent - mean) / (std + eps)
with eps = 1e-8 so you never divide by zero on a flat latent. That's it - one global mean, one global standard deviation, applied to every element. Because it's global, it doesn't care about channels or batches: a latent that's uniformly too hot gets pulled back to a standard distribution; a latent with one channel blown out gets all four dragged toward the same footing.
The detail that matters on real hardware: the whole computation runs in float32 and casts back to the latent's original dtype. That _float wrapper is shared by every normalize node in this pack, and it's the right call - fp16 overflow on variance math is a classic ComfyUI failure mode (the KB's troubleshooting doc flags it explicitly for some models), and a global mean/variance over a big tensor is exactly the kind of operation that hits it. You won't get NaNs from this node; that's the point of the wrapper.
Where it fits in the family
The pack gives you a ladder of normalizations, and the difference is scope:
LatentOperationNormalize- global, whole tensor.LatentOperationNormalizeAlongAxis- one axis (withaxis=1you get per-channel normalization, which respects each channel's own distribution).LatentOperationLayerNorm- per sample across all non-batch dims.LatentOperationNormalizeMinMax- squash into 0–1.LatentOperationNormalizePercentile- min-max, but robust to outliers.
If your latent is uniformly sick, Normalize is the one. If only one channel is off, you want AlongAxis. If you don't know which, start here - it's the safest single "recalibrate" button.
The apply gotcha, as always
LatentOperationNormalize has zero inputs and one output: op, typed LATENT_OPERATION. It's a deferred closure, not an applied result - the pack builds operations and doesn't ship the node that runs them. You'll need a consumer of LATENT_OPERATION (Sonar's SonarApplyLatentOperationCFG is the real-world one) or your own apply node. Plug op into a VAE Decode and you get a type error. This catches everyone, including me, the first time.
Install
Standard for this pack. 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 that ComfyUI already ships. This is hnmr293's personal latent workbench (same author as sd-webui-cutoff and llul); it's obscure on purpose, with no community tutorials to lean on, and everything lives under hnmr/latent_ops. When the README and the source disagree - they do, occasionally - the source in latent_ops/normalize.py is the authority, and it's a short read.
Inputs (0)
No inputs
Outputs (1)
| Name | Type | Description |
|---|---|---|
| op | LATENT_OPERATION | — |