jz Shift Sigmas (flow match)
The resolution shift ComfyUI forgot to give you
- sigmas
- latent
- sigmas
- mu
- tokens
Every modern image model since SD3 - Flux, Z-Image, Klein, Anima, ERNIE, Qwen-Image - is a flow-matching model. Straight path from noise to image, which is why your old Karras habits now actively hurt and why a new knob appeared: shift. Shift re-times the schedule, deciding how much sampling effort goes into composition versus detail. And on flow-matching models it's resolution-dependent - a bigger image is more tokens, and more tokens want more shift.
Here's the gap this node fills. ComfyUI can compute that. ModelSamplingFlux works out exactly the right mu for your resolution. But it patches the MODEL - it never hands you a SIGMAS tensor. Meanwhile ManualSigmas will happily emit an explicit schedule, with no shift applied at all. So if you're driving a sampler with hand-written sigmas, as you do with SamplerCustom, there is no stock way to shift them. You get to pick one half or the other.
That's not a theoretical annoyance. ManualSigmas shows up in only 16 corpus threads - it's niche, deliberately - and the people who do use it end up reaching for RES4LYF (Sigmas Resample, Sigmas Rescale) to get any control over the curve. This node is the missing step in that chain.
How it works
Two lines of math, and both come from ComfyUI's own flux implementation so they can't drift:
mu = base_shift + (max_shift - base_shift) * (tokens - min_tokens)
/ (max_tokens - min_tokens)
sigma = e^mu / (e^mu + 1/t - 1)
tokens = (width / 16) * (height / 16)
You feed it raw, unshifted values in (0, 1] and it returns shifted ones. The families differ only in constants, so one node covers them: flux wants max_shift 1.15, max_tokens 4096; qwen-image wants 0.9 and 8192.
The README gives the recipe it was built to make possible - ManualSigmas with 1.0, 0.9375, 0.875, 0.75, 0.5, 0.25, into this node, into SamplerCustom, reproduces qwen-image viggle-turbo exactly.
The inputs and outputs
sigmas is the raw schedule from ManualSigmas. Then either width/height widgets, or a connected latent - and if you connect a latent, it wins and the widgets are ignored, which is what you want nine times out of ten since it reads the latent's own downscale_ratio_spacial rather than assuming.
The four constants: base_shift (mu at min_tokens), max_shift (mu at max_tokens), min_tokens, max_tokens. Defaults are the qwen-image numbers, so set them for your family - grabbing the flux values is a two-second edit.
append_zero defaults on, and it's doing something real: samplers need a terminal 0 at the end of the schedule, and ManualSigmas doesn't add one. It skips the append if a zero is already there.
Three outputs. sigmas goes to your sampler. mu and tokens look decorative and aren't - a wrong token count is otherwise silently wrong. The whole point of this node is that the shift depends on how many tokens your image is, so if the count is off, the schedule is off, and nothing anywhere tells you. Print both.
Install
Manager → search comfyui-jz, or:
cd ComfyUI/custom_nodes
git clone https://github.com/j-zhang19/comfyui-jz
Restart ComfyUI. requests, pillow, numpy and torch - no models, no heavy dependencies. Install it by hand rather than letting a workflow's missing-nodes prompt find it for you if you like knowing what you're running, because it's a pack that holds API keys; you don't need those nodes to use this one.
Where people get burned
Don't shift twice. Feed this node values that have already been shifted - for instance a schedule you pulled out of BasicScheduler on a flow-matching model - and you've applied the shift on top of itself. Raw values from ManualSigmas, in (0, 1], and nothing else.
A scheduler that applies its own shift. This is the classic flow-matching confusion, and it isn't specific to this node: some third-party schedulers (bong_tangent in RES4LYF, for instance) ignore the workflow's shift and use their own. If your mu changes and the image doesn't, check the scheduler before you conclude this node is broken.
Getting the token count wrong by wiring a latent from an unusual source. The node falls back to assuming a /16 grid, and a /8 latent would be overcounted by roughly 4×. Hence the tokens output - compare it against what you expect for your resolution.
This isn't a node you'll install for a normal workflow. If you're using the built-in samplers with a model's own shift settings, ModelSamplingFlux and friends already do the right thing. This one is for the case where you've taken the schedule into your own hands and need the shift back.
Inputs (9)
| Name | Type | Default | Description |
|---|---|---|---|
| sigmas | SIGMAS | raw unshifted values in (0,1] — pair with ManualSigmas, e.g. 1.0, 0.9375, 0.875, 0.75, 0.5, 0.25 for qwen viggle-turbo | |
| width | INT | 102416–16384 | image pixels, ignored when a latent is connected |
| height | INT | 102416–16384 | — |
| base_shift | FLOAT | 0.500–100 | mu at min_tokens |
| max_shift | FLOAT | 0.900–100 | mu at max_tokens. qwen-image 0.9, flux 1.15 |
| min_tokens | INT | 2561–1048576 | — |
| max_tokens | INT | 81921–1048576 | qwen-image 8192, flux 4096 |
| append_zero | BOOLEAN | true | samplers need a terminal 0 and ManualSigmas does not add one; skipped if already present |
| latentopt | LATENT | take the resolution from the latent being sampled instead of the width/height widgets |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| sigmas | SIGMAS | — |
| mu | FLOAT | — |
| tokens | INT | — |