Nodes/ComfyUI Smart Model Loader/Qwen Image 2.1 Scheduler
ComfyUI Node

Qwen Image 2.1 Scheduler

The sigma curve BasicScheduler won't build for you

By r-vage·Created 2 months ago·Updated 15 days ago· 5
Qwen Image 2.1 Scheduler
  • pipe
  • latent
  • pipe
  • sigmas
◄steps40►
◄denoise1.00►
◄width1024►
◄height1024►
◄base_image_seq_len256►
◄base_shift0.50►
◄max_image_seq_len8192►
◄max_shift0.90►
◄use_dynamic_shiftingtrue►
◄time_shift_typeexponential►
◄shift_terminal0.020►
◄num_train_timesteps1000►
◄shift1.00►

If you're sampling Qwen Image 2.1 with SamplerCustomAdvanced and feeding it sigmas from BasicScheduler, you're running the wrong curve. Qwen 2.1's noise schedule depends on how many image tokens you actually asked for, and the published config also squeezes the tail so the last positive sigma is tiny. BasicScheduler does neither. This node does both, in plain PyTorch, and hands you a SIGMAS tensor.

Where it fits

Qwen Image 2.1 is a MMDiT where one latent spatial position equals one 16×16 patch of output pixels, with no Flux-style 2×2 packing. That detail is why the shift math is Qwen-specific: a 1024×1024 target is 4096 tokens, not 1024. ComfyUI's shift handling for this family is a single fixed number (0.69 is roughly 1024×1024), and it doesn't stretch the terminal sigma at all. The reference schedule - Qwen's own scheduler_config.json, resolution-dependent shift included - is what this node builds.

It ships in ComfyUI_SmartModelLoader, a pack built around one PIPE wire that carries model, CLIP, VAE, latent, dimensions and sampler settings. This node doesn't need any of that - it plays fine with stock nodes.

How the schedule is built

The node generates the normalized inference grid from 1 down to 1/steps, then applies the two Diffusers FlowMatchEulerDiscreteScheduler transforms Qwen's reference expects:

  • Dynamic shift. mu is interpolated between base_shift at the lower anchor and max_shift at the upper anchor, based on your actual token count. With the defaults and time_shift_type: exponential, the factor is exp(mu) - about 0.69 at 1024×1024, about 1.31 at 2048×2048, extrapolating past the 8192-token anchor.
  • Terminal stretching. Every sigma is rescaled so the last positive one equals shift_terminal (0.02). Without it, the final denoise step runs from whatever the interpolation left - a much bigger jump - down to zero.

Then a final 0 is appended, the tensor is checked for finite, strictly descending values, and it's done. No Diffusers install, no model patching - and because these sigmas are explicit, the schedule the model would otherwise generate is bypassed, so the loader's separate model_sampling shift patch isn't needed here. Shifts get applied once.

The inputs that matter

You'll set three things on a normal run: steps (default 40 - the bundled Qwen 2.1 template in the pack uses 30), denoise (keep it at 1.0 unless you're doing img2img, where it keeps the final steps of a longer schedule), and the shift anchors, which the defaults already get right: base_image_seq_len 256 / base_shift 0.5, max_image_seq_len 8192 / max_shift 0.9, shift_terminal 0.02.

Size resolution is where people trip up. The target comes from, in order: a directly connected latent, the pipe's latent, the pipe's width/height, then its own width/height widgets (1024×1024). A 64-channel encoded Qwen latent is fine. A standard EmptyLatentImage is also fine - its zero-filled 4-channel placeholder isn't a Qwen latent, so the node reads its downscale_ratio_spacial metadata to derive the same grid without touching the latent. Anything else with the wrong channel count throws.

use_dynamic_shifting off swaps you to the fixed shift multiplier. num_train_timesteps looks scary and does nothing - the units cancel in normalized sigmas, as its tooltip admits.

Wiring it

Two outputs: sigmas and pipe. Send sigmas to SamplerCustomAdvanced → sigmas, and send the same latent to both the scheduler's latent input and the sampler's latent_image. A typical Qwen 2.1 run is prompt → Text Encode Qwen Image 2.1 → CFGGuider (with the model), plus RandomNoise and KSamplerSelect (euler is the sane default). The pipe output is for carrying the schedule downstream into an IO Checkpoint Loader, which exposes sigmas again for the sampler - IO only passes a schedule along, it doesn't create one.

Note that steps and denoise are written into the output pipe, replacing whatever was there. The input pipe does not hand you its own steps; if you want the loader's values, wire the IO node's steps and denoise outputs into the matching inputs here.

Install

ComfyUI Manager → search ComfyUI Smart Model Loader → install → restart. Manually:

cd ComfyUI/custom_nodes
git clone https://github.com/r-vage/ComfyUI_SmartModelLoader.git
cd ComfyUI_SmartModelLoader
python -m pip install -r requirements.txt

That requirements file is heavier than this node needs - gguf, einops, aiohttp, huggingface_hub and friends are for the loaders and Download Manager. Nunchaku is optional and installed separately. Nodes land under Smart Model Loader → Sampler.

Common issues

  • Denoise 0 returns an empty schedule. Intentional - zero means "don't sample". A one-step full-denoise schedule is [1, 0], and no terminal stretch is attempted because there's no interval to stretch.
  • "The settings do not produce a finite, strictly descending sigma schedule." You've set shifts that break monotonicity. Reset shift_terminal, shift and the anchors to defaults before experimenting.
  • "Qwen Image 2.1 Scheduler needs a 64-channel encoded latent." You're feeding it a Flux, SDXL or other non-Qwen latent. Use EmptyLatentImage or the Qwen text encoder's target latent.
  • The schedule looks unchanged after the first step. Check you didn't also leave a BasicScheduler wired to the custom sampler - the explicit sigmas input wins, so the other node is just noise on the canvas.
  • Stale ComfyUI. The pack normalizes Qwen 2.1 Diffusers VAE weights and leans on ComfyUI's own qwen_image21 support; on an old install you get an explicit "requires an updated ComfyUI" error rather than a bad render. Update first.
Category🌒 Smart Model Loader/ Sampler

Inputs (15)

NameTypeDefaultDescription
stepsINT401–10000Sampling steps for this schedule; also written to the output pipe.
denoiseFLOAT1.000–1Keep the final steps of a longer schedule for img2img. 0 disables sampling.
widthINT102416–32768Fallback pixel width. A connected target latent, pipe latent or pipe width wins.
heightINT102416–32768Fallback pixel height. A connected target latent, pipe latent or pipe height wins.
base_image_seq_lenINT2561–4194304—
base_shiftFLOAT0.500–100—
max_image_seq_lenINT81922–4194304Upper shift anchor; larger images extrapolate beyond max_shift.
max_shiftFLOAT0.900–100—
use_dynamic_shiftingBOOLEANtrueCompute the shift from target image tokens and both shift anchors.
time_shift_typeCOMBOexponentialDynamic shift transform. Qwen Image 2.1 uses exponential.
shift_terminalFLOAT0.0200–0.999Last positive sigma before the final zero. 0 disables terminal stretching.
num_train_timestepsINT10001–1000000Training timestep units. They cancel in Qwen's normalized inference SIGMAS.
shiftFLOAT1.000.01–100Fixed multiplicative shift, used only when dynamic shifting is disabled.
pipeoptPIPEOptional loader pipe. Adds SIGMAS while retaining its contents.
latentoptLATENTActual Qwen 2.1 target latent, including from the text encoder. Overrides pipe latent.

Outputs (2)

NameTypeDescription
pipePIPE—
sigmasSIGMAS—