⬡ Polyhedron Power Upscale
A tiled upscaler that speaks the dual-expert language of Wan 2.2
- model
- positive
- negative
- vae
- upscale_model
- image
- video
- model_low
- upscale_model_low
- image
- video
Power Upscale is the heavy lifter of this pack's upscaling pair, and it's built around one specific fact: the model you're most likely upscaling with is Wan 2.2, and Wan 2.2 doesn't come as one model. It ships as a high-noise and a low-noise expert, and most upscalers make you pretend otherwise. This one doesn't.
The core recipe is the classic tiled diffusion upscale - cut the image into overlapping tiles, refine each with a diffusion pass, blend them back - but with the details that make the pattern reliable. The tiles stitch with smoothstep-feathered weights whose accumulated weight is exactly 1 everywhere, which is why the node carries no "seam fix" at all: there's nothing to fix. Tile size is chosen against free VRAM rather than a fixed guess, and tiles process in batches (per_batch) so a long video doesn't have to fit in VRAM at once. The seed is worth a mention: every tile derives its noise from (seed, tile index), deliberately not from the frame number, so a video batch keeps the same noise structure per tile over time - less flicker.
The inputs that matter
dual_moe- the headline. Off, it's a single stage: ESRGAN up toupscale_by, then a tiled refine onmodel. On, it becomes a stage chain: stage H (the high-noise expert) shapes structure atupscale_by/denoise/steps/cfg, then stage L (model_low) polishes detail at the_lowversions. Each stage gets its own upscale model. This is the correct MoE translation for refine-strength denoises - a sigma-boundary split like the Sampler's Handoff would hand the high expert zero steps below the boundary.denoise- the refine strength, default ~0.19. The upscaling playbook says it plainly: ~0.2 keeps the input and adds detail; higher re-imagines. With distilled/Lightning experts you can afford low steps (stepsdefaults to 3) and CFG near 1.sigma_shift- the flow-matching shift. Wan 2.2 wants its native shift (typically 8.0) or the tile schedules are mis-scaled.sigma_shift_low(default-1= same as high) gives the low stage its own; set it0to leave stage L untouched.pixel_stage- where the ESRGAN pass sits. The measured insight: a pixel model in front of a VAE round trip is nearly free of effect, because the /8 compression can't carry its fine detail. Somodel finalruns the pixel model once behind the final decode, where its detail goes straight to the file - andfinal_upscale_bylets you keep the canvas size you dialed while the model's imprint still reaches it.tile_size/tile_overlap- uniform tiles are what diffusion models like; overlap targets ~64 and the planner narrows it when the canvas can't host it safely.vae_tiling- spatial VAE tiling for big canvases, with one rule the tooltip hammers: never temporal. A Wan VAE compresses time 4:1, so time-tiling would stutter.
Outputs are image and video; a wired VIDEO carries its audio and frame rate through, and a Wan VAE turns a tile's whole frame stack into one 5D video latent, so the model stays temporally consistent inside the tile.
Installing it
Same pack, same story:
cd ComfyUI/custom_nodes
git clone https://github.com/PolyhedronAI/ComfyUI-PolyhedronLoRAStack.git
# restart ComfyUI
Or ComfyUI Manager, search "Polyhedron Suite". No hard dependencies. If you're missing video features, pip install "polyhedron-lora-stack[media]" adds PyAV.
Where people get burned
Don't forget per_batch. There's no 0 any more, because 0 meant "whole batch" - and 129 frames through a 4× ESRGAN model in one go is a ~15GB tensor. Set 256 if you genuinely want the old behavior. And if sigma_shift_low is wired away from -1, re-check your proven denoise_low values: the shift moves where on the noise scale a given denoise fraction lands.
Inputs (35)
| Name | Type | Default | Description |
|---|---|---|---|
| model | MODEL | The diffusion model refining the tiles. In High + Low this is the HIGH-noise expert (structure); the LOW expert plugs into 'model_low'. | |
| positive | CONDITIONING | Conditioning to include (shared by both stages). | |
| negative | CONDITIONING | Conditioning to exclude (shared by both stages). | |
| vae | VAE | The VAE encoding/decoding every tile. A Wan VAE turns the whole frame stack of a tile into ONE 5D video latent, so the video model itself keeps time consistent inside the tile. | |
| upscale_model | UPSCALE_MODEL | Pixel upscale model for the stage pre-pass (ESRGAN family via core/spandrel). In High + Low this serves the HIGH stage; the LOW stage uses 'upscale_model_low' when wired, else this one. | |
| dual_moe | BOOLEAN | false | Upscaling architecture. Single: one stage — ESRGAN x upscale_by, then a tiled refine on 'model'. High + Low: the mixture-of-experts STAGE CHAIN — stage H (the HIGH-noise expert, 'model') shapes structure at upscale_by/denoise/steps/cfg, then stage L (the LOW-noise expert, 'model_low') polishes detail at the *_low values, each stage with its own upscale model. This is the correct MoE translation for refine-strength denoises: a sigma-boundary split (the sampler's Handoff) would hand the HIGH expert zero steps below the boundary. |
| upscale_by | FLOAT | 1.101–8 | Stage-H size factor (Single: the whole factor). The stage canvas snaps to the VAE /8 grid. |
| denoise | FLOAT | 0.190–1 | Stage-H refine strength. ~0.2 keeps the input and adds detail; higher re-imagines. |
| steps | INT | 31–10000 | Stage-H steps per tile (Lightning/distilled experts need few). |
| cfg | FLOAT | 1.60–100 | Stage-H CFG. Distilled / Lightning LoRAs live near 1. |
| upscale_by_low | FLOAT | 1.301–8 | High + Low: stage-L size factor (total = product of both). |
| denoise_low | FLOAT | 0.250–1 | High + Low: stage-L refine strength (detail polish). |
| steps_low | INT | 51–10000 | High + Low: stage-L steps per tile. |
| cfg_low | FLOAT | 1.90–100 | High + Low: stage-L CFG. |
| seed | INT | 00–18446744073709550000 | Base seed. Every tile derives its noise seed from (seed, tile index) — deliberately NOT from the frame number, so a video batch keeps the same noise structure per tile over time (less flicker). |
| sampler_name | COMBO | Sampling algorithm — the full list, no per-model subset. | |
| scheduler | COMBO | Sigma schedule — the full list. | |
| tile_size | INT | 102464–4096 | Tile edge for the refine (constant per axis — uniform tiles are what diffusion models like). Clamped to the canvas. |
| tile_overlap | INT | 640–1024 | Target overlap between neighbouring tiles. Tiles are stitched as a weighted sum with smoothstep feathers whose accumulated weight is EXACTLY 1 everywhere — the reason this node has no seam-fix family: none is needed. When a canvas can't host the requested overlap safely, the planner narrows it (correct beats wide). |
| sigma_shift | FLOAT | 0.000–20 | Flow-matching sigma shift, the sampler's own mechanism (0 = off). Wan 2.2 wants its native shift (typically 8.0) or the tile schedules are mis-scaled. Applied to both experts in High + Low. |
| sampler_low | COMBO | same as high | High + Low: sampling ALGORITHM for stage L ('sampler_name' drives stage H). The stages are independent runs, so this is always honoured. Ancestral / SDE samplers (*_a, *_sde) inject fresh noise per step — at refine strengths that changes the grain. |
| scheduler_low | COMBO | same as high | High + Low: sigma SCHEDULE for stage L. Always honoured — stage L builds its own schedule from steps_low / denoise_low anyway, so a second scheduler breaks nothing (no handoff, no seam). |
| result_preview | BOOLEAN | true | Show the finished frames inside the node (small temp JPEGs; multi-frame runs play as a flipbook at the source frame rate). Never touches the outputs. |
| process_preview | COMBO | latent2rgb | Watch the refine LIVE: the tile being sampled right now, a minimap locating it on the stage canvas, and stage/tile/step counters. Off = zero overhead (the probe is never built). latent2rgb = free, but it reads the LATENT's grid (/8 - a 768 tile is 96px, and it looks it). vae (sharp) = a real decode of one frame per event: full resolution, at the cost of one vae forward - measured against the stage clock and dropped back to latent2rgb on its own if it starts eating the pass. |
| mute_staging_logs | BOOLEAN | true | Silence ComfyUI's per-tile staging INFO lines ('prepared for dynamic VRAM loading', 'x models unloaded') while a stage runs. Restored byte-exactly after. Turn Off when debugging VRAM. |
| resize_method | COMBO | lanczos (cpu) | Fit onto the stage canvas after the pixel upscale. 'lanczos (cpu)' = the historic path, byte-identical. bicubic/bilinear/area/nearest run chunked on the GPU (fast). nvidia_rtx_vsr = Maxine VideoSuperRes (needs nvvfx + RTX). |
| per_batch | INT | 81–256 | Frames per chunk for the ESRGAN pass and its fit (the upscale + fit are FUSED per chunk, so the peak holds only this many frames at the intermediate size). 129 frames through a 4x model is a ~15 GB tensor in one go - this is the OOM guard. There is no 0 any more: it meant 'whole batch', which is exactly the 14.7 GB path this node removed. For the old behaviour set 256. |
| vae_tiling | COMBO | Off | Spatial VAE tiling for each tile's encode/decode. Off = the historic path (byte-identical). A size (512/640/768) cuts the VAE peak on big canvases, at the cost of a tiny internal blend at the VAE tile borders. NEVER temporal: a Wan VAE compresses time 4:1, so time tiles would stutter. |
| pixel_stage | COMBO | model + fit | How each stage reaches its canvas. 'model + fit' = the wired UPSCALE_MODEL runs first, the fit lands it on the exact stage size (the classic recipe). 'fit only' IGNORES the wired model; resize_method does the whole upscale -- seconds instead of minutes, and the refine paints the detail anyway. 'model only' = the canvas IS the model factor; upscale_by is ignored (loudly), the fit only corrects to the /8 snap. 'model final' = the stages run pure fit and the last stage's pixel model runs ONCE behind the final decode -- the measured place for it: no VAE follows, its detail goes straight to the file (resize_method='none' keeps the raw model result; a kernel supersamples it back to the dialled canvas). Measured: a pixel model in FRONT of a VAE round trip is nearly free of effect -- the /8 compression cannot carry its fine detail. Switch without unplugging. |
| final_upscale_by | FLOAT | 1.000.25–8 | Canvas of the FINAL pixel pass (pixel_stage='model final'), as a factor of the stage result. The model always computes its native factor; the kernel then lands it on THIS canvas -- the model's imprint reaches the file while the size stays yours (swap a 2x/4x/8x model: the imprint changes, the size never). 1.0 = the stage canvas (classic supersample refine); below 1.0 = supersampled DOWNscale (sprite work). Ignored (loudly) when resize_method='none', which keeps the raw model result. |
| sigma_shift_low | FLOAT | -1.00-1–20 | Flow-matching sigma shift for stage L (the LOW-noise expert). -1 = 'same as high': stage L follows sigma_shift, so both stages are shifted alike. 0 = OFF for stage L only -- its model_sampling is left alone even when sigma_shift is set. Any positive value gives stage L its OWN shift (e.g. sigma_shift 8.0 with sigma_shift_low 5.0). HIGH + LOW ONLY: Single runs one stage and never reads a low expert, so this dial does nothing there -- the row is hidden in Single, and a dialled value on a Single run is reported once in the console instead of quietly building a clone. Works with or without a wired 'model_low' -- without one, stage L falls back to the 'model' input and gets its own shifted copy of it. |
| imageopt | IMAGE | Frame input [N,H,W,C] — stills or an unpacked video. Wire EXACTLY one of image / video. | |
| videoopt | VIDEO | Native VIDEO input (green). Its frames are upscaled; its audio and frame rate ride through losslessly into the VIDEO output. Wire EXACTLY one of image / video. | |
| model_lowopt | MODEL | High + Low: the LOW-noise expert for stage L. Unwired -> stage L falls back to 'model'. | |
| upscale_model_lowopt | UPSCALE_MODEL | High + Low: stage-L pixel upscale model - and, with pixel_stage='model final', the model of the FINAL pass behind the last decode. Unwired -> stage L runs a plain fit (the H model is NOT inherited) and a 'model final' run says so and skips the final pass. |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| image | IMAGE | — |
| video | VIDEO | — |