Nodes/Polyhedron Suite/⬡ Polyhedron Power Upscale
ComfyUI Node

⬡ Polyhedron Power Upscale

A tiled upscaler that speaks the dual-expert language of Wan 2.2

By PolyhedronAI·Created 3 months ago·Updated 2 days ago· 2
⬡ Polyhedron Power Upscale
  • model
  • positive
  • negative
  • vae
  • upscale_model
  • image
  • video
  • model_low
  • upscale_model_low
  • image
  • video
dual_moefalse
upscale_by1.10
denoise0.19
steps3
cfg1.6
upscale_by_low1.30
denoise_low0.25
steps_low5
cfg_low1.9
seed0
sampler_name
scheduler
tile_size1024
tile_overlap64
sigma_shift0.00
sampler_lowsame as high
scheduler_lowsame as high
result_previewtrue
process_previewlatent2rgb
mute_staging_logstrue
resize_methodlanczos (cpu)
per_batch8
vae_tilingOff
pixel_stagemodel + fit
final_upscale_by1.00
sigma_shift_low-1.00

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 to upscale_by, then a tiled refine on model. On, it becomes a stage chain: stage H (the high-noise expert) shapes structure at upscale_by/denoise/steps/cfg, then stage L (model_low) polishes detail at the _low versions. 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 (steps defaults 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 it 0 to 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. So model final runs the pixel model once behind the final decode, where its detail goes straight to the file - and final_upscale_by lets 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.

CategoryPolyhedron/Upscaling

Inputs (35)

NameTypeDefaultDescription
modelMODELThe diffusion model refining the tiles. In High + Low this is the HIGH-noise expert (structure); the LOW expert plugs into 'model_low'.
positiveCONDITIONINGConditioning to include (shared by both stages).
negativeCONDITIONINGConditioning to exclude (shared by both stages).
vaeVAEThe 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_modelUPSCALE_MODELPixel 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_moeBOOLEANfalseUpscaling 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_byFLOAT1.101–8Stage-H size factor (Single: the whole factor). The stage canvas snaps to the VAE /8 grid.
denoiseFLOAT0.190–1Stage-H refine strength. ~0.2 keeps the input and adds detail; higher re-imagines.
stepsINT31–10000Stage-H steps per tile (Lightning/distilled experts need few).
cfgFLOAT1.60–100Stage-H CFG. Distilled / Lightning LoRAs live near 1.
upscale_by_lowFLOAT1.301–8High + Low: stage-L size factor (total = product of both).
denoise_lowFLOAT0.250–1High + Low: stage-L refine strength (detail polish).
steps_lowINT51–10000High + Low: stage-L steps per tile.
cfg_lowFLOAT1.90–100High + Low: stage-L CFG.
seedINT00–18446744073709550000Base 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_nameCOMBOSampling algorithm — the full list, no per-model subset.
schedulerCOMBOSigma schedule — the full list.
tile_sizeINT102464–4096Tile edge for the refine (constant per axis — uniform tiles are what diffusion models like). Clamped to the canvas.
tile_overlapINT640–1024Target 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_shiftFLOAT0.000–20Flow-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_lowCOMBOsame as highHigh + 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_lowCOMBOsame as highHigh + 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_previewBOOLEANtrueShow 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_previewCOMBOlatent2rgbWatch 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_logsBOOLEANtrueSilence 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_methodCOMBOlanczos (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_batchINT81–256Frames 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_tilingCOMBOOffSpatial 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_stageCOMBOmodel + fitHow 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_byFLOAT1.000.25–8Canvas 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_lowFLOAT-1.00-1–20Flow-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.
imageoptIMAGEFrame input [N,H,W,C] — stills or an unpacked video. Wire EXACTLY one of image / video.
videooptVIDEONative 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_lowoptMODELHigh + Low: the LOW-noise expert for stage L. Unwired -> stage L falls back to 'model'.
upscale_model_lowoptUPSCALE_MODELHigh + 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)

NameTypeDescription
imageIMAGE
videoVIDEO