Qwen Image 2.1 Scheduler
The sigmas your resolution actually needs
- pipe
- latent
- pipe
- sigmas
Here's a thing that quietly degrades Qwen Image output at non-square sizes: the model's schedule is supposed to shift depending on how many image tokens you're generating. Load a fixed scheduler, and a 1536×640 image gets the same noise curve as a 1024×1024 one. It mostly works, and it's wrong.
Qwen Image 2.1 Scheduler [SML2] builds the SIGMAS properly - resolution-dependent shifting, terminal stretching, the whole config from the model's own scheduler_config.json - and hands them out as a SIGMAS you can feed to SamplerCustomAdvanced.
How the schedule is built
The normalized inference grid runs from 1 down to 1/steps, then gets shifted, then terminally stretched, then closed with a final zero. The shift is where resolution enters: compute the target latent's image-token count, then interpolate between two anchors -
base_image_seq_len(default 256) withbase_shift(default 0.5) at the low end,max_image_seq_len(default 8192) withmax_shift(default 0.9) at the high end,
with time_shift_type (exponential, which is what Qwen Image 2.1 uses, or linear) shaping the interpolation. Sizes beyond the upper anchor extrapolate rather than clamp. use_dynamic_shifting off means you get a fixed shift (default 1.0) instead of the resolution-aware curve.
shift_terminal (default 0.02) sets the last positive sigma before the final zero, and 0 disables stretching. num_train_timesteps exists because the formula is expressed in training-timestep units - they cancel out in Qwen's normalized inference sigmas, so changing it alone changes nothing. That's stated in the docs because people do try.
Token maths: the Qwen 2.1 target has 64 latent channels and one token per latent spatial position, equivalent to a 16×16 output-pixel area. Reference-image tokens are not counted.
Where the size comes from
Priority order, and it surprises people: a connected target latent wins, then the pipe's latent, then the pipe's width/height, then the node's own width/height widgets (1024×1024). So if you're doing an edit, connect Text Encode Qwen Image 2.1's latent here and the schedule matches your references without you typing a number.
steps (default 40) and denoise are set on this node and they overwrite the corresponding fields in the output pipe. The incoming pipe does not supply them - if you want the loader's values, wire IO Checkpoint Loader's steps and denoise outputs into the matching inputs. Denoise works the way ComfyUI schedulers always do: it keeps the final steps of the longer shifted and stretched schedule for img2img. Denoise 0 returns an empty schedule; a single full-denoise step returns [1, 0] without trying to stretch a nonexistent interval.
Outputs are pipe (contents retained, sigmas added, steps/denoise/width/height updated, input pipe not mutated) and sigmas.
Wiring it
Smart Model Loader PIPE → Qwen Image 2.1 Scheduler PIPE → IO Checkpoint Loader
└─ sigmas → SamplerCustomAdvanced
CFGGuider takes the IO node's model, SamplerCustomAdvanced takes its latent, and you also supply the guider's positive/negative, a RandomNoise and a KSamplerSelect. The scheduler's direct sigmas output works too, or route it into the IO node's final sigmas input. Standard EmptyLatentImage is supported - its 4-channel placeholder is converted by ComfyUI before sampling, and the scheduler reads the latent's own downscale metadata to keep the size maths honest.
Installing it
ComfyUI Manager → search Smart Model Loader Nova, or:
cd ComfyUI/custom_nodes
git clone https://github.com/r-vage/ComfyUI_SmartModelLoader_Nova.git
cd ComfyUI_SmartModelLoader_Nova
python -m pip install -r requirements.txt
ComfyUI's own Python environment, then restart, then Add Node → 🌒 Smart Model Loader Nova → Sampler. Note what it doesn't need: Diffusers. The schedule is computed inline; you're not pulling another library in for a timestep formula.
Where people get burned
KSampler Pipe ignores these sigmas. It uses its own scheduler selection and won't consume explicit SIGMAS from a pipe. If you built this schedule and then sampled with KSampler Pipe, you got none of it - use SamplerCustomAdvanced, or move the sigmas through IO Checkpoint Loader.
Non-empty image latents must have Qwen's 64 channels. An SDXL-encoded image latent is 4-channel and will be rejected. Empty latents are fine because ComfyUI converts them.
Assuming the loader's steps apply. They don't unless you wire them. A graph where the loader says 20 steps and the scheduler says 40 runs a 40-step schedule, and the pipe afterwards records 40.
Double-shifting. If you use the loader's model-sampling override and this node, shifts get applied twice. The docs are explicit that explicit SIGMAS make the override unnecessary - pick the scheduler path and leave the loader's sampling section off.
Right VAE or no decode. As with any Qwen 2.1 workflow: matching VAE, or the sampling pass finishes and the decode fails.
Inputs (15)
| Name | Type | Default | Description |
|---|---|---|---|
| steps | INT | 401–10000 | Sampling steps for this schedule; also written to the output pipe. |
| denoise | FLOAT | 1.000–1 | Keep the final steps of a longer schedule for img2img. 0 disables sampling. |
| width | INT | 102416–32768 | Fallback pixel width. A connected target latent, pipe latent or pipe width wins. |
| height | INT | 102416–32768 | Fallback pixel height. A connected target latent, pipe latent or pipe height wins. |
| base_image_seq_len | INT | 2561–4194304 | — |
| base_shift | FLOAT | 0.50–100 | — |
| max_image_seq_len | INT | 81922–4194304 | Upper shift anchor; larger images extrapolate beyond max_shift. |
| max_shift | FLOAT | 0.90–100 | — |
| use_dynamic_shifting | BOOLEAN | true | Compute the shift from target image tokens and both shift anchors. |
| time_shift_type | COMBO | exponential | Dynamic shift transform. Qwen Image 2.1 uses exponential. |
| shift_terminal | FLOAT | 0.0200–0.999 | Last positive sigma before the final zero. 0 disables terminal stretching. |
| num_train_timesteps | INT | 10001–1000000 | Training timestep units. They cancel in Qwen's normalized inference SIGMAS. |
| shift | FLOAT | 1.000.01–100 | Fixed multiplicative shift, used only when dynamic shifting is disabled. |
| pipeopt | PIPE | Optional loader pipe. Adds SIGMAS while retaining its contents. | |
| latentopt | LATENT | Actual Qwen 2.1 target latent, including from the text encoder. Overrides pipe latent. |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| pipe | PIPE | — |
| sigmas | SIGMAS | — |