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 5 months ago·Updated a day ago· 7
⬡ Polyhedron Power Upscale
  • model
  • positive
  • negative
  • vae
  • upscale_model
  • image
  • video
  • model_low
  • upscale_model_low
  • latent
  • sigmas
  • image
  • video
◄dual_moefalse►
◄upscale_by1.10►
◄denoise0.19►
◄steps3►
◄cfg1.60►
◄upscale_by_low1.30►
◄denoise_low0.25►
◄steps_low5►
◄cfg_low1.90►
◄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►
◄refine_orderafter pixel►
◄audio_streamkeep►
◄pixel_speedoff►
◄restore_colorlab►
◄restore_overlap1►
◄restore_frames0►
◄restore_modeloff►
◄restore_orderafter refine►

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 (45)

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.600–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.900–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. The joint (video+audio) refine's whole-clip encode/decode reads it too -- a weak card's first lever against a VAE OOM.
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. refine_order='before pixel' is the same move for every model (it turns 'model + fit' / 'model only' into 'model final'); 'off' skips the refine.
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.
refine_orderCOMBOafter pixelWHERE the model refine sits against the pixel model (ESRGAN), for EVERY model. 'after pixel' = ESRGAN + fit first, then the refine on that canvas (tile path: exactly what pixel_stage says; video+audio models: the refine on the big canvas). 'before pixel' = the refine first, then the pixel model BEHIND the last decode, where its detail reaches the file untouched (tile path: the same as pixel_stage='model final'; video+audio models: the refine at the input size -- also the light option for weak cards). 'off' = no model pass at all: ESRGAN + fit deliver the canvas (final_upscale_by). Measured law: a pixel model in FRONT of a VAE round trip is nearly free of effect -- the VAE's compression erases most of its detail.
audio_streamCOMBOkeepVideo+audio (joint) models only, e.g. MiniMax H3: what the refine does with the AUDIO stream of the packed latent. 'keep' freezes it (denoise mask 0 -- the run's audio comes out bit-identical); 'denoise' lets the model touch it with the same sigmas as the video. Ordinary models never read it.
pixel_speedCOMBOoffHow the pixel upscale model is RUN -- same weights, same picture. 'off' = fp16 autocast, eager. 'channels_last' lays the tensors in the layout tensor-core convolutions prefer. 'compile' hands the model to torch.compile once per loaded model (needs Triton; the first call pays the compile, tens of seconds, said in the console; a model that pads its input stays eager). The summary line prints ms/frame and the mode -- compare runs, then keep the faster one. Falls back to eager on any failure.
restore_colorCOMBOlabrestore_model only: how the restored frames are colour-matched back to the frames that went in. 'lab' transfers colour in CIELAB and keeps the detail (most faithful); 'wavelet' transfers the low frequencies only; 'adain' matches mean/std per channel (a global tint, fastest); 'none' = geometry alignment only. Read only while restore_model is on.
restore_overlapINT10–64restore_model only: latent frames shared between adjacent temporal chunks and crossfaded at the join (1 latent frame = 4 picture frames). 0 = plain cuts between chunks. Read only while restore_model is on.
restore_framesINT00–16384restore_model only: picture frames per temporal chunk -- 0 = auto (Core's measured VRAM law decides from the free memory and the frame size, never below 5 frames -- if even 5 do not fit, the run stops with the numbers; the console says what it chose), else a 4n+1 count (1, 5, 9, 13, 17, 21 ...). Smaller chunks fit a smaller card and cost joins. Read only while restore_model is on.
restore_modelCOMBOoffA RESTORATION model (one-step, conditioned on the picture itself, no prompt) run over the frames as a stage of its own -- the node loads it with its family's own VAE (both offered by the Download on the node's line). 'off' = no restore. 'model' keeps the refine model: refine and restore run in ONE node; restore_order says which comes first. A restore cannot invent structure the frames lack -- the refine can; the restore cleans and sharpens what is there.
restore_orderCOMBOafter refinerestore_model only: 'after refine' = the restore polishes the finished frames (the refine's corrections stay, the restore cleans them); 'before refine' = the restore cleans the frames first and the refine works on the clean picture (at the size the refine runs at). With the refine off, the restore runs at the end.
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.
latentoptLATENTVideo+audio (joint) models only: the packed AV latent of the run (the Sampler's LATENT output). Its AUDIO stream rides into the refine; the video stream is re-encoded from the frames. Unwired -> no joint refine (pixel path only, said out loud). Ordinary models ignore this wire.
sigmasoptSIGMASThe sampler's own sigma curve (e.g. a Hyperflow / lightx2v / Turbo list), for EVERY model. The refine runs its tail: on a flow curve (values in 0..1: Wan, Flux, H3) it starts EXACTLY at sigma = denoise (0.30 = 30 % noise, whatever the curve's grid); on any other curve (SD1/SDXL, sigma is not a noise fraction) it runs the last round(steps x denoise) steps. 'steps' and 'scheduler' are not read while this is wired (said in the console). Unwired -> 'scheduler' + 'steps' with Core's denoise slicing.

Outputs (2)

NameTypeDescription
imageIMAGE—
videoVIDEO—