Whirlpool Upscaler
The upscaler that sneaks up on 2x one pass at a time
- image
- model
- positive
- negative
- vae
- upscale_model
- image
One node that does the whole multi-pass upscale dance
Most upscaling in ComfyUI is a chain: generate small, hi-res fix to 1.5–2x, maybe a tiled pass for the last mile, maybe a face fix. Whirlpool Upscaler folds the middle of that chain into a single node. It's a rework of Impact Pack's iterative upscaler, built on the idea that dumping all your compute into one giant high-res pass is wasteful - so instead it walks the image up to your target resolution across several small sampling passes, easing CFG, steps, and denoise down as it goes.
That's a different job from the restoration upscalers. It's not SeedVR2 or SUPIR (it won't invent pores or repair a damaged photo), and it's not a plain ESRGAN-style pixel upscale. It's generative "more detail" upscaling in the hi-res-fix family, except the fix is spread over N passes so each one only does a small, cheap increment. On a 6GB card it's noticeably friendlier than jumping straight to 2x in one img2img pass, because it never asks the VAE to handle a huge image until the last iteration.
How it works
The core loop is simple: for each of iterations passes, it upscales the current pixels to the next resolution, encodes them to a latent, runs a short sampling step, and decodes back to pixels - one encode and one decode per iteration. What makes it interesting is the easing. All four progression values (resolution, CFG, steps, denoise) move from their _start to their _end values, and upscale_curve shapes how linear that journey is.
With upscale_by = 2.0 and 4 iterations, a linear curve (upscale_curve = 1.0) gives 1.25x → 1.5x → 1.75x → 2.0x. Bump the curve to 2.0 and you get 1.13x → 1.42x → 1.69x → 2.0x - the early passes stay cheap and the heavy work lands at the end. The defaults lean that way already (17 steps down to 7, CFG 7 down to 1, denoise 1.0 down to 0.15), which is a sensible "refine, don't rewrite" arc. There's also fix_vae_color (on by default), which does LAB histogram matching back to the original image after each pass so color doesn't drift across all that encode/decode churn - a real failure mode this design would otherwise hit hard.
The inputs that matter
You'll fiddle with maybe five of these:
upscale_by- the final multiplier. 2.0 doubles width and height.upscale_curve- how the resolution/CFG/steps/denoise progress. Higher = cheaper early passes, closer to the base image, less body horror. Lower = more time at high res, more detail, more chance of horror. The README's blunt summary: strike a balance, and if you changeupscale_byyou should change the curve too.iterations- how many passes. More is finer-grained and slower.decode_size- tile size for the VAE ops. If you're getting lag or OOM, drop it (320 minimum).upscale_model(optional) - plug in anyUPSCALE_MODELfrom the standard UpscaleModelLoader (4x-UltraSharp, Remacri, an anime ESRGAN) and it replaces Lanczos as the per-iteration pixel scaler.
The rest - sampler_name (default res_2m) and scheduler (default bong_tangent) - come straight from ComfyUI's built-in lists, so anything you know from a KSampler works. You also wire in the model, positive/negative conditioning, a seed, and the VAE like you would any sampler. Output is a single image tensor, ready to feed a save node or another upscale stage.
Installing it
No models to download, no extra weights - which is a relief after SUPIR's multi-gigabyte download. The only code dependency beyond ComfyUI itself is scipy (for the color matching), which the portable install already ships. Through ComfyUI Manager, search "Whirlpool Upscaler" and install. Manually:
cd ComfyUI/custom_nodes
git clone https://github.com/rainlizard/ComfyUI-WhirlpoolUpscaler
Restart ComfyUI. If you're on a bare venv rather than the portable build and the color-matching import chokes, pip install scipy fixes it.
Gotchas
- Cancelling isn't instant. The node checks for interruption but only between iterations, so you'll wait for the current pass to finish. Annoying on a 6+ iteration run; the README itself asks for a better solution.
- Body horror (extra fingers, warped anatomy): lower
denoise_start, feed it a smaller base image, or raiseupscale_curve. Per the README, the Skimmed CFG trick is the enthusiast move - route the model through a Skimmed CFG node with a highcfg_startand the results usually improve. - Artifacts: reduce CFG, raise steps, or same Skimmed CFG path.
It's a niche node with a niche audience - community talk about it is almost nonexistent - but if you're already an Impact Pack iterative-upscale fan and want the same idea with built-in color correction and an ESRGAN hook, it's a tidy single-node drop-in.
Inputs (22)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | — | |
| model | MODEL | — | |
| seed | INT | 00–18446744073709550000 | — |
| upscale_by | FLOAT | 2.000.1–10000 | Final resolution multiplier (e.g., 2.0 = double width and height). |
| upscale_curve | FLOAT | 1.000–10 | Progression curve for all parameters. 1.0 = linear. >1.0: resolution accelerates, CFG/steps/denoise front-loaded. <1.0: resolution front-loaded, CFG/steps/denoise accelerate. |
| iterations | INT | 40–20 | Number of complete sampling cycles to perform. |
| steps_start | INT | 171–10000 | Number of sampling steps for the first iteration. |
| steps_end | INT | 71–10000 | Number of sampling steps for the last iteration. |
| cfg_start | FLOAT | 7.00–10000 | CFG scale for the first iteration. |
| cfg_end | FLOAT | 1.00–10000 | CFG scale for the last iteration. |
| denoise_start | FLOAT | 1.000–1 | Denoise strength for the first iteration. |
| denoise_end | FLOAT | 0.150–1 | Denoise strength for the last iteration. |
| add_noise | FLOAT | 0.00–100 | Adds noise before denoising each iteration. The amount added is relative to the current denoise. |
| fix_vae_color | BOOLEAN | true | Apply color correction after each iteration to maintain color consistency with the original image. |
| sampler_name | COMBO | res_2m | 44 options: euler, euler_cfg_pp, euler_ancestral, euler_ancestral_cfg_pp, heun, heunpp2, +38 |
| scheduler | COMBO | bong_tangent | 9 options: simple, sgm_uniform, karras, exponential, ddim_uniform, beta, +3 |
| positive | CONDITIONING | — | |
| negative | CONDITIONING | — | |
| resize_filter | COMBO | lanczos | Image resizing filter algorithm. |
| decode_size | INT | 1024320–2048 | Decode size for VAE operations. |
| vae | VAE | — | |
| upscale_modelopt | UPSCALE_MODEL | Optional AI upscaling model (e.g., ESRGAN, Real-ESRGAN) for enhanced image quality during upscaling. |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| image | IMAGE | — |