mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2025-12-20 11:32:58 +08:00
Merge branch 'master' into dr-support-pip-cm
This commit is contained in:
commit
552fe9df02
@ -86,6 +86,7 @@ See what ComfyUI can do with the [example workflows](https://comfyanonymous.gith
|
|||||||
- Smart memory management: can automatically run models on GPUs with as low as 1GB vram.
|
- Smart memory management: can automatically run models on GPUs with as low as 1GB vram.
|
||||||
- Works even if you don't have a GPU with: ```--cpu``` (slow)
|
- Works even if you don't have a GPU with: ```--cpu``` (slow)
|
||||||
- Can load ckpt, safetensors and diffusers models/checkpoints. Standalone VAEs and CLIP models.
|
- Can load ckpt, safetensors and diffusers models/checkpoints. Standalone VAEs and CLIP models.
|
||||||
|
- Safe loading of ckpt, pt, pth, etc.. files.
|
||||||
- Embeddings/Textual inversion
|
- Embeddings/Textual inversion
|
||||||
- [Loras (regular, locon and loha)](https://comfyanonymous.github.io/ComfyUI_examples/lora/)
|
- [Loras (regular, locon and loha)](https://comfyanonymous.github.io/ComfyUI_examples/lora/)
|
||||||
- [Hypernetworks](https://comfyanonymous.github.io/ComfyUI_examples/hypernetworks/)
|
- [Hypernetworks](https://comfyanonymous.github.io/ComfyUI_examples/hypernetworks/)
|
||||||
@ -101,7 +102,6 @@ See what ComfyUI can do with the [example workflows](https://comfyanonymous.gith
|
|||||||
- [Model Merging](https://comfyanonymous.github.io/ComfyUI_examples/model_merging/)
|
- [Model Merging](https://comfyanonymous.github.io/ComfyUI_examples/model_merging/)
|
||||||
- [LCM models and Loras](https://comfyanonymous.github.io/ComfyUI_examples/lcm/)
|
- [LCM models and Loras](https://comfyanonymous.github.io/ComfyUI_examples/lcm/)
|
||||||
- Latent previews with [TAESD](#how-to-show-high-quality-previews)
|
- Latent previews with [TAESD](#how-to-show-high-quality-previews)
|
||||||
- Starts up very fast.
|
|
||||||
- Works fully offline: core will never download anything unless you want to.
|
- Works fully offline: core will never download anything unless you want to.
|
||||||
- Optional API nodes to use paid models from external providers through the online [Comfy API](https://docs.comfy.org/tutorials/api-nodes/overview).
|
- Optional API nodes to use paid models from external providers through the online [Comfy API](https://docs.comfy.org/tutorials/api-nodes/overview).
|
||||||
- [Config file](extra_model_paths.yaml.example) to set the search paths for models.
|
- [Config file](extra_model_paths.yaml.example) to set the search paths for models.
|
||||||
|
|||||||
@ -1,55 +1,10 @@
|
|||||||
import math
|
import math
|
||||||
import torch
|
import torch
|
||||||
from torch import nn
|
from torch import nn
|
||||||
from .ldm.modules.attention import CrossAttention
|
from .ldm.modules.attention import CrossAttention, FeedForward
|
||||||
from inspect import isfunction
|
|
||||||
import comfy.ops
|
import comfy.ops
|
||||||
ops = comfy.ops.manual_cast
|
ops = comfy.ops.manual_cast
|
||||||
|
|
||||||
def exists(val):
|
|
||||||
return val is not None
|
|
||||||
|
|
||||||
|
|
||||||
def uniq(arr):
|
|
||||||
return{el: True for el in arr}.keys()
|
|
||||||
|
|
||||||
|
|
||||||
def default(val, d):
|
|
||||||
if exists(val):
|
|
||||||
return val
|
|
||||||
return d() if isfunction(d) else d
|
|
||||||
|
|
||||||
|
|
||||||
# feedforward
|
|
||||||
class GEGLU(nn.Module):
|
|
||||||
def __init__(self, dim_in, dim_out):
|
|
||||||
super().__init__()
|
|
||||||
self.proj = ops.Linear(dim_in, dim_out * 2)
|
|
||||||
|
|
||||||
def forward(self, x):
|
|
||||||
x, gate = self.proj(x).chunk(2, dim=-1)
|
|
||||||
return x * torch.nn.functional.gelu(gate)
|
|
||||||
|
|
||||||
|
|
||||||
class FeedForward(nn.Module):
|
|
||||||
def __init__(self, dim, dim_out=None, mult=4, glu=False, dropout=0.):
|
|
||||||
super().__init__()
|
|
||||||
inner_dim = int(dim * mult)
|
|
||||||
dim_out = default(dim_out, dim)
|
|
||||||
project_in = nn.Sequential(
|
|
||||||
ops.Linear(dim, inner_dim),
|
|
||||||
nn.GELU()
|
|
||||||
) if not glu else GEGLU(dim, inner_dim)
|
|
||||||
|
|
||||||
self.net = nn.Sequential(
|
|
||||||
project_in,
|
|
||||||
nn.Dropout(dropout),
|
|
||||||
ops.Linear(inner_dim, dim_out)
|
|
||||||
)
|
|
||||||
|
|
||||||
def forward(self, x):
|
|
||||||
return self.net(x)
|
|
||||||
|
|
||||||
|
|
||||||
class GatedCrossAttentionDense(nn.Module):
|
class GatedCrossAttentionDense(nn.Module):
|
||||||
def __init__(self, query_dim, context_dim, n_heads, d_head):
|
def __init__(self, query_dim, context_dim, n_heads, d_head):
|
||||||
|
|||||||
@ -412,9 +412,13 @@ def sample_lms(model, x, sigmas, extra_args=None, callback=None, disable=None, o
|
|||||||
ds.pop(0)
|
ds.pop(0)
|
||||||
if callback is not None:
|
if callback is not None:
|
||||||
callback({'x': x, 'i': i, 'sigma': sigmas[i], 'sigma_hat': sigmas[i], 'denoised': denoised})
|
callback({'x': x, 'i': i, 'sigma': sigmas[i], 'sigma_hat': sigmas[i], 'denoised': denoised})
|
||||||
cur_order = min(i + 1, order)
|
if sigmas[i + 1] == 0:
|
||||||
coeffs = [linear_multistep_coeff(cur_order, sigmas_cpu, i, j) for j in range(cur_order)]
|
# Denoising step
|
||||||
x = x + sum(coeff * d for coeff, d in zip(coeffs, reversed(ds)))
|
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)))
|
||||||
return x
|
return x
|
||||||
|
|
||||||
|
|
||||||
@ -1067,7 +1071,9 @@ def sample_ipndm(model, x, sigmas, extra_args=None, callback=None, disable=None,
|
|||||||
d_cur = (x_cur - denoised) / t_cur
|
d_cur = (x_cur - denoised) / t_cur
|
||||||
|
|
||||||
order = min(max_order, i+1)
|
order = min(max_order, i+1)
|
||||||
if order == 1: # First Euler step.
|
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
|
x_next = x_cur + (t_next - t_cur) * d_cur
|
||||||
elif order == 2: # Use one history point.
|
elif order == 2: # Use one history point.
|
||||||
x_next = x_cur + (t_next - t_cur) * (3 * d_cur - buffer_model[-1]) / 2
|
x_next = x_cur + (t_next - t_cur) * (3 * d_cur - buffer_model[-1]) / 2
|
||||||
@ -1085,6 +1091,7 @@ def sample_ipndm(model, x, sigmas, extra_args=None, callback=None, disable=None,
|
|||||||
|
|
||||||
return x_next
|
return x_next
|
||||||
|
|
||||||
|
|
||||||
#From https://github.com/zju-pi/diff-sampler/blob/main/diff-solvers-main/solvers.py
|
#From https://github.com/zju-pi/diff-sampler/blob/main/diff-solvers-main/solvers.py
|
||||||
#under Apache 2 license
|
#under Apache 2 license
|
||||||
def sample_ipndm_v(model, x, sigmas, extra_args=None, callback=None, disable=None, max_order=4):
|
def sample_ipndm_v(model, x, sigmas, extra_args=None, callback=None, disable=None, max_order=4):
|
||||||
@ -1108,7 +1115,9 @@ def sample_ipndm_v(model, x, sigmas, extra_args=None, callback=None, disable=Non
|
|||||||
d_cur = (x_cur - denoised) / t_cur
|
d_cur = (x_cur - denoised) / t_cur
|
||||||
|
|
||||||
order = min(max_order, i+1)
|
order = min(max_order, i+1)
|
||||||
if order == 1: # First Euler step.
|
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
|
x_next = x_cur + (t_next - t_cur) * d_cur
|
||||||
elif order == 2: # Use one history point.
|
elif order == 2: # Use one history point.
|
||||||
h_n = (t_next - t_cur)
|
h_n = (t_next - t_cur)
|
||||||
@ -1148,6 +1157,7 @@ def sample_ipndm_v(model, x, sigmas, extra_args=None, callback=None, disable=Non
|
|||||||
|
|
||||||
return x_next
|
return x_next
|
||||||
|
|
||||||
|
|
||||||
#From https://github.com/zju-pi/diff-sampler/blob/main/diff-solvers-main/solvers.py
|
#From https://github.com/zju-pi/diff-sampler/blob/main/diff-solvers-main/solvers.py
|
||||||
#under Apache 2 license
|
#under Apache 2 license
|
||||||
@torch.no_grad()
|
@torch.no_grad()
|
||||||
@ -1198,6 +1208,7 @@ def sample_deis(model, x, sigmas, extra_args=None, callback=None, disable=None,
|
|||||||
|
|
||||||
return x_next
|
return x_next
|
||||||
|
|
||||||
|
|
||||||
@torch.no_grad()
|
@torch.no_grad()
|
||||||
def sample_euler_cfg_pp(model, x, sigmas, extra_args=None, callback=None, disable=None):
|
def sample_euler_cfg_pp(model, x, sigmas, extra_args=None, callback=None, disable=None):
|
||||||
extra_args = {} if extra_args is None else extra_args
|
extra_args = {} if extra_args is None else extra_args
|
||||||
@ -1404,6 +1415,7 @@ def sample_res_multistep_ancestral(model, x, sigmas, extra_args=None, callback=N
|
|||||||
def sample_res_multistep_ancestral_cfg_pp(model, x, sigmas, extra_args=None, callback=None, disable=None, eta=1., s_noise=1., noise_sampler=None):
|
def sample_res_multistep_ancestral_cfg_pp(model, x, sigmas, extra_args=None, callback=None, disable=None, eta=1., s_noise=1., noise_sampler=None):
|
||||||
return res_multistep(model, x, sigmas, extra_args=extra_args, callback=callback, disable=disable, s_noise=s_noise, noise_sampler=noise_sampler, eta=eta, cfg_pp=True)
|
return res_multistep(model, x, sigmas, extra_args=extra_args, callback=callback, disable=disable, s_noise=s_noise, noise_sampler=noise_sampler, eta=eta, cfg_pp=True)
|
||||||
|
|
||||||
|
|
||||||
@torch.no_grad()
|
@torch.no_grad()
|
||||||
def sample_gradient_estimation(model, x, sigmas, extra_args=None, callback=None, disable=None, ge_gamma=2., cfg_pp=False):
|
def sample_gradient_estimation(model, x, sigmas, extra_args=None, callback=None, disable=None, ge_gamma=2., cfg_pp=False):
|
||||||
"""Gradient-estimation sampler. Paper: https://openreview.net/pdf?id=o2ND9v0CeK"""
|
"""Gradient-estimation sampler. Paper: https://openreview.net/pdf?id=o2ND9v0CeK"""
|
||||||
@ -1430,19 +1442,19 @@ def sample_gradient_estimation(model, x, sigmas, extra_args=None, callback=None,
|
|||||||
if callback is not None:
|
if callback is not None:
|
||||||
callback({'x': x, 'i': i, 'sigma': sigmas[i], 'sigma_hat': sigmas[i], 'denoised': denoised})
|
callback({'x': x, 'i': i, 'sigma': sigmas[i], 'sigma_hat': sigmas[i], 'denoised': denoised})
|
||||||
dt = sigmas[i + 1] - sigmas[i]
|
dt = sigmas[i + 1] - sigmas[i]
|
||||||
if i == 0:
|
if sigmas[i + 1] == 0:
|
||||||
|
# Denoising step
|
||||||
|
x = denoised
|
||||||
|
else:
|
||||||
# Euler method
|
# Euler method
|
||||||
if cfg_pp:
|
if cfg_pp:
|
||||||
x = denoised + d * sigmas[i + 1]
|
x = denoised + d * sigmas[i + 1]
|
||||||
else:
|
else:
|
||||||
x = x + d * dt
|
x = x + d * dt
|
||||||
else:
|
|
||||||
# Gradient estimation
|
if i >= 1:
|
||||||
if cfg_pp:
|
# Gradient estimation
|
||||||
d_bar = (ge_gamma - 1) * (d - old_d)
|
d_bar = (ge_gamma - 1) * (d - old_d)
|
||||||
x = denoised + d * sigmas[i + 1] + d_bar * dt
|
|
||||||
else:
|
|
||||||
d_bar = ge_gamma * d + (1 - ge_gamma) * old_d
|
|
||||||
x = x + d_bar * dt
|
x = x + d_bar * dt
|
||||||
old_d = d
|
old_d = d
|
||||||
return x
|
return x
|
||||||
|
|||||||
@ -77,6 +77,7 @@ def load_torch_file(ckpt, safe_load=False, device=None, return_metadata=False):
|
|||||||
if safe_load or ALWAYS_SAFE_LOAD:
|
if safe_load or ALWAYS_SAFE_LOAD:
|
||||||
pl_sd = torch.load(ckpt, map_location=device, weights_only=True, **torch_args)
|
pl_sd = torch.load(ckpt, map_location=device, weights_only=True, **torch_args)
|
||||||
else:
|
else:
|
||||||
|
logging.warning("WARNING: loading {} unsafely, upgrade your pytorch to 2.4 or newer to load this file safely.".format(ckpt))
|
||||||
pl_sd = torch.load(ckpt, map_location=device, pickle_module=comfy.checkpoint_pickle)
|
pl_sd = torch.load(ckpt, map_location=device, pickle_module=comfy.checkpoint_pickle)
|
||||||
if "state_dict" in pl_sd:
|
if "state_dict" in pl_sd:
|
||||||
sd = pl_sd["state_dict"]
|
sd = pl_sd["state_dict"]
|
||||||
|
|||||||
@ -133,14 +133,6 @@ def save_audio(self, audio, filename_prefix="ComfyUI", format="flac", prompt=Non
|
|||||||
if sample_rate != audio["sample_rate"]:
|
if sample_rate != audio["sample_rate"]:
|
||||||
waveform = torchaudio.functional.resample(waveform, audio["sample_rate"], sample_rate)
|
waveform = torchaudio.functional.resample(waveform, audio["sample_rate"], sample_rate)
|
||||||
|
|
||||||
# Create in-memory WAV buffer
|
|
||||||
wav_buffer = io.BytesIO()
|
|
||||||
torchaudio.save(wav_buffer, waveform, sample_rate, format="WAV")
|
|
||||||
wav_buffer.seek(0) # Rewind for reading
|
|
||||||
|
|
||||||
# Use PyAV to convert and add metadata
|
|
||||||
input_container = av.open(wav_buffer)
|
|
||||||
|
|
||||||
# Create output with specified format
|
# Create output with specified format
|
||||||
output_buffer = io.BytesIO()
|
output_buffer = io.BytesIO()
|
||||||
output_container = av.open(output_buffer, mode='w', format=format)
|
output_container = av.open(output_buffer, mode='w', format=format)
|
||||||
@ -150,7 +142,6 @@ def save_audio(self, audio, filename_prefix="ComfyUI", format="flac", prompt=Non
|
|||||||
output_container.metadata[key] = value
|
output_container.metadata[key] = value
|
||||||
|
|
||||||
# Set up the output stream with appropriate properties
|
# Set up the output stream with appropriate properties
|
||||||
input_container.streams.audio[0]
|
|
||||||
if format == "opus":
|
if format == "opus":
|
||||||
out_stream = output_container.add_stream("libopus", rate=sample_rate)
|
out_stream = output_container.add_stream("libopus", rate=sample_rate)
|
||||||
if quality == "64k":
|
if quality == "64k":
|
||||||
@ -175,18 +166,16 @@ def save_audio(self, audio, filename_prefix="ComfyUI", format="flac", prompt=Non
|
|||||||
else: #format == "flac":
|
else: #format == "flac":
|
||||||
out_stream = output_container.add_stream("flac", rate=sample_rate)
|
out_stream = output_container.add_stream("flac", rate=sample_rate)
|
||||||
|
|
||||||
|
frame = av.AudioFrame.from_ndarray(waveform.movedim(0, 1).reshape(1, -1).float().numpy(), format='flt', layout='mono' if waveform.shape[0] == 1 else 'stereo')
|
||||||
# Copy frames from input to output
|
frame.sample_rate = sample_rate
|
||||||
for frame in input_container.decode(audio=0):
|
frame.pts = 0
|
||||||
frame.pts = None # Let PyAV handle timestamps
|
output_container.mux(out_stream.encode(frame))
|
||||||
output_container.mux(out_stream.encode(frame))
|
|
||||||
|
|
||||||
# Flush encoder
|
# Flush encoder
|
||||||
output_container.mux(out_stream.encode(None))
|
output_container.mux(out_stream.encode(None))
|
||||||
|
|
||||||
# Close containers
|
# Close containers
|
||||||
output_container.close()
|
output_container.close()
|
||||||
input_container.close()
|
|
||||||
|
|
||||||
# Write the output to file
|
# Write the output to file
|
||||||
output_buffer.seek(0)
|
output_buffer.seek(0)
|
||||||
|
|||||||
@ -583,6 +583,49 @@ class GetImageSize:
|
|||||||
|
|
||||||
return width, height, batch_size
|
return width, height, batch_size
|
||||||
|
|
||||||
|
class ImageRotate:
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(s):
|
||||||
|
return {"required": { "image": (IO.IMAGE,),
|
||||||
|
"rotation": (["none", "90 degrees", "180 degrees", "270 degrees"],),
|
||||||
|
}}
|
||||||
|
RETURN_TYPES = (IO.IMAGE,)
|
||||||
|
FUNCTION = "rotate"
|
||||||
|
|
||||||
|
CATEGORY = "image/transform"
|
||||||
|
|
||||||
|
def rotate(self, image, rotation):
|
||||||
|
rotate_by = 0
|
||||||
|
if rotation.startswith("90"):
|
||||||
|
rotate_by = 1
|
||||||
|
elif rotation.startswith("180"):
|
||||||
|
rotate_by = 2
|
||||||
|
elif rotation.startswith("270"):
|
||||||
|
rotate_by = 3
|
||||||
|
|
||||||
|
image = torch.rot90(image, k=rotate_by, dims=[2, 1])
|
||||||
|
return (image,)
|
||||||
|
|
||||||
|
class ImageFlip:
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(s):
|
||||||
|
return {"required": { "image": (IO.IMAGE,),
|
||||||
|
"flip_method": (["x-axis: vertically", "y-axis: horizontally"],),
|
||||||
|
}}
|
||||||
|
RETURN_TYPES = (IO.IMAGE,)
|
||||||
|
FUNCTION = "flip"
|
||||||
|
|
||||||
|
CATEGORY = "image/transform"
|
||||||
|
|
||||||
|
def flip(self, image, flip_method):
|
||||||
|
if flip_method.startswith("x"):
|
||||||
|
image = torch.flip(image, dims=[1])
|
||||||
|
elif flip_method.startswith("y"):
|
||||||
|
image = torch.flip(image, dims=[2])
|
||||||
|
|
||||||
|
return (image,)
|
||||||
|
|
||||||
|
|
||||||
NODE_CLASS_MAPPINGS = {
|
NODE_CLASS_MAPPINGS = {
|
||||||
"ImageCrop": ImageCrop,
|
"ImageCrop": ImageCrop,
|
||||||
"RepeatImageBatch": RepeatImageBatch,
|
"RepeatImageBatch": RepeatImageBatch,
|
||||||
@ -594,4 +637,6 @@ NODE_CLASS_MAPPINGS = {
|
|||||||
"ImageStitch": ImageStitch,
|
"ImageStitch": ImageStitch,
|
||||||
"ResizeAndPadImage": ResizeAndPadImage,
|
"ResizeAndPadImage": ResizeAndPadImage,
|
||||||
"GetImageSize": GetImageSize,
|
"GetImageSize": GetImageSize,
|
||||||
|
"ImageRotate": ImageRotate,
|
||||||
|
"ImageFlip": ImageFlip,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
comfyui-frontend-package==1.23.4
|
comfyui-frontend-package==1.23.4
|
||||||
comfyui-workflow-templates==0.1.32
|
comfyui-workflow-templates==0.1.33
|
||||||
comfyui-embedded-docs==0.2.3
|
comfyui-embedded-docs==0.2.4
|
||||||
comfyui_manager
|
comfyui_manager
|
||||||
torch
|
torch
|
||||||
torchsde
|
torchsde
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user