Nodes/ComfyUI-Differential-Encode/CLIP Text Encode (Differential)
ComfyUI Node

CLIP Text Encode (Differential)

Different seeds, different images — the fix for Z-Image's seed collapse

By starsFriday·Created 9 months ago·Updated 9 months ago· 1
CLIP Text Encode (Differential)
  • clip
  • CONDITIONING
text
seed0
noise_std0.18
token_dropout0.10
per_token_gain_std0.12
global_offset_std0.08
pooled_noise_std0.05
pooled_global_offset_std0.04
preserve_magnitudetrue
orthogonalize_noisetrue
min_std_scale0.001

You know the Z-Image problem: you punch in a prompt, hit generate five times with five different seeds, and get back five near-identical images. Same face, same pose, same everything. It was the single most-discussed weakness of Z-Image Turbo, a distilled model that bakes so much into its guidance that the seed basically stops mattering. CLIP Text Encode (Differential) is a drop-in replacement for the standard CLIPTextEncode that fights exactly that: it injects small, seed-driven perturbations into the text embeddings so a new seed actually produces new conditioning - and new images.

It's a one-node pack called ComfyUI-Differential-Encode, by a hobbyist author (starsFriday) who appears to have built it for their own Z-Image workflow. It's a targeted fix, not a general-purpose tool, and for its one job it works.

What it actually does

The mechanism is simple once you see it. The standard node encodes your prompt into a conditioning tensor and hands it to the sampler - identical input every time, which is why the seed has nothing to grab onto. This node takes that same encoding and adds seeded noise before the sampler ever sees it. From the source, the recipe per token is:

  • additive Gaussian noise (noise_std), optionally projected off the original token directions so you add variety without shredding meaning
  • a shared "global offset" that shifts every token together, nudging the whole conditioning between seeds
  • multiplicative per-token gain jitter, a scaled re-emphasis
  • token dropout - a Bernoulli mask that zeroes whole token embeddings now and then

All of it is driven by a torch.Generator seeded from your seed input, so it's fully deterministic: same seed, same prompt, same settings always gives the same conditioning. It also perturbs the pooled_output branch, which matters for SDXL-style and refiner-heavy models that condition on that pooled vector as well as the token embeddings. The conditioning it emits carries differential_* metadata keys in its dict, so downstream nodes can inspect what was applied - mostly a debugging nicety, but it means you can see the noise settings baked into any saved workflow.

Because it encodes through the same scheduled path as the built-in node (encode_from_tokens_scheduled), it works with dual-encoder models like SDXL and the modern LLM text encoders - Z-Image's Qwen3-4B included. The author's own demo prompt is in Chinese, incidentally, which is a nice touch: Z-Image genuinely adheres better to Chinese prompts, and the node doesn't care about language at all.

The inputs that matter

Only four inputs are required, and you'll touch maybe two of them:

  • text and clip - same as the regular node, your prompt and the CLIP from your checkpoint/CLIP loader.
  • seed - tie it to your sampler seed if you want a given run to be reproducible, or let it vary independently for extra variety. This is the whole point of the node.
  • noise_std (default 0.18) - the main diversity dial. This is the one you'll actually tune.

Everything else is optional and has sane defaults. The short list that's worth knowing: token_dropout (0.10), per_token_gain_std (0.12), global_offset_std (0.08), and the two pooled knobs (pooled_noise_std 0.05, pooled_global_offset_std 0.04). The toggles preserve_magnitude and orthogonalize_noise are on by default and you basically never turn them off - they're what keep the perturbation from blowing up magnitudes or drifting semantics.

The single output is a CONDITIONING tensor, and it wires exactly where your old positive/negative conditioning went - straight into the KSampler.

Installing it

It's a one-folder clone, no pip packages, no model downloads, no heavy dependencies. The node imports only torch and ComfyUI's own type helpers - the "install" is genuinely just putting the folder in place:

cd ComfyUI/custom_nodes
git clone https://github.com/starsFriday/ComfyUI-Differential-Encode

Restart ComfyUI, then add CLIP Text Encode (Differential) from the conditioning category. ComfyUI Manager should pick it up if you search the pack title. There's a ready-made Z-Image example workflow in the repo's example/ folder if you want to see it wired up.

Tuning and troubleshooting

Start with the defaults and generate the same prompt across a few seeds. If the images still look cloned, the README's advice is sound: raise noise_std to 0.25–0.35 and global_offset_std to ~0.12, or push token_dropout to 0.15. If prompts start coming out loose or semantically off, back off per_token_gain_std and keep orthogonalize_noise on.

Where people get burned: cranking noise_std too high. Past a point the noise stops being "variety" and starts being "random embedding garbage" - the prompt drifts and you get artifacts that look like model failure when it's actually over-perturbation. This is a conditioning-level workaround, and like every seed-collapse workaround for distilled models it trades a little conditioning fidelity for diversity; there's no free lunch. If you're on Z-Image Base, which already has proper seed diversity, you don't need this node at all.

One more practical note: keep the node's seed in sync with your sampler seed. If they diverge, your "reproducible seed" runs won't reproduce - the whole determinism story depends on those two matching.

Categoryconditioning

Inputs (12)

NameTypeDefaultDescription
textSTRINGPrompt text to encode with seed-dependent perturbations.
clipCLIPCLIP model used for encoding.
seedINT00–9223372036854776000Seed used for the differential noise.
noise_stdFLOAT0.180–1Scale of seed-driven noise added to token embeddings (per-token Gaussian).
token_dropoutoptFLOAT0.100–0.8Chance to zero-out entire token embeddings (seeded) to create variation.
per_token_gain_stdoptFLOAT0.120–1Std of multiplicative gain noise per token (1 + N(0, std)), adds structured jitter.
global_offset_stdoptFLOAT0.080–1Gaussian shift shared by all tokens to steer conditioning per seed.
pooled_noise_stdoptFLOAT0.050–1Noise applied to pooled_output so refiner/backbones also see variation.
pooled_global_offset_stdoptFLOAT0.040–1Global pooled shift; helps video/backbones that rely more on pooled_output.
preserve_magnitudeoptBOOLEANtrueRe-normalize after perturbation to keep overall embedding statistics stable.
orthogonalize_noiseoptBOOLEANtrueProject noise off the original token directions to keep semantics while adding diversity.
min_std_scaleoptFLOAT0.0010–0.1Lower bound for std/mean used to scale noise so low-variance prompts still get variation.

Outputs (1)

NameTypeDescription
CONDITIONINGCONDITIONINGSeed-sensitive conditioning embedding.