igerry commited on
Commit
f56785d
·
verified ·
1 Parent(s): 9b9fdbb

Upload comfy/custom_nodes/pingpongsampler_node.py with huggingface_hub

Browse files
comfy/custom_nodes/pingpongsampler_node.py ADDED
@@ -0,0 +1,252 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # By https://github.com/blepping
2
+ # LICENSE: Apache2
3
+ # Usage: Place this file in the custom_nodes directory and restart ComfyUI+refresh browser.
4
+ # It will add a PingPongSampler node that can be used with SamplerCustom, etc.
5
+
6
+ import random
7
+ import torch
8
+
9
+ from tqdm.auto import trange
10
+
11
+ from comfy import model_sampling
12
+ from comfy.samplers import KSAMPLER
13
+ import nodes
14
+
15
+
16
+ BLEND_MODES = None
17
+
18
+ def _ensure_blend_modes():
19
+ global BLEND_MODES
20
+ if BLEND_MODES is not None:
21
+ return
22
+ bleh = getattr(nodes, "_blepping_integrations", {}).get("bleh")
23
+ if bleh is not None:
24
+ BLEND_MODES = bleh.py.latent_utils.BLENDING_MODES
25
+ else:
26
+ BLEND_MODES = {"lerp": torch.lerp, "a_only": lambda a, _b, _t: a, "b_only": lambda _a, b, _t: b}
27
+
28
+ class ModelProxy:
29
+ def __init__(self, model, last_x, last_sigma, last_denoised):
30
+ self.__model = model
31
+ self.__last_x = last_x
32
+ self.__last_sigma = last_sigma
33
+ self.__last_denoised = last_denoised
34
+
35
+ def __call__(self, x, sigma, *args, **kwargs):
36
+ if torch.allclose(sigma.to(self.__last_sigma), self.__last_sigma) and torch.allclose(x.to(self.__last_x), self.__last_x):
37
+ return self.__last_denoised.to(x, copy=True)
38
+ return self.__model(x, sigma, *args, **kwargs)
39
+
40
+ def __getattr__(self, k):
41
+ return getattr(self.__model, k)
42
+
43
+ class PingPongSampler:
44
+ def __init__(self, model, x, sigmas, *args, extra_args=None, callback=None, disable=None, noise_sampler=None, s_noise=1.0, pingpong_options=None, **kwargs):
45
+ self.args = args
46
+ self.kwargs = kwargs
47
+ self.model_ = model
48
+ self.sigmas = sigmas
49
+ self.x = x
50
+ self.s_in = x.new_ones((x.shape[0],))
51
+ self.extra_args = extra_args.copy() if extra_args is not None else {}
52
+ self.seed = self.extra_args.pop("seed", 42)
53
+ self.disable = disable
54
+ self.callback_ = callback
55
+ if pingpong_options is None:
56
+ pingpong_options= {}
57
+ self.first_ancestral_step = pingpong_options.get("first_ancestral_step", 0)
58
+ self.last_ancestral_step = pingpong_options.get("last_ancestral_step", 0)
59
+
60
+ self.pingpong_blend = pingpong_options.get("pingpong_blend")
61
+ sampler_opt = pingpong_options.get("external_sampler")
62
+ if self.pingpong_blend != 1.0 and sampler_opt is None:
63
+ raise ValueError("Sampler input must be connect when pingpong_blend isn't 1.0")
64
+ self.external_sampler = sampler_opt
65
+ self.step_blend_function = pingpong_options.get("step_blend_function", torch.lerp)
66
+ self.blend_function = pingpong_options.get("blend_function", torch.lerp)
67
+ self.s_noise = s_noise
68
+ self.is_rf = isinstance(model.inner_model.inner_model.model_sampling, model_sampling.CONST)
69
+ if noise_sampler is None:
70
+ def noise_sampler(*_unused):
71
+ return torch.randn_like(x)
72
+ self.noise_sampler = noise_sampler
73
+
74
+ @classmethod
75
+ def go(cls, model, x, sigmas, extra_args=None, callback=None, disable=None, noise_sampler=None, s_noise=1.0, pingpong_options=None, **kwargs):
76
+ return cls(model, x, sigmas, extra_args=extra_args, callback=callback, disable=disable, noise_sampler=noise_sampler, s_noise=s_noise, pingpong_options=pingpong_options, **kwargs)()
77
+
78
+ def model(self, x, sigma, **kwargs):
79
+ return self.model_(x, sigma * self.s_in, **self.extra_args, **kwargs)
80
+
81
+ def callback(self, idx, x, sigma, denoised):
82
+ if self.callback_ is None:
83
+ return
84
+ self.callback_({
85
+ "i": idx,
86
+ "x": x,
87
+ "sigma": sigma,
88
+ "sigma_hat": sigma,
89
+ "denoised": denoised,
90
+ })
91
+
92
+ def __call__(self):
93
+ x = self.x
94
+ noise_sampler = self.noise_sampler
95
+ astart_step = self.first_ancestral_step
96
+ aend_step = self.last_ancestral_step
97
+ last_step_idx = len(self.sigmas) - 2
98
+ step_count = len(self.sigmas) - 1
99
+ if astart_step < 0:
100
+ astart_step = step_count + astart_step
101
+ if aend_step < 0:
102
+ aend_step = step_count + aend_step
103
+ astart_step = min(last_step_idx, max(0, astart_step))
104
+ aend_step = min(last_step_idx, max(0, aend_step))
105
+ s_noise = self.s_noise
106
+ seed_offset = 10
107
+ for idx in trange(step_count, disable=self.disable):
108
+ sigma, sigma_next = self.sigmas[idx:idx + 2]
109
+ orig_x = x
110
+ denoised = self.model(orig_x, sigma)
111
+ self.callback(idx, x, sigma, denoised)
112
+ use_ancestral = astart_step <= idx <= aend_step
113
+ if sigma_next <= 1e-06:
114
+ return denoised
115
+ if not use_ancestral:
116
+ x = self.step_blend_function(denoised, x, sigma_next / sigma)
117
+ continue
118
+ if self.pingpong_blend != 1.0:
119
+ alt_x = self.external_sampler.sampler_function(
120
+ ModelProxy(self.model_, x, sigma, denoised),
121
+ orig_x.clone(),
122
+ self.sigmas[idx:idx + 2].clone(),
123
+ *self.args,
124
+ disable=True,
125
+ callback=None,
126
+ extra_args=self.extra_args | {"seed": self.seed + seed_offset},
127
+ **self.external_sampler.extra_options,
128
+ **self.kwargs,
129
+ )
130
+ seed_offset += 10
131
+ if self.pingpong_blend <= 0:
132
+ x = alt_x
133
+ continue
134
+ noise = noise_sampler(sigma, sigma_next).mul_(self.s_noise)
135
+ if self.is_rf:
136
+ x = self.step_blend_function(denoised, noise, sigma_next)
137
+ else:
138
+ x = denoised + noise * sigma_next
139
+ if self.pingpong_blend != 1.0:
140
+ x = self.blend_function(alt_x, x, self.pingpong_blend)
141
+ del alt_x
142
+ return x
143
+
144
+
145
+ class PingPongSamplerNode:
146
+ CATEGORY = "sampling/custom_sampling/samplers"
147
+ RETURN_TYPES = ("SAMPLER",)
148
+ FUNCTION = "go"
149
+
150
+ @classmethod
151
+ def INPUT_TYPES(cls):
152
+ _ensure_blend_modes()
153
+ return {
154
+ "required": {
155
+ "s_noise": ("FLOAT", {"default": 1.0, "min": -1000.0, "max": 1000.0}),
156
+ "first_ancestral_step": ("INT", {"default": 0, "min": -10000, "max": 10000}),
157
+ "last_ancestral_step": ("INT", {"default": -1, "min": -10000, "max": 10000}),
158
+ "pingpong_blend": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001, "tooltip": "Allows blending pingpong sampling with a different sampler. Only has an effect during the ancestral_step range. If set to a value below 1.0 (100% pingpong) then sampler_opt must be attached."}),
159
+ "blend_mode": (tuple(BLEND_MODES), {"default": "lerp", "tooltip": "Blend mode to use when blending pingpong sampling with the external sampler. See tooltip for pingpong_blend. Can integrate with ComfyUI-bleh to add more blend modes."}),
160
+ "step_blend_mode": (tuple(BLEND_MODES), {"default": "lerp", "tooltip": "Blend mode to use for pingpong steps. Changing this is likely a bad idea. Does not apply for ancestral steps on non-flow models. Can integrate with ComfyUI-bleh to add more blend modes."}),
161
+
162
+ },
163
+ "optional": {
164
+ "sampler_opt": ("SAMPLER", {"tooltip": "Optional when pingpong_blend is 1.0. Result of a pingpong step will be blended with output from this sampler with the configured ratio. Calls the sampler on a single step so will not work well with samplers that care about state (I.E. history samplers such as deis, res_multistep, etc)."}),
165
+ },
166
+ }
167
+
168
+ @classmethod
169
+ def go(cls, *, s_noise: float, first_ancestral_step: int, last_ancestral_step: int, pingpong_blend: float, blend_mode: str, step_blend_mode: str, sampler_opt = None):
170
+ options = {
171
+ "s_noise": s_noise,
172
+ "pingpong_options": {
173
+ "first_ancestral_step": first_ancestral_step,
174
+ "last_ancestral_step": last_ancestral_step,
175
+ "pingpong_blend": pingpong_blend,
176
+ "blend_function": BLEND_MODES[blend_mode],
177
+ "step_blend_function": BLEND_MODES[step_blend_mode],
178
+ "external_sampler": sampler_opt,
179
+ },
180
+ }
181
+ return (KSAMPLER(PingPongSampler.go, extra_options=options),)
182
+
183
+ class RestlessSchedulerNode:
184
+ DESCRIPTION = "HACK: A weird scheduler that will randomly jump around a list of sigmas you input. Not recommended. Breaks most multi-step and history samplers. Works okay-ish with Pingpong."
185
+ CATEGORY = "sampling/custom_sampling/schedulers"
186
+ RETURN_TYPES = ("SIGMAS",)
187
+ FUNCTION = "go"
188
+
189
+ @classmethod
190
+ def INPUT_TYPES(cls):
191
+ return {
192
+ "required": {
193
+ "sigmas": ("SIGMAS",),
194
+ "seed": (
195
+ "INT",
196
+ {
197
+ "default": 0,
198
+ "min": 0,
199
+ "max": 0xFFFFFFFFFFFFFFFF,
200
+ "tooltip": "Seed to use for generating schedule.",
201
+ },
202
+ ),
203
+ "shrink_factor": ("FLOAT", {
204
+ "default": 0.3,
205
+ "tooltip": "Amount the window for restless scheduling shrinks by per iteration.",
206
+ }),
207
+ "first_restless_step": ("INT", {
208
+ "default": 3, "min": 1,
209
+ "tooltip": "First step (0-based) to include for restless scheduling. Must be greater than 1 and less than last_restless_step.",
210
+ }),
211
+ "last_restless_step": ("INT", {
212
+ "default": -4, "min": -10000, "max": 10000,
213
+ "tooltip": "Last step (0-based) to include for restless scheduling. Can be negative to count from the end, but you cannot target the last sigma in the list.",
214
+ }),
215
+ },
216
+ }
217
+
218
+ @classmethod
219
+ def go(cls, *, sigmas: torch.Tensor, seed: int, shrink_factor: float, first_restless_step: int, last_restless_step: int) -> tuple:
220
+ n_sigmas = len(sigmas)
221
+ if n_sigmas < 3:
222
+ return (sigmas,)
223
+ if last_restless_step < 0:
224
+ last_restless_step = n_sigmas + last_restless_step
225
+ if last_restless_step <= first_restless_step:
226
+ raise ValueError("Last restless step <= first restless step!")
227
+ if last_restless_step >= n_sigmas - 1:
228
+ raise ValueError("Last restless step cannot include the final sigma")
229
+ orig_sigmas = sigmas
230
+ random.seed(seed)
231
+ result = sigmas[:first_restless_step].tolist()
232
+ end_chunk = sigmas[last_restless_step + 1:].tolist()
233
+ sigmas = sigmas[first_restless_step:last_restless_step + 1].tolist()
234
+ n_sigmas = len(sigmas)
235
+ shrinkage = 0.0
236
+ curr_idx = None
237
+ while (window_size := int((n_sigmas - 1) - shrinkage)) > 0:
238
+ next_idx = random.randint(0, window_size)
239
+ if next_idx == curr_idx:
240
+ next_idx += 1
241
+ result.append(sigmas[int(shrinkage) + next_idx])
242
+ curr_idx = next_idx
243
+ shrinkage += shrink_factor
244
+ result += end_chunk
245
+ return (torch.tensor(result, dtype=torch.float32, device="cpu").to(orig_sigmas),)
246
+
247
+
248
+
249
+ NODE_CLASS_MAPPINGS = {
250
+ "PingPongSampler": PingPongSamplerNode,
251
+ "RestlessScheduler": RestlessSchedulerNode,
252
+ }