NS Refiner Boundary Optimizer
Let the image decide where the base model hands off to the refiner
- model_base
- model_refiner
- positive
- negative
- positive_refiner
- negative_refiner
- latent_image
- LATENT
- switch_step
In the classic SDXL base → refiner pipeline, someone has to decide the switch step: the moment the base model stops denoising and the refiner takes over. Most workflows just pick a number (0.8 is the folk wisdom) and never revisit it. NS Refiner Boundary Optimizer is a full sampler node that replaces that guess - it runs the base model, watches how the image is actually developing, and picks the handoff point based on what it sees. Then it runs the rest of the generation to completion. You get a finished latent and the switch step it chose.
The pitch is genuinely clever. The base model builds the coarse structure and the refiner refines detail, but the ideal handoff isn't a constant - some images lock their structure early, some keep reorganizing late. A frequency analysis of the denoised output can tell you which kind you're dealing with. That's the core insight, and this node operationalizes it.
How it works
It's a three-phase sampler, all inside one node:
- Analysis pass - runs the base model for up to
max_base_ratio(default 0.9) of the steps, tracking two metrics per step: frequency (the high-frequency energy of the predicted image, via a Laplacian filter) and stability (how much the prediction changes between steps). - Decision - picks the switch step by detecting where those metrics saturate, bounded between
min_base_ratioandmax_base_ratio(defaults 0.5–0.9).evaluation_metricchoosesfrequency,stability, orcombined;sensitivity(0–1, default 0.5) controls how eagerly the detector declares "this is where structure stops changing." Higher sensitivity = earlier switch. - Real run - runs the base model to the chosen step, then the refiner the rest of the way, reusing the analysis pass's output when the decision lands at the max so you don't pay for a wasted second base run.
You feed it both models, both prompt sets (positive/negative for base, positive_refiner/negative_refiner for refiner), the latent, and standard sampler settings (steps, cfg, sampler_name, scheduler, seed, denoise). Outputs are the finished LATENT and the chosen switch_step as an INT - the second output is the interesting one, because it tells you what the node decided.
Where it hurts
The honest cost: in the common case it runs the base model twice (analysis to max, then real run to the switch step). That's roughly 1.5–2x the base-model compute of a normal fixed-switch workflow, before the refiner even starts. On a fast card it's tolerable; on a 3060 it's a real time sink. The sensitivity knob exists to help - lower it and the detector switches earlier, so the second base run is shorter - but you're trading time for an adaptive decision.
Also set expectations: "optimizer" sounds like it finds a better image. It finds a better timing. If your base and refiner conditioning aren't well-set-up (and remember the community largely abandoned the refiner - the KB's verdict is "shipped, then ignored"), no amount of boundary optimization fixes that.
Installing it
Part of ComfyUI-NS-Util:
cd ComfyUI/custom_nodes
git clone https://github.com/NakamuraShippo/ComfyUI-NS-Util
or ComfyUI Manager → Install via Git URL with the same URL, restart. No model downloads - it needs the base and refiner checkpoints you already have - and the dependencies are the pack's light set.
The honest take
This is the most ambitious node in the pack: a full replacement sampler with an adaptive decision inside it. The idea - let frequency/stability saturation pick the handoff - is sound and genuinely under-explored. But it's also a time-expensive tool for a pipeline (base + refiner) that most of the community has moved on from. If you're deliberately keeping the two-stage workflow alive and want to stop hand-tuning the switch step, this is the only tool I know that does the job automatically. Just budget the extra compute.
Inputs (17)
| Name | Type | Default | Description |
|---|---|---|---|
| model_base | MODEL | — | |
| model_refiner | MODEL | — | |
| positive | CONDITIONING | — | |
| negative | CONDITIONING | — | |
| positive_refiner | CONDITIONING | — | |
| negative_refiner | CONDITIONING | — | |
| latent_image | LATENT | — | |
| seed | INT | 00–18446744073709550000 | — |
| steps | INT | 301–10000 | — |
| cfg | FLOAT | 7.00–100 | — |
| sampler_name | COMBO | 44 options: euler, euler_cfg_pp, euler_ancestral, euler_ancestral_cfg_pp, heun, heunpp2, +38 | |
| scheduler | COMBO | 9 options: simple, sgm_uniform, karras, exponential, ddim_uniform, beta, +3 | |
| min_base_ratio | FLOAT | 0.500.3–0.9 | — |
| max_base_ratio | FLOAT | 0.900.5–1 | — |
| evaluation_metric | COMBO | 3 options: combined, stability, frequency | |
| sensitivity | FLOAT | 0.50–1 | — |
| denoise | FLOAT | 1.000–1 | — |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| LATENT | LATENT | — |
| switch_step | INT | — |