import math from functools import partial import logging from scipy import integrate import torch from torch import nn import torchsde from tqdm.auto import trange, tqdm from comfy.k_diffusion import utils from comfy.k_diffusion import deis import comfy.model_patcher import comfy.model_sampling from comfy.samplers import KSamplerX0Inpaint, Sampler # ===================================================================== # EasyCache state container # --------------------------------------------------------------------- # 参考 EasyCache4HunyuanVideo/easycache_sample_video.py: # 1) cache = denoised - x (上一次实算时输出与输入的"变换向量") # 跳过实算时直接 denoised = x + cache 复用。 # 2) 用一阶线性灵敏度 k = |Δoutput| / |Δinput| 估计跳过该 step 的预测误差 # pred_change = k * |Δx| / |prev_output| # 累加为 accumulated_error;< threshold 时继续跳过,否则强制实算并清零。 # 3) 前 ret_steps 步必算(启动保护),最后一步必算(收尾保护)。 # 由于一些 sampler(Heun/DPM-2 等)每 step 内会多次调用 model(...), # 这里通过 begin_step/model_call/end_step 三个接口把 EasyCache 状态绑定到外层 # step 索引 i:本 step 的所有 model 调用共享同一个 should_calc 决策。 # ===================================================================== class EasyCacheState: def __init__(self, num_steps, threshold=0.025, ret_steps=5): self.num_steps = int(num_steps) self.threshold = float(threshold) self.ret_steps = int(ret_steps) self.cnt = 0 self.should_calc = True self.accumulated_error = 0.0 self.cache = None # 上一次实算的 (denoised - x) self.k = None # 灵敏度 |Δoutput|/|Δinput| self.previous_raw_input = None self.prev_prev_raw_input = None self.previous_output = None self._step_calc_done = False def begin_step(self, i, total_steps, raw_input): self.cnt = i self._step_calc_done = False # 启动 / 收尾保护 if self.cnt < self.ret_steps or self.cnt >= total_steps - 1: self.should_calc = True self.accumulated_error = 0.0 return if (self.previous_raw_input is None or self.previous_output is None or self.cache is None or self.k is None): self.should_calc = True return raw_input_change = (raw_input - self.previous_raw_input).abs().mean() output_norm = self.previous_output.abs().mean().clamp(min=1e-8) pred_change = float(self.k) * float(raw_input_change / output_norm) self.accumulated_error += pred_change if self.accumulated_error < self.threshold: self.should_calc = False else: self.should_calc = True self.accumulated_error = 0.0 def model_call(self, model, x, sigma, **extra_args): if (not self.should_calc) and (self.cache is not None): return x + self.cache denoised = model(x, sigma, **extra_args) # 仅在每个 step 第一次实算时更新缓存与灵敏度 if not self._step_calc_done: new_cache = denoised - x if self.previous_output is not None and self.prev_prev_raw_input is not None: output_change = (denoised - self.previous_output).abs().mean() input_change = (self.previous_raw_input - self.prev_prev_raw_input).abs().mean().clamp(min=1e-8) self.k = float(output_change / input_change) self.cache = new_cache self.previous_output = denoised.detach().clone() self._step_calc_done = True return denoised def end_step(self, raw_input): self.prev_prev_raw_input = self.previous_raw_input self.previous_raw_input = raw_input.detach().clone() class KSAMPLER(Sampler): def __init__(self, sampler_function, threshold, ret_steps, extra_options={}, inpaint_options={}): # parameters for EasyCache self.threshold = threshold self.ret_steps = ret_steps self.sampler_function = sampler_function self.extra_options = extra_options self.inpaint_options = inpaint_options def sample(self, model_wrap, sigmas, extra_args, callback, noise, latent_image=None, denoise_mask=None, disable_pbar=False): extra_args["denoise_mask"] = denoise_mask model_k = KSamplerX0Inpaint(model_wrap, sigmas) model_k.latent_image = latent_image if self.inpaint_options.get("random", False): #TODO: Should this be the default? generator = torch.manual_seed(extra_args.get("seed", 41) + 1) model_k.noise = torch.randn(noise.shape, generator=generator, device="cpu").to(noise.dtype).to(noise.device) else: model_k.noise = noise noise = model_wrap.inner_model.model_sampling.noise_scaling(sigmas[0], noise, latent_image, self.max_denoise(model_wrap, sigmas)) k_callback = None total_steps = len(sigmas) - 1 if callback is not None: k_callback = lambda x: callback(x["i"], x["denoised"], x["x"], total_steps) samples = self.sampler_function(model_k, noise, sigmas, threshold=self.threshold, ret_steps=self.ret_steps, extra_args=extra_args, callback=k_callback, disable=disable_pbar, **self.extra_options) samples = model_wrap.inner_model.model_sampling.inverse_noise_scaling(sigmas[-1], samples) return samples def append_zero(x): return torch.cat([x, x.new_zeros([1])]) def get_sigmas_karras(n, sigma_min, sigma_max, rho=7., device='cpu'): """Constructs the noise schedule of Karras et al. (2022).""" ramp = torch.linspace(0, 1, n, device=device) min_inv_rho = sigma_min ** (1 / rho) max_inv_rho = sigma_max ** (1 / rho) sigmas = (max_inv_rho + ramp * (min_inv_rho - max_inv_rho)) ** rho return append_zero(sigmas).to(device) def get_sigmas_exponential(n, sigma_min, sigma_max, device='cpu'): """Constructs an exponential noise schedule.""" sigmas = torch.linspace(math.log(sigma_max), math.log(sigma_min), n, device=device).exp() return append_zero(sigmas) def get_sigmas_polyexponential(n, sigma_min, sigma_max, rho=1., device='cpu'): """Constructs an polynomial in log sigma noise schedule.""" ramp = torch.linspace(1, 0, n, device=device) ** rho sigmas = torch.exp(ramp * (math.log(sigma_max) - math.log(sigma_min)) + math.log(sigma_min)) return append_zero(sigmas) def get_sigmas_vp(n, beta_d=19.9, beta_min=0.1, eps_s=1e-3, device='cpu'): """Constructs a continuous VP noise schedule.""" t = torch.linspace(1, eps_s, n, device=device) sigmas = torch.sqrt(torch.special.expm1(beta_d * t ** 2 / 2 + beta_min * t)) return append_zero(sigmas) def get_sigmas_laplace(n, sigma_min, sigma_max, mu=0., beta=0.5, device='cpu'): """Constructs the noise schedule proposed by Tiankai et al. (2024). """ epsilon = 1e-5 # avoid log(0) x = torch.linspace(0, 1, n, device=device) clamp = lambda x: torch.clamp(x, min=sigma_min, max=sigma_max) lmb = mu - beta * torch.sign(0.5-x) * torch.log(1 - 2 * torch.abs(0.5-x) + epsilon) sigmas = clamp(torch.exp(lmb)) return sigmas def to_d(x, sigma, denoised): """Converts a denoiser output to a Karras ODE derivative.""" return (x - denoised) / utils.append_dims(sigma, x.ndim) def get_ancestral_step(sigma_from, sigma_to, eta=1.): """Calculates the noise level (sigma_down) to step down to and the amount of noise to add (sigma_up) when doing an ancestral sampling step.""" if not eta: return sigma_to, 0. sigma_up = min(sigma_to, eta * (sigma_to ** 2 * (sigma_from ** 2 - sigma_to ** 2) / sigma_from ** 2) ** 0.5) sigma_down = (sigma_to ** 2 - sigma_up ** 2) ** 0.5 return sigma_down, sigma_up def default_noise_sampler(x, seed=None): if seed is not None: generator = torch.Generator(device=x.device) generator.manual_seed(seed) else: generator = None return lambda sigma, sigma_next: torch.randn(x.size(), dtype=x.dtype, layout=x.layout, device=x.device, generator=generator) class BatchedBrownianTree: """A wrapper around torchsde.BrownianTree that enables batches of entropy.""" def __init__(self, x, t0, t1, seed=None, **kwargs): self.cpu_tree = kwargs.pop("cpu", True) t0, t1, self.sign = self.sort(t0, t1) w0 = kwargs.pop('w0', None) if w0 is None: w0 = torch.zeros_like(x) self.batched = False if seed is None: seed = (torch.randint(0, 2 ** 63 - 1, ()).item(),) elif isinstance(seed, (tuple, list)): if len(seed) != x.shape[0]: raise ValueError("Passing a list or tuple of seeds to BatchedBrownianTree requires a length matching the batch size.") self.batched = True w0 = w0[0] else: seed = (seed,) if self.cpu_tree: t0, w0, t1 = t0.detach().cpu(), w0.detach().cpu(), t1.detach().cpu() self.trees = tuple(torchsde.BrownianTree(t0, w0, t1, entropy=s, **kwargs) for s in seed) @staticmethod def sort(a, b): return (a, b, 1) if a < b else (b, a, -1) def __call__(self, t0, t1): t0, t1, sign = self.sort(t0, t1) device, dtype = t0.device, t0.dtype if self.cpu_tree: t0, t1 = t0.detach().cpu().float(), t1.detach().cpu().float() w = torch.stack([tree(t0, t1) for tree in self.trees]).to(device=device, dtype=dtype) * (self.sign * sign) return w if self.batched else w[0] class BrownianTreeNoiseSampler: """A noise sampler backed by a torchsde.BrownianTree. Args: x (Tensor): The tensor whose shape, device and dtype to use to generate random samples. sigma_min (float): The low end of the valid interval. sigma_max (float): The high end of the valid interval. seed (int or List[int]): The random seed. If a list of seeds is supplied instead of a single integer, then the noise sampler will use one BrownianTree per batch item, each with its own seed. transform (callable): A function that maps sigma to the sampler's internal timestep. """ def __init__(self, x, sigma_min, sigma_max, seed=None, transform=lambda x: x, cpu=False): self.transform = transform t0, t1 = self.transform(torch.as_tensor(sigma_min)), self.transform(torch.as_tensor(sigma_max)) self.tree = BatchedBrownianTree(x, t0, t1, seed, cpu=cpu) def __call__(self, sigma, sigma_next): t0, t1 = self.transform(torch.as_tensor(sigma)), self.transform(torch.as_tensor(sigma_next)) return self.tree(t0, t1) / (t1 - t0).abs().sqrt() def sigma_to_half_log_snr(sigma, model_sampling): """Convert sigma to half-logSNR log(alpha_t / sigma_t).""" if isinstance(model_sampling, comfy.model_sampling.CONST): # log((1 - t) / t) = log((1 - sigma) / sigma) return sigma.logit().neg() return sigma.log().neg() def half_log_snr_to_sigma(half_log_snr, model_sampling): """Convert half-logSNR log(alpha_t / sigma_t) to sigma.""" if isinstance(model_sampling, comfy.model_sampling.CONST): # 1 / (1 + exp(half_log_snr)) return half_log_snr.neg().sigmoid() return half_log_snr.neg().exp() def offset_first_sigma_for_snr(sigmas, model_sampling, percent_offset=1e-4): """Adjust the first sigma to avoid invalid logSNR.""" if len(sigmas) <= 1: return sigmas if isinstance(model_sampling, comfy.model_sampling.CONST): if sigmas[0] >= 1: sigmas = sigmas.clone() sigmas[0] = model_sampling.percent_to_sigma(percent_offset) return sigmas def ei_h_phi_1(h: torch.Tensor) -> torch.Tensor: """Compute the result of h*phi_1(h) in exponential integrator methods.""" return torch.expm1(h) def ei_h_phi_2(h: torch.Tensor) -> torch.Tensor: """Compute the result of h*phi_2(h) in exponential integrator methods.""" return (torch.expm1(h) - h) / h def linear_multistep_coeff(order, t, i, j): if order - 1 > i: raise ValueError(f'Order {order} too high for step {i}') def fn(tau): prod = 1. for k in range(order): if j == k: continue prod *= (tau - t[i - k]) / (t[i - j] - t[i - k]) return prod return integrate.quad(fn, t[i], t[i + 1], epsrel=1e-4)[0] class PIDStepSizeController: """A PID controller for ODE adaptive step size control.""" def __init__(self, h, pcoeff, icoeff, dcoeff, order=1, accept_safety=0.81, eps=1e-8): self.h = h self.b1 = (pcoeff + icoeff + dcoeff) / order self.b2 = -(pcoeff + 2 * dcoeff) / order self.b3 = dcoeff / order self.accept_safety = accept_safety self.eps = eps self.errs = [] def limiter(self, x): return 1 + math.atan(x - 1) def propose_step(self, error): inv_error = 1 / (float(error) + self.eps) if not self.errs: self.errs = [inv_error, inv_error, inv_error] self.errs[0] = inv_error factor = self.errs[0] ** self.b1 * self.errs[1] ** self.b2 * self.errs[2] ** self.b3 factor = self.limiter(factor) accept = factor >= self.accept_safety if accept: self.errs[2] = self.errs[1] self.errs[1] = self.errs[0] self.h *= factor return accept class DPMSolver(nn.Module): """DPM-Solver. See https://arxiv.org/abs/2206.00927.""" def __init__(self, model,extra_args=None, eps_callback=None, info_callback=None): super().__init__() self.model = model self.extra_args = {} if extra_args is None else extra_args self.eps_callback = eps_callback self.info_callback = info_callback def t(self, sigma): return -sigma.log() def sigma(self, t): return t.neg().exp() def eps(self, eps_cache, key, x, t, final_step = False, *args, **kwargs): if key in eps_cache: return eps_cache[key], eps_cache sigma = self.sigma(t) * x.new_ones([x.shape[0]]) model_output = self.model(x, sigma, *args, **self.extra_args, **kwargs) eps = (x - model_output) / self.sigma(t) if self.eps_callback is not None: self.eps_callback() return eps, {key: eps, **eps_cache} def dpm_solver_1_step(self, x, t, t_next, eps_cache=None,final_step=False): eps_cache = {} if eps_cache is None else eps_cache h = t_next - t eps, eps_cache = self.eps(eps_cache, 'eps', x, t,final_step=final_step) x_1 = x - self.sigma(t_next) * h.expm1() * eps return x_1, eps_cache def dpm_solver_2_step(self, x, t, t_next, r1=1 / 2, eps_cache=None,final_step=False): eps_cache = {} if eps_cache is None else eps_cache h = t_next - t eps, eps_cache = self.eps(eps_cache, 'eps', x, t) s1 = t + r1 * h u1 = x - self.sigma(s1) * (r1 * h).expm1() * eps eps_r1, eps_cache = self.eps(eps_cache, 'eps_r1', u1, s1,final_step=final_step) x_2 = x - self.sigma(t_next) * h.expm1() * eps - self.sigma(t_next) / (2 * r1) * h.expm1() * (eps_r1 - eps) return x_2, eps_cache def dpm_solver_3_step(self, x, t, t_next, r1=1 / 3, r2=2 / 3, eps_cache=None,final_step=False): eps_cache = {} if eps_cache is None else eps_cache h = t_next - t eps, eps_cache = self.eps(eps_cache, 'eps', x, t) s1 = t + r1 * h s2 = t + r2 * h u1 = x - self.sigma(s1) * (r1 * h).expm1() * eps eps_r1, eps_cache = self.eps(eps_cache, 'eps_r1', u1, s1) u2 = x - self.sigma(s2) * (r2 * h).expm1() * eps - self.sigma(s2) * (r2 / r1) * ((r2 * h).expm1() / (r2 * h) - 1) * (eps_r1 - eps) eps_r2, eps_cache = self.eps(eps_cache, 'eps_r2', u2, s2,final_step=final_step) x_3 = x - self.sigma(t_next) * h.expm1() * eps - self.sigma(t_next) / r2 * (h.expm1() / h - 1) * (eps_r2 - eps) return x_3, eps_cache def dpm_solver_fast(self, x, t_start, t_end, nfe, eta=0., s_noise=1., noise_sampler=None): noise_sampler = default_noise_sampler(x, seed=self.extra_args.get("seed", None)) if noise_sampler is None else noise_sampler if not t_end > t_start and eta: raise ValueError('eta must be 0 for reverse sampling') m = math.floor(nfe / 3) + 1 ts = torch.linspace(t_start, t_end, m + 1, device=x.device) if nfe % 3 == 0: orders = [3] * (m - 2) + [2, 1] else: orders = [3] * (m - 1) + [nfe % 3] for i in range(len(orders)): eps_cache = {} t, t_next = ts[i], ts[i + 1] if eta: sd, su = get_ancestral_step(self.sigma(t), self.sigma(t_next), eta) t_next_ = torch.minimum(t_end, self.t(sd)) su = (self.sigma(t_next) ** 2 - self.sigma(t_next_) ** 2) ** 0.5 else: t_next_, su = t_next, 0. final_step = True if t_next_ > t_end - 1e-5 else False eps, eps_cache = self.eps(eps_cache, 'eps', x, t, final_step=final_step) denoised = x - self.sigma(t) * eps if self.info_callback is not None: self.info_callback({'x': x, 'i': i, 't': ts[i], 't_up': t, 'denoised': denoised}) if orders[i] == 1: x, eps_cache = self.dpm_solver_1_step(x, t, t_next_, eps_cache=eps_cache, final_step=final_step) elif orders[i] == 2: x, eps_cache = self.dpm_solver_2_step(x, t, t_next_, eps_cache=eps_cache, final_step=final_step) else: x, eps_cache = self.dpm_solver_3_step(x, t, t_next_, eps_cache=eps_cache, final_step=final_step) x = x + su * s_noise * noise_sampler(self.sigma(t), self.sigma(t_next)) return x def dpm_solver_adaptive(self, x, t_start, t_end, order=3, rtol=0.05, atol=0.0078, h_init=0.05, pcoeff=0., icoeff=1., dcoeff=0., accept_safety=0.81, eta=0., s_noise=1., noise_sampler=None): noise_sampler = default_noise_sampler(x, seed=self.extra_args.get("seed", None)) if noise_sampler is None else noise_sampler if order not in {2, 3}: raise ValueError('order should be 2 or 3') forward = t_end > t_start if not forward and eta: raise ValueError('eta must be 0 for reverse sampling') h_init = abs(h_init) * (1 if forward else -1) atol = torch.tensor(atol) rtol = torch.tensor(rtol) s = t_start x_prev = x accept = True pid = PIDStepSizeController(h_init, pcoeff, icoeff, dcoeff, 1.5 if eta else order, accept_safety) info = {'steps': 0, 'nfe': 0, 'n_accept': 0, 'n_reject': 0} while s < t_end - 1e-5 if forward else s > t_end + 1e-5: eps_cache = {} t = torch.minimum(t_end, s + pid.h) if forward else torch.maximum(t_end, s + pid.h) if eta: sd, su = get_ancestral_step(self.sigma(s), self.sigma(t), eta) t_ = torch.minimum(t_end, self.t(sd)) su = (self.sigma(t) ** 2 - self.sigma(t_) ** 2) ** 0.5 else: t_, su = t, 0. if forward: final_step = True if t_ > t_end - 1e-5 else False else: final_step = True if t_ < t_end + 1e-5 else False eps, eps_cache = self.eps(eps_cache, 'eps', x, s, final_step=final_step) denoised = x - self.sigma(s) * eps if order == 2: x_low, eps_cache = self.dpm_solver_1_step(x, s, t_, eps_cache=eps_cache, final_step=final_step) x_high, eps_cache = self.dpm_solver_2_step(x, s, t_, eps_cache=eps_cache, final_step=final_step) else: x_low, eps_cache = self.dpm_solver_2_step(x, s, t_, r1=1 / 3, eps_cache=eps_cache, final_step=final_step) x_high, eps_cache = self.dpm_solver_3_step(x, s, t_, eps_cache=eps_cache, final_step=final_step) delta = torch.maximum(atol, rtol * torch.maximum(x_low.abs(), x_prev.abs())) error = torch.linalg.norm((x_low - x_high) / delta) / x.numel() ** 0.5 accept = pid.propose_step(error) if accept: x_prev = x_low x = x_high + su * s_noise * noise_sampler(self.sigma(s), self.sigma(t)) s = t info['n_accept'] += 1 else: info['n_reject'] += 1 info['nfe'] += order info['steps'] += 1 if self.info_callback is not None: self.info_callback({'x': x, 'i': info['steps'] - 1, 't': s, 't_up': s, 'denoised': denoised, 'error': error, 'h': pid.h, **info}) return x, info @torch.no_grad() def sample_euler(model, x, sigmas, extra_args=None, callback=None, disable=None, s_churn=0., s_tmin=0., s_tmax=float('inf'), s_noise=1., threshold=0.025, ret_steps=5): """Implements Algorithm 2 (Euler steps) from Karras et al. (2022).""" extra_args = {} if extra_args is None else extra_args s_in = x.new_ones([x.shape[0]]) #------------add for EasyCache------------ total_steps = len(sigmas) - 1 ec = EasyCacheState(total_steps, threshold=threshold, ret_steps=ret_steps) #----------------------------------------- for i in trange(total_steps, disable=disable): if s_churn > 0: gamma = min(s_churn / (len(sigmas) - 1), 2 ** 0.5 - 1) if s_tmin <= sigmas[i] <= s_tmax else 0. sigma_hat = sigmas[i] * (gamma + 1) else: gamma = 0 sigma_hat = sigmas[i] if gamma > 0: eps = torch.randn_like(x) * s_noise x = x + eps * (sigma_hat ** 2 - sigmas[i] ** 2) ** 0.5 #------------add for EasyCache------------ x_step_in = x ec.begin_step(i, total_steps, x_step_in) denoised = ec.model_call(model, x, sigma_hat * s_in, **extra_args) #----------------------------------------- d = to_d(x, sigma_hat, 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 # Euler method x = x + d * dt ec.end_step(x_step_in) return x @torch.no_grad() def sample_heun(model, x, sigmas, extra_args=None, callback=None, disable=None, s_churn=0., s_tmin=0., s_tmax=float('inf'), s_noise=1., threshold=0.025, ret_steps=5): """Implements Algorithm 2 (Heun steps) from Karras et al. (2022).""" extra_args = {} if extra_args is None else extra_args s_in = x.new_ones([x.shape[0]]) #------------add for EasyCache------------ total_steps = len(sigmas) - 1 ec = EasyCacheState(total_steps, threshold=threshold, ret_steps=ret_steps) #----------------------------------------- for i in trange(total_steps, disable=disable): if s_churn > 0: gamma = min(s_churn / (len(sigmas) - 1), 2 ** 0.5 - 1) if s_tmin <= sigmas[i] <= s_tmax else 0. sigma_hat = sigmas[i] * (gamma + 1) else: gamma = 0 sigma_hat = sigmas[i] sigma_hat = sigmas[i] * (gamma + 1) if gamma > 0: eps = torch.randn_like(x) * s_noise x = x + eps * (sigma_hat ** 2 - sigmas[i] ** 2) ** 0.5 #------------add for EasyCache------------ x_step_in = x ec.begin_step(i, total_steps, x_step_in) denoised = ec.model_call(model, x, sigma_hat * s_in, **extra_args) #----------------------------------------- d = to_d(x, sigma_hat, 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 if sigmas[i + 1] == 0: # Euler method x = x + d * dt else: # Heun's method x_2 = x + d * dt denoised_2 = ec.model_call(model, x_2, sigmas[i + 1] * s_in, **extra_args) d_2 = to_d(x_2, sigmas[i + 1], denoised_2) d_prime = (d + d_2) / 2 x = x + d_prime * dt ec.end_step(x_step_in) return x @torch.no_grad() def sample_dpm_2(model, x, sigmas, extra_args=None, callback=None, disable=None, s_churn=0., s_tmin=0., s_tmax=float('inf'), s_noise=1., threshold=0.025, ret_steps=5): """A sampler inspired by DPM-Solver-2 and Algorithm 2 from Karras et al. (2022).""" extra_args = {} if extra_args is None else extra_args s_in = x.new_ones([x.shape[0]]) #------------add for EasyCache------------ total_steps = len(sigmas) - 1 ec = EasyCacheState(total_steps, threshold=threshold, ret_steps=ret_steps) #----------------------------------------- for i in trange(total_steps, disable=disable): if s_churn > 0: gamma = min(s_churn / (len(sigmas) - 1), 2 ** 0.5 - 1) if s_tmin <= sigmas[i] <= s_tmax else 0. sigma_hat = sigmas[i] * (gamma + 1) else: gamma = 0 sigma_hat = sigmas[i] if gamma > 0: eps = torch.randn_like(x) * s_noise x = x + eps * (sigma_hat ** 2 - sigmas[i] ** 2) ** 0.5 #------------add for EasyCache------------ x_step_in = x ec.begin_step(i, total_steps, x_step_in) denoised = ec.model_call(model, x, sigma_hat * s_in, **extra_args) #----------------------------------------- d = to_d(x, sigma_hat, denoised) if callback is not None: callback({'x': x, 'i': i, 'sigma': sigmas[i], 'sigma_hat': sigma_hat, 'denoised': denoised}) if sigmas[i + 1] == 0: # Euler method dt = sigmas[i + 1] - sigma_hat x = x + d * dt else: # DPM-Solver-2 sigma_mid = sigma_hat.log().lerp(sigmas[i + 1].log(), 0.5).exp() dt_1 = sigma_mid - sigma_hat dt_2 = sigmas[i + 1] - sigma_hat x_2 = x + d * dt_1 denoised_2 = ec.model_call(model, x_2, sigma_mid * s_in, **extra_args) d_2 = to_d(x_2, sigma_mid, denoised_2) x = x + d_2 * dt_2 ec.end_step(x_step_in) return x @torch.no_grad() def sample_lms(model, x, sigmas, extra_args=None, callback=None, disable=None, order=4, threshold=0.025, ret_steps=5): extra_args = {} if extra_args is None else extra_args s_in = x.new_ones([x.shape[0]]) sigmas_cpu = sigmas.detach().cpu().numpy() ds = [] #------------add for EasyCache------------ total_steps = len(sigmas) - 1 ec = EasyCacheState(total_steps, threshold=threshold, ret_steps=ret_steps) #----------------------------------------- for i in trange(total_steps, disable=disable): #------------add for EasyCache------------ x_step_in = x ec.begin_step(i, total_steps, x_step_in) denoised = ec.model_call(model, x, sigmas[i] * s_in, **extra_args) #----------------------------------------- d = to_d(x, sigmas[i], denoised) ds.append(d) if len(ds) > order: ds.pop(0) if callback is not None: callback({'x': x, 'i': i, 'sigma': sigmas[i], 'sigma_hat': sigmas[i], 'denoised': denoised}) if sigmas[i + 1] == 0: # Denoising step x = denoised else: cur_order = min(i + 1, order) coeffs = [linear_multistep_coeff(cur_order, sigmas_cpu, i, j) for j in range(cur_order)] x = x + sum(coeff * d for coeff, d in zip(coeffs, reversed(ds))) ec.end_step(x_step_in) return x @torch.no_grad() def sample_dpm_fast(model, x, sigma_min, sigma_max, n, extra_args=None, callback=None, disable=None, eta=0., s_noise=1., noise_sampler=None, threshold=0.025, ret_steps=5): """DPM-Solver-Fast (fixed step size). EasyCache 注入到底层 model 调用。""" if sigma_min <= 0 or sigma_max <= 0: raise ValueError('sigma_min and sigma_max must not be 0') #------------add for EasyCache------------ ec = EasyCacheState(n, threshold=threshold, ret_steps=ret_steps) class _ECWrappedModel: def __init__(self, base_model): self._base = base_model def __call__(self, x, sigma, **kw): return ec.model_call(self._base, x, sigma, **kw) cur_step = {"i": 0} #----------------------------------------- with tqdm(total=n, disable=disable) as pbar: wrapped = _ECWrappedModel(model) dpm_solver = DPMSolver(wrapped, extra_args=extra_args, eps_callback=pbar.update) def _info(info): i = info.get("i", cur_step["i"]) ec.begin_step(i, n, x) ec.end_step(x) cur_step["i"] = i + 1 if callback is not None: callback({'sigma': dpm_solver.sigma(info['t']), 'sigma_hat': dpm_solver.sigma(info['t_up']), **info}) dpm_solver.info_callback = _info return dpm_solver.dpm_solver_fast(x, dpm_solver.t(torch.tensor(sigma_max)), dpm_solver.t(torch.tensor(sigma_min)), n, eta, s_noise, noise_sampler) @torch.no_grad() def sample_dpm_adaptive(model, x, sigma_min, sigma_max, n, extra_args=None, callback=None, disable=None, order=3, rtol=0.05, atol=0.0078, h_init=0.05, pcoeff=0., icoeff=1., dcoeff=0., accept_safety=0.81, eta=0., s_noise=1., noise_sampler=None, return_info=False, threshold=0.025, ret_steps=5): """DPM-Solver-12 / 23 (adaptive step size). EasyCache 注入到底层 model 调用。""" if sigma_min <= 0 or sigma_max <= 0: raise ValueError('sigma_min and sigma_max must not be 0') #------------add for EasyCache------------ ec = EasyCacheState(max(n, 1), threshold=threshold, ret_steps=ret_steps) class _ECWrappedModel: def __init__(self, base_model): self._base = base_model def __call__(self, x, sigma, **kw): return ec.model_call(self._base, x, sigma, **kw) cur_step = {"i": 0} #----------------------------------------- with tqdm(disable=disable) as pbar: wrapped = _ECWrappedModel(model) dpm_solver = DPMSolver(wrapped, extra_args=extra_args, eps_callback=pbar.update) def _info(info): i = info.get("i", cur_step["i"]) ec.begin_step(i, max(n, 1), x) ec.end_step(x) cur_step["i"] = i + 1 if callback is not None: callback({'sigma': dpm_solver.sigma(info['t']), 'sigma_hat': dpm_solver.sigma(info['t_up']), **info}) dpm_solver.info_callback = _info x_out, info = dpm_solver.dpm_solver_adaptive(x, dpm_solver.t(torch.tensor(sigma_max)), dpm_solver.t(torch.tensor(sigma_min)), order, rtol, atol, h_init, pcoeff, icoeff, dcoeff, accept_safety, eta, s_noise, noise_sampler) if return_info: return x_out, info return x_out @torch.no_grad() def sample_dpmpp_2m(model, x, sigmas, extra_args=None, callback=None, disable=None, threshold=0.025, ret_steps=5): """DPM-Solver++(2M).""" extra_args = {} if extra_args is None else extra_args s_in = x.new_ones([x.shape[0]]) sigma_fn = lambda t: t.neg().exp() t_fn = lambda sigma: sigma.log().neg() old_denoised = None #------------add for EasyCache------------ total_steps = len(sigmas) - 1 ec = EasyCacheState(total_steps, threshold=threshold, ret_steps=ret_steps) #----------------------------------------- for i in trange(total_steps, disable=disable): #------------add for EasyCache------------ x_step_in = x ec.begin_step(i, total_steps, x_step_in) denoised = ec.model_call(model, x, sigmas[i] * s_in, **extra_args) #----------------------------------------- if callback is not None: callback({'x': x, 'i': i, 'sigma': sigmas[i], 'sigma_hat': sigmas[i], 'denoised': denoised}) t, t_next = t_fn(sigmas[i]), t_fn(sigmas[i + 1]) h = t_next - t if old_denoised is None or sigmas[i + 1] == 0: x = (sigma_fn(t_next) / sigma_fn(t)) * x - (-h).expm1() * denoised else: h_last = t - t_fn(sigmas[i - 1]) r = h_last / h denoised_d = (1 + 1 / (2 * r)) * denoised - (1 / (2 * r)) * old_denoised x = (sigma_fn(t_next) / sigma_fn(t)) * x - (-h).expm1() * denoised_d old_denoised = denoised ec.end_step(x_step_in) return x @torch.no_grad() def sample_heunpp2(model, x, sigmas, extra_args=None, callback=None, disable=None, s_churn=0., s_tmin=0., s_tmax=float('inf'), s_noise=1., threshold=0.025, ret_steps=5): # From MIT licensed: https://github.com/Carzit/sd-webui-samplers-scheduler/ extra_args = {} if extra_args is None else extra_args s_in = x.new_ones([x.shape[0]]) s_end = sigmas[-1] #------------add for EasyCache------------ total_steps = len(sigmas) - 1 ec = EasyCacheState(total_steps, threshold=threshold, ret_steps=ret_steps) #----------------------------------------- for i in trange(total_steps, disable=disable): gamma = min(s_churn / (len(sigmas) - 1), 2 ** 0.5 - 1) if s_tmin <= sigmas[i] <= s_tmax else 0. eps = torch.randn_like(x) * s_noise sigma_hat = sigmas[i] * (gamma + 1) if gamma > 0: x = x + eps * (sigma_hat ** 2 - sigmas[i] ** 2) ** 0.5 #------------add for EasyCache------------ x_step_in = x ec.begin_step(i, total_steps, x_step_in) denoised = ec.model_call(model, x, sigma_hat * s_in, **extra_args) #----------------------------------------- d = to_d(x, sigma_hat, 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 if sigmas[i + 1] == s_end: # Euler method x = x + d * dt elif sigmas[i + 2] == s_end: # Heun's method x_2 = x + d * dt denoised_2 = ec.model_call(model, x_2, sigmas[i + 1] * s_in, **extra_args) d_2 = to_d(x_2, sigmas[i + 1], denoised_2) w = 2 * sigmas[0] w2 = sigmas[i+1]/w w1 = 1 - w2 d_prime = d * w1 + d_2 * w2 x = x + d_prime * dt else: # Heun++ x_2 = x + d * dt denoised_2 = ec.model_call(model, x_2, sigmas[i + 1] * s_in, **extra_args) d_2 = to_d(x_2, sigmas[i + 1], denoised_2) dt_2 = sigmas[i + 2] - sigmas[i + 1] x_3 = x_2 + d_2 * dt_2 denoised_3 = ec.model_call(model, x_3, sigmas[i + 2] * s_in, **extra_args) d_3 = to_d(x_3, sigmas[i + 2], denoised_3) w = 3 * sigmas[0] w2 = sigmas[i + 1] / w w3 = sigmas[i + 2] / w w1 = 1 - w2 - w3 d_prime = w1 * d + w2 * d_2 + w3 * d_3 x = x + d_prime * dt ec.end_step(x_step_in) return x #From https://github.com/zju-pi/diff-sampler/blob/main/diff-solvers-main/solvers.py #under Apache 2 license def sample_ipndm(model, x, sigmas, extra_args=None, callback=None, disable=None, max_order=4, threshold=0.025, ret_steps=5): extra_args = {} if extra_args is None else extra_args s_in = x.new_ones([x.shape[0]]) x_next = x buffer_model = [] #------------add for EasyCache------------ total_steps = len(sigmas) - 1 ec = EasyCacheState(total_steps, threshold=threshold, ret_steps=ret_steps) #----------------------------------------- for i in trange(total_steps, disable=disable): t_cur = sigmas[i] t_next = sigmas[i + 1] x_cur = x_next #------------add for EasyCache------------ x_step_in = x_cur ec.begin_step(i, total_steps, x_step_in) denoised = ec.model_call(model, x_cur, t_cur * s_in, **extra_args) #----------------------------------------- if callback is not None: callback({'x': x, 'i': i, 'sigma': sigmas[i], 'sigma_hat': sigmas[i], 'denoised': denoised}) d_cur = (x_cur - denoised) / t_cur order = min(max_order, i+1) if t_next == 0: # Denoising step x_next = denoised elif order == 1: # First Euler step. x_next = x_cur + (t_next - t_cur) * d_cur elif order == 2: # Use one history point. x_next = x_cur + (t_next - t_cur) * (3 * d_cur - buffer_model[-1]) / 2 elif order == 3: # Use two history points. x_next = x_cur + (t_next - t_cur) * (23 * d_cur - 16 * buffer_model[-1] + 5 * buffer_model[-2]) / 12 elif order == 4: # Use three history points. x_next = x_cur + (t_next - t_cur) * (55 * d_cur - 59 * buffer_model[-1] + 37 * buffer_model[-2] - 9 * buffer_model[-3]) / 24 if len(buffer_model) == max_order - 1: for k in range(max_order - 2): buffer_model[k] = buffer_model[k+1] buffer_model[-1] = d_cur else: buffer_model.append(d_cur) ec.end_step(x_step_in) return x_next #From https://github.com/zju-pi/diff-sampler/blob/main/diff-solvers-main/solvers.py #under Apache 2 license def sample_ipndm_v(model, x, sigmas, extra_args=None, callback=None, disable=None, max_order=4, threshold=0.025, ret_steps=5): extra_args = {} if extra_args is None else extra_args s_in = x.new_ones([x.shape[0]]) x_next = x t_steps = sigmas buffer_model = [] #------------add for EasyCache------------ total_steps = len(sigmas) - 1 ec = EasyCacheState(total_steps, threshold=threshold, ret_steps=ret_steps) #----------------------------------------- for i in trange(total_steps, disable=disable): t_cur = sigmas[i] t_next = sigmas[i + 1] x_cur = x_next #------------add for EasyCache------------ x_step_in = x_cur ec.begin_step(i, total_steps, x_step_in) denoised = ec.model_call(model, x_cur, t_cur * s_in, **extra_args) #----------------------------------------- if callback is not None: callback({'x': x, 'i': i, 'sigma': sigmas[i], 'sigma_hat': sigmas[i], 'denoised': denoised}) d_cur = (x_cur - denoised) / t_cur order = min(max_order, i+1) if t_next == 0: # Denoising step x_next = denoised elif order == 1: # First Euler step. x_next = x_cur + (t_next - t_cur) * d_cur elif order == 2: # Use one history point. h_n = (t_next - t_cur) h_n_1 = (t_cur - t_steps[i-1]) coeff1 = (2 + (h_n / h_n_1)) / 2 coeff2 = -(h_n / h_n_1) / 2 x_next = x_cur + (t_next - t_cur) * (coeff1 * d_cur + coeff2 * buffer_model[-1]) elif order == 3: # Use two history points. h_n = (t_next - t_cur) h_n_1 = (t_cur - t_steps[i-1]) h_n_2 = (t_steps[i-1] - t_steps[i-2]) temp = (1 - h_n / (3 * (h_n + h_n_1)) * (h_n * (h_n + h_n_1)) / (h_n_1 * (h_n_1 + h_n_2))) / 2 coeff1 = (2 + (h_n / h_n_1)) / 2 + temp coeff2 = -(h_n / h_n_1) / 2 - (1 + h_n_1 / h_n_2) * temp coeff3 = temp * h_n_1 / h_n_2 x_next = x_cur + (t_next - t_cur) * (coeff1 * d_cur + coeff2 * buffer_model[-1] + coeff3 * buffer_model[-2]) elif order == 4: # Use three history points. h_n = (t_next - t_cur) h_n_1 = (t_cur - t_steps[i-1]) h_n_2 = (t_steps[i-1] - t_steps[i-2]) h_n_3 = (t_steps[i-2] - t_steps[i-3]) temp1 = (1 - h_n / (3 * (h_n + h_n_1)) * (h_n * (h_n + h_n_1)) / (h_n_1 * (h_n_1 + h_n_2))) / 2 temp2 = ((1 - h_n / (3 * (h_n + h_n_1))) / 2 + (1 - h_n / (2 * (h_n + h_n_1))) * h_n / (6 * (h_n + h_n_1 + h_n_2))) \ * (h_n * (h_n + h_n_1) * (h_n + h_n_1 + h_n_2)) / (h_n_1 * (h_n_1 + h_n_2) * (h_n_1 + h_n_2 + h_n_3)) coeff1 = (2 + (h_n / h_n_1)) / 2 + temp1 + temp2 coeff2 = -(h_n / h_n_1) / 2 - (1 + h_n_1 / h_n_2) * temp1 - (1 + (h_n_1 / h_n_2) + (h_n_1 * (h_n_1 + h_n_2) / (h_n_2 * (h_n_2 + h_n_3)))) * temp2 coeff3 = temp1 * h_n_1 / h_n_2 + ((h_n_1 / h_n_2) + (h_n_1 * (h_n_1 + h_n_2) / (h_n_2 * (h_n_2 + h_n_3))) * (1 + h_n_2 / h_n_3)) * temp2 coeff4 = -temp2 * (h_n_1 * (h_n_1 + h_n_2) / (h_n_2 * (h_n_2 + h_n_3))) * h_n_1 / h_n_2 x_next = x_cur + (t_next - t_cur) * (coeff1 * d_cur + coeff2 * buffer_model[-1] + coeff3 * buffer_model[-2] + coeff4 * buffer_model[-3]) if len(buffer_model) == max_order - 1: for k in range(max_order - 2): buffer_model[k] = buffer_model[k+1] buffer_model[-1] = d_cur.detach() else: buffer_model.append(d_cur.detach()) ec.end_step(x_step_in) return x_next #From https://github.com/zju-pi/diff-sampler/blob/main/diff-solvers-main/solvers.py #under Apache 2 license @torch.no_grad() def sample_deis(model, x, sigmas, extra_args=None, callback=None, disable=None, max_order=3, deis_mode='tab', threshold=0.025, ret_steps=5): extra_args = {} if extra_args is None else extra_args s_in = x.new_ones([x.shape[0]]) x_next = x t_steps = sigmas coeff_list = deis.get_deis_coeff_list(t_steps, max_order, deis_mode=deis_mode) buffer_model = [] #------------add for EasyCache------------ total_steps = len(sigmas) - 1 ec = EasyCacheState(total_steps, threshold=threshold, ret_steps=ret_steps) #----------------------------------------- for i in trange(total_steps, disable=disable): t_cur = sigmas[i] t_next = sigmas[i + 1] x_cur = x_next #------------add for EasyCache------------ x_step_in = x_cur ec.begin_step(i, total_steps, x_step_in) denoised = ec.model_call(model, x_cur, t_cur * s_in, **extra_args) #----------------------------------------- if callback is not None: callback({'x': x, 'i': i, 'sigma': sigmas[i], 'sigma_hat': sigmas[i], 'denoised': denoised}) d_cur = (x_cur - denoised) / t_cur order = min(max_order, i+1) if t_next <= 0: order = 1 if order == 1: # First Euler step. x_next = x_cur + (t_next - t_cur) * d_cur elif order == 2: # Use one history point. coeff_cur, coeff_prev1 = coeff_list[i] x_next = x_cur + coeff_cur * d_cur + coeff_prev1 * buffer_model[-1] elif order == 3: # Use two history points. coeff_cur, coeff_prev1, coeff_prev2 = coeff_list[i] x_next = x_cur + coeff_cur * d_cur + coeff_prev1 * buffer_model[-1] + coeff_prev2 * buffer_model[-2] elif order == 4: # Use three history points. coeff_cur, coeff_prev1, coeff_prev2, coeff_prev3 = coeff_list[i] x_next = x_cur + coeff_cur * d_cur + coeff_prev1 * buffer_model[-1] + coeff_prev2 * buffer_model[-2] + coeff_prev3 * buffer_model[-3] if len(buffer_model) == max_order - 1: for k in range(max_order - 2): buffer_model[k] = buffer_model[k+1] buffer_model[-1] = d_cur.detach() else: buffer_model.append(d_cur.detach()) ec.end_step(x_step_in) return x_next @torch.no_grad() def res_multistep(model, x, sigmas, extra_args=None, callback=None, disable=None, s_noise=1., noise_sampler=None, eta=1., cfg_pp=False, threshold=0.025, ret_steps=5): extra_args = {} if extra_args is None else extra_args seed = extra_args.get("seed", None) noise_sampler = default_noise_sampler(x, seed=seed) if noise_sampler is None else noise_sampler s_in = x.new_ones([x.shape[0]]) sigma_fn = lambda t: t.neg().exp() t_fn = lambda sigma: sigma.log().neg() phi1_fn = lambda t: torch.expm1(t) / t phi2_fn = lambda t: (phi1_fn(t) - 1.0) / t old_sigma_down = None old_denoised = None uncond_denoised = None def post_cfg_function(args): nonlocal uncond_denoised uncond_denoised = args["uncond_denoised"] return args["denoised"] if cfg_pp: model_options = extra_args.get("model_options", {}).copy() extra_args["model_options"] = comfy.model_patcher.set_model_options_post_cfg_function(model_options, post_cfg_function, disable_cfg1_optimization=True) #------------add for EasyCache------------ total_steps = len(sigmas) - 1 ec = EasyCacheState(total_steps, threshold=threshold, ret_steps=ret_steps) #----------------------------------------- for i in trange(total_steps, disable=disable): #------------add for EasyCache------------ x_step_in = x ec.begin_step(i, total_steps, x_step_in) denoised = ec.model_call(model, x, sigmas[i] * s_in, **extra_args) #----------------------------------------- sigma_down, sigma_up = get_ancestral_step(sigmas[i], sigmas[i + 1], eta=eta) if callback is not None: callback({"x": x, "i": i, "sigma": sigmas[i], "sigma_hat": sigmas[i], "denoised": denoised}) if sigma_down == 0 or old_denoised is None: # Euler method if cfg_pp: d = to_d(x, sigmas[i], uncond_denoised) x = denoised + d * sigma_down else: d = to_d(x, sigmas[i], denoised) dt = sigma_down - sigmas[i] x = x + d * dt else: # Second order multistep method in https://arxiv.org/pdf/2308.02157 t, t_old, t_next, t_prev = t_fn(sigmas[i]), t_fn(old_sigma_down), t_fn(sigma_down), t_fn(sigmas[i - 1]) h = t_next - t c2 = (t_prev - t_old) / h phi1_val, phi2_val = phi1_fn(-h), phi2_fn(-h) b1 = torch.nan_to_num(phi1_val - phi2_val / c2, nan=0.0) b2 = torch.nan_to_num(phi2_val / c2, nan=0.0) if cfg_pp: x = x + (denoised - uncond_denoised) x = sigma_fn(h) * x + h * (b1 * uncond_denoised + b2 * old_denoised) else: x = sigma_fn(h) * x + h * (b1 * denoised + b2 * old_denoised) # Noise addition if sigmas[i + 1] > 0: x = x + noise_sampler(sigmas[i], sigmas[i + 1]) * s_noise * sigma_up if cfg_pp: old_denoised = uncond_denoised else: old_denoised = denoised old_sigma_down = sigma_down ec.end_step(x_step_in) return x @torch.no_grad() def sample_res_multistep(model, x, sigmas, extra_args=None, callback=None, disable=None, s_noise=1., noise_sampler=None, threshold=0.025, ret_steps=5): return res_multistep(model, x, sigmas, extra_args=extra_args, callback=callback, disable=disable, s_noise=s_noise, noise_sampler=noise_sampler, eta=0., cfg_pp=False, threshold=threshold, ret_steps=ret_steps) @torch.no_grad() def sample_gradient_estimation(model, x, sigmas, extra_args=None, callback=None, disable=None, ge_gamma=2., cfg_pp=False, threshold=0.025, ret_steps=5): """Gradient-estimation sampler. Paper: https://openreview.net/pdf?id=o2ND9v0CeK""" extra_args = {} if extra_args is None else extra_args s_in = x.new_ones([x.shape[0]]) old_d = None uncond_denoised = None def post_cfg_function(args): nonlocal uncond_denoised uncond_denoised = args["uncond_denoised"] return args["denoised"] if cfg_pp: model_options = extra_args.get("model_options", {}).copy() extra_args["model_options"] = comfy.model_patcher.set_model_options_post_cfg_function(model_options, post_cfg_function, disable_cfg1_optimization=True) #------------add for EasyCache------------ total_steps = len(sigmas) - 1 ec = EasyCacheState(total_steps, threshold=threshold, ret_steps=ret_steps) #----------------------------------------- for i in trange(total_steps, disable=disable): #------------add for EasyCache------------ x_step_in = x ec.begin_step(i, total_steps, x_step_in) denoised = ec.model_call(model, x, sigmas[i] * s_in, **extra_args) #----------------------------------------- if cfg_pp: d = to_d(x, sigmas[i], uncond_denoised) else: d = to_d(x, sigmas[i], denoised) if callback is not None: callback({'x': x, 'i': i, 'sigma': sigmas[i], 'sigma_hat': sigmas[i], 'denoised': denoised}) dt = sigmas[i + 1] - sigmas[i] if sigmas[i + 1] == 0: # Denoising step x = denoised else: # Euler method if cfg_pp: x = denoised + d * sigmas[i + 1] else: x = x + d * dt if i >= 1: # Gradient estimation d_bar = (ge_gamma - 1) * (d - old_d) x = x + d_bar * dt old_d = d ec.end_step(x_step_in) return x #ancestral、sde方法,可多样性 @torch.no_grad() def sample_euler_ancestral(model, x, sigmas, extra_args=None, callback=None, disable=None, eta=1., s_noise=1., noise_sampler=None, threshold=0.025, ret_steps=5): if isinstance(model.inner_model.inner_model.model_sampling, comfy.model_sampling.CONST): return sample_euler_ancestral_RF(model, x, sigmas, extra_args, callback, disable, eta, s_noise, noise_sampler, threshold=threshold, ret_steps=ret_steps) """Ancestral sampling with Euler method steps.""" extra_args = {} if extra_args is None else extra_args seed = extra_args.get("seed", None) noise_sampler = default_noise_sampler(x, seed=seed) if noise_sampler is None else noise_sampler s_in = x.new_ones([x.shape[0]]) #------------add for EasyCache------------ total_steps = len(sigmas) - 1 ec = EasyCacheState(total_steps, threshold=threshold, ret_steps=ret_steps) #----------------------------------------- for i in trange(total_steps, disable=disable): #------------add for EasyCache------------ x_step_in = x ec.begin_step(i, total_steps, x_step_in) denoised = ec.model_call(model, x, sigmas[i] * s_in, **extra_args) #----------------------------------------- sigma_down, sigma_up = get_ancestral_step(sigmas[i], sigmas[i + 1], eta=eta) if callback is not None: callback({'x': x, 'i': i, 'sigma': sigmas[i], 'sigma_hat': sigmas[i], 'denoised': denoised}) if sigma_down == 0: x = denoised else: d = to_d(x, sigmas[i], denoised) # Euler method dt = sigma_down - sigmas[i] x = x + d * dt + noise_sampler(sigmas[i], sigmas[i + 1]) * s_noise * sigma_up ec.end_step(x_step_in) return x @torch.no_grad() def sample_euler_ancestral_RF(model, x, sigmas, extra_args=None, callback=None, disable=None, eta=1.0, s_noise=1., noise_sampler=None, threshold=0.025, ret_steps=5): """Ancestral sampling with Euler method steps.""" extra_args = {} if extra_args is None else extra_args seed = extra_args.get("seed", None) noise_sampler = default_noise_sampler(x, seed=seed) if noise_sampler is None else noise_sampler s_in = x.new_ones([x.shape[0]]) #------------add for EasyCache------------ total_steps = len(sigmas) - 1 ec = EasyCacheState(total_steps, threshold=threshold, ret_steps=ret_steps) #----------------------------------------- for i in trange(total_steps, disable=disable): #------------add for EasyCache------------ x_step_in = x ec.begin_step(i, total_steps, x_step_in) denoised = ec.model_call(model, x, sigmas[i] * s_in, **extra_args) #----------------------------------------- # sigma_down, sigma_up = get_ancestral_step(sigmas[i], sigmas[i + 1], eta=eta) if callback is not None: callback({'x': x, 'i': i, 'sigma': sigmas[i], 'sigma_hat': sigmas[i], 'denoised': denoised}) if sigmas[i + 1] == 0: x = denoised else: downstep_ratio = 1 + (sigmas[i + 1] / sigmas[i] - 1) * eta sigma_down = sigmas[i + 1] * downstep_ratio alpha_ip1 = 1 - sigmas[i + 1] alpha_down = 1 - sigma_down renoise_coeff = (sigmas[i + 1]**2 - sigma_down**2 * alpha_ip1**2 / alpha_down**2)**0.5 # Euler method sigma_down_i_ratio = sigma_down / sigmas[i] x = sigma_down_i_ratio * x + (1 - sigma_down_i_ratio) * denoised if eta > 0: x = (alpha_ip1 / alpha_down) * x + noise_sampler(sigmas[i], sigmas[i + 1]) * s_noise * renoise_coeff ec.end_step(x_step_in) return x @torch.no_grad() def sample_dpm_2_ancestral(model, x, sigmas, extra_args=None, callback=None, disable=None, eta=1., s_noise=1., noise_sampler=None, threshold=0.025, ret_steps=5): if isinstance(model.inner_model.inner_model.model_sampling, comfy.model_sampling.CONST): return sample_dpm_2_ancestral_RF(model, x, sigmas, extra_args, callback, disable, eta, s_noise, noise_sampler, threshold=threshold, ret_steps=ret_steps) """Ancestral sampling with DPM-Solver second-order steps.""" extra_args = {} if extra_args is None else extra_args seed = extra_args.get("seed", None) noise_sampler = default_noise_sampler(x, seed=seed) if noise_sampler is None else noise_sampler s_in = x.new_ones([x.shape[0]]) #------------add for EasyCache------------ total_steps = len(sigmas) - 1 ec = EasyCacheState(total_steps, threshold=threshold, ret_steps=ret_steps) #----------------------------------------- for i in trange(total_steps, disable=disable): #------------add for EasyCache------------ x_step_in = x ec.begin_step(i, total_steps, x_step_in) denoised = ec.model_call(model, x, sigmas[i] * s_in, **extra_args) #----------------------------------------- sigma_down, sigma_up = get_ancestral_step(sigmas[i], sigmas[i + 1], eta=eta) if callback is not None: callback({'x': x, 'i': i, 'sigma': sigmas[i], 'sigma_hat': sigmas[i], 'denoised': denoised}) d = to_d(x, sigmas[i], denoised) if sigma_down == 0: # Euler method dt = sigma_down - sigmas[i] x = x + d * dt else: # DPM-Solver-2 sigma_mid = sigmas[i].log().lerp(sigma_down.log(), 0.5).exp() dt_1 = sigma_mid - sigmas[i] dt_2 = sigma_down - sigmas[i] x_2 = x + d * dt_1 denoised_2 = ec.model_call(model, x_2, sigma_mid * s_in, **extra_args) d_2 = to_d(x_2, sigma_mid, denoised_2) x = x + d_2 * dt_2 x = x + noise_sampler(sigmas[i], sigmas[i + 1]) * s_noise * sigma_up ec.end_step(x_step_in) return x @torch.no_grad() def sample_dpm_2_ancestral_RF(model, x, sigmas, extra_args=None, callback=None, disable=None, eta=1., s_noise=1., noise_sampler=None, threshold=0.025, ret_steps=5): """Ancestral sampling with DPM-Solver second-order steps.""" extra_args = {} if extra_args is None else extra_args seed = extra_args.get("seed", None) noise_sampler = default_noise_sampler(x, seed=seed) if noise_sampler is None else noise_sampler s_in = x.new_ones([x.shape[0]]) #------------add for EasyCache------------ total_steps = len(sigmas) - 1 ec = EasyCacheState(total_steps, threshold=threshold, ret_steps=ret_steps) #----------------------------------------- for i in trange(total_steps, disable=disable): #------------add for EasyCache------------ x_step_in = x ec.begin_step(i, total_steps, x_step_in) denoised = ec.model_call(model, x, sigmas[i] * s_in, **extra_args) #----------------------------------------- downstep_ratio = 1 + (sigmas[i+1]/sigmas[i] - 1) * eta sigma_down = sigmas[i+1] * downstep_ratio alpha_ip1 = 1 - sigmas[i+1] alpha_down = 1 - sigma_down renoise_coeff = (sigmas[i+1]**2 - sigma_down**2*alpha_ip1**2/alpha_down**2)**0.5 if callback is not None: callback({'x': x, 'i': i, 'sigma': sigmas[i], 'sigma_hat': sigmas[i], 'denoised': denoised}) d = to_d(x, sigmas[i], denoised) if sigma_down == 0: # Euler method dt = sigma_down - sigmas[i] x = x + d * dt else: # DPM-Solver-2 sigma_mid = sigmas[i].log().lerp(sigma_down.log(), 0.5).exp() dt_1 = sigma_mid - sigmas[i] dt_2 = sigma_down - sigmas[i] x_2 = x + d * dt_1 denoised_2 = ec.model_call(model, x_2, sigma_mid * s_in, **extra_args) d_2 = to_d(x_2, sigma_mid, denoised_2) x = x + d_2 * dt_2 x = (alpha_ip1/alpha_down) * x + noise_sampler(sigmas[i], sigmas[i + 1]) * s_noise * renoise_coeff ec.end_step(x_step_in) return x