Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions __init__.py
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
from .custom_samplers import SamplerDistanceAdvanced
from .custom_samplers import SamplerDistance, SamplerDistanceAdvanced
from .presets_to_add import extra_samplers

def add_samplers():
Expand All@@ -21,5 +21,6 @@ def add_samplers():
add_samplers()

NODE_CLASS_MAPPINGS = {
"SamplerDistance": SamplerDistanceAdvanced,
}
"SamplerDistance": SamplerDistance,
"SamplerDistanceAdvanced": SamplerDistanceAdvanced,
}
240 changes: 191 additions & 49 deletions custom_samplers.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,6 +2,8 @@
from comfy.k_diffusion.sampling import trange, to_d
import comfy.model_patcher
import comfy.samplers
from comfy.k_diffusion import sampling
from comfy import model_sampling
from math import pi
mmnorm = lambda x: (x - x.min()) / (x.max() - x.min())
selfnorm = lambda x: x / x.norm()
Expand DownExpand Up@@ -70,12 +72,60 @@ def normalize_adjust(a,b,strength=1):
a[~torch.isfinite(a)] = c[~torch.isfinite(a)]
return a

def get_ancestral_step_ext(sigma, sigma_next, eta=1.0, is_rf=False):
if sigma_next == 0 or eta == 0:
return sigma_next, sigma_next * 0.0, 1.0
if not is_rf:
return (*sampling.get_ancestral_step(sigma, sigma_next, eta=eta), 1.0)
# Referenced from ComfyUI.
downstep_ratio = 1.0 + (sigma_next / sigma - 1.0) * eta
sigma_down = sigma_next * downstep_ratio
alpha_ip1, alpha_down = 1.0 - sigma_next, 1.0 - sigma_down
sigma_up = (sigma_next**2 - sigma_down**2 * alpha_ip1**2 / alpha_down**2)**0.5
x_coeff = alpha_ip1 / alpha_down
return sigma_down, sigma_up, x_coeff

def internal_step(x, d, dt, sigma, sigma_next, sigma_up, x_coeff, noise_sampler):
x = x + d * dt
if sigma_up == 0 or noise_sampler is None:
return x
noise = noise_sampler(sigma, sigma_next).mul_(sigma_up)
if x_coeff != 1:
# x gets scaled for flow models.
x *= x_coeff
return x.add_(noise)

def fix_step_range(steps, start, end):
if start < 0:
start = steps + start
if end < 0:
end = steps + end
start = max(0, min(steps - 1, start))
end = max(0, min(steps - 1, end))
return (end, start) if start > end else (start, end)

# Euler and CFGpp part taken from comfy_extras/nodes_advanced_samplers
def distance_wrap(resample,resample_end=-1,cfgpp=False,sharpen=False,use_softmax=False,first_only=False,use_slerp=False,perp_step=False,smooth=False,use_negative=False):
def distance_wrap(
resample, resample_end=-1, cfgpp=False, sharpen=False, use_softmax=False,
distance_first=0, distance_last=-1, eta_first=0, eta_last=-1, distance_eta_first=0, distance_eta_last=-1,
use_slerp=False, perp_step=False, smooth=False, use_negative=False, eta=0.0, s_noise=1.0,
distance_step_eta=0.0, distance_step_s_noise=1.0, distance_step_seed_offset=42,
):
@torch.no_grad()
def sample_distance_advanced(model, x, sigmas, extra_args=None, callback=None, disable=None):
def sample_distance_advanced(model, x, sigmas, eta=eta, s_noise=s_noise, noise_sampler=None, distance_step_noise_sampler=None, extra_args=None, callback=None, disable=None):
nonlocal distance_first, distance_last, eta_first, eta_last, distance_eta_first, distance_eta_last

extra_args = {} if extra_args is None else extra_args
seed = extra_args.get("seed")
dstep_noise_sampler = None if distance_step_eta == 0 else distance_step_noise_sampler or noise_sampler or sampling.default_noise_sampler(x, seed=seed + distance_step_seed_offset if seed is not None else None)
noise_sampler = None if eta == 0 else noise_sampler or sampling.default_noise_sampler(x, seed=seed)
is_rf = isinstance(model.inner_model.inner_model.model_sampling, model_sampling.CONST)
uncond = None
steps = len(sigmas) - 1

distance_first, distance_last = fix_step_range(steps, distance_first, distance_last)
eta_first, eta_last = fix_step_range(steps, eta_first, eta_last)
distance_eta_first, distance_eta_last = fix_step_range(steps, distance_eta_first, distance_eta_last)

if cfgpp or use_negative:
uncond = None
Expand All@@ -96,58 +146,66 @@ def post_cfg_function(args):
current_resample = resample
total = 0
s_in = x.new_ones([x.shape[0]])
for i in trange(len(sigmas) - 1, disable=disable):
sigma_hat = sigmas[i]
for i in trange(steps, disable=disable):
use_distance = distance_first <= i <= distance_last
use_eta = eta_first <= i <= eta_last
use_distance_eta = distance_eta_first <= i <= distance_eta_last
sigma, sigma_next = sigmas[i:i + 2]
sigma_down, sigma_up, x_coeff = get_ancestral_step_ext(sigma, sigma_next, eta=eta if use_eta else 0.0, is_rf=is_rf)
sigma_up *= s_noise
dstep_sigma_down, dstep_sigma_up, dstep_x_coeff = get_ancestral_step_ext(sigma, sigma_next, eta=distance_step_eta if use_distance_eta else 0.0, is_rf=is_rf)
dstep_sigma_up *= distance_step_s_noise

res_mul = progression(sigma_hat)
res_mul = progression(sigma)
if resample_end >= 0:
resample_steps = max(min(current_resample,resample_end),min(max(current_resample,resample_end),int(current_resample * res_mul + resample_end * (1 - res_mul))))
else:
resample_steps = current_resample

denoised = model(x, sigma_hat * s_in, **extra_args)
denoised = model(x, sigma * s_in, **extra_args)
total += 1

if cfgpp and torch.any(uncond):
d = to_d(x - denoised + uncond, sigmas[i], denoised)
d = to_d(x - denoised + uncond, sigma, denoised)
else:
d = to_d(x, sigma_hat, denoised)
d = to_d(x, sigma, denoised)

if callback is not None:
callback({'x': x, 'i': i, 'sigma': sigmas[i], 'sigma_hat': sigma_hat, 'denoised': denoised})
dt = sigmas[i + 1] - sigma_hat
callback({'x': x, 'i': i, 'sigma': sigmas, 'sigma_hat': sigma, 'denoised': denoised})
dt = sigma_down - sigma
dstep_dt = dstep_sigma_down - sigma

if sigmas[i + 1] == 0 or resample_steps == 0 or (i > 0 and first_only):
if sigma_next == 0 or resample_steps == 0 or not use_distance:
# Euler method
x = x + d * dt
else:
# not Euler method
x_n = [d]
for re_step in range(resample_steps):
x_new = x + d * dt
new_denoised = model(x_new, sigmas[i + 1] * s_in, **extra_args)
if smooth:
new_denoised = new_denoised.abs().pow(1 / new_denoised.std().sqrt()) * new_denoised.sign()
new_denoised = new_denoised.div(new_denoised.std().sqrt())
total += 1
if cfgpp and torch.any(uncond):
new_d = to_d(x_new - new_denoised + uncond, sigmas[i + 1], new_denoised)
else:
new_d = to_d(x_new, sigmas[i + 1] * s_in, new_denoised)
x_n.append(new_d)
if re_step == 0:
d = (new_d + d) / 2
else:
u = uncond if (use_negative and uncond is not None and torch.any(uncond)) else None
d = fast_distance_weights(torch.stack(x_n), use_softmax=use_softmax, use_slerp=use_slerp, uncond=u)
if sharpen or perp_step:
if sharpen and d_prev is not None:
d = normalize_adjust(d, d_prev, 1)
elif perp_step and d_prev is not None:
d = diff_step(d, d_prev, 0.5)
d_prev = d.clone()
x_n.append(d)
x = x + d * dt
x = internal_step(x, d, dt, sigma, sigma_next, sigma_up, x_coeff, noise_sampler)
continue
# not Euler method
x_n = [d]
for re_step in trange(resample_steps, initial=1, disable=disable or resample_steps < 2, leave=False, desc=" Distance"):
x_new = internal_step(x, d, dstep_dt, sigma, sigma_next, dstep_sigma_up, dstep_x_coeff, dstep_noise_sampler)
new_denoised = model(x_new, sigma_next * s_in, **extra_args)
if smooth:
new_denoised = new_denoised.abs().pow(1 / new_denoised.std().sqrt()) * new_denoised.sign()
new_denoised = new_denoised.div(new_denoised.std().sqrt())
total += 1
if cfgpp and torch.any(uncond):
new_d = to_d(x_new - new_denoised + uncond, sigma_next, new_denoised)
else:
new_d = to_d(x_new, sigma_next * s_in, new_denoised)
x_n.append(new_d)
if re_step == 0:
d = (new_d + d) / 2
continue
u = uncond if (use_negative and uncond is not None and torch.any(uncond)) else None
d = fast_distance_weights(torch.stack(x_n), use_softmax=use_softmax, use_slerp=use_slerp, uncond=u)
if sharpen or perp_step:
if sharpen and d_prev is not None:
d = normalize_adjust(d, d_prev, 1)
elif perp_step and d_prev is not None:
d = diff_step(d, d_prev, 0.5)
d_prev = d.clone()
x_n.append(d)
x = internal_step(x, d, dt, sigma, sigma_next, sigma_up, x_coeff, noise_sampler)
return x
return sample_distance_advanced

Expand DownExpand Up@@ -202,19 +260,103 @@ def simplified_euler(model, x, sigmas, extra_args=None, callback=None, disable=N
x = x + d * dt
return x

class SamplerDistanceAdvanced:
class SamplerDistanceBase:
_DISTANCE_OPTIONS = None # All options by default.
_DISTANCE_PARAMS = {
"resample": ("INT", {
"default": 3, "min": -1, "max": 32, "step": 1,
"tooltip": "0 all along gives Euler. 1 gives Heun.\nAnything starting from 2 will use the distance method.\n-1 will do remaining steps + 1 as the resample value. This can be pretty slow.",
}),
"resample_end": ("INT", {
"default": -1, "min": -1, "max": 32, "step": 1,
"tooltip": "How many resamples for the end. -1 means constant.",
}),
"cfgpp": ("BOOLEAN", {
"default": True,
"tooltip": "Controls whether to use CFG++ sampling. When enabled, you should set CFG to a fairly low value.",
}),
"eta": ("FLOAT", {
"default": 0.0, "min": 0.0, "max": 32.0, "step": 0.01,
"tooltip": "Controls the ancestralness of the main sampler steps. 0.0 means to use non-ancestral sampling. Note: May not work well with some of the other options.",
}),
"s_noise": ("FLOAT", {
"default": 1.0, "min": -100.0, "max": 100.0, "step": 0.01,
"tooltip": "Scale factor for ancestral noise added during sampling. Generally should be left at 1.0 and only has an effect when ancestral sampling is used.",
}),
"distance_step_eta": ("FLOAT", {
"default": 0.0, "min": 0.0, "max": 32.0, "step": 0.01,
"tooltip": "Experimental option that allows using ancestral sampling for the internal distance steps. When used, should generally be a fairly low value such as 0.25. 0.0 means to use non-ancestral sampling for the internal distance steps.",
}),
"distance_step_s_noise": ("FLOAT", {
"default": 1.0, "min": -100.0, "max": 100.0, "step": 0.01,
"tooltip": "Scale factor for ancestral noise added in the internal distance steps. Generally should be left at 1.0 and only has an effect when distance_step_eta is non-zero.",
}),
"use_softmax": ("BOOLEAN", {
"default": False,
"tooltip": "Rather than using a min/max normalization and an exponent will use a softmax instead.",
}),
"use_slerp": ("BOOLEAN", {
"default": False,
"tooltip": "Will SLERP the predictions instead of doing a weighted average. The difference is more obvious when using use_negative.",
}),
"perp_step": ("BOOLEAN", {
"default": False,
"tooltip": "Experimental, not yet recommended.",
}),
"use_negative": ("BOOLEAN", {
"default": False,
"tooltip": "Will use the negative prediction to prepare the distance scores. This tends to give images with less errors from my testing.",
}),
"smooth": ("BOOLEAN", {
"default": False,
"tooltip": "Not recommended, will make everything brighter. Not smoother.",
}),
"sharpen": ("BOOLEAN", {
"default": False,
"tooltip": "Not recommended, attempts to sharpen the results but instead tends to make things fuzzy.",
}),
"distance_first": ("INT", {
"default": 0, "min": -10000, "max": 10000, "step": 1,
"tooltip": "First step to use distance sampling. You can use negative values to count from the end. Note: Steps are zero-based.",
}),
"distance_last": ("INT", {
"default": -1, "min": -10000, "max": 10000, "step": 1,
"tooltip": "Last step to use distance sampling. You can use negative values to count from the end. Note: Steps are zero-based.",
}),
"eta_first": ("INT", {
"default": 0, "min": -10000, "max": 10000, "step": 1,
"tooltip": "First step to use ancestral sampling. Only applies when ETA is non-zero. You can use negative values to count from the end. Note: Steps are zero-based.",
}),
"eta_last": ("INT", {
"default": -1, "min": -10000, "max": 10000, "step": 1,
"tooltip": "Last step to use ancestral sampling. Only applies when ETA is non-zero. You can use negative values to count from the end. Note: Steps are zero-based.",
}),
"distance_eta_first": ("INT", {
"default": 0, "min": -10000, "max": 10000, "step": 1,
"tooltip": "First step to use ancestral sampling for the distance steps. Only applies when distance ETA is non-zero. You can use negative values to count from the end. Note: Steps are zero-based.",
}),
"distance_eta_last": ("INT", {
"default": -1, "min": -10000, "max": 10000, "step": 1,
"tooltip": "Last step to use ancestral sampling for the distance steps. Only applies when distance ETA is non-zero. You can use negative values to count from the end. Note: Steps are zero-based.",
}),
}

@classmethod
def INPUT_TYPES(s):
return {"required": {"resample": ("INT", {"default": 3, "min": -1, "max": 32, "step": 1,
"tooltip":"0 all along gives Euler. 1 gives Heun.\nAnything starting from 2 will use the distance method.\n-1 will do remaining steps + 1 as the resample value. This can be pretty slow."}),
"resample_end": ("INT", {"default": -1, "min": -1, "max": 32, "step": 1, "tooltip":"How many resamples for the end. -1 means constant."}),
"cfgpp" : ("BOOLEAN", {"default": True}),
}}
if s._DISTANCE_OPTIONS is None:
return {"required": s._DISTANCE_PARAMS.copy()}
return {"required": {k: s._DISTANCE_PARAMS[k] for k in s._DISTANCE_OPTIONS}}

RETURN_TYPES = ("SAMPLER",)
CATEGORY = "sampling/custom_sampling/samplers"
FUNCTION = "get_sampler"

def get_sampler(self,resample,resample_end,cfgpp):
sampler = comfy.samplers.KSAMPLER(
distance_wrap(resample=resample,cfgpp=cfgpp,resample_end=resample_end))
def get_sampler(self, **kwargs):
sampler = comfy.samplers.KSAMPLER(distance_wrap(**kwargs))
return (sampler, )

class SamplerDistance(SamplerDistanceBase):
_DISTANCE_OPTIONS = ("resample", "resample_end", "cfgpp")

class SamplerDistanceAdvanced(SamplerDistanceBase):
pass # Includes all options by default.
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions __init__.py
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
from .custom_samplers import SamplerDistanceAdvanced
from .custom_samplers import SamplerDistance, SamplerDistanceAdvanced
from .presets_to_add import extra_samplers

def add_samplers():
Expand All@@ -21,5 +21,6 @@ def add_samplers():
add_samplers()

NODE_CLASS_MAPPINGS = {
"SamplerDistance": SamplerDistanceAdvanced,
}
"SamplerDistance": SamplerDistance,
"SamplerDistanceAdvanced": SamplerDistanceAdvanced,
}
240 changes: 191 additions & 49 deletions custom_samplers.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,6 +2,8 @@
from comfy.k_diffusion.sampling import trange, to_d
import comfy.model_patcher
import comfy.samplers
from comfy.k_diffusion import sampling
from comfy import model_sampling
from math import pi
mmnorm = lambda x: (x - x.min()) / (x.max() - x.min())
selfnorm = lambda x: x / x.norm()
Expand DownExpand Up@@ -70,12 +72,60 @@ def normalize_adjust(a,b,strength=1):
a[~torch.isfinite(a)] = c[~torch.isfinite(a)]
return a

def get_ancestral_step_ext(sigma, sigma_next, eta=1.0, is_rf=False):
if sigma_next == 0 or eta == 0:
return sigma_next, sigma_next * 0.0, 1.0
if not is_rf:
return (*sampling.get_ancestral_step(sigma, sigma_next, eta=eta), 1.0)
# Referenced from ComfyUI.
downstep_ratio = 1.0 + (sigma_next / sigma - 1.0) * eta
sigma_down = sigma_next * downstep_ratio
alpha_ip1, alpha_down = 1.0 - sigma_next, 1.0 - sigma_down
sigma_up = (sigma_next**2 - sigma_down**2 * alpha_ip1**2 / alpha_down**2)**0.5
x_coeff = alpha_ip1 / alpha_down
return sigma_down, sigma_up, x_coeff

def internal_step(x, d, dt, sigma, sigma_next, sigma_up, x_coeff, noise_sampler):
x = x + d * dt
if sigma_up == 0 or noise_sampler is None:
return x
noise = noise_sampler(sigma, sigma_next).mul_(sigma_up)
if x_coeff != 1:
# x gets scaled for flow models.
x *= x_coeff
return x.add_(noise)

def fix_step_range(steps, start, end):
if start < 0:
start = steps + start
if end < 0:
end = steps + end
start = max(0, min(steps - 1, start))
end = max(0, min(steps - 1, end))
return (end, start) if start > end else (start, end)

# Euler and CFGpp part taken from comfy_extras/nodes_advanced_samplers
def distance_wrap(resample,resample_end=-1,cfgpp=False,sharpen=False,use_softmax=False,first_only=False,use_slerp=False,perp_step=False,smooth=False,use_negative=False):
def distance_wrap(
resample, resample_end=-1, cfgpp=False, sharpen=False, use_softmax=False,
distance_first=0, distance_last=-1, eta_first=0, eta_last=-1, distance_eta_first=0, distance_eta_last=-1,
use_slerp=False, perp_step=False, smooth=False, use_negative=False, eta=0.0, s_noise=1.0,
distance_step_eta=0.0, distance_step_s_noise=1.0, distance_step_seed_offset=42,
):
@torch.no_grad()
def sample_distance_advanced(model, x, sigmas, extra_args=None, callback=None, disable=None):
def sample_distance_advanced(model, x, sigmas, eta=eta, s_noise=s_noise, noise_sampler=None, distance_step_noise_sampler=None, extra_args=None, callback=None, disable=None):
nonlocal distance_first, distance_last, eta_first, eta_last, distance_eta_first, distance_eta_last

extra_args = {} if extra_args is None else extra_args
seed = extra_args.get("seed")
dstep_noise_sampler = None if distance_step_eta == 0 else distance_step_noise_sampler or noise_sampler or sampling.default_noise_sampler(x, seed=seed + distance_step_seed_offset if seed is not None else None)
noise_sampler = None if eta == 0 else noise_sampler or sampling.default_noise_sampler(x, seed=seed)
is_rf = isinstance(model.inner_model.inner_model.model_sampling, model_sampling.CONST)
uncond = None
steps = len(sigmas) - 1

distance_first, distance_last = fix_step_range(steps, distance_first, distance_last)
eta_first, eta_last = fix_step_range(steps, eta_first, eta_last)
distance_eta_first, distance_eta_last = fix_step_range(steps, distance_eta_first, distance_eta_last)

if cfgpp or use_negative:
uncond = None
Expand All@@ -96,58 +146,66 @@ def post_cfg_function(args):
current_resample = resample
total = 0
s_in = x.new_ones([x.shape[0]])
for i in trange(len(sigmas) - 1, disable=disable):
sigma_hat = sigmas[i]
for i in trange(steps, disable=disable):
use_distance = distance_first <= i <= distance_last
use_eta = eta_first <= i <= eta_last
use_distance_eta = distance_eta_first <= i <= distance_eta_last
sigma, sigma_next = sigmas[i:i + 2]
sigma_down, sigma_up, x_coeff = get_ancestral_step_ext(sigma, sigma_next, eta=eta if use_eta else 0.0, is_rf=is_rf)
sigma_up *= s_noise
dstep_sigma_down, dstep_sigma_up, dstep_x_coeff = get_ancestral_step_ext(sigma, sigma_next, eta=distance_step_eta if use_distance_eta else 0.0, is_rf=is_rf)
dstep_sigma_up *= distance_step_s_noise

res_mul = progression(sigma_hat)
res_mul = progression(sigma)
if resample_end >= 0:
resample_steps = max(min(current_resample,resample_end),min(max(current_resample,resample_end),int(current_resample * res_mul + resample_end * (1 - res_mul))))
else:
resample_steps = current_resample

denoised = model(x, sigma_hat * s_in, **extra_args)
denoised = model(x, sigma * s_in, **extra_args)
total += 1

if cfgpp and torch.any(uncond):
d = to_d(x - denoised + uncond, sigmas[i], denoised)
d = to_d(x - denoised + uncond, sigma, denoised)
else:
d = to_d(x, sigma_hat, denoised)
d = to_d(x, sigma, denoised)

if callback is not None:
callback({'x': x, 'i': i, 'sigma': sigmas[i], 'sigma_hat': sigma_hat, 'denoised': denoised})
dt = sigmas[i + 1] - sigma_hat
callback({'x': x, 'i': i, 'sigma': sigmas, 'sigma_hat': sigma, 'denoised': denoised})
dt = sigma_down - sigma
dstep_dt = dstep_sigma_down - sigma

if sigmas[i + 1] == 0 or resample_steps == 0 or (i > 0 and first_only):
if sigma_next == 0 or resample_steps == 0 or not use_distance:
# Euler method
x = x + d * dt
else:
# not Euler method
x_n = [d]
for re_step in range(resample_steps):
x_new = x + d * dt
new_denoised = model(x_new, sigmas[i + 1] * s_in, **extra_args)
if smooth:
new_denoised = new_denoised.abs().pow(1 / new_denoised.std().sqrt()) * new_denoised.sign()
new_denoised = new_denoised.div(new_denoised.std().sqrt())
total += 1
if cfgpp and torch.any(uncond):
new_d = to_d(x_new - new_denoised + uncond, sigmas[i + 1], new_denoised)
else:
new_d = to_d(x_new, sigmas[i + 1] * s_in, new_denoised)
x_n.append(new_d)
if re_step == 0:
d = (new_d + d) / 2
else:
u = uncond if (use_negative and uncond is not None and torch.any(uncond)) else None
d = fast_distance_weights(torch.stack(x_n), use_softmax=use_softmax, use_slerp=use_slerp, uncond=u)
if sharpen or perp_step:
if sharpen and d_prev is not None:
d = normalize_adjust(d, d_prev, 1)
elif perp_step and d_prev is not None:
d = diff_step(d, d_prev, 0.5)
d_prev = d.clone()
x_n.append(d)
x = x + d * dt
x = internal_step(x, d, dt, sigma, sigma_next, sigma_up, x_coeff, noise_sampler)
continue
# not Euler method
x_n = [d]
for re_step in trange(resample_steps, initial=1, disable=disable or resample_steps < 2, leave=False, desc=" Distance"):
x_new = internal_step(x, d, dstep_dt, sigma, sigma_next, dstep_sigma_up, dstep_x_coeff, dstep_noise_sampler)
new_denoised = model(x_new, sigma_next * s_in, **extra_args)
if smooth:
new_denoised = new_denoised.abs().pow(1 / new_denoised.std().sqrt()) * new_denoised.sign()
new_denoised = new_denoised.div(new_denoised.std().sqrt())
total += 1
if cfgpp and torch.any(uncond):
new_d = to_d(x_new - new_denoised + uncond, sigma_next, new_denoised)
else:
new_d = to_d(x_new, sigma_next * s_in, new_denoised)
x_n.append(new_d)
if re_step == 0:
d = (new_d + d) / 2
continue
u = uncond if (use_negative and uncond is not None and torch.any(uncond)) else None
d = fast_distance_weights(torch.stack(x_n), use_softmax=use_softmax, use_slerp=use_slerp, uncond=u)
if sharpen or perp_step:
if sharpen and d_prev is not None:
d = normalize_adjust(d, d_prev, 1)
elif perp_step and d_prev is not None:
d = diff_step(d, d_prev, 0.5)
d_prev = d.clone()
x_n.append(d)
x = internal_step(x, d, dt, sigma, sigma_next, sigma_up, x_coeff, noise_sampler)
return x
return sample_distance_advanced

Expand DownExpand Up@@ -202,19 +260,103 @@ def simplified_euler(model, x, sigmas, extra_args=None, callback=None, disable=N
x = x + d * dt
return x

class SamplerDistanceAdvanced:
class SamplerDistanceBase:
_DISTANCE_OPTIONS = None # All options by default.
_DISTANCE_PARAMS = {
"resample": ("INT", {
"default": 3, "min": -1, "max": 32, "step": 1,
"tooltip": "0 all along gives Euler. 1 gives Heun.\nAnything starting from 2 will use the distance method.\n-1 will do remaining steps + 1 as the resample value. This can be pretty slow.",
}),
"resample_end": ("INT", {
"default": -1, "min": -1, "max": 32, "step": 1,
"tooltip": "How many resamples for the end. -1 means constant.",
}),
"cfgpp": ("BOOLEAN", {
"default": True,
"tooltip": "Controls whether to use CFG++ sampling. When enabled, you should set CFG to a fairly low value.",
}),
"eta": ("FLOAT", {
"default": 0.0, "min": 0.0, "max": 32.0, "step": 0.01,
"tooltip": "Controls the ancestralness of the main sampler steps. 0.0 means to use non-ancestral sampling. Note: May not work well with some of the other options.",
}),
"s_noise": ("FLOAT", {
"default": 1.0, "min": -100.0, "max": 100.0, "step": 0.01,
"tooltip": "Scale factor for ancestral noise added during sampling. Generally should be left at 1.0 and only has an effect when ancestral sampling is used.",
}),
"distance_step_eta": ("FLOAT", {
"default": 0.0, "min": 0.0, "max": 32.0, "step": 0.01,
"tooltip": "Experimental option that allows using ancestral sampling for the internal distance steps. When used, should generally be a fairly low value such as 0.25. 0.0 means to use non-ancestral sampling for the internal distance steps.",
}),
"distance_step_s_noise": ("FLOAT", {
"default": 1.0, "min": -100.0, "max": 100.0, "step": 0.01,
"tooltip": "Scale factor for ancestral noise added in the internal distance steps. Generally should be left at 1.0 and only has an effect when distance_step_eta is non-zero.",
}),
"use_softmax": ("BOOLEAN", {
"default": False,
"tooltip": "Rather than using a min/max normalization and an exponent will use a softmax instead.",
}),
"use_slerp": ("BOOLEAN", {
"default": False,
"tooltip": "Will SLERP the predictions instead of doing a weighted average. The difference is more obvious when using use_negative.",
}),
"perp_step": ("BOOLEAN", {
"default": False,
"tooltip": "Experimental, not yet recommended.",
}),
"use_negative": ("BOOLEAN", {
"default": False,
"tooltip": "Will use the negative prediction to prepare the distance scores. This tends to give images with less errors from my testing.",
}),
"smooth": ("BOOLEAN", {
"default": False,
"tooltip": "Not recommended, will make everything brighter. Not smoother.",
}),
"sharpen": ("BOOLEAN", {
"default": False,
"tooltip": "Not recommended, attempts to sharpen the results but instead tends to make things fuzzy.",
}),
"distance_first": ("INT", {
"default": 0, "min": -10000, "max": 10000, "step": 1,
"tooltip": "First step to use distance sampling. You can use negative values to count from the end. Note: Steps are zero-based.",
}),
"distance_last": ("INT", {
"default": -1, "min": -10000, "max": 10000, "step": 1,
"tooltip": "Last step to use distance sampling. You can use negative values to count from the end. Note: Steps are zero-based.",
}),
"eta_first": ("INT", {
"default": 0, "min": -10000, "max": 10000, "step": 1,
"tooltip": "First step to use ancestral sampling. Only applies when ETA is non-zero. You can use negative values to count from the end. Note: Steps are zero-based.",
}),
"eta_last": ("INT", {
"default": -1, "min": -10000, "max": 10000, "step": 1,
"tooltip": "Last step to use ancestral sampling. Only applies when ETA is non-zero. You can use negative values to count from the end. Note: Steps are zero-based.",
}),
"distance_eta_first": ("INT", {
"default": 0, "min": -10000, "max": 10000, "step": 1,
"tooltip": "First step to use ancestral sampling for the distance steps. Only applies when distance ETA is non-zero. You can use negative values to count from the end. Note: Steps are zero-based.",
}),
"distance_eta_last": ("INT", {
"default": -1, "min": -10000, "max": 10000, "step": 1,
"tooltip": "Last step to use ancestral sampling for the distance steps. Only applies when distance ETA is non-zero. You can use negative values to count from the end. Note: Steps are zero-based.",
}),
}

@classmethod
def INPUT_TYPES(s):
return {"required": {"resample": ("INT", {"default": 3, "min": -1, "max": 32, "step": 1,
"tooltip":"0 all along gives Euler. 1 gives Heun.\nAnything starting from 2 will use the distance method.\n-1 will do remaining steps + 1 as the resample value. This can be pretty slow."}),
"resample_end": ("INT", {"default": -1, "min": -1, "max": 32, "step": 1, "tooltip":"How many resamples for the end. -1 means constant."}),
"cfgpp" : ("BOOLEAN", {"default": True}),
}}
if s._DISTANCE_OPTIONS is None:
return {"required": s._DISTANCE_PARAMS.copy()}
return {"required": {k: s._DISTANCE_PARAMS[k] for k in s._DISTANCE_OPTIONS}}

RETURN_TYPES = ("SAMPLER",)
CATEGORY = "sampling/custom_sampling/samplers"
FUNCTION = "get_sampler"

def get_sampler(self,resample,resample_end,cfgpp):
sampler = comfy.samplers.KSAMPLER(
distance_wrap(resample=resample,cfgpp=cfgpp,resample_end=resample_end))
def get_sampler(self, **kwargs):
sampler = comfy.samplers.KSAMPLER(distance_wrap(**kwargs))
return (sampler, )

class SamplerDistance(SamplerDistanceBase):
_DISTANCE_OPTIONS = ("resample", "resample_end", "cfgpp")

class SamplerDistanceAdvanced(SamplerDistanceBase):
pass # Includes all options by default.
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions __init__.py
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
from .custom_samplers import SamplerDistanceAdvanced
from .custom_samplers import SamplerDistance, SamplerDistanceAdvanced
from .presets_to_add import extra_samplers

def add_samplers():
Expand All@@ -21,5 +21,6 @@ def add_samplers():
add_samplers()

NODE_CLASS_MAPPINGS = {
"SamplerDistance": SamplerDistanceAdvanced,
}
"SamplerDistance": SamplerDistance,
"SamplerDistanceAdvanced": SamplerDistanceAdvanced,
}
240 changes: 191 additions & 49 deletions custom_samplers.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,6 +2,8 @@
from comfy.k_diffusion.sampling import trange, to_d
import comfy.model_patcher
import comfy.samplers
from comfy.k_diffusion import sampling
from comfy import model_sampling
from math import pi
mmnorm = lambda x: (x - x.min()) / (x.max() - x.min())
selfnorm = lambda x: x / x.norm()
Expand DownExpand Up@@ -70,12 +72,60 @@ def normalize_adjust(a,b,strength=1):
a[~torch.isfinite(a)] = c[~torch.isfinite(a)]
return a

def get_ancestral_step_ext(sigma, sigma_next, eta=1.0, is_rf=False):
if sigma_next == 0 or eta == 0:
return sigma_next, sigma_next * 0.0, 1.0
if not is_rf:
return (*sampling.get_ancestral_step(sigma, sigma_next, eta=eta), 1.0)
# Referenced from ComfyUI.
downstep_ratio = 1.0 + (sigma_next / sigma - 1.0) * eta
sigma_down = sigma_next * downstep_ratio
alpha_ip1, alpha_down = 1.0 - sigma_next, 1.0 - sigma_down
sigma_up = (sigma_next**2 - sigma_down**2 * alpha_ip1**2 / alpha_down**2)**0.5
x_coeff = alpha_ip1 / alpha_down
return sigma_down, sigma_up, x_coeff

def internal_step(x, d, dt, sigma, sigma_next, sigma_up, x_coeff, noise_sampler):
x = x + d * dt
if sigma_up == 0 or noise_sampler is None:
return x
noise = noise_sampler(sigma, sigma_next).mul_(sigma_up)
if x_coeff != 1:
# x gets scaled for flow models.
x *= x_coeff
return x.add_(noise)

def fix_step_range(steps, start, end):
if start < 0:
start = steps + start
if end < 0:
end = steps + end
start = max(0, min(steps - 1, start))
end = max(0, min(steps - 1, end))
return (end, start) if start > end else (start, end)

# Euler and CFGpp part taken from comfy_extras/nodes_advanced_samplers
def distance_wrap(resample,resample_end=-1,cfgpp=False,sharpen=False,use_softmax=False,first_only=False,use_slerp=False,perp_step=False,smooth=False,use_negative=False):
def distance_wrap(
resample, resample_end=-1, cfgpp=False, sharpen=False, use_softmax=False,
distance_first=0, distance_last=-1, eta_first=0, eta_last=-1, distance_eta_first=0, distance_eta_last=-1,
use_slerp=False, perp_step=False, smooth=False, use_negative=False, eta=0.0, s_noise=1.0,
distance_step_eta=0.0, distance_step_s_noise=1.0, distance_step_seed_offset=42,
):
@torch.no_grad()
def sample_distance_advanced(model, x, sigmas, extra_args=None, callback=None, disable=None):
def sample_distance_advanced(model, x, sigmas, eta=eta, s_noise=s_noise, noise_sampler=None, distance_step_noise_sampler=None, extra_args=None, callback=None, disable=None):
nonlocal distance_first, distance_last, eta_first, eta_last, distance_eta_first, distance_eta_last

extra_args = {} if extra_args is None else extra_args
seed = extra_args.get("seed")
dstep_noise_sampler = None if distance_step_eta == 0 else distance_step_noise_sampler or noise_sampler or sampling.default_noise_sampler(x, seed=seed + distance_step_seed_offset if seed is not None else None)
noise_sampler = None if eta == 0 else noise_sampler or sampling.default_noise_sampler(x, seed=seed)
is_rf = isinstance(model.inner_model.inner_model.model_sampling, model_sampling.CONST)
uncond = None
steps = len(sigmas) - 1

distance_first, distance_last = fix_step_range(steps, distance_first, distance_last)
eta_first, eta_last = fix_step_range(steps, eta_first, eta_last)
distance_eta_first, distance_eta_last = fix_step_range(steps, distance_eta_first, distance_eta_last)

if cfgpp or use_negative:
uncond = None
Expand All@@ -96,58 +146,66 @@ def post_cfg_function(args):
current_resample = resample
total = 0
s_in = x.new_ones([x.shape[0]])
for i in trange(len(sigmas) - 1, disable=disable):
sigma_hat = sigmas[i]
for i in trange(steps, disable=disable):
use_distance = distance_first <= i <= distance_last
use_eta = eta_first <= i <= eta_last
use_distance_eta = distance_eta_first <= i <= distance_eta_last
sigma, sigma_next = sigmas[i:i + 2]
sigma_down, sigma_up, x_coeff = get_ancestral_step_ext(sigma, sigma_next, eta=eta if use_eta else 0.0, is_rf=is_rf)
sigma_up *= s_noise
dstep_sigma_down, dstep_sigma_up, dstep_x_coeff = get_ancestral_step_ext(sigma, sigma_next, eta=distance_step_eta if use_distance_eta else 0.0, is_rf=is_rf)
dstep_sigma_up *= distance_step_s_noise

res_mul = progression(sigma_hat)
res_mul = progression(sigma)
if resample_end >= 0:
resample_steps = max(min(current_resample,resample_end),min(max(current_resample,resample_end),int(current_resample * res_mul + resample_end * (1 - res_mul))))
else:
resample_steps = current_resample

denoised = model(x, sigma_hat * s_in, **extra_args)
denoised = model(x, sigma * s_in, **extra_args)
total += 1

if cfgpp and torch.any(uncond):
d = to_d(x - denoised + uncond, sigmas[i], denoised)
d = to_d(x - denoised + uncond, sigma, denoised)
else:
d = to_d(x, sigma_hat, denoised)
d = to_d(x, sigma, denoised)

if callback is not None:
callback({'x': x, 'i': i, 'sigma': sigmas[i], 'sigma_hat': sigma_hat, 'denoised': denoised})
dt = sigmas[i + 1] - sigma_hat
callback({'x': x, 'i': i, 'sigma': sigmas, 'sigma_hat': sigma, 'denoised': denoised})
dt = sigma_down - sigma
dstep_dt = dstep_sigma_down - sigma

if sigmas[i + 1] == 0 or resample_steps == 0 or (i > 0 and first_only):
if sigma_next == 0 or resample_steps == 0 or not use_distance:
# Euler method
x = x + d * dt
else:
# not Euler method
x_n = [d]
for re_step in range(resample_steps):
x_new = x + d * dt
new_denoised = model(x_new, sigmas[i + 1] * s_in, **extra_args)
if smooth:
new_denoised = new_denoised.abs().pow(1 / new_denoised.std().sqrt()) * new_denoised.sign()
new_denoised = new_denoised.div(new_denoised.std().sqrt())
total += 1
if cfgpp and torch.any(uncond):
new_d = to_d(x_new - new_denoised + uncond, sigmas[i + 1], new_denoised)
else:
new_d = to_d(x_new, sigmas[i + 1] * s_in, new_denoised)
x_n.append(new_d)
if re_step == 0:
d = (new_d + d) / 2
else:
u = uncond if (use_negative and uncond is not None and torch.any(uncond)) else None
d = fast_distance_weights(torch.stack(x_n), use_softmax=use_softmax, use_slerp=use_slerp, uncond=u)
if sharpen or perp_step:
if sharpen and d_prev is not None:
d = normalize_adjust(d, d_prev, 1)
elif perp_step and d_prev is not None:
d = diff_step(d, d_prev, 0.5)
d_prev = d.clone()
x_n.append(d)
x = x + d * dt
x = internal_step(x, d, dt, sigma, sigma_next, sigma_up, x_coeff, noise_sampler)
continue
# not Euler method
x_n = [d]
for re_step in trange(resample_steps, initial=1, disable=disable or resample_steps < 2, leave=False, desc=" Distance"):
x_new = internal_step(x, d, dstep_dt, sigma, sigma_next, dstep_sigma_up, dstep_x_coeff, dstep_noise_sampler)
new_denoised = model(x_new, sigma_next * s_in, **extra_args)
if smooth:
new_denoised = new_denoised.abs().pow(1 / new_denoised.std().sqrt()) * new_denoised.sign()
new_denoised = new_denoised.div(new_denoised.std().sqrt())
total += 1
if cfgpp and torch.any(uncond):
new_d = to_d(x_new - new_denoised + uncond, sigma_next, new_denoised)
else:
new_d = to_d(x_new, sigma_next * s_in, new_denoised)
x_n.append(new_d)
if re_step == 0:
d = (new_d + d) / 2
continue
u = uncond if (use_negative and uncond is not None and torch.any(uncond)) else None
d = fast_distance_weights(torch.stack(x_n), use_softmax=use_softmax, use_slerp=use_slerp, uncond=u)
if sharpen or perp_step:
if sharpen and d_prev is not None:
d = normalize_adjust(d, d_prev, 1)
elif perp_step and d_prev is not None:
d = diff_step(d, d_prev, 0.5)
d_prev = d.clone()
x_n.append(d)
x = internal_step(x, d, dt, sigma, sigma_next, sigma_up, x_coeff, noise_sampler)
return x
return sample_distance_advanced

Expand DownExpand Up@@ -202,19 +260,103 @@ def simplified_euler(model, x, sigmas, extra_args=None, callback=None, disable=N
x = x + d * dt
return x

class SamplerDistanceAdvanced:
class SamplerDistanceBase:
_DISTANCE_OPTIONS = None # All options by default.
_DISTANCE_PARAMS = {
"resample": ("INT", {
"default": 3, "min": -1, "max": 32, "step": 1,
"tooltip": "0 all along gives Euler. 1 gives Heun.\nAnything starting from 2 will use the distance method.\n-1 will do remaining steps + 1 as the resample value. This can be pretty slow.",
}),
"resample_end": ("INT", {
"default": -1, "min": -1, "max": 32, "step": 1,
"tooltip": "How many resamples for the end. -1 means constant.",
}),
"cfgpp": ("BOOLEAN", {
"default": True,
"tooltip": "Controls whether to use CFG++ sampling. When enabled, you should set CFG to a fairly low value.",
}),
"eta": ("FLOAT", {
"default": 0.0, "min": 0.0, "max": 32.0, "step": 0.01,
"tooltip": "Controls the ancestralness of the main sampler steps. 0.0 means to use non-ancestral sampling. Note: May not work well with some of the other options.",
}),
"s_noise": ("FLOAT", {
"default": 1.0, "min": -100.0, "max": 100.0, "step": 0.01,
"tooltip": "Scale factor for ancestral noise added during sampling. Generally should be left at 1.0 and only has an effect when ancestral sampling is used.",
}),
"distance_step_eta": ("FLOAT", {
"default": 0.0, "min": 0.0, "max": 32.0, "step": 0.01,
"tooltip": "Experimental option that allows using ancestral sampling for the internal distance steps. When used, should generally be a fairly low value such as 0.25. 0.0 means to use non-ancestral sampling for the internal distance steps.",
}),
"distance_step_s_noise": ("FLOAT", {
"default": 1.0, "min": -100.0, "max": 100.0, "step": 0.01,
"tooltip": "Scale factor for ancestral noise added in the internal distance steps. Generally should be left at 1.0 and only has an effect when distance_step_eta is non-zero.",
}),
"use_softmax": ("BOOLEAN", {
"default": False,
"tooltip": "Rather than using a min/max normalization and an exponent will use a softmax instead.",
}),
"use_slerp": ("BOOLEAN", {
"default": False,
"tooltip": "Will SLERP the predictions instead of doing a weighted average. The difference is more obvious when using use_negative.",
}),
"perp_step": ("BOOLEAN", {
"default": False,
"tooltip": "Experimental, not yet recommended.",
}),
"use_negative": ("BOOLEAN", {
"default": False,
"tooltip": "Will use the negative prediction to prepare the distance scores. This tends to give images with less errors from my testing.",
}),
"smooth": ("BOOLEAN", {
"default": False,
"tooltip": "Not recommended, will make everything brighter. Not smoother.",
}),
"sharpen": ("BOOLEAN", {
"default": False,
"tooltip": "Not recommended, attempts to sharpen the results but instead tends to make things fuzzy.",
}),
"distance_first": ("INT", {
"default": 0, "min": -10000, "max": 10000, "step": 1,
"tooltip": "First step to use distance sampling. You can use negative values to count from the end. Note: Steps are zero-based.",
}),
"distance_last": ("INT", {
"default": -1, "min": -10000, "max": 10000, "step": 1,
"tooltip": "Last step to use distance sampling. You can use negative values to count from the end. Note: Steps are zero-based.",
}),
"eta_first": ("INT", {
"default": 0, "min": -10000, "max": 10000, "step": 1,
"tooltip": "First step to use ancestral sampling. Only applies when ETA is non-zero. You can use negative values to count from the end. Note: Steps are zero-based.",
}),
"eta_last": ("INT", {
"default": -1, "min": -10000, "max": 10000, "step": 1,
"tooltip": "Last step to use ancestral sampling. Only applies when ETA is non-zero. You can use negative values to count from the end. Note: Steps are zero-based.",
}),
"distance_eta_first": ("INT", {
"default": 0, "min": -10000, "max": 10000, "step": 1,
"tooltip": "First step to use ancestral sampling for the distance steps. Only applies when distance ETA is non-zero. You can use negative values to count from the end. Note: Steps are zero-based.",
}),
"distance_eta_last": ("INT", {
"default": -1, "min": -10000, "max": 10000, "step": 1,
"tooltip": "Last step to use ancestral sampling for the distance steps. Only applies when distance ETA is non-zero. You can use negative values to count from the end. Note: Steps are zero-based.",
}),
}

@classmethod
def INPUT_TYPES(s):
return {"required": {"resample": ("INT", {"default": 3, "min": -1, "max": 32, "step": 1,
"tooltip":"0 all along gives Euler. 1 gives Heun.\nAnything starting from 2 will use the distance method.\n-1 will do remaining steps + 1 as the resample value. This can be pretty slow."}),
"resample_end": ("INT", {"default": -1, "min": -1, "max": 32, "step": 1, "tooltip":"How many resamples for the end. -1 means constant."}),
"cfgpp" : ("BOOLEAN", {"default": True}),
}}
if s._DISTANCE_OPTIONS is None:
return {"required": s._DISTANCE_PARAMS.copy()}
return {"required": {k: s._DISTANCE_PARAMS[k] for k in s._DISTANCE_OPTIONS}}

RETURN_TYPES = ("SAMPLER",)
CATEGORY = "sampling/custom_sampling/samplers"
FUNCTION = "get_sampler"

def get_sampler(self,resample,resample_end,cfgpp):
sampler = comfy.samplers.KSAMPLER(
distance_wrap(resample=resample,cfgpp=cfgpp,resample_end=resample_end))
def get_sampler(self, **kwargs):
sampler = comfy.samplers.KSAMPLER(distance_wrap(**kwargs))
return (sampler, )

class SamplerDistance(SamplerDistanceBase):
_DISTANCE_OPTIONS = ("resample", "resample_end", "cfgpp")

class SamplerDistanceAdvanced(SamplerDistanceBase):
pass # Includes all options by default.
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions __init__.py
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
from .custom_samplers import SamplerDistanceAdvanced
from .custom_samplers import SamplerDistance, SamplerDistanceAdvanced
from .presets_to_add import extra_samplers

def add_samplers():
Expand All@@ -21,5 +21,6 @@ def add_samplers():
add_samplers()

NODE_CLASS_MAPPINGS = {
"SamplerDistance": SamplerDistanceAdvanced,
}
"SamplerDistance": SamplerDistance,
"SamplerDistanceAdvanced": SamplerDistanceAdvanced,
}
240 changes: 191 additions & 49 deletions custom_samplers.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,6 +2,8 @@
from comfy.k_diffusion.sampling import trange, to_d
import comfy.model_patcher
import comfy.samplers
from comfy.k_diffusion import sampling
from comfy import model_sampling
from math import pi
mmnorm = lambda x: (x - x.min()) / (x.max() - x.min())
selfnorm = lambda x: x / x.norm()
Expand DownExpand Up@@ -70,12 +72,60 @@ def normalize_adjust(a,b,strength=1):
a[~torch.isfinite(a)] = c[~torch.isfinite(a)]
return a

def get_ancestral_step_ext(sigma, sigma_next, eta=1.0, is_rf=False):
if sigma_next == 0 or eta == 0:
return sigma_next, sigma_next * 0.0, 1.0
if not is_rf:
return (*sampling.get_ancestral_step(sigma, sigma_next, eta=eta), 1.0)
# Referenced from ComfyUI.
downstep_ratio = 1.0 + (sigma_next / sigma - 1.0) * eta
sigma_down = sigma_next * downstep_ratio
alpha_ip1, alpha_down = 1.0 - sigma_next, 1.0 - sigma_down
sigma_up = (sigma_next**2 - sigma_down**2 * alpha_ip1**2 / alpha_down**2)**0.5
x_coeff = alpha_ip1 / alpha_down
return sigma_down, sigma_up, x_coeff

def internal_step(x, d, dt, sigma, sigma_next, sigma_up, x_coeff, noise_sampler):
x = x + d * dt
if sigma_up == 0 or noise_sampler is None:
return x
noise = noise_sampler(sigma, sigma_next).mul_(sigma_up)
if x_coeff != 1:
# x gets scaled for flow models.
x *= x_coeff
return x.add_(noise)

def fix_step_range(steps, start, end):
if start < 0:
start = steps + start
if end < 0:
end = steps + end
start = max(0, min(steps - 1, start))
end = max(0, min(steps - 1, end))
return (end, start) if start > end else (start, end)

# Euler and CFGpp part taken from comfy_extras/nodes_advanced_samplers
def distance_wrap(resample,resample_end=-1,cfgpp=False,sharpen=False,use_softmax=False,first_only=False,use_slerp=False,perp_step=False,smooth=False,use_negative=False):
def distance_wrap(
resample, resample_end=-1, cfgpp=False, sharpen=False, use_softmax=False,
distance_first=0, distance_last=-1, eta_first=0, eta_last=-1, distance_eta_first=0, distance_eta_last=-1,
use_slerp=False, perp_step=False, smooth=False, use_negative=False, eta=0.0, s_noise=1.0,
distance_step_eta=0.0, distance_step_s_noise=1.0, distance_step_seed_offset=42,
):
@torch.no_grad()
def sample_distance_advanced(model, x, sigmas, extra_args=None, callback=None, disable=None):
def sample_distance_advanced(model, x, sigmas, eta=eta, s_noise=s_noise, noise_sampler=None, distance_step_noise_sampler=None, extra_args=None, callback=None, disable=None):
nonlocal distance_first, distance_last, eta_first, eta_last, distance_eta_first, distance_eta_last

extra_args = {} if extra_args is None else extra_args
seed = extra_args.get("seed")
dstep_noise_sampler = None if distance_step_eta == 0 else distance_step_noise_sampler or noise_sampler or sampling.default_noise_sampler(x, seed=seed + distance_step_seed_offset if seed is not None else None)
noise_sampler = None if eta == 0 else noise_sampler or sampling.default_noise_sampler(x, seed=seed)
is_rf = isinstance(model.inner_model.inner_model.model_sampling, model_sampling.CONST)
uncond = None
steps = len(sigmas) - 1

distance_first, distance_last = fix_step_range(steps, distance_first, distance_last)
eta_first, eta_last = fix_step_range(steps, eta_first, eta_last)
distance_eta_first, distance_eta_last = fix_step_range(steps, distance_eta_first, distance_eta_last)

if cfgpp or use_negative:
uncond = None
Expand All@@ -96,58 +146,66 @@ def post_cfg_function(args):
current_resample = resample
total = 0
s_in = x.new_ones([x.shape[0]])
for i in trange(len(sigmas) - 1, disable=disable):
sigma_hat = sigmas[i]
for i in trange(steps, disable=disable):
use_distance = distance_first <= i <= distance_last
use_eta = eta_first <= i <= eta_last
use_distance_eta = distance_eta_first <= i <= distance_eta_last
sigma, sigma_next = sigmas[i:i + 2]
sigma_down, sigma_up, x_coeff = get_ancestral_step_ext(sigma, sigma_next, eta=eta if use_eta else 0.0, is_rf=is_rf)
sigma_up *= s_noise
dstep_sigma_down, dstep_sigma_up, dstep_x_coeff = get_ancestral_step_ext(sigma, sigma_next, eta=distance_step_eta if use_distance_eta else 0.0, is_rf=is_rf)
dstep_sigma_up *= distance_step_s_noise

res_mul = progression(sigma_hat)
res_mul = progression(sigma)
if resample_end >= 0:
resample_steps = max(min(current_resample,resample_end),min(max(current_resample,resample_end),int(current_resample * res_mul + resample_end * (1 - res_mul))))
else:
resample_steps = current_resample

denoised = model(x, sigma_hat * s_in, **extra_args)
denoised = model(x, sigma * s_in, **extra_args)
total += 1

if cfgpp and torch.any(uncond):
d = to_d(x - denoised + uncond, sigmas[i], denoised)
d = to_d(x - denoised + uncond, sigma, denoised)
else:
d = to_d(x, sigma_hat, denoised)
d = to_d(x, sigma, denoised)

if callback is not None:
callback({'x': x, 'i': i, 'sigma': sigmas[i], 'sigma_hat': sigma_hat, 'denoised': denoised})
dt = sigmas[i + 1] - sigma_hat
callback({'x': x, 'i': i, 'sigma': sigmas, 'sigma_hat': sigma, 'denoised': denoised})
dt = sigma_down - sigma
dstep_dt = dstep_sigma_down - sigma

if sigmas[i + 1] == 0 or resample_steps == 0 or (i > 0 and first_only):
if sigma_next == 0 or resample_steps == 0 or not use_distance:
# Euler method
x = x + d * dt
else:
# not Euler method
x_n = [d]
for re_step in range(resample_steps):
x_new = x + d * dt
new_denoised = model(x_new, sigmas[i + 1] * s_in, **extra_args)
if smooth:
new_denoised = new_denoised.abs().pow(1 / new_denoised.std().sqrt()) * new_denoised.sign()
new_denoised = new_denoised.div(new_denoised.std().sqrt())
total += 1
if cfgpp and torch.any(uncond):
new_d = to_d(x_new - new_denoised + uncond, sigmas[i + 1], new_denoised)
else:
new_d = to_d(x_new, sigmas[i + 1] * s_in, new_denoised)
x_n.append(new_d)
if re_step == 0:
d = (new_d + d) / 2
else:
u = uncond if (use_negative and uncond is not None and torch.any(uncond)) else None
d = fast_distance_weights(torch.stack(x_n), use_softmax=use_softmax, use_slerp=use_slerp, uncond=u)
if sharpen or perp_step:
if sharpen and d_prev is not None:
d = normalize_adjust(d, d_prev, 1)
elif perp_step and d_prev is not None:
d = diff_step(d, d_prev, 0.5)
d_prev = d.clone()
x_n.append(d)
x = x + d * dt
x = internal_step(x, d, dt, sigma, sigma_next, sigma_up, x_coeff, noise_sampler)
continue
# not Euler method
x_n = [d]
for re_step in trange(resample_steps, initial=1, disable=disable or resample_steps < 2, leave=False, desc=" Distance"):
x_new = internal_step(x, d, dstep_dt, sigma, sigma_next, dstep_sigma_up, dstep_x_coeff, dstep_noise_sampler)
new_denoised = model(x_new, sigma_next * s_in, **extra_args)
if smooth:
new_denoised = new_denoised.abs().pow(1 / new_denoised.std().sqrt()) * new_denoised.sign()
new_denoised = new_denoised.div(new_denoised.std().sqrt())
total += 1
if cfgpp and torch.any(uncond):
new_d = to_d(x_new - new_denoised + uncond, sigma_next, new_denoised)
else:
new_d = to_d(x_new, sigma_next * s_in, new_denoised)
x_n.append(new_d)
if re_step == 0:
d = (new_d + d) / 2
continue
u = uncond if (use_negative and uncond is not None and torch.any(uncond)) else None
d = fast_distance_weights(torch.stack(x_n), use_softmax=use_softmax, use_slerp=use_slerp, uncond=u)
if sharpen or perp_step:
if sharpen and d_prev is not None:
d = normalize_adjust(d, d_prev, 1)
elif perp_step and d_prev is not None:
d = diff_step(d, d_prev, 0.5)
d_prev = d.clone()
x_n.append(d)
x = internal_step(x, d, dt, sigma, sigma_next, sigma_up, x_coeff, noise_sampler)
return x
return sample_distance_advanced

Expand DownExpand Up@@ -202,19 +260,103 @@ def simplified_euler(model, x, sigmas, extra_args=None, callback=None, disable=N
x = x + d * dt
return x

class SamplerDistanceAdvanced:
class SamplerDistanceBase:
_DISTANCE_OPTIONS = None # All options by default.
_DISTANCE_PARAMS = {
"resample": ("INT", {
"default": 3, "min": -1, "max": 32, "step": 1,
"tooltip": "0 all along gives Euler. 1 gives Heun.\nAnything starting from 2 will use the distance method.\n-1 will do remaining steps + 1 as the resample value. This can be pretty slow.",
}),
"resample_end": ("INT", {
"default": -1, "min": -1, "max": 32, "step": 1,
"tooltip": "How many resamples for the end. -1 means constant.",
}),
"cfgpp": ("BOOLEAN", {
"default": True,
"tooltip": "Controls whether to use CFG++ sampling. When enabled, you should set CFG to a fairly low value.",
}),
"eta": ("FLOAT", {
"default": 0.0, "min": 0.0, "max": 32.0, "step": 0.01,
"tooltip": "Controls the ancestralness of the main sampler steps. 0.0 means to use non-ancestral sampling. Note: May not work well with some of the other options.",
}),
"s_noise": ("FLOAT", {
"default": 1.0, "min": -100.0, "max": 100.0, "step": 0.01,
"tooltip": "Scale factor for ancestral noise added during sampling. Generally should be left at 1.0 and only has an effect when ancestral sampling is used.",
}),
"distance_step_eta": ("FLOAT", {
"default": 0.0, "min": 0.0, "max": 32.0, "step": 0.01,
"tooltip": "Experimental option that allows using ancestral sampling for the internal distance steps. When used, should generally be a fairly low value such as 0.25. 0.0 means to use non-ancestral sampling for the internal distance steps.",
}),
"distance_step_s_noise": ("FLOAT", {
"default": 1.0, "min": -100.0, "max": 100.0, "step": 0.01,
"tooltip": "Scale factor for ancestral noise added in the internal distance steps. Generally should be left at 1.0 and only has an effect when distance_step_eta is non-zero.",
}),
"use_softmax": ("BOOLEAN", {
"default": False,
"tooltip": "Rather than using a min/max normalization and an exponent will use a softmax instead.",
}),
"use_slerp": ("BOOLEAN", {
"default": False,
"tooltip": "Will SLERP the predictions instead of doing a weighted average. The difference is more obvious when using use_negative.",
}),
"perp_step": ("BOOLEAN", {
"default": False,
"tooltip": "Experimental, not yet recommended.",
}),
"use_negative": ("BOOLEAN", {
"default": False,
"tooltip": "Will use the negative prediction to prepare the distance scores. This tends to give images with less errors from my testing.",
}),
"smooth": ("BOOLEAN", {
"default": False,
"tooltip": "Not recommended, will make everything brighter. Not smoother.",
}),
"sharpen": ("BOOLEAN", {
"default": False,
"tooltip": "Not recommended, attempts to sharpen the results but instead tends to make things fuzzy.",
}),
"distance_first": ("INT", {
"default": 0, "min": -10000, "max": 10000, "step": 1,
"tooltip": "First step to use distance sampling. You can use negative values to count from the end. Note: Steps are zero-based.",
}),
"distance_last": ("INT", {
"default": -1, "min": -10000, "max": 10000, "step": 1,
"tooltip": "Last step to use distance sampling. You can use negative values to count from the end. Note: Steps are zero-based.",
}),
"eta_first": ("INT", {
"default": 0, "min": -10000, "max": 10000, "step": 1,
"tooltip": "First step to use ancestral sampling. Only applies when ETA is non-zero. You can use negative values to count from the end. Note: Steps are zero-based.",
}),
"eta_last": ("INT", {
"default": -1, "min": -10000, "max": 10000, "step": 1,
"tooltip": "Last step to use ancestral sampling. Only applies when ETA is non-zero. You can use negative values to count from the end. Note: Steps are zero-based.",
}),
"distance_eta_first": ("INT", {
"default": 0, "min": -10000, "max": 10000, "step": 1,
"tooltip": "First step to use ancestral sampling for the distance steps. Only applies when distance ETA is non-zero. You can use negative values to count from the end. Note: Steps are zero-based.",
}),
"distance_eta_last": ("INT", {
"default": -1, "min": -10000, "max": 10000, "step": 1,
"tooltip": "Last step to use ancestral sampling for the distance steps. Only applies when distance ETA is non-zero. You can use negative values to count from the end. Note: Steps are zero-based.",
}),
}

@classmethod
def INPUT_TYPES(s):
return {"required": {"resample": ("INT", {"default": 3, "min": -1, "max": 32, "step": 1,
"tooltip":"0 all along gives Euler. 1 gives Heun.\nAnything starting from 2 will use the distance method.\n-1 will do remaining steps + 1 as the resample value. This can be pretty slow."}),
"resample_end": ("INT", {"default": -1, "min": -1, "max": 32, "step": 1, "tooltip":"How many resamples for the end. -1 means constant."}),
"cfgpp" : ("BOOLEAN", {"default": True}),
}}
if s._DISTANCE_OPTIONS is None:
return {"required": s._DISTANCE_PARAMS.copy()}
return {"required": {k: s._DISTANCE_PARAMS[k] for k in s._DISTANCE_OPTIONS}}

RETURN_TYPES = ("SAMPLER",)
CATEGORY = "sampling/custom_sampling/samplers"
FUNCTION = "get_sampler"

def get_sampler(self,resample,resample_end,cfgpp):
sampler = comfy.samplers.KSAMPLER(
distance_wrap(resample=resample,cfgpp=cfgpp,resample_end=resample_end))
def get_sampler(self, **kwargs):
sampler = comfy.samplers.KSAMPLER(distance_wrap(**kwargs))
return (sampler, )

class SamplerDistance(SamplerDistanceBase):
_DISTANCE_OPTIONS = ("resample", "resample_end", "cfgpp")

class SamplerDistanceAdvanced(SamplerDistanceBase):
pass # Includes all options by default.
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions __init__.py
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
from .custom_samplers import SamplerDistanceAdvanced
from .custom_samplers import SamplerDistance, SamplerDistanceAdvanced
from .presets_to_add import extra_samplers

def add_samplers():
Expand All@@ -21,5 +21,6 @@ def add_samplers():
add_samplers()

NODE_CLASS_MAPPINGS = {
"SamplerDistance": SamplerDistanceAdvanced,
}
"SamplerDistance": SamplerDistance,
"SamplerDistanceAdvanced": SamplerDistanceAdvanced,
}
240 changes: 191 additions & 49 deletions custom_samplers.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,6 +2,8 @@
from comfy.k_diffusion.sampling import trange, to_d
import comfy.model_patcher
import comfy.samplers
from comfy.k_diffusion import sampling
from comfy import model_sampling
from math import pi
mmnorm = lambda x: (x - x.min()) / (x.max() - x.min())
selfnorm = lambda x: x / x.norm()
Expand DownExpand Up@@ -70,12 +72,60 @@ def normalize_adjust(a,b,strength=1):
a[~torch.isfinite(a)] = c[~torch.isfinite(a)]
return a

def get_ancestral_step_ext(sigma, sigma_next, eta=1.0, is_rf=False):
if sigma_next == 0 or eta == 0:
return sigma_next, sigma_next * 0.0, 1.0
if not is_rf:
return (*sampling.get_ancestral_step(sigma, sigma_next, eta=eta), 1.0)
# Referenced from ComfyUI.
downstep_ratio = 1.0 + (sigma_next / sigma - 1.0) * eta
sigma_down = sigma_next * downstep_ratio
alpha_ip1, alpha_down = 1.0 - sigma_next, 1.0 - sigma_down
sigma_up = (sigma_next**2 - sigma_down**2 * alpha_ip1**2 / alpha_down**2)**0.5
x_coeff = alpha_ip1 / alpha_down
return sigma_down, sigma_up, x_coeff

def internal_step(x, d, dt, sigma, sigma_next, sigma_up, x_coeff, noise_sampler):
x = x + d * dt
if sigma_up == 0 or noise_sampler is None:
return x
noise = noise_sampler(sigma, sigma_next).mul_(sigma_up)
if x_coeff != 1:
# x gets scaled for flow models.
x *= x_coeff
return x.add_(noise)

def fix_step_range(steps, start, end):
if start < 0:
start = steps + start
if end < 0:
end = steps + end
start = max(0, min(steps - 1, start))
end = max(0, min(steps - 1, end))
return (end, start) if start > end else (start, end)

# Euler and CFGpp part taken from comfy_extras/nodes_advanced_samplers
def distance_wrap(resample,resample_end=-1,cfgpp=False,sharpen=False,use_softmax=False,first_only=False,use_slerp=False,perp_step=False,smooth=False,use_negative=False):
def distance_wrap(
resample, resample_end=-1, cfgpp=False, sharpen=False, use_softmax=False,
distance_first=0, distance_last=-1, eta_first=0, eta_last=-1, distance_eta_first=0, distance_eta_last=-1,
use_slerp=False, perp_step=False, smooth=False, use_negative=False, eta=0.0, s_noise=1.0,
distance_step_eta=0.0, distance_step_s_noise=1.0, distance_step_seed_offset=42,
):
@torch.no_grad()
def sample_distance_advanced(model, x, sigmas, extra_args=None, callback=None, disable=None):
def sample_distance_advanced(model, x, sigmas, eta=eta, s_noise=s_noise, noise_sampler=None, distance_step_noise_sampler=None, extra_args=None, callback=None, disable=None):
nonlocal distance_first, distance_last, eta_first, eta_last, distance_eta_first, distance_eta_last

extra_args = {} if extra_args is None else extra_args
seed = extra_args.get("seed")
dstep_noise_sampler = None if distance_step_eta == 0 else distance_step_noise_sampler or noise_sampler or sampling.default_noise_sampler(x, seed=seed + distance_step_seed_offset if seed is not None else None)
noise_sampler = None if eta == 0 else noise_sampler or sampling.default_noise_sampler(x, seed=seed)
is_rf = isinstance(model.inner_model.inner_model.model_sampling, model_sampling.CONST)
uncond = None
steps = len(sigmas) - 1

distance_first, distance_last = fix_step_range(steps, distance_first, distance_last)
eta_first, eta_last = fix_step_range(steps, eta_first, eta_last)
distance_eta_first, distance_eta_last = fix_step_range(steps, distance_eta_first, distance_eta_last)

if cfgpp or use_negative:
uncond = None
Expand All@@ -96,58 +146,66 @@ def post_cfg_function(args):
current_resample = resample
total = 0
s_in = x.new_ones([x.shape[0]])
for i in trange(len(sigmas) - 1, disable=disable):
sigma_hat = sigmas[i]
for i in trange(steps, disable=disable):
use_distance = distance_first <= i <= distance_last
use_eta = eta_first <= i <= eta_last
use_distance_eta = distance_eta_first <= i <= distance_eta_last
sigma, sigma_next = sigmas[i:i + 2]
sigma_down, sigma_up, x_coeff = get_ancestral_step_ext(sigma, sigma_next, eta=eta if use_eta else 0.0, is_rf=is_rf)
sigma_up *= s_noise
dstep_sigma_down, dstep_sigma_up, dstep_x_coeff = get_ancestral_step_ext(sigma, sigma_next, eta=distance_step_eta if use_distance_eta else 0.0, is_rf=is_rf)
dstep_sigma_up *= distance_step_s_noise

res_mul = progression(sigma_hat)
res_mul = progression(sigma)
if resample_end >= 0:
resample_steps = max(min(current_resample,resample_end),min(max(current_resample,resample_end),int(current_resample * res_mul + resample_end * (1 - res_mul))))
else:
resample_steps = current_resample

denoised = model(x, sigma_hat * s_in, **extra_args)
denoised = model(x, sigma * s_in, **extra_args)
total += 1

if cfgpp and torch.any(uncond):
d = to_d(x - denoised + uncond, sigmas[i], denoised)
d = to_d(x - denoised + uncond, sigma, denoised)
else:
d = to_d(x, sigma_hat, denoised)
d = to_d(x, sigma, denoised)

if callback is not None:
callback({'x': x, 'i': i, 'sigma': sigmas[i], 'sigma_hat': sigma_hat, 'denoised': denoised})
dt = sigmas[i + 1] - sigma_hat
callback({'x': x, 'i': i, 'sigma': sigmas, 'sigma_hat': sigma, 'denoised': denoised})
dt = sigma_down - sigma
dstep_dt = dstep_sigma_down - sigma

if sigmas[i + 1] == 0 or resample_steps == 0 or (i > 0 and first_only):
if sigma_next == 0 or resample_steps == 0 or not use_distance:
# Euler method
x = x + d * dt
else:
# not Euler method
x_n = [d]
for re_step in range(resample_steps):
x_new = x + d * dt
new_denoised = model(x_new, sigmas[i + 1] * s_in, **extra_args)
if smooth:
new_denoised = new_denoised.abs().pow(1 / new_denoised.std().sqrt()) * new_denoised.sign()
new_denoised = new_denoised.div(new_denoised.std().sqrt())
total += 1
if cfgpp and torch.any(uncond):
new_d = to_d(x_new - new_denoised + uncond, sigmas[i + 1], new_denoised)
else:
new_d = to_d(x_new, sigmas[i + 1] * s_in, new_denoised)
x_n.append(new_d)
if re_step == 0:
d = (new_d + d) / 2
else:
u = uncond if (use_negative and uncond is not None and torch.any(uncond)) else None
d = fast_distance_weights(torch.stack(x_n), use_softmax=use_softmax, use_slerp=use_slerp, uncond=u)
if sharpen or perp_step:
if sharpen and d_prev is not None:
d = normalize_adjust(d, d_prev, 1)
elif perp_step and d_prev is not None:
d = diff_step(d, d_prev, 0.5)
d_prev = d.clone()
x_n.append(d)
x = x + d * dt
x = internal_step(x, d, dt, sigma, sigma_next, sigma_up, x_coeff, noise_sampler)
continue
# not Euler method
x_n = [d]
for re_step in trange(resample_steps, initial=1, disable=disable or resample_steps < 2, leave=False, desc=" Distance"):
x_new = internal_step(x, d, dstep_dt, sigma, sigma_next, dstep_sigma_up, dstep_x_coeff, dstep_noise_sampler)
new_denoised = model(x_new, sigma_next * s_in, **extra_args)
if smooth:
new_denoised = new_denoised.abs().pow(1 / new_denoised.std().sqrt()) * new_denoised.sign()
new_denoised = new_denoised.div(new_denoised.std().sqrt())
total += 1
if cfgpp and torch.any(uncond):
new_d = to_d(x_new - new_denoised + uncond, sigma_next, new_denoised)
else:
new_d = to_d(x_new, sigma_next * s_in, new_denoised)
x_n.append(new_d)
if re_step == 0:
d = (new_d + d) / 2
continue
u = uncond if (use_negative and uncond is not None and torch.any(uncond)) else None
d = fast_distance_weights(torch.stack(x_n), use_softmax=use_softmax, use_slerp=use_slerp, uncond=u)
if sharpen or perp_step:
if sharpen and d_prev is not None:
d = normalize_adjust(d, d_prev, 1)
elif perp_step and d_prev is not None:
d = diff_step(d, d_prev, 0.5)
d_prev = d.clone()
x_n.append(d)
x = internal_step(x, d, dt, sigma, sigma_next, sigma_up, x_coeff, noise_sampler)
return x
return sample_distance_advanced

Expand DownExpand Up@@ -202,19 +260,103 @@ def simplified_euler(model, x, sigmas, extra_args=None, callback=None, disable=N
x = x + d * dt
return x

class SamplerDistanceAdvanced:
class SamplerDistanceBase:
_DISTANCE_OPTIONS = None # All options by default.
_DISTANCE_PARAMS = {
"resample": ("INT", {
"default": 3, "min": -1, "max": 32, "step": 1,
"tooltip": "0 all along gives Euler. 1 gives Heun.\nAnything starting from 2 will use the distance method.\n-1 will do remaining steps + 1 as the resample value. This can be pretty slow.",
}),
"resample_end": ("INT", {
"default": -1, "min": -1, "max": 32, "step": 1,
"tooltip": "How many resamples for the end. -1 means constant.",
}),
"cfgpp": ("BOOLEAN", {
"default": True,
"tooltip": "Controls whether to use CFG++ sampling. When enabled, you should set CFG to a fairly low value.",
}),
"eta": ("FLOAT", {
"default": 0.0, "min": 0.0, "max": 32.0, "step": 0.01,
"tooltip": "Controls the ancestralness of the main sampler steps. 0.0 means to use non-ancestral sampling. Note: May not work well with some of the other options.",
}),
"s_noise": ("FLOAT", {
"default": 1.0, "min": -100.0, "max": 100.0, "step": 0.01,
"tooltip": "Scale factor for ancestral noise added during sampling. Generally should be left at 1.0 and only has an effect when ancestral sampling is used.",
}),
"distance_step_eta": ("FLOAT", {
"default": 0.0, "min": 0.0, "max": 32.0, "step": 0.01,
"tooltip": "Experimental option that allows using ancestral sampling for the internal distance steps. When used, should generally be a fairly low value such as 0.25. 0.0 means to use non-ancestral sampling for the internal distance steps.",
}),
"distance_step_s_noise": ("FLOAT", {
"default": 1.0, "min": -100.0, "max": 100.0, "step": 0.01,
"tooltip": "Scale factor for ancestral noise added in the internal distance steps. Generally should be left at 1.0 and only has an effect when distance_step_eta is non-zero.",
}),
"use_softmax": ("BOOLEAN", {
"default": False,
"tooltip": "Rather than using a min/max normalization and an exponent will use a softmax instead.",
}),
"use_slerp": ("BOOLEAN", {
"default": False,
"tooltip": "Will SLERP the predictions instead of doing a weighted average. The difference is more obvious when using use_negative.",
}),
"perp_step": ("BOOLEAN", {
"default": False,
"tooltip": "Experimental, not yet recommended.",
}),
"use_negative": ("BOOLEAN", {
"default": False,
"tooltip": "Will use the negative prediction to prepare the distance scores. This tends to give images with less errors from my testing.",
}),
"smooth": ("BOOLEAN", {
"default": False,
"tooltip": "Not recommended, will make everything brighter. Not smoother.",
}),
"sharpen": ("BOOLEAN", {
"default": False,
"tooltip": "Not recommended, attempts to sharpen the results but instead tends to make things fuzzy.",
}),
"distance_first": ("INT", {
"default": 0, "min": -10000, "max": 10000, "step": 1,
"tooltip": "First step to use distance sampling. You can use negative values to count from the end. Note: Steps are zero-based.",
}),
"distance_last": ("INT", {
"default": -1, "min": -10000, "max": 10000, "step": 1,
"tooltip": "Last step to use distance sampling. You can use negative values to count from the end. Note: Steps are zero-based.",
}),
"eta_first": ("INT", {
"default": 0, "min": -10000, "max": 10000, "step": 1,
"tooltip": "First step to use ancestral sampling. Only applies when ETA is non-zero. You can use negative values to count from the end. Note: Steps are zero-based.",
}),
"eta_last": ("INT", {
"default": -1, "min": -10000, "max": 10000, "step": 1,
"tooltip": "Last step to use ancestral sampling. Only applies when ETA is non-zero. You can use negative values to count from the end. Note: Steps are zero-based.",
}),
"distance_eta_first": ("INT", {
"default": 0, "min": -10000, "max": 10000, "step": 1,
"tooltip": "First step to use ancestral sampling for the distance steps. Only applies when distance ETA is non-zero. You can use negative values to count from the end. Note: Steps are zero-based.",
}),
"distance_eta_last": ("INT", {
"default": -1, "min": -10000, "max": 10000, "step": 1,
"tooltip": "Last step to use ancestral sampling for the distance steps. Only applies when distance ETA is non-zero. You can use negative values to count from the end. Note: Steps are zero-based.",
}),
}

@classmethod
def INPUT_TYPES(s):
return {"required": {"resample": ("INT", {"default": 3, "min": -1, "max": 32, "step": 1,
"tooltip":"0 all along gives Euler. 1 gives Heun.\nAnything starting from 2 will use the distance method.\n-1 will do remaining steps + 1 as the resample value. This can be pretty slow."}),
"resample_end": ("INT", {"default": -1, "min": -1, "max": 32, "step": 1, "tooltip":"How many resamples for the end. -1 means constant."}),
"cfgpp" : ("BOOLEAN", {"default": True}),
}}
if s._DISTANCE_OPTIONS is None:
return {"required": s._DISTANCE_PARAMS.copy()}
return {"required": {k: s._DISTANCE_PARAMS[k] for k in s._DISTANCE_OPTIONS}}

RETURN_TYPES = ("SAMPLER",)
CATEGORY = "sampling/custom_sampling/samplers"
FUNCTION = "get_sampler"

def get_sampler(self,resample,resample_end,cfgpp):
sampler = comfy.samplers.KSAMPLER(
distance_wrap(resample=resample,cfgpp=cfgpp,resample_end=resample_end))
def get_sampler(self, **kwargs):
sampler = comfy.samplers.KSAMPLER(distance_wrap(**kwargs))
return (sampler, )

class SamplerDistance(SamplerDistanceBase):
_DISTANCE_OPTIONS = ("resample", "resample_end", "cfgpp")

class SamplerDistanceAdvanced(SamplerDistanceBase):
pass # Includes all options by default.
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions __init__.py
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
from .custom_samplers import SamplerDistanceAdvanced
from .custom_samplers import SamplerDistance, SamplerDistanceAdvanced
from .presets_to_add import extra_samplers

def add_samplers():
Expand All@@ -21,5 +21,6 @@ def add_samplers():
add_samplers()

NODE_CLASS_MAPPINGS = {
"SamplerDistance": SamplerDistanceAdvanced,
}
"SamplerDistance": SamplerDistance,
"SamplerDistanceAdvanced": SamplerDistanceAdvanced,
}
240 changes: 191 additions & 49 deletions custom_samplers.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,6 +2,8 @@
from comfy.k_diffusion.sampling import trange, to_d
import comfy.model_patcher
import comfy.samplers
from comfy.k_diffusion import sampling
from comfy import model_sampling
from math import pi
mmnorm = lambda x: (x - x.min()) / (x.max() - x.min())
selfnorm = lambda x: x / x.norm()
Expand DownExpand Up@@ -70,12 +72,60 @@ def normalize_adjust(a,b,strength=1):
a[~torch.isfinite(a)] = c[~torch.isfinite(a)]
return a

def get_ancestral_step_ext(sigma, sigma_next, eta=1.0, is_rf=False):
if sigma_next == 0 or eta == 0:
return sigma_next, sigma_next * 0.0, 1.0
if not is_rf:
return (*sampling.get_ancestral_step(sigma, sigma_next, eta=eta), 1.0)
# Referenced from ComfyUI.
downstep_ratio = 1.0 + (sigma_next / sigma - 1.0) * eta
sigma_down = sigma_next * downstep_ratio
alpha_ip1, alpha_down = 1.0 - sigma_next, 1.0 - sigma_down
sigma_up = (sigma_next**2 - sigma_down**2 * alpha_ip1**2 / alpha_down**2)**0.5
x_coeff = alpha_ip1 / alpha_down
return sigma_down, sigma_up, x_coeff

def internal_step(x, d, dt, sigma, sigma_next, sigma_up, x_coeff, noise_sampler):
x = x + d * dt
if sigma_up == 0 or noise_sampler is None:
return x
noise = noise_sampler(sigma, sigma_next).mul_(sigma_up)
if x_coeff != 1:
# x gets scaled for flow models.
x *= x_coeff
return x.add_(noise)

def fix_step_range(steps, start, end):
if start < 0:
start = steps + start
if end < 0:
end = steps + end
start = max(0, min(steps - 1, start))
end = max(0, min(steps - 1, end))
return (end, start) if start > end else (start, end)

# Euler and CFGpp part taken from comfy_extras/nodes_advanced_samplers
def distance_wrap(resample,resample_end=-1,cfgpp=False,sharpen=False,use_softmax=False,first_only=False,use_slerp=False,perp_step=False,smooth=False,use_negative=False):
def distance_wrap(
resample, resample_end=-1, cfgpp=False, sharpen=False, use_softmax=False,
distance_first=0, distance_last=-1, eta_first=0, eta_last=-1, distance_eta_first=0, distance_eta_last=-1,
use_slerp=False, perp_step=False, smooth=False, use_negative=False, eta=0.0, s_noise=1.0,
distance_step_eta=0.0, distance_step_s_noise=1.0, distance_step_seed_offset=42,
):
@torch.no_grad()
def sample_distance_advanced(model, x, sigmas, extra_args=None, callback=None, disable=None):
def sample_distance_advanced(model, x, sigmas, eta=eta, s_noise=s_noise, noise_sampler=None, distance_step_noise_sampler=None, extra_args=None, callback=None, disable=None):
nonlocal distance_first, distance_last, eta_first, eta_last, distance_eta_first, distance_eta_last

extra_args = {} if extra_args is None else extra_args
seed = extra_args.get("seed")
dstep_noise_sampler = None if distance_step_eta == 0 else distance_step_noise_sampler or noise_sampler or sampling.default_noise_sampler(x, seed=seed + distance_step_seed_offset if seed is not None else None)
noise_sampler = None if eta == 0 else noise_sampler or sampling.default_noise_sampler(x, seed=seed)
is_rf = isinstance(model.inner_model.inner_model.model_sampling, model_sampling.CONST)
uncond = None
steps = len(sigmas) - 1

distance_first, distance_last = fix_step_range(steps, distance_first, distance_last)
eta_first, eta_last = fix_step_range(steps, eta_first, eta_last)
distance_eta_first, distance_eta_last = fix_step_range(steps, distance_eta_first, distance_eta_last)

if cfgpp or use_negative:
uncond = None
Expand All@@ -96,58 +146,66 @@ def post_cfg_function(args):
current_resample = resample
total = 0
s_in = x.new_ones([x.shape[0]])
for i in trange(len(sigmas) - 1, disable=disable):
sigma_hat = sigmas[i]
for i in trange(steps, disable=disable):
use_distance = distance_first <= i <= distance_last
use_eta = eta_first <= i <= eta_last
use_distance_eta = distance_eta_first <= i <= distance_eta_last
sigma, sigma_next = sigmas[i:i + 2]
sigma_down, sigma_up, x_coeff = get_ancestral_step_ext(sigma, sigma_next, eta=eta if use_eta else 0.0, is_rf=is_rf)
sigma_up *= s_noise
dstep_sigma_down, dstep_sigma_up, dstep_x_coeff = get_ancestral_step_ext(sigma, sigma_next, eta=distance_step_eta if use_distance_eta else 0.0, is_rf=is_rf)
dstep_sigma_up *= distance_step_s_noise

res_mul = progression(sigma_hat)
res_mul = progression(sigma)
if resample_end >= 0:
resample_steps = max(min(current_resample,resample_end),min(max(current_resample,resample_end),int(current_resample * res_mul + resample_end * (1 - res_mul))))
else:
resample_steps = current_resample

denoised = model(x, sigma_hat * s_in, **extra_args)
denoised = model(x, sigma * s_in, **extra_args)
total += 1

if cfgpp and torch.any(uncond):
d = to_d(x - denoised + uncond, sigmas[i], denoised)
d = to_d(x - denoised + uncond, sigma, denoised)
else:
d = to_d(x, sigma_hat, denoised)
d = to_d(x, sigma, denoised)

if callback is not None:
callback({'x': x, 'i': i, 'sigma': sigmas[i], 'sigma_hat': sigma_hat, 'denoised': denoised})
dt = sigmas[i + 1] - sigma_hat
callback({'x': x, 'i': i, 'sigma': sigmas, 'sigma_hat': sigma, 'denoised': denoised})
dt = sigma_down - sigma
dstep_dt = dstep_sigma_down - sigma

if sigmas[i + 1] == 0 or resample_steps == 0 or (i > 0 and first_only):
if sigma_next == 0 or resample_steps == 0 or not use_distance:
# Euler method
x = x + d * dt
else:
# not Euler method
x_n = [d]
for re_step in range(resample_steps):
x_new = x + d * dt
new_denoised = model(x_new, sigmas[i + 1] * s_in, **extra_args)
if smooth:
new_denoised = new_denoised.abs().pow(1 / new_denoised.std().sqrt()) * new_denoised.sign()
new_denoised = new_denoised.div(new_denoised.std().sqrt())
total += 1
if cfgpp and torch.any(uncond):
new_d = to_d(x_new - new_denoised + uncond, sigmas[i + 1], new_denoised)
else:
new_d = to_d(x_new, sigmas[i + 1] * s_in, new_denoised)
x_n.append(new_d)
if re_step == 0:
d = (new_d + d) / 2
else:
u = uncond if (use_negative and uncond is not None and torch.any(uncond)) else None
d = fast_distance_weights(torch.stack(x_n), use_softmax=use_softmax, use_slerp=use_slerp, uncond=u)
if sharpen or perp_step:
if sharpen and d_prev is not None:
d = normalize_adjust(d, d_prev, 1)
elif perp_step and d_prev is not None:
d = diff_step(d, d_prev, 0.5)
d_prev = d.clone()
x_n.append(d)
x = x + d * dt
x = internal_step(x, d, dt, sigma, sigma_next, sigma_up, x_coeff, noise_sampler)
continue
# not Euler method
x_n = [d]
for re_step in trange(resample_steps, initial=1, disable=disable or resample_steps < 2, leave=False, desc=" Distance"):
x_new = internal_step(x, d, dstep_dt, sigma, sigma_next, dstep_sigma_up, dstep_x_coeff, dstep_noise_sampler)
new_denoised = model(x_new, sigma_next * s_in, **extra_args)
if smooth:
new_denoised = new_denoised.abs().pow(1 / new_denoised.std().sqrt()) * new_denoised.sign()
new_denoised = new_denoised.div(new_denoised.std().sqrt())
total += 1
if cfgpp and torch.any(uncond):
new_d = to_d(x_new - new_denoised + uncond, sigma_next, new_denoised)
else:
new_d = to_d(x_new, sigma_next * s_in, new_denoised)
x_n.append(new_d)
if re_step == 0:
d = (new_d + d) / 2
continue
u = uncond if (use_negative and uncond is not None and torch.any(uncond)) else None
d = fast_distance_weights(torch.stack(x_n), use_softmax=use_softmax, use_slerp=use_slerp, uncond=u)
if sharpen or perp_step:
if sharpen and d_prev is not None:
d = normalize_adjust(d, d_prev, 1)
elif perp_step and d_prev is not None:
d = diff_step(d, d_prev, 0.5)
d_prev = d.clone()
x_n.append(d)
x = internal_step(x, d, dt, sigma, sigma_next, sigma_up, x_coeff, noise_sampler)
return x
return sample_distance_advanced

Expand DownExpand Up@@ -202,19 +260,103 @@ def simplified_euler(model, x, sigmas, extra_args=None, callback=None, disable=N
x = x + d * dt
return x

class SamplerDistanceAdvanced:
class SamplerDistanceBase:
_DISTANCE_OPTIONS = None # All options by default.
_DISTANCE_PARAMS = {
"resample": ("INT", {
"default": 3, "min": -1, "max": 32, "step": 1,
"tooltip": "0 all along gives Euler. 1 gives Heun.\nAnything starting from 2 will use the distance method.\n-1 will do remaining steps + 1 as the resample value. This can be pretty slow.",
}),
"resample_end": ("INT", {
"default": -1, "min": -1, "max": 32, "step": 1,
"tooltip": "How many resamples for the end. -1 means constant.",
}),
"cfgpp": ("BOOLEAN", {
"default": True,
"tooltip": "Controls whether to use CFG++ sampling. When enabled, you should set CFG to a fairly low value.",
}),
"eta": ("FLOAT", {
"default": 0.0, "min": 0.0, "max": 32.0, "step": 0.01,
"tooltip": "Controls the ancestralness of the main sampler steps. 0.0 means to use non-ancestral sampling. Note: May not work well with some of the other options.",
}),
"s_noise": ("FLOAT", {
"default": 1.0, "min": -100.0, "max": 100.0, "step": 0.01,
"tooltip": "Scale factor for ancestral noise added during sampling. Generally should be left at 1.0 and only has an effect when ancestral sampling is used.",
}),
"distance_step_eta": ("FLOAT", {
"default": 0.0, "min": 0.0, "max": 32.0, "step": 0.01,
"tooltip": "Experimental option that allows using ancestral sampling for the internal distance steps. When used, should generally be a fairly low value such as 0.25. 0.0 means to use non-ancestral sampling for the internal distance steps.",
}),
"distance_step_s_noise": ("FLOAT", {
"default": 1.0, "min": -100.0, "max": 100.0, "step": 0.01,
"tooltip": "Scale factor for ancestral noise added in the internal distance steps. Generally should be left at 1.0 and only has an effect when distance_step_eta is non-zero.",
}),
"use_softmax": ("BOOLEAN", {
"default": False,
"tooltip": "Rather than using a min/max normalization and an exponent will use a softmax instead.",
}),
"use_slerp": ("BOOLEAN", {
"default": False,
"tooltip": "Will SLERP the predictions instead of doing a weighted average. The difference is more obvious when using use_negative.",
}),
"perp_step": ("BOOLEAN", {
"default": False,
"tooltip": "Experimental, not yet recommended.",
}),
"use_negative": ("BOOLEAN", {
"default": False,
"tooltip": "Will use the negative prediction to prepare the distance scores. This tends to give images with less errors from my testing.",
}),
"smooth": ("BOOLEAN", {
"default": False,
"tooltip": "Not recommended, will make everything brighter. Not smoother.",
}),
"sharpen": ("BOOLEAN", {
"default": False,
"tooltip": "Not recommended, attempts to sharpen the results but instead tends to make things fuzzy.",
}),
"distance_first": ("INT", {
"default": 0, "min": -10000, "max": 10000, "step": 1,
"tooltip": "First step to use distance sampling. You can use negative values to count from the end. Note: Steps are zero-based.",
}),
"distance_last": ("INT", {
"default": -1, "min": -10000, "max": 10000, "step": 1,
"tooltip": "Last step to use distance sampling. You can use negative values to count from the end. Note: Steps are zero-based.",
}),
"eta_first": ("INT", {
"default": 0, "min": -10000, "max": 10000, "step": 1,
"tooltip": "First step to use ancestral sampling. Only applies when ETA is non-zero. You can use negative values to count from the end. Note: Steps are zero-based.",
}),
"eta_last": ("INT", {
"default": -1, "min": -10000, "max": 10000, "step": 1,
"tooltip": "Last step to use ancestral sampling. Only applies when ETA is non-zero. You can use negative values to count from the end. Note: Steps are zero-based.",
}),
"distance_eta_first": ("INT", {
"default": 0, "min": -10000, "max": 10000, "step": 1,
"tooltip": "First step to use ancestral sampling for the distance steps. Only applies when distance ETA is non-zero. You can use negative values to count from the end. Note: Steps are zero-based.",
}),
"distance_eta_last": ("INT", {
"default": -1, "min": -10000, "max": 10000, "step": 1,
"tooltip": "Last step to use ancestral sampling for the distance steps. Only applies when distance ETA is non-zero. You can use negative values to count from the end. Note: Steps are zero-based.",
}),
}

@classmethod
def INPUT_TYPES(s):
return {"required": {"resample": ("INT", {"default": 3, "min": -1, "max": 32, "step": 1,
"tooltip":"0 all along gives Euler. 1 gives Heun.\nAnything starting from 2 will use the distance method.\n-1 will do remaining steps + 1 as the resample value. This can be pretty slow."}),
"resample_end": ("INT", {"default": -1, "min": -1, "max": 32, "step": 1, "tooltip":"How many resamples for the end. -1 means constant."}),
"cfgpp" : ("BOOLEAN", {"default": True}),
}}
if s._DISTANCE_OPTIONS is None:
return {"required": s._DISTANCE_PARAMS.copy()}
return {"required": {k: s._DISTANCE_PARAMS[k] for k in s._DISTANCE_OPTIONS}}

RETURN_TYPES = ("SAMPLER",)
CATEGORY = "sampling/custom_sampling/samplers"
FUNCTION = "get_sampler"

def get_sampler(self,resample,resample_end,cfgpp):
sampler = comfy.samplers.KSAMPLER(
distance_wrap(resample=resample,cfgpp=cfgpp,resample_end=resample_end))
def get_sampler(self, **kwargs):
sampler = comfy.samplers.KSAMPLER(distance_wrap(**kwargs))
return (sampler, )

class SamplerDistance(SamplerDistanceBase):
_DISTANCE_OPTIONS = ("resample", "resample_end", "cfgpp")

class SamplerDistanceAdvanced(SamplerDistanceBase):
pass # Includes all options by default.
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions __init__.py
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
from .custom_samplers import SamplerDistanceAdvanced
from .custom_samplers import SamplerDistance, SamplerDistanceAdvanced
from .presets_to_add import extra_samplers

def add_samplers():
Expand All@@ -21,5 +21,6 @@ def add_samplers():
add_samplers()

NODE_CLASS_MAPPINGS = {
"SamplerDistance": SamplerDistanceAdvanced,
}
"SamplerDistance": SamplerDistance,
"SamplerDistanceAdvanced": SamplerDistanceAdvanced,
}
240 changes: 191 additions & 49 deletions custom_samplers.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,6 +2,8 @@
from comfy.k_diffusion.sampling import trange, to_d
import comfy.model_patcher
import comfy.samplers
from comfy.k_diffusion import sampling
from comfy import model_sampling
from math import pi
mmnorm = lambda x: (x - x.min()) / (x.max() - x.min())
selfnorm = lambda x: x / x.norm()
Expand DownExpand Up@@ -70,12 +72,60 @@ def normalize_adjust(a,b,strength=1):
a[~torch.isfinite(a)] = c[~torch.isfinite(a)]
return a

def get_ancestral_step_ext(sigma, sigma_next, eta=1.0, is_rf=False):
if sigma_next == 0 or eta == 0:
return sigma_next, sigma_next * 0.0, 1.0
if not is_rf:
return (*sampling.get_ancestral_step(sigma, sigma_next, eta=eta), 1.0)
# Referenced from ComfyUI.
downstep_ratio = 1.0 + (sigma_next / sigma - 1.0) * eta
sigma_down = sigma_next * downstep_ratio
alpha_ip1, alpha_down = 1.0 - sigma_next, 1.0 - sigma_down
sigma_up = (sigma_next**2 - sigma_down**2 * alpha_ip1**2 / alpha_down**2)**0.5
x_coeff = alpha_ip1 / alpha_down
return sigma_down, sigma_up, x_coeff

def internal_step(x, d, dt, sigma, sigma_next, sigma_up, x_coeff, noise_sampler):
x = x + d * dt
if sigma_up == 0 or noise_sampler is None:
return x
noise = noise_sampler(sigma, sigma_next).mul_(sigma_up)
if x_coeff != 1:
# x gets scaled for flow models.
x *= x_coeff
return x.add_(noise)

def fix_step_range(steps, start, end):
if start < 0:
start = steps + start
if end < 0:
end = steps + end
start = max(0, min(steps - 1, start))
end = max(0, min(steps - 1, end))
return (end, start) if start > end else (start, end)

# Euler and CFGpp part taken from comfy_extras/nodes_advanced_samplers
def distance_wrap(resample,resample_end=-1,cfgpp=False,sharpen=False,use_softmax=False,first_only=False,use_slerp=False,perp_step=False,smooth=False,use_negative=False):
def distance_wrap(
resample, resample_end=-1, cfgpp=False, sharpen=False, use_softmax=False,
distance_first=0, distance_last=-1, eta_first=0, eta_last=-1, distance_eta_first=0, distance_eta_last=-1,
use_slerp=False, perp_step=False, smooth=False, use_negative=False, eta=0.0, s_noise=1.0,
distance_step_eta=0.0, distance_step_s_noise=1.0, distance_step_seed_offset=42,
):
@torch.no_grad()
def sample_distance_advanced(model, x, sigmas, extra_args=None, callback=None, disable=None):
def sample_distance_advanced(model, x, sigmas, eta=eta, s_noise=s_noise, noise_sampler=None, distance_step_noise_sampler=None, extra_args=None, callback=None, disable=None):
nonlocal distance_first, distance_last, eta_first, eta_last, distance_eta_first, distance_eta_last

extra_args = {} if extra_args is None else extra_args
seed = extra_args.get("seed")
dstep_noise_sampler = None if distance_step_eta == 0 else distance_step_noise_sampler or noise_sampler or sampling.default_noise_sampler(x, seed=seed + distance_step_seed_offset if seed is not None else None)
noise_sampler = None if eta == 0 else noise_sampler or sampling.default_noise_sampler(x, seed=seed)
is_rf = isinstance(model.inner_model.inner_model.model_sampling, model_sampling.CONST)
uncond = None
steps = len(sigmas) - 1

distance_first, distance_last = fix_step_range(steps, distance_first, distance_last)
eta_first, eta_last = fix_step_range(steps, eta_first, eta_last)
distance_eta_first, distance_eta_last = fix_step_range(steps, distance_eta_first, distance_eta_last)

if cfgpp or use_negative:
uncond = None
Expand All@@ -96,58 +146,66 @@ def post_cfg_function(args):
current_resample = resample
total = 0
s_in = x.new_ones([x.shape[0]])
for i in trange(len(sigmas) - 1, disable=disable):
sigma_hat = sigmas[i]
for i in trange(steps, disable=disable):
use_distance = distance_first <= i <= distance_last
use_eta = eta_first <= i <= eta_last
use_distance_eta = distance_eta_first <= i <= distance_eta_last
sigma, sigma_next = sigmas[i:i + 2]
sigma_down, sigma_up, x_coeff = get_ancestral_step_ext(sigma, sigma_next, eta=eta if use_eta else 0.0, is_rf=is_rf)
sigma_up *= s_noise
dstep_sigma_down, dstep_sigma_up, dstep_x_coeff = get_ancestral_step_ext(sigma, sigma_next, eta=distance_step_eta if use_distance_eta else 0.0, is_rf=is_rf)
dstep_sigma_up *= distance_step_s_noise

res_mul = progression(sigma_hat)
res_mul = progression(sigma)
if resample_end >= 0:
resample_steps = max(min(current_resample,resample_end),min(max(current_resample,resample_end),int(current_resample * res_mul + resample_end * (1 - res_mul))))
else:
resample_steps = current_resample

denoised = model(x, sigma_hat * s_in, **extra_args)
denoised = model(x, sigma * s_in, **extra_args)
total += 1

if cfgpp and torch.any(uncond):
d = to_d(x - denoised + uncond, sigmas[i], denoised)
d = to_d(x - denoised + uncond, sigma, denoised)
else:
d = to_d(x, sigma_hat, denoised)
d = to_d(x, sigma, denoised)

if callback is not None:
callback({'x': x, 'i': i, 'sigma': sigmas[i], 'sigma_hat': sigma_hat, 'denoised': denoised})
dt = sigmas[i + 1] - sigma_hat
callback({'x': x, 'i': i, 'sigma': sigmas, 'sigma_hat': sigma, 'denoised': denoised})
dt = sigma_down - sigma
dstep_dt = dstep_sigma_down - sigma

if sigmas[i + 1] == 0 or resample_steps == 0 or (i > 0 and first_only):
if sigma_next == 0 or resample_steps == 0 or not use_distance:
# Euler method
x = x + d * dt
else:
# not Euler method
x_n = [d]
for re_step in range(resample_steps):
x_new = x + d * dt
new_denoised = model(x_new, sigmas[i + 1] * s_in, **extra_args)
if smooth:
new_denoised = new_denoised.abs().pow(1 / new_denoised.std().sqrt()) * new_denoised.sign()
new_denoised = new_denoised.div(new_denoised.std().sqrt())
total += 1
if cfgpp and torch.any(uncond):
new_d = to_d(x_new - new_denoised + uncond, sigmas[i + 1], new_denoised)
else:
new_d = to_d(x_new, sigmas[i + 1] * s_in, new_denoised)
x_n.append(new_d)
if re_step == 0:
d = (new_d + d) / 2
else:
u = uncond if (use_negative and uncond is not None and torch.any(uncond)) else None
d = fast_distance_weights(torch.stack(x_n), use_softmax=use_softmax, use_slerp=use_slerp, uncond=u)
if sharpen or perp_step:
if sharpen and d_prev is not None:
d = normalize_adjust(d, d_prev, 1)
elif perp_step and d_prev is not None:
d = diff_step(d, d_prev, 0.5)
d_prev = d.clone()
x_n.append(d)
x = x + d * dt
x = internal_step(x, d, dt, sigma, sigma_next, sigma_up, x_coeff, noise_sampler)
continue
# not Euler method
x_n = [d]
for re_step in trange(resample_steps, initial=1, disable=disable or resample_steps < 2, leave=False, desc=" Distance"):
x_new = internal_step(x, d, dstep_dt, sigma, sigma_next, dstep_sigma_up, dstep_x_coeff, dstep_noise_sampler)
new_denoised = model(x_new, sigma_next * s_in, **extra_args)
if smooth:
new_denoised = new_denoised.abs().pow(1 / new_denoised.std().sqrt()) * new_denoised.sign()
new_denoised = new_denoised.div(new_denoised.std().sqrt())
total += 1
if cfgpp and torch.any(uncond):
new_d = to_d(x_new - new_denoised + uncond, sigma_next, new_denoised)
else:
new_d = to_d(x_new, sigma_next * s_in, new_denoised)
x_n.append(new_d)
if re_step == 0:
d = (new_d + d) / 2
continue
u = uncond if (use_negative and uncond is not None and torch.any(uncond)) else None
d = fast_distance_weights(torch.stack(x_n), use_softmax=use_softmax, use_slerp=use_slerp, uncond=u)
if sharpen or perp_step:
if sharpen and d_prev is not None:
d = normalize_adjust(d, d_prev, 1)
elif perp_step and d_prev is not None:
d = diff_step(d, d_prev, 0.5)
d_prev = d.clone()
x_n.append(d)
x = internal_step(x, d, dt, sigma, sigma_next, sigma_up, x_coeff, noise_sampler)
return x
return sample_distance_advanced

Expand DownExpand Up@@ -202,19 +260,103 @@ def simplified_euler(model, x, sigmas, extra_args=None, callback=None, disable=N
x = x + d * dt
return x

class SamplerDistanceAdvanced:
class SamplerDistanceBase:
_DISTANCE_OPTIONS = None # All options by default.
_DISTANCE_PARAMS = {
"resample": ("INT", {
"default": 3, "min": -1, "max": 32, "step": 1,
"tooltip": "0 all along gives Euler. 1 gives Heun.\nAnything starting from 2 will use the distance method.\n-1 will do remaining steps + 1 as the resample value. This can be pretty slow.",
}),
"resample_end": ("INT", {
"default": -1, "min": -1, "max": 32, "step": 1,
"tooltip": "How many resamples for the end. -1 means constant.",
}),
"cfgpp": ("BOOLEAN", {
"default": True,
"tooltip": "Controls whether to use CFG++ sampling. When enabled, you should set CFG to a fairly low value.",
}),
"eta": ("FLOAT", {
"default": 0.0, "min": 0.0, "max": 32.0, "step": 0.01,
"tooltip": "Controls the ancestralness of the main sampler steps. 0.0 means to use non-ancestral sampling. Note: May not work well with some of the other options.",
}),
"s_noise": ("FLOAT", {
"default": 1.0, "min": -100.0, "max": 100.0, "step": 0.01,
"tooltip": "Scale factor for ancestral noise added during sampling. Generally should be left at 1.0 and only has an effect when ancestral sampling is used.",
}),
"distance_step_eta": ("FLOAT", {
"default": 0.0, "min": 0.0, "max": 32.0, "step": 0.01,
"tooltip": "Experimental option that allows using ancestral sampling for the internal distance steps. When used, should generally be a fairly low value such as 0.25. 0.0 means to use non-ancestral sampling for the internal distance steps.",
}),
"distance_step_s_noise": ("FLOAT", {
"default": 1.0, "min": -100.0, "max": 100.0, "step": 0.01,
"tooltip": "Scale factor for ancestral noise added in the internal distance steps. Generally should be left at 1.0 and only has an effect when distance_step_eta is non-zero.",
}),
"use_softmax": ("BOOLEAN", {
"default": False,
"tooltip": "Rather than using a min/max normalization and an exponent will use a softmax instead.",
}),
"use_slerp": ("BOOLEAN", {
"default": False,
"tooltip": "Will SLERP the predictions instead of doing a weighted average. The difference is more obvious when using use_negative.",
}),
"perp_step": ("BOOLEAN", {
"default": False,
"tooltip": "Experimental, not yet recommended.",
}),
"use_negative": ("BOOLEAN", {
"default": False,
"tooltip": "Will use the negative prediction to prepare the distance scores. This tends to give images with less errors from my testing.",
}),
"smooth": ("BOOLEAN", {
"default": False,
"tooltip": "Not recommended, will make everything brighter. Not smoother.",
}),
"sharpen": ("BOOLEAN", {
"default": False,
"tooltip": "Not recommended, attempts to sharpen the results but instead tends to make things fuzzy.",
}),
"distance_first": ("INT", {
"default": 0, "min": -10000, "max": 10000, "step": 1,
"tooltip": "First step to use distance sampling. You can use negative values to count from the end. Note: Steps are zero-based.",
}),
"distance_last": ("INT", {
"default": -1, "min": -10000, "max": 10000, "step": 1,
"tooltip": "Last step to use distance sampling. You can use negative values to count from the end. Note: Steps are zero-based.",
}),
"eta_first": ("INT", {
"default": 0, "min": -10000, "max": 10000, "step": 1,
"tooltip": "First step to use ancestral sampling. Only applies when ETA is non-zero. You can use negative values to count from the end. Note: Steps are zero-based.",
}),
"eta_last": ("INT", {
"default": -1, "min": -10000, "max": 10000, "step": 1,
"tooltip": "Last step to use ancestral sampling. Only applies when ETA is non-zero. You can use negative values to count from the end. Note: Steps are zero-based.",
}),
"distance_eta_first": ("INT", {
"default": 0, "min": -10000, "max": 10000, "step": 1,
"tooltip": "First step to use ancestral sampling for the distance steps. Only applies when distance ETA is non-zero. You can use negative values to count from the end. Note: Steps are zero-based.",
}),
"distance_eta_last": ("INT", {
"default": -1, "min": -10000, "max": 10000, "step": 1,
"tooltip": "Last step to use ancestral sampling for the distance steps. Only applies when distance ETA is non-zero. You can use negative values to count from the end. Note: Steps are zero-based.",
}),
}

@classmethod
def INPUT_TYPES(s):
return {"required": {"resample": ("INT", {"default": 3, "min": -1, "max": 32, "step": 1,
"tooltip":"0 all along gives Euler. 1 gives Heun.\nAnything starting from 2 will use the distance method.\n-1 will do remaining steps + 1 as the resample value. This can be pretty slow."}),
"resample_end": ("INT", {"default": -1, "min": -1, "max": 32, "step": 1, "tooltip":"How many resamples for the end. -1 means constant."}),
"cfgpp" : ("BOOLEAN", {"default": True}),
}}
if s._DISTANCE_OPTIONS is None:
return {"required": s._DISTANCE_PARAMS.copy()}
return {"required": {k: s._DISTANCE_PARAMS[k] for k in s._DISTANCE_OPTIONS}}

RETURN_TYPES = ("SAMPLER",)
CATEGORY = "sampling/custom_sampling/samplers"
FUNCTION = "get_sampler"

def get_sampler(self,resample,resample_end,cfgpp):
sampler = comfy.samplers.KSAMPLER(
distance_wrap(resample=resample,cfgpp=cfgpp,resample_end=resample_end))
def get_sampler(self, **kwargs):
sampler = comfy.samplers.KSAMPLER(distance_wrap(**kwargs))
return (sampler, )

class SamplerDistance(SamplerDistanceBase):
_DISTANCE_OPTIONS = ("resample", "resample_end", "cfgpp")

class SamplerDistanceAdvanced(SamplerDistanceBase):
pass # Includes all options by default.
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions __init__.py
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
from .custom_samplers import SamplerDistanceAdvanced
from .custom_samplers import SamplerDistance, SamplerDistanceAdvanced
from .presets_to_add import extra_samplers

def add_samplers():
Expand All@@ -21,5 +21,6 @@ def add_samplers():
add_samplers()

NODE_CLASS_MAPPINGS = {
"SamplerDistance": SamplerDistanceAdvanced,
}
"SamplerDistance": SamplerDistance,
"SamplerDistanceAdvanced": SamplerDistanceAdvanced,
}
240 changes: 191 additions & 49 deletions custom_samplers.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,6 +2,8 @@
from comfy.k_diffusion.sampling import trange, to_d
import comfy.model_patcher
import comfy.samplers
from comfy.k_diffusion import sampling
from comfy import model_sampling
from math import pi
mmnorm = lambda x: (x - x.min()) / (x.max() - x.min())
selfnorm = lambda x: x / x.norm()
Expand DownExpand Up@@ -70,12 +72,60 @@ def normalize_adjust(a,b,strength=1):
a[~torch.isfinite(a)] = c[~torch.isfinite(a)]
return a

def get_ancestral_step_ext(sigma, sigma_next, eta=1.0, is_rf=False):
if sigma_next == 0 or eta == 0:
return sigma_next, sigma_next * 0.0, 1.0
if not is_rf:
return (*sampling.get_ancestral_step(sigma, sigma_next, eta=eta), 1.0)
# Referenced from ComfyUI.
downstep_ratio = 1.0 + (sigma_next / sigma - 1.0) * eta
sigma_down = sigma_next * downstep_ratio
alpha_ip1, alpha_down = 1.0 - sigma_next, 1.0 - sigma_down
sigma_up = (sigma_next**2 - sigma_down**2 * alpha_ip1**2 / alpha_down**2)**0.5
x_coeff = alpha_ip1 / alpha_down
return sigma_down, sigma_up, x_coeff

def internal_step(x, d, dt, sigma, sigma_next, sigma_up, x_coeff, noise_sampler):
x = x + d * dt
if sigma_up == 0 or noise_sampler is None:
return x
noise = noise_sampler(sigma, sigma_next).mul_(sigma_up)
if x_coeff != 1:
# x gets scaled for flow models.
x *= x_coeff
return x.add_(noise)

def fix_step_range(steps, start, end):
if start < 0:
start = steps + start
if end < 0:
end = steps + end
start = max(0, min(steps - 1, start))
end = max(0, min(steps - 1, end))
return (end, start) if start > end else (start, end)

# Euler and CFGpp part taken from comfy_extras/nodes_advanced_samplers
def distance_wrap(resample,resample_end=-1,cfgpp=False,sharpen=False,use_softmax=False,first_only=False,use_slerp=False,perp_step=False,smooth=False,use_negative=False):
def distance_wrap(
resample, resample_end=-1, cfgpp=False, sharpen=False, use_softmax=False,
distance_first=0, distance_last=-1, eta_first=0, eta_last=-1, distance_eta_first=0, distance_eta_last=-1,
use_slerp=False, perp_step=False, smooth=False, use_negative=False, eta=0.0, s_noise=1.0,
distance_step_eta=0.0, distance_step_s_noise=1.0, distance_step_seed_offset=42,
):
@torch.no_grad()
def sample_distance_advanced(model, x, sigmas, extra_args=None, callback=None, disable=None):
def sample_distance_advanced(model, x, sigmas, eta=eta, s_noise=s_noise, noise_sampler=None, distance_step_noise_sampler=None, extra_args=None, callback=None, disable=None):
nonlocal distance_first, distance_last, eta_first, eta_last, distance_eta_first, distance_eta_last

extra_args = {} if extra_args is None else extra_args
seed = extra_args.get("seed")
dstep_noise_sampler = None if distance_step_eta == 0 else distance_step_noise_sampler or noise_sampler or sampling.default_noise_sampler(x, seed=seed + distance_step_seed_offset if seed is not None else None)
noise_sampler = None if eta == 0 else noise_sampler or sampling.default_noise_sampler(x, seed=seed)
is_rf = isinstance(model.inner_model.inner_model.model_sampling, model_sampling.CONST)
uncond = None
steps = len(sigmas) - 1

distance_first, distance_last = fix_step_range(steps, distance_first, distance_last)
eta_first, eta_last = fix_step_range(steps, eta_first, eta_last)
distance_eta_first, distance_eta_last = fix_step_range(steps, distance_eta_first, distance_eta_last)

if cfgpp or use_negative:
uncond = None
Expand All@@ -96,58 +146,66 @@ def post_cfg_function(args):
current_resample = resample
total = 0
s_in = x.new_ones([x.shape[0]])
for i in trange(len(sigmas) - 1, disable=disable):
sigma_hat = sigmas[i]
for i in trange(steps, disable=disable):
use_distance = distance_first <= i <= distance_last
use_eta = eta_first <= i <= eta_last
use_distance_eta = distance_eta_first <= i <= distance_eta_last
sigma, sigma_next = sigmas[i:i + 2]
sigma_down, sigma_up, x_coeff = get_ancestral_step_ext(sigma, sigma_next, eta=eta if use_eta else 0.0, is_rf=is_rf)
sigma_up *= s_noise
dstep_sigma_down, dstep_sigma_up, dstep_x_coeff = get_ancestral_step_ext(sigma, sigma_next, eta=distance_step_eta if use_distance_eta else 0.0, is_rf=is_rf)
dstep_sigma_up *= distance_step_s_noise

res_mul = progression(sigma_hat)
res_mul = progression(sigma)
if resample_end >= 0:
resample_steps = max(min(current_resample,resample_end),min(max(current_resample,resample_end),int(current_resample * res_mul + resample_end * (1 - res_mul))))
else:
resample_steps = current_resample

denoised = model(x, sigma_hat * s_in, **extra_args)
denoised = model(x, sigma * s_in, **extra_args)
total += 1

if cfgpp and torch.any(uncond):
d = to_d(x - denoised + uncond, sigmas[i], denoised)
d = to_d(x - denoised + uncond, sigma, denoised)
else:
d = to_d(x, sigma_hat, denoised)
d = to_d(x, sigma, denoised)

if callback is not None:
callback({'x': x, 'i': i, 'sigma': sigmas[i], 'sigma_hat': sigma_hat, 'denoised': denoised})
dt = sigmas[i + 1] - sigma_hat
callback({'x': x, 'i': i, 'sigma': sigmas, 'sigma_hat': sigma, 'denoised': denoised})
dt = sigma_down - sigma
dstep_dt = dstep_sigma_down - sigma

if sigmas[i + 1] == 0 or resample_steps == 0 or (i > 0 and first_only):
if sigma_next == 0 or resample_steps == 0 or not use_distance:
# Euler method
x = x + d * dt
else:
# not Euler method
x_n = [d]
for re_step in range(resample_steps):
x_new = x + d * dt
new_denoised = model(x_new, sigmas[i + 1] * s_in, **extra_args)
if smooth:
new_denoised = new_denoised.abs().pow(1 / new_denoised.std().sqrt()) * new_denoised.sign()
new_denoised = new_denoised.div(new_denoised.std().sqrt())
total += 1
if cfgpp and torch.any(uncond):
new_d = to_d(x_new - new_denoised + uncond, sigmas[i + 1], new_denoised)
else:
new_d = to_d(x_new, sigmas[i + 1] * s_in, new_denoised)
x_n.append(new_d)
if re_step == 0:
d = (new_d + d) / 2
else:
u = uncond if (use_negative and uncond is not None and torch.any(uncond)) else None
d = fast_distance_weights(torch.stack(x_n), use_softmax=use_softmax, use_slerp=use_slerp, uncond=u)
if sharpen or perp_step:
if sharpen and d_prev is not None:
d = normalize_adjust(d, d_prev, 1)
elif perp_step and d_prev is not None:
d = diff_step(d, d_prev, 0.5)
d_prev = d.clone()
x_n.append(d)
x = x + d * dt
x = internal_step(x, d, dt, sigma, sigma_next, sigma_up, x_coeff, noise_sampler)
continue
# not Euler method
x_n = [d]
for re_step in trange(resample_steps, initial=1, disable=disable or resample_steps < 2, leave=False, desc=" Distance"):
x_new = internal_step(x, d, dstep_dt, sigma, sigma_next, dstep_sigma_up, dstep_x_coeff, dstep_noise_sampler)
new_denoised = model(x_new, sigma_next * s_in, **extra_args)
if smooth:
new_denoised = new_denoised.abs().pow(1 / new_denoised.std().sqrt()) * new_denoised.sign()
new_denoised = new_denoised.div(new_denoised.std().sqrt())
total += 1
if cfgpp and torch.any(uncond):
new_d = to_d(x_new - new_denoised + uncond, sigma_next, new_denoised)
else:
new_d = to_d(x_new, sigma_next * s_in, new_denoised)
x_n.append(new_d)
if re_step == 0:
d = (new_d + d) / 2
continue
u = uncond if (use_negative and uncond is not None and torch.any(uncond)) else None
d = fast_distance_weights(torch.stack(x_n), use_softmax=use_softmax, use_slerp=use_slerp, uncond=u)
if sharpen or perp_step:
if sharpen and d_prev is not None:
d = normalize_adjust(d, d_prev, 1)
elif perp_step and d_prev is not None:
d = diff_step(d, d_prev, 0.5)
d_prev = d.clone()
x_n.append(d)
x = internal_step(x, d, dt, sigma, sigma_next, sigma_up, x_coeff, noise_sampler)
return x
return sample_distance_advanced

Expand DownExpand Up@@ -202,19 +260,103 @@ def simplified_euler(model, x, sigmas, extra_args=None, callback=None, disable=N
x = x + d * dt
return x

class SamplerDistanceAdvanced:
class SamplerDistanceBase:
_DISTANCE_OPTIONS = None # All options by default.
_DISTANCE_PARAMS = {
"resample": ("INT", {
"default": 3, "min": -1, "max": 32, "step": 1,
"tooltip": "0 all along gives Euler. 1 gives Heun.\nAnything starting from 2 will use the distance method.\n-1 will do remaining steps + 1 as the resample value. This can be pretty slow.",
}),
"resample_end": ("INT", {
"default": -1, "min": -1, "max": 32, "step": 1,
"tooltip": "How many resamples for the end. -1 means constant.",
}),
"cfgpp": ("BOOLEAN", {
"default": True,
"tooltip": "Controls whether to use CFG++ sampling. When enabled, you should set CFG to a fairly low value.",
}),
"eta": ("FLOAT", {
"default": 0.0, "min": 0.0, "max": 32.0, "step": 0.01,
"tooltip": "Controls the ancestralness of the main sampler steps. 0.0 means to use non-ancestral sampling. Note: May not work well with some of the other options.",
}),
"s_noise": ("FLOAT", {
"default": 1.0, "min": -100.0, "max": 100.0, "step": 0.01,
"tooltip": "Scale factor for ancestral noise added during sampling. Generally should be left at 1.0 and only has an effect when ancestral sampling is used.",
}),
"distance_step_eta": ("FLOAT", {
"default": 0.0, "min": 0.0, "max": 32.0, "step": 0.01,
"tooltip": "Experimental option that allows using ancestral sampling for the internal distance steps. When used, should generally be a fairly low value such as 0.25. 0.0 means to use non-ancestral sampling for the internal distance steps.",
}),
"distance_step_s_noise": ("FLOAT", {
"default": 1.0, "min": -100.0, "max": 100.0, "step": 0.01,
"tooltip": "Scale factor for ancestral noise added in the internal distance steps. Generally should be left at 1.0 and only has an effect when distance_step_eta is non-zero.",
}),
"use_softmax": ("BOOLEAN", {
"default": False,
"tooltip": "Rather than using a min/max normalization and an exponent will use a softmax instead.",
}),
"use_slerp": ("BOOLEAN", {
"default": False,
"tooltip": "Will SLERP the predictions instead of doing a weighted average. The difference is more obvious when using use_negative.",
}),
"perp_step": ("BOOLEAN", {
"default": False,
"tooltip": "Experimental, not yet recommended.",
}),
"use_negative": ("BOOLEAN", {
"default": False,
"tooltip": "Will use the negative prediction to prepare the distance scores. This tends to give images with less errors from my testing.",
}),
"smooth": ("BOOLEAN", {
"default": False,
"tooltip": "Not recommended, will make everything brighter. Not smoother.",
}),
"sharpen": ("BOOLEAN", {
"default": False,
"tooltip": "Not recommended, attempts to sharpen the results but instead tends to make things fuzzy.",
}),
"distance_first": ("INT", {
"default": 0, "min": -10000, "max": 10000, "step": 1,
"tooltip": "First step to use distance sampling. You can use negative values to count from the end. Note: Steps are zero-based.",
}),
"distance_last": ("INT", {
"default": -1, "min": -10000, "max": 10000, "step": 1,
"tooltip": "Last step to use distance sampling. You can use negative values to count from the end. Note: Steps are zero-based.",
}),
"eta_first": ("INT", {
"default": 0, "min": -10000, "max": 10000, "step": 1,
"tooltip": "First step to use ancestral sampling. Only applies when ETA is non-zero. You can use negative values to count from the end. Note: Steps are zero-based.",
}),
"eta_last": ("INT", {
"default": -1, "min": -10000, "max": 10000, "step": 1,
"tooltip": "Last step to use ancestral sampling. Only applies when ETA is non-zero. You can use negative values to count from the end. Note: Steps are zero-based.",
}),
"distance_eta_first": ("INT", {
"default": 0, "min": -10000, "max": 10000, "step": 1,
"tooltip": "First step to use ancestral sampling for the distance steps. Only applies when distance ETA is non-zero. You can use negative values to count from the end. Note: Steps are zero-based.",
}),
"distance_eta_last": ("INT", {
"default": -1, "min": -10000, "max": 10000, "step": 1,
"tooltip": "Last step to use ancestral sampling for the distance steps. Only applies when distance ETA is non-zero. You can use negative values to count from the end. Note: Steps are zero-based.",
}),
}

@classmethod
def INPUT_TYPES(s):
return {"required": {"resample": ("INT", {"default": 3, "min": -1, "max": 32, "step": 1,
"tooltip":"0 all along gives Euler. 1 gives Heun.\nAnything starting from 2 will use the distance method.\n-1 will do remaining steps + 1 as the resample value. This can be pretty slow."}),
"resample_end": ("INT", {"default": -1, "min": -1, "max": 32, "step": 1, "tooltip":"How many resamples for the end. -1 means constant."}),
"cfgpp" : ("BOOLEAN", {"default": True}),
}}
if s._DISTANCE_OPTIONS is None:
return {"required": s._DISTANCE_PARAMS.copy()}
return {"required": {k: s._DISTANCE_PARAMS[k] for k in s._DISTANCE_OPTIONS}}

RETURN_TYPES = ("SAMPLER",)
CATEGORY = "sampling/custom_sampling/samplers"
FUNCTION = "get_sampler"

def get_sampler(self,resample,resample_end,cfgpp):
sampler = comfy.samplers.KSAMPLER(
distance_wrap(resample=resample,cfgpp=cfgpp,resample_end=resample_end))
def get_sampler(self, **kwargs):
sampler = comfy.samplers.KSAMPLER(distance_wrap(**kwargs))
return (sampler, )

class SamplerDistance(SamplerDistanceBase):
_DISTANCE_OPTIONS = ("resample", "resample_end", "cfgpp")

class SamplerDistanceAdvanced(SamplerDistanceBase):
pass # Includes all options by default.