mirror of
https://git.datalinker.icu/comfyanonymous/ComfyUI
synced 2026-11-12 22:57:03 +08:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
0c745430df
65
README.md
65
README.md
@ -41,29 +41,32 @@ Workflow examples can be found on the [Examples page](https://comfyanonymous.git
|
|||||||
|
|
||||||
## Shortcuts
|
## Shortcuts
|
||||||
|
|
||||||
| Keybind | Explanation |
|
| Keybind | Explanation |
|
||||||
|---------------------------|--------------------------------------------------------------------------------------------------------------------|
|
|------------------------------------|--------------------------------------------------------------------------------------------------------------------|
|
||||||
| Ctrl + Enter | Queue up current graph for generation |
|
| Ctrl + Enter | Queue up current graph for generation |
|
||||||
| Ctrl + Shift + Enter | Queue up current graph as first for generation |
|
| Ctrl + Shift + Enter | Queue up current graph as first for generation |
|
||||||
| Ctrl + Z/Ctrl + Y | Undo/Redo |
|
| Ctrl + Z/Ctrl + Y | Undo/Redo |
|
||||||
| Ctrl + S | Save workflow |
|
| Ctrl + S | Save workflow |
|
||||||
| Ctrl + O | Load workflow |
|
| Ctrl + O | Load workflow |
|
||||||
| Ctrl + A | Select all nodes |
|
| Ctrl + A | Select all nodes |
|
||||||
| Alt + C | Collapse/uncollapse selected nodes |
|
| Alt + C | Collapse/uncollapse selected nodes |
|
||||||
| Ctrl + M | Mute/unmute selected nodes |
|
| Ctrl + M | Mute/unmute selected nodes |
|
||||||
| Ctrl + B | Bypass selected nodes (acts like the node was removed from the graph and the wires reconnected through) |
|
| Ctrl + B | Bypass selected nodes (acts like the node was removed from the graph and the wires reconnected through) |
|
||||||
| Delete/Backspace | Delete selected nodes |
|
| Delete/Backspace | Delete selected nodes |
|
||||||
| Ctrl + Delete/Backspace | Delete the current graph |
|
| Ctrl + Delete/Backspace | Delete the current graph |
|
||||||
| Space | Move the canvas around when held and moving the cursor |
|
| Space | Move the canvas around when held and moving the cursor |
|
||||||
| Ctrl/Shift + Click | Add clicked node to selection |
|
| Ctrl/Shift + Click | Add clicked node to selection |
|
||||||
| Ctrl + C/Ctrl + V | Copy and paste selected nodes (without maintaining connections to outputs of unselected nodes) |
|
| Ctrl + C/Ctrl + V | Copy and paste selected nodes (without maintaining connections to outputs of unselected nodes) |
|
||||||
| Ctrl + C/Ctrl + Shift + V | Copy and paste selected nodes (maintaining connections from outputs of unselected nodes to inputs of pasted nodes) |
|
| Ctrl + C/Ctrl + Shift + V | Copy and paste selected nodes (maintaining connections from outputs of unselected nodes to inputs of pasted nodes) |
|
||||||
| Shift + Drag | Move multiple selected nodes at the same time |
|
| Shift + Drag | Move multiple selected nodes at the same time |
|
||||||
| Ctrl + D | Load default graph |
|
| Ctrl + D | Load default graph |
|
||||||
| Q | Toggle visibility of the queue |
|
| Alt + `+` | Canvas Zoom in |
|
||||||
| H | Toggle visibility of history |
|
| Alt + `-` | Canvas Zoom out |
|
||||||
| R | Refresh graph |
|
| Ctrl + Shift + LMB + Vertical drag | Canvas Zoom in/out |
|
||||||
| Double-Click LMB | Open node quick search palette |
|
| Q | Toggle visibility of the queue |
|
||||||
|
| H | Toggle visibility of history |
|
||||||
|
| R | Refresh graph |
|
||||||
|
| Double-Click LMB | Open node quick search palette |
|
||||||
|
|
||||||
Ctrl can also be replaced with Cmd instead for macOS users
|
Ctrl can also be replaced with Cmd instead for macOS users
|
||||||
|
|
||||||
@ -125,7 +128,7 @@ Keep in mind that zluda is still very experimental and some things may not work
|
|||||||
### AMD GPUs (Linux only)
|
### AMD GPUs (Linux only)
|
||||||
AMD users can install rocm and pytorch with pip if you don't have it already installed, this is the command to install the stable version:
|
AMD users can install rocm and pytorch with pip if you don't have it already installed, this is the command to install the stable version:
|
||||||
|
|
||||||
```pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/rocm5.7```
|
```pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/rocm6.0```
|
||||||
|
|
||||||
This is the command to install the nightly with ROCm 6.0 which might have some performance improvements:
|
This is the command to install the nightly with ROCm 6.0 which might have some performance improvements:
|
||||||
|
|
||||||
@ -168,7 +171,7 @@ You can install ComfyUI in Apple Mac silicon (M1 or M2) with any recent macOS ve
|
|||||||
1. Install pytorch nightly. For instructions, read the [Accelerated PyTorch training on Mac](https://developer.apple.com/metal/pytorch/) Apple Developer guide (make sure to install the latest pytorch nightly).
|
1. Install pytorch nightly. For instructions, read the [Accelerated PyTorch training on Mac](https://developer.apple.com/metal/pytorch/) Apple Developer guide (make sure to install the latest pytorch nightly).
|
||||||
1. Follow the [ComfyUI manual installation](#manual-install-windows-linux) instructions for Windows and Linux.
|
1. Follow the [ComfyUI manual installation](#manual-install-windows-linux) instructions for Windows and Linux.
|
||||||
1. Install the ComfyUI [dependencies](#dependencies). If you have another Stable Diffusion UI [you might be able to reuse the dependencies](#i-already-have-another-ui-for-stable-diffusion-installed-do-i-really-have-to-install-all-of-these-dependencies).
|
1. Install the ComfyUI [dependencies](#dependencies). If you have another Stable Diffusion UI [you might be able to reuse the dependencies](#i-already-have-another-ui-for-stable-diffusion-installed-do-i-really-have-to-install-all-of-these-dependencies).
|
||||||
1. Launch ComfyUI by running `python main.py --force-fp16`. Note that --force-fp16 will only work if you installed the latest pytorch nightly.
|
1. Launch ComfyUI by running `python main.py`
|
||||||
|
|
||||||
> **Note**: Remember to add your models, VAE, LoRAs etc. to the corresponding Comfy folders, as discussed in [ComfyUI manual installation](#manual-install-windows-linux).
|
> **Note**: Remember to add your models, VAE, LoRAs etc. to the corresponding Comfy folders, as discussed in [ComfyUI manual installation](#manual-install-windows-linux).
|
||||||
|
|
||||||
@ -223,9 +226,7 @@ To use a textual inversion concepts/embeddings in a text prompt put them in the
|
|||||||
|
|
||||||
## How to increase generation speed?
|
## How to increase generation speed?
|
||||||
|
|
||||||
Make sure you use the regular loaders/Load Checkpoint node to load checkpoints. It will auto pick the right settings depending on your GPU.
|
On non Nvidia hardware you can set this command line setting to disable the upcasting to fp32 in some cross attention operations which will increase your speed. Note that this will very likely give you black images on SD2.x models. If you use xformers or pytorch attention this option does not do anything.
|
||||||
|
|
||||||
You can set this command line setting to disable the upcasting to fp32 in some cross attention operations which will increase your speed. Note that this will very likely give you black images on SD2.x models. If you use xformers or pytorch attention this option does not do anything.
|
|
||||||
|
|
||||||
```--dont-upcast-attention```
|
```--dont-upcast-attention```
|
||||||
|
|
||||||
@ -235,6 +236,14 @@ Use ```--preview-method auto``` to enable previews.
|
|||||||
|
|
||||||
The default installation includes a fast latent preview method that's low-resolution. To enable higher-quality previews with [TAESD](https://github.com/madebyollin/taesd), download the [taesd_decoder.pth](https://github.com/madebyollin/taesd/raw/main/taesd_decoder.pth) (for SD1.x and SD2.x) and [taesdxl_decoder.pth](https://github.com/madebyollin/taesd/raw/main/taesdxl_decoder.pth) (for SDXL) models and place them in the `models/vae_approx` folder. Once they're installed, restart ComfyUI to enable high-quality previews.
|
The default installation includes a fast latent preview method that's low-resolution. To enable higher-quality previews with [TAESD](https://github.com/madebyollin/taesd), download the [taesd_decoder.pth](https://github.com/madebyollin/taesd/raw/main/taesd_decoder.pth) (for SD1.x and SD2.x) and [taesdxl_decoder.pth](https://github.com/madebyollin/taesd/raw/main/taesdxl_decoder.pth) (for SDXL) models and place them in the `models/vae_approx` folder. Once they're installed, restart ComfyUI to enable high-quality previews.
|
||||||
|
|
||||||
|
## How to use TLS/SSL?
|
||||||
|
Generate a self-signed certificate (not appropriate for shared/production use) and key by running the command: `openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -sha256 -days 3650 -nodes -subj "/C=XX/ST=StateName/L=CityName/O=CompanyName/OU=CompanySectionName/CN=CommonNameOrHostname"`
|
||||||
|
|
||||||
|
Use `--tls-keyfile key.pem --tls-certfile cert.pem` to enable TLS/SSL, the app will now be accessible with `https://...` instead of `http://...`.
|
||||||
|
|
||||||
|
> Note: Windows users can use [alexisrolland/docker-openssl](https://github.com/alexisrolland/docker-openssl) or one of the [3rd party binary distributions](https://wiki.openssl.org/index.php/Binaries) to run the command example above.
|
||||||
|
<br/><br/>If you use a container, note that the volume mount `-v` can be a relative path so `... -v ".\:/openssl-certs" ...` would create the key & cert files in the current directory of your command prompt or powershell terminal.
|
||||||
|
|
||||||
## Support and dev channel
|
## Support and dev channel
|
||||||
|
|
||||||
[Matrix space: #comfyui_space:matrix.org](https://app.element.io/#/room/%23comfyui_space%3Amatrix.org) (it's like discord but open source).
|
[Matrix space: #comfyui_space:matrix.org](https://app.element.io/#/room/%23comfyui_space%3Amatrix.org) (it's like discord but open source).
|
||||||
|
|||||||
@ -35,6 +35,8 @@ parser = argparse.ArgumentParser()
|
|||||||
|
|
||||||
parser.add_argument("--listen", type=str, default="127.0.0.1", metavar="IP", nargs="?", const="0.0.0.0", help="Specify the IP address to listen on (default: 127.0.0.1). If --listen is provided without an argument, it defaults to 0.0.0.0. (listens on all)")
|
parser.add_argument("--listen", type=str, default="127.0.0.1", metavar="IP", nargs="?", const="0.0.0.0", help="Specify the IP address to listen on (default: 127.0.0.1). If --listen is provided without an argument, it defaults to 0.0.0.0. (listens on all)")
|
||||||
parser.add_argument("--port", type=int, default=8188, help="Set the listen port.")
|
parser.add_argument("--port", type=int, default=8188, help="Set the listen port.")
|
||||||
|
parser.add_argument("--tls-keyfile", type=str, help="Path to TLS (SSL) key file. Enables TLS, makes app accessible at https://... requires --tls-certfile to function")
|
||||||
|
parser.add_argument("--tls-certfile", type=str, help="Path to TLS (SSL) certificate file. Enables TLS, makes app accessible at https://... requires --tls-keyfile to function")
|
||||||
parser.add_argument("--enable-cors-header", type=str, default=None, metavar="ORIGIN", nargs="?", const="*", help="Enable CORS (Cross-Origin Resource Sharing) with optional origin or allow all with default '*'.")
|
parser.add_argument("--enable-cors-header", type=str, default=None, metavar="ORIGIN", nargs="?", const="*", help="Enable CORS (Cross-Origin Resource Sharing) with optional origin or allow all with default '*'.")
|
||||||
parser.add_argument("--max-upload-size", type=float, default=100, help="Set the maximum upload size in MB.")
|
parser.add_argument("--max-upload-size", type=float, default=100, help="Set the maximum upload size in MB.")
|
||||||
|
|
||||||
@ -114,6 +116,9 @@ parser.add_argument("--disable-metadata", action="store_true", help="Disable sav
|
|||||||
|
|
||||||
parser.add_argument("--multi-user", action="store_true", help="Enables per-user storage.")
|
parser.add_argument("--multi-user", action="store_true", help="Enables per-user storage.")
|
||||||
|
|
||||||
|
parser.add_argument("--verbose", action="store_true", help="Enables more debug prints.")
|
||||||
|
|
||||||
|
|
||||||
if comfy.options.args_parsing:
|
if comfy.options.args_parsing:
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
else:
|
else:
|
||||||
@ -124,3 +129,10 @@ if args.windows_standalone_build:
|
|||||||
|
|
||||||
if args.disable_auto_launch:
|
if args.disable_auto_launch:
|
||||||
args.auto_launch = False
|
args.auto_launch = False
|
||||||
|
|
||||||
|
import logging
|
||||||
|
logging_level = logging.INFO
|
||||||
|
if args.verbose:
|
||||||
|
logging_level = logging.DEBUG
|
||||||
|
|
||||||
|
logging.basicConfig(format="%(message)s", level=logging_level)
|
||||||
|
|||||||
@ -2,6 +2,7 @@ from .utils import load_torch_file, transformers_convert, state_dict_prefix_repl
|
|||||||
import os
|
import os
|
||||||
import torch
|
import torch
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
|
|
||||||
import comfy.ops
|
import comfy.ops
|
||||||
import comfy.model_patcher
|
import comfy.model_patcher
|
||||||
@ -99,7 +100,7 @@ def load_clipvision_from_sd(sd, prefix="", convert_keys=False):
|
|||||||
clip = ClipVisionModel(json_config)
|
clip = ClipVisionModel(json_config)
|
||||||
m, u = clip.load_sd(sd)
|
m, u = clip.load_sd(sd)
|
||||||
if len(m) > 0:
|
if len(m) > 0:
|
||||||
print("missing clip vision:", m)
|
logging.warning("missing clip vision: {}".format(m))
|
||||||
u = set(u)
|
u = set(u)
|
||||||
keys = list(sd.keys())
|
keys = list(sd.keys())
|
||||||
for k in keys:
|
for k in keys:
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import torch
|
import torch
|
||||||
import math
|
import math
|
||||||
import os
|
import os
|
||||||
|
import logging
|
||||||
import comfy.utils
|
import comfy.utils
|
||||||
import comfy.model_management
|
import comfy.model_management
|
||||||
import comfy.model_detection
|
import comfy.model_detection
|
||||||
@ -137,11 +138,13 @@ class ControlBase:
|
|||||||
return out
|
return out
|
||||||
|
|
||||||
class ControlNet(ControlBase):
|
class ControlNet(ControlBase):
|
||||||
def __init__(self, control_model, global_average_pooling=False, device=None, load_device=None, manual_cast_dtype=None):
|
def __init__(self, control_model=None, global_average_pooling=False, device=None, load_device=None, manual_cast_dtype=None):
|
||||||
super().__init__(device)
|
super().__init__(device)
|
||||||
self.control_model = control_model
|
self.control_model = control_model
|
||||||
self.load_device = load_device
|
self.load_device = load_device
|
||||||
self.control_model_wrapped = comfy.model_patcher.ModelPatcher(self.control_model, load_device=load_device, offload_device=comfy.model_management.unet_offload_device())
|
if control_model is not None:
|
||||||
|
self.control_model_wrapped = comfy.model_patcher.ModelPatcher(self.control_model, load_device=load_device, offload_device=comfy.model_management.unet_offload_device())
|
||||||
|
|
||||||
self.global_average_pooling = global_average_pooling
|
self.global_average_pooling = global_average_pooling
|
||||||
self.model_sampling_current = None
|
self.model_sampling_current = None
|
||||||
self.manual_cast_dtype = manual_cast_dtype
|
self.manual_cast_dtype = manual_cast_dtype
|
||||||
@ -182,7 +185,9 @@ class ControlNet(ControlBase):
|
|||||||
return self.control_merge(None, control, control_prev, output_dtype)
|
return self.control_merge(None, control, control_prev, output_dtype)
|
||||||
|
|
||||||
def copy(self):
|
def copy(self):
|
||||||
c = ControlNet(self.control_model, global_average_pooling=self.global_average_pooling, load_device=self.load_device, manual_cast_dtype=self.manual_cast_dtype)
|
c = ControlNet(None, global_average_pooling=self.global_average_pooling, load_device=self.load_device, manual_cast_dtype=self.manual_cast_dtype)
|
||||||
|
c.control_model = self.control_model
|
||||||
|
c.control_model_wrapped = self.control_model_wrapped
|
||||||
self.copy_to(c)
|
self.copy_to(c)
|
||||||
return c
|
return c
|
||||||
|
|
||||||
@ -200,7 +205,7 @@ class ControlNet(ControlBase):
|
|||||||
super().cleanup()
|
super().cleanup()
|
||||||
|
|
||||||
class ControlLoraOps:
|
class ControlLoraOps:
|
||||||
class Linear(torch.nn.Module):
|
class Linear(torch.nn.Module, comfy.ops.CastWeightBiasOp):
|
||||||
def __init__(self, in_features: int, out_features: int, bias: bool = True,
|
def __init__(self, in_features: int, out_features: int, bias: bool = True,
|
||||||
device=None, dtype=None) -> None:
|
device=None, dtype=None) -> None:
|
||||||
factory_kwargs = {'device': device, 'dtype': dtype}
|
factory_kwargs = {'device': device, 'dtype': dtype}
|
||||||
@ -219,7 +224,7 @@ class ControlLoraOps:
|
|||||||
else:
|
else:
|
||||||
return torch.nn.functional.linear(input, weight, bias)
|
return torch.nn.functional.linear(input, weight, bias)
|
||||||
|
|
||||||
class Conv2d(torch.nn.Module):
|
class Conv2d(torch.nn.Module, comfy.ops.CastWeightBiasOp):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
in_channels,
|
in_channels,
|
||||||
@ -367,7 +372,7 @@ def load_controlnet(ckpt_path, model=None):
|
|||||||
|
|
||||||
leftover_keys = controlnet_data.keys()
|
leftover_keys = controlnet_data.keys()
|
||||||
if len(leftover_keys) > 0:
|
if len(leftover_keys) > 0:
|
||||||
print("leftover keys:", leftover_keys)
|
logging.warning("leftover keys: {}".format(leftover_keys))
|
||||||
controlnet_data = new_sd
|
controlnet_data = new_sd
|
||||||
|
|
||||||
pth_key = 'control_model.zero_convs.0.0.weight'
|
pth_key = 'control_model.zero_convs.0.0.weight'
|
||||||
@ -382,7 +387,7 @@ def load_controlnet(ckpt_path, model=None):
|
|||||||
else:
|
else:
|
||||||
net = load_t2i_adapter(controlnet_data)
|
net = load_t2i_adapter(controlnet_data)
|
||||||
if net is None:
|
if net is None:
|
||||||
print("error checkpoint does not contain controlnet or t2i adapter data", ckpt_path)
|
logging.error("error checkpoint does not contain controlnet or t2i adapter data {}".format(ckpt_path))
|
||||||
return net
|
return net
|
||||||
|
|
||||||
if controlnet_config is None:
|
if controlnet_config is None:
|
||||||
@ -417,7 +422,7 @@ def load_controlnet(ckpt_path, model=None):
|
|||||||
cd = controlnet_data[x]
|
cd = controlnet_data[x]
|
||||||
cd += model_sd[sd_key].type(cd.dtype).to(cd.device)
|
cd += model_sd[sd_key].type(cd.dtype).to(cd.device)
|
||||||
else:
|
else:
|
||||||
print("WARNING: Loaded a diff controlnet without a model. It will very likely not work.")
|
logging.warning("WARNING: Loaded a diff controlnet without a model. It will very likely not work.")
|
||||||
|
|
||||||
class WeightsLoader(torch.nn.Module):
|
class WeightsLoader(torch.nn.Module):
|
||||||
pass
|
pass
|
||||||
@ -426,7 +431,12 @@ def load_controlnet(ckpt_path, model=None):
|
|||||||
missing, unexpected = w.load_state_dict(controlnet_data, strict=False)
|
missing, unexpected = w.load_state_dict(controlnet_data, strict=False)
|
||||||
else:
|
else:
|
||||||
missing, unexpected = control_model.load_state_dict(controlnet_data, strict=False)
|
missing, unexpected = control_model.load_state_dict(controlnet_data, strict=False)
|
||||||
print(missing, unexpected)
|
|
||||||
|
if len(missing) > 0:
|
||||||
|
logging.warning("missing controlnet keys: {}".format(missing))
|
||||||
|
|
||||||
|
if len(unexpected) > 0:
|
||||||
|
logging.debug("unexpected controlnet keys: {}".format(unexpected))
|
||||||
|
|
||||||
global_average_pooling = False
|
global_average_pooling = False
|
||||||
filename = os.path.splitext(ckpt_path)[0]
|
filename = os.path.splitext(ckpt_path)[0]
|
||||||
@ -536,9 +546,9 @@ def load_t2i_adapter(t2i_data):
|
|||||||
|
|
||||||
missing, unexpected = model_ad.load_state_dict(t2i_data)
|
missing, unexpected = model_ad.load_state_dict(t2i_data)
|
||||||
if len(missing) > 0:
|
if len(missing) > 0:
|
||||||
print("t2i missing", missing)
|
logging.warning("t2i missing {}".format(missing))
|
||||||
|
|
||||||
if len(unexpected) > 0:
|
if len(unexpected) > 0:
|
||||||
print("t2i unexpected", unexpected)
|
logging.debug("t2i unexpected {}".format(unexpected))
|
||||||
|
|
||||||
return T2IAdapter(model_ad, model_ad.input_channels, compression_ratio, upscale_algorithm)
|
return T2IAdapter(model_ad, model_ad.input_channels, compression_ratio, upscale_algorithm)
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import re
|
import re
|
||||||
import torch
|
import torch
|
||||||
|
import logging
|
||||||
|
|
||||||
# conversion code from https://github.com/huggingface/diffusers/blob/main/scripts/convert_diffusers_to_original_stable_diffusion.py
|
# conversion code from https://github.com/huggingface/diffusers/blob/main/scripts/convert_diffusers_to_original_stable_diffusion.py
|
||||||
|
|
||||||
@ -177,7 +178,7 @@ def convert_vae_state_dict(vae_state_dict):
|
|||||||
for k, v in new_state_dict.items():
|
for k, v in new_state_dict.items():
|
||||||
for weight_name in weights_to_convert:
|
for weight_name in weights_to_convert:
|
||||||
if f"mid.attn_1.{weight_name}.weight" in k:
|
if f"mid.attn_1.{weight_name}.weight" in k:
|
||||||
print(f"Reshaping {k} for SD format")
|
logging.debug(f"Reshaping {k} for SD format")
|
||||||
new_state_dict[k] = reshape_weight_for_sd(v)
|
new_state_dict[k] = reshape_weight_for_sd(v)
|
||||||
return new_state_dict
|
return new_state_dict
|
||||||
|
|
||||||
@ -205,6 +206,21 @@ textenc_pattern = re.compile("|".join(protected.keys()))
|
|||||||
# Ordering is from https://github.com/pytorch/pytorch/blob/master/test/cpp/api/modules.cpp
|
# Ordering is from https://github.com/pytorch/pytorch/blob/master/test/cpp/api/modules.cpp
|
||||||
code2idx = {"q": 0, "k": 1, "v": 2}
|
code2idx = {"q": 0, "k": 1, "v": 2}
|
||||||
|
|
||||||
|
# This function exists because at the time of writing torch.cat can't do fp8 with cuda
|
||||||
|
def cat_tensors(tensors):
|
||||||
|
x = 0
|
||||||
|
for t in tensors:
|
||||||
|
x += t.shape[0]
|
||||||
|
|
||||||
|
shape = [x] + list(tensors[0].shape)[1:]
|
||||||
|
out = torch.empty(shape, device=tensors[0].device, dtype=tensors[0].dtype)
|
||||||
|
|
||||||
|
x = 0
|
||||||
|
for t in tensors:
|
||||||
|
out[x:x + t.shape[0]] = t
|
||||||
|
x += t.shape[0]
|
||||||
|
|
||||||
|
return out
|
||||||
|
|
||||||
def convert_text_enc_state_dict_v20(text_enc_dict, prefix=""):
|
def convert_text_enc_state_dict_v20(text_enc_dict, prefix=""):
|
||||||
new_state_dict = {}
|
new_state_dict = {}
|
||||||
@ -248,13 +264,13 @@ def convert_text_enc_state_dict_v20(text_enc_dict, prefix=""):
|
|||||||
if None in tensors:
|
if None in tensors:
|
||||||
raise Exception("CORRUPTED MODEL: one of the q-k-v values for the text encoder was missing")
|
raise Exception("CORRUPTED MODEL: one of the q-k-v values for the text encoder was missing")
|
||||||
relabelled_key = textenc_pattern.sub(lambda m: protected[re.escape(m.group(0))], k_pre)
|
relabelled_key = textenc_pattern.sub(lambda m: protected[re.escape(m.group(0))], k_pre)
|
||||||
new_state_dict[relabelled_key + ".in_proj_weight"] = torch.cat(tensors)
|
new_state_dict[relabelled_key + ".in_proj_weight"] = cat_tensors(tensors)
|
||||||
|
|
||||||
for k_pre, tensors in capture_qkv_bias.items():
|
for k_pre, tensors in capture_qkv_bias.items():
|
||||||
if None in tensors:
|
if None in tensors:
|
||||||
raise Exception("CORRUPTED MODEL: one of the q-k-v values for the text encoder was missing")
|
raise Exception("CORRUPTED MODEL: one of the q-k-v values for the text encoder was missing")
|
||||||
relabelled_key = textenc_pattern.sub(lambda m: protected[re.escape(m.group(0))], k_pre)
|
relabelled_key = textenc_pattern.sub(lambda m: protected[re.escape(m.group(0))], k_pre)
|
||||||
new_state_dict[relabelled_key + ".in_proj_bias"] = torch.cat(tensors)
|
new_state_dict[relabelled_key + ".in_proj_bias"] = cat_tensors(tensors)
|
||||||
|
|
||||||
return new_state_dict
|
return new_state_dict
|
||||||
|
|
||||||
|
|||||||
@ -527,6 +527,9 @@ def sample_dpmpp_2s_ancestral(model, x, sigmas, extra_args=None, callback=None,
|
|||||||
@torch.no_grad()
|
@torch.no_grad()
|
||||||
def sample_dpmpp_sde(model, x, sigmas, extra_args=None, callback=None, disable=None, eta=1., s_noise=1., noise_sampler=None, r=1 / 2):
|
def sample_dpmpp_sde(model, x, sigmas, extra_args=None, callback=None, disable=None, eta=1., s_noise=1., noise_sampler=None, r=1 / 2):
|
||||||
"""DPM-Solver++ (stochastic)."""
|
"""DPM-Solver++ (stochastic)."""
|
||||||
|
if len(sigmas) <= 1:
|
||||||
|
return x
|
||||||
|
|
||||||
sigma_min, sigma_max = sigmas[sigmas > 0].min(), sigmas.max()
|
sigma_min, sigma_max = sigmas[sigmas > 0].min(), sigmas.max()
|
||||||
seed = extra_args.get("seed", None)
|
seed = extra_args.get("seed", None)
|
||||||
noise_sampler = BrownianTreeNoiseSampler(x, sigma_min, sigma_max, seed=seed, cpu=True) if noise_sampler is None else noise_sampler
|
noise_sampler = BrownianTreeNoiseSampler(x, sigma_min, sigma_max, seed=seed, cpu=True) if noise_sampler is None else noise_sampler
|
||||||
@ -595,6 +598,8 @@ def sample_dpmpp_2m(model, x, sigmas, extra_args=None, callback=None, disable=No
|
|||||||
@torch.no_grad()
|
@torch.no_grad()
|
||||||
def sample_dpmpp_2m_sde(model, x, sigmas, extra_args=None, callback=None, disable=None, eta=1., s_noise=1., noise_sampler=None, solver_type='midpoint'):
|
def sample_dpmpp_2m_sde(model, x, sigmas, extra_args=None, callback=None, disable=None, eta=1., s_noise=1., noise_sampler=None, solver_type='midpoint'):
|
||||||
"""DPM-Solver++(2M) SDE."""
|
"""DPM-Solver++(2M) SDE."""
|
||||||
|
if len(sigmas) <= 1:
|
||||||
|
return x
|
||||||
|
|
||||||
if solver_type not in {'heun', 'midpoint'}:
|
if solver_type not in {'heun', 'midpoint'}:
|
||||||
raise ValueError('solver_type must be \'heun\' or \'midpoint\'')
|
raise ValueError('solver_type must be \'heun\' or \'midpoint\'')
|
||||||
@ -642,6 +647,9 @@ def sample_dpmpp_2m_sde(model, x, sigmas, extra_args=None, callback=None, disabl
|
|||||||
def sample_dpmpp_3m_sde(model, x, sigmas, extra_args=None, callback=None, disable=None, eta=1., s_noise=1., noise_sampler=None):
|
def sample_dpmpp_3m_sde(model, x, sigmas, extra_args=None, callback=None, disable=None, eta=1., s_noise=1., noise_sampler=None):
|
||||||
"""DPM-Solver++(3M) SDE."""
|
"""DPM-Solver++(3M) SDE."""
|
||||||
|
|
||||||
|
if len(sigmas) <= 1:
|
||||||
|
return x
|
||||||
|
|
||||||
seed = extra_args.get("seed", None)
|
seed = extra_args.get("seed", None)
|
||||||
sigma_min, sigma_max = sigmas[sigmas > 0].min(), sigmas.max()
|
sigma_min, sigma_max = sigmas[sigmas > 0].min(), sigmas.max()
|
||||||
noise_sampler = BrownianTreeNoiseSampler(x, sigma_min, sigma_max, seed=seed, cpu=True) if noise_sampler is None else noise_sampler
|
noise_sampler = BrownianTreeNoiseSampler(x, sigma_min, sigma_max, seed=seed, cpu=True) if noise_sampler is None else noise_sampler
|
||||||
@ -690,18 +698,27 @@ def sample_dpmpp_3m_sde(model, x, sigmas, extra_args=None, callback=None, disabl
|
|||||||
|
|
||||||
@torch.no_grad()
|
@torch.no_grad()
|
||||||
def sample_dpmpp_3m_sde_gpu(model, x, sigmas, extra_args=None, callback=None, disable=None, eta=1., s_noise=1., noise_sampler=None):
|
def sample_dpmpp_3m_sde_gpu(model, x, sigmas, extra_args=None, callback=None, disable=None, eta=1., s_noise=1., noise_sampler=None):
|
||||||
|
if len(sigmas) <= 1:
|
||||||
|
return x
|
||||||
|
|
||||||
sigma_min, sigma_max = sigmas[sigmas > 0].min(), sigmas.max()
|
sigma_min, sigma_max = sigmas[sigmas > 0].min(), sigmas.max()
|
||||||
noise_sampler = BrownianTreeNoiseSampler(x, sigma_min, sigma_max, seed=extra_args.get("seed", None), cpu=False) if noise_sampler is None else noise_sampler
|
noise_sampler = BrownianTreeNoiseSampler(x, sigma_min, sigma_max, seed=extra_args.get("seed", None), cpu=False) if noise_sampler is None else noise_sampler
|
||||||
return sample_dpmpp_3m_sde(model, x, sigmas, extra_args=extra_args, callback=callback, disable=disable, eta=eta, s_noise=s_noise, noise_sampler=noise_sampler)
|
return sample_dpmpp_3m_sde(model, x, sigmas, extra_args=extra_args, callback=callback, disable=disable, eta=eta, s_noise=s_noise, noise_sampler=noise_sampler)
|
||||||
|
|
||||||
@torch.no_grad()
|
@torch.no_grad()
|
||||||
def sample_dpmpp_2m_sde_gpu(model, x, sigmas, extra_args=None, callback=None, disable=None, eta=1., s_noise=1., noise_sampler=None, solver_type='midpoint'):
|
def sample_dpmpp_2m_sde_gpu(model, x, sigmas, extra_args=None, callback=None, disable=None, eta=1., s_noise=1., noise_sampler=None, solver_type='midpoint'):
|
||||||
|
if len(sigmas) <= 1:
|
||||||
|
return x
|
||||||
|
|
||||||
sigma_min, sigma_max = sigmas[sigmas > 0].min(), sigmas.max()
|
sigma_min, sigma_max = sigmas[sigmas > 0].min(), sigmas.max()
|
||||||
noise_sampler = BrownianTreeNoiseSampler(x, sigma_min, sigma_max, seed=extra_args.get("seed", None), cpu=False) if noise_sampler is None else noise_sampler
|
noise_sampler = BrownianTreeNoiseSampler(x, sigma_min, sigma_max, seed=extra_args.get("seed", None), cpu=False) if noise_sampler is None else noise_sampler
|
||||||
return sample_dpmpp_2m_sde(model, x, sigmas, extra_args=extra_args, callback=callback, disable=disable, eta=eta, s_noise=s_noise, noise_sampler=noise_sampler, solver_type=solver_type)
|
return sample_dpmpp_2m_sde(model, x, sigmas, extra_args=extra_args, callback=callback, disable=disable, eta=eta, s_noise=s_noise, noise_sampler=noise_sampler, solver_type=solver_type)
|
||||||
|
|
||||||
@torch.no_grad()
|
@torch.no_grad()
|
||||||
def sample_dpmpp_sde_gpu(model, x, sigmas, extra_args=None, callback=None, disable=None, eta=1., s_noise=1., noise_sampler=None, r=1 / 2):
|
def sample_dpmpp_sde_gpu(model, x, sigmas, extra_args=None, callback=None, disable=None, eta=1., s_noise=1., noise_sampler=None, r=1 / 2):
|
||||||
|
if len(sigmas) <= 1:
|
||||||
|
return x
|
||||||
|
|
||||||
sigma_min, sigma_max = sigmas[sigmas > 0].min(), sigmas.max()
|
sigma_min, sigma_max = sigmas[sigmas > 0].min(), sigmas.max()
|
||||||
noise_sampler = BrownianTreeNoiseSampler(x, sigma_min, sigma_max, seed=extra_args.get("seed", None), cpu=False) if noise_sampler is None else noise_sampler
|
noise_sampler = BrownianTreeNoiseSampler(x, sigma_min, sigma_max, seed=extra_args.get("seed", None), cpu=False) if noise_sampler is None else noise_sampler
|
||||||
return sample_dpmpp_sde(model, x, sigmas, extra_args=extra_args, callback=callback, disable=disable, eta=eta, s_noise=s_noise, noise_sampler=noise_sampler, r=r)
|
return sample_dpmpp_sde(model, x, sigmas, extra_args=extra_args, callback=callback, disable=disable, eta=eta, s_noise=s_noise, noise_sampler=noise_sampler, r=r)
|
||||||
@ -748,7 +765,7 @@ def sample_lcm(model, x, sigmas, extra_args=None, callback=None, disable=None, n
|
|||||||
|
|
||||||
x = denoised
|
x = denoised
|
||||||
if sigmas[i + 1] > 0:
|
if sigmas[i + 1] > 0:
|
||||||
x += sigmas[i + 1] * noise_sampler(sigmas[i], sigmas[i + 1])
|
x = model.inner_model.inner_model.model_sampling.noise_scaling(sigmas[i + 1], noise_sampler(sigmas[i], sigmas[i + 1]), x)
|
||||||
return x
|
return x
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -95,7 +95,7 @@ class SC_Prior(LatentFormat):
|
|||||||
|
|
||||||
class SC_B(LatentFormat):
|
class SC_B(LatentFormat):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.scale_factor = 1.0
|
self.scale_factor = 1.0 / 0.43
|
||||||
self.latent_rgb_factors = [
|
self.latent_rgb_factors = [
|
||||||
[ 0.1121, 0.2006, 0.1023],
|
[ 0.1121, 0.2006, 0.1023],
|
||||||
[-0.2093, -0.0222, -0.0195],
|
[-0.2093, -0.0222, -0.0195],
|
||||||
|
|||||||
@ -163,11 +163,9 @@ class ResBlock(nn.Module):
|
|||||||
|
|
||||||
|
|
||||||
class StageA(nn.Module):
|
class StageA(nn.Module):
|
||||||
def __init__(self, levels=2, bottleneck_blocks=12, c_hidden=384, c_latent=4, codebook_size=8192,
|
def __init__(self, levels=2, bottleneck_blocks=12, c_hidden=384, c_latent=4, codebook_size=8192):
|
||||||
scale_factor=0.43): # 0.3764
|
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.c_latent = c_latent
|
self.c_latent = c_latent
|
||||||
self.scale_factor = scale_factor
|
|
||||||
c_levels = [c_hidden // (2 ** i) for i in reversed(range(levels))]
|
c_levels = [c_hidden // (2 ** i) for i in reversed(range(levels))]
|
||||||
|
|
||||||
# Encoder blocks
|
# Encoder blocks
|
||||||
@ -214,12 +212,11 @@ class StageA(nn.Module):
|
|||||||
x = self.down_blocks(x)
|
x = self.down_blocks(x)
|
||||||
if quantize:
|
if quantize:
|
||||||
qe, (vq_loss, commit_loss), indices = self.vquantizer.forward(x, dim=1)
|
qe, (vq_loss, commit_loss), indices = self.vquantizer.forward(x, dim=1)
|
||||||
return qe / self.scale_factor, x / self.scale_factor, indices, vq_loss + commit_loss * 0.25
|
return qe, x, indices, vq_loss + commit_loss * 0.25
|
||||||
else:
|
else:
|
||||||
return x / self.scale_factor
|
return x
|
||||||
|
|
||||||
def decode(self, x):
|
def decode(self, x):
|
||||||
x = x * self.scale_factor
|
|
||||||
x = self.up_blocks(x)
|
x = self.up_blocks(x)
|
||||||
x = self.out_block(x)
|
x = self.out_block(x)
|
||||||
return x
|
return x
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import torch.nn.functional as F
|
|||||||
from torch import nn, einsum
|
from torch import nn, einsum
|
||||||
from einops import rearrange, repeat
|
from einops import rearrange, repeat
|
||||||
from typing import Optional, Any
|
from typing import Optional, Any
|
||||||
|
import logging
|
||||||
|
|
||||||
from .diffusionmodules.util import checkpoint, AlphaBlender, timestep_embedding
|
from .diffusionmodules.util import checkpoint, AlphaBlender, timestep_embedding
|
||||||
from .sub_quadratic_attention import efficient_dot_product_attention
|
from .sub_quadratic_attention import efficient_dot_product_attention
|
||||||
@ -20,7 +21,7 @@ ops = comfy.ops.disable_weight_init
|
|||||||
|
|
||||||
# CrossAttn precision handling
|
# CrossAttn precision handling
|
||||||
if args.dont_upcast_attention:
|
if args.dont_upcast_attention:
|
||||||
print("disabling upcasting of attention")
|
logging.info("disabling upcasting of attention")
|
||||||
_ATTN_PRECISION = "fp16"
|
_ATTN_PRECISION = "fp16"
|
||||||
else:
|
else:
|
||||||
_ATTN_PRECISION = "fp32"
|
_ATTN_PRECISION = "fp32"
|
||||||
@ -274,12 +275,12 @@ def attention_split(q, k, v, heads, mask=None):
|
|||||||
model_management.soft_empty_cache(True)
|
model_management.soft_empty_cache(True)
|
||||||
if cleared_cache == False:
|
if cleared_cache == False:
|
||||||
cleared_cache = True
|
cleared_cache = True
|
||||||
print("out of memory error, emptying cache and trying again")
|
logging.warning("out of memory error, emptying cache and trying again")
|
||||||
continue
|
continue
|
||||||
steps *= 2
|
steps *= 2
|
||||||
if steps > 64:
|
if steps > 64:
|
||||||
raise e
|
raise e
|
||||||
print("out of memory error, increasing steps and trying again", steps)
|
logging.warning("out of memory error, increasing steps and trying again {}".format(steps))
|
||||||
else:
|
else:
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
@ -296,8 +297,8 @@ def attention_split(q, k, v, heads, mask=None):
|
|||||||
BROKEN_XFORMERS = False
|
BROKEN_XFORMERS = False
|
||||||
try:
|
try:
|
||||||
x_vers = xformers.__version__
|
x_vers = xformers.__version__
|
||||||
#I think 0.0.23 is also broken (q with bs bigger than 65535 gives CUDA error)
|
# XFormers bug confirmed on all versions from 0.0.21 to 0.0.26 (q with bs bigger than 65535 gives CUDA error)
|
||||||
BROKEN_XFORMERS = x_vers.startswith("0.0.21") or x_vers.startswith("0.0.22") or x_vers.startswith("0.0.23")
|
BROKEN_XFORMERS = x_vers.startswith("0.0.2") and not x_vers.startswith("0.0.20")
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -351,17 +352,17 @@ def attention_pytorch(q, k, v, heads, mask=None):
|
|||||||
optimized_attention = attention_basic
|
optimized_attention = attention_basic
|
||||||
|
|
||||||
if model_management.xformers_enabled():
|
if model_management.xformers_enabled():
|
||||||
print("Using xformers cross attention")
|
logging.info("Using xformers cross attention")
|
||||||
optimized_attention = attention_xformers
|
optimized_attention = attention_xformers
|
||||||
elif model_management.pytorch_attention_enabled():
|
elif model_management.pytorch_attention_enabled():
|
||||||
print("Using pytorch cross attention")
|
logging.info("Using pytorch cross attention")
|
||||||
optimized_attention = attention_pytorch
|
optimized_attention = attention_pytorch
|
||||||
else:
|
else:
|
||||||
if args.use_split_cross_attention:
|
if args.use_split_cross_attention:
|
||||||
print("Using split optimization for cross attention")
|
logging.info("Using split optimization for cross attention")
|
||||||
optimized_attention = attention_split
|
optimized_attention = attention_split
|
||||||
else:
|
else:
|
||||||
print("Using sub quadratic optimization for cross attention, if you have memory or speed issues try using: --use-split-cross-attention")
|
logging.info("Using sub quadratic optimization for cross attention, if you have memory or speed issues try using: --use-split-cross-attention")
|
||||||
optimized_attention = attention_sub_quad
|
optimized_attention = attention_sub_quad
|
||||||
|
|
||||||
optimized_attention_masked = optimized_attention
|
optimized_attention_masked = optimized_attention
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import torch.nn as nn
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
from einops import rearrange
|
from einops import rearrange
|
||||||
from typing import Optional, Any
|
from typing import Optional, Any
|
||||||
|
import logging
|
||||||
|
|
||||||
from comfy import model_management
|
from comfy import model_management
|
||||||
import comfy.ops
|
import comfy.ops
|
||||||
@ -190,7 +191,7 @@ def slice_attention(q, k, v):
|
|||||||
steps *= 2
|
steps *= 2
|
||||||
if steps > 128:
|
if steps > 128:
|
||||||
raise e
|
raise e
|
||||||
print("out of memory error, increasing steps and trying again", steps)
|
logging.warning("out of memory error, increasing steps and trying again {}".format(steps))
|
||||||
|
|
||||||
return r1
|
return r1
|
||||||
|
|
||||||
@ -235,7 +236,7 @@ def pytorch_attention(q, k, v):
|
|||||||
out = torch.nn.functional.scaled_dot_product_attention(q, k, v, attn_mask=None, dropout_p=0.0, is_causal=False)
|
out = torch.nn.functional.scaled_dot_product_attention(q, k, v, attn_mask=None, dropout_p=0.0, is_causal=False)
|
||||||
out = out.transpose(2, 3).reshape(B, C, H, W)
|
out = out.transpose(2, 3).reshape(B, C, H, W)
|
||||||
except model_management.OOM_EXCEPTION as e:
|
except model_management.OOM_EXCEPTION as e:
|
||||||
print("scaled_dot_product_attention OOMed: switched to slice attention")
|
logging.warning("scaled_dot_product_attention OOMed: switched to slice attention")
|
||||||
out = slice_attention(q.view(B, -1, C), k.view(B, -1, C).transpose(1, 2), v.view(B, -1, C).transpose(1, 2)).reshape(B, C, H, W)
|
out = slice_attention(q.view(B, -1, C), k.view(B, -1, C).transpose(1, 2), v.view(B, -1, C).transpose(1, 2)).reshape(B, C, H, W)
|
||||||
return out
|
return out
|
||||||
|
|
||||||
@ -268,13 +269,13 @@ class AttnBlock(nn.Module):
|
|||||||
padding=0)
|
padding=0)
|
||||||
|
|
||||||
if model_management.xformers_enabled_vae():
|
if model_management.xformers_enabled_vae():
|
||||||
print("Using xformers attention in VAE")
|
logging.info("Using xformers attention in VAE")
|
||||||
self.optimized_attention = xformers_attention
|
self.optimized_attention = xformers_attention
|
||||||
elif model_management.pytorch_attention_enabled():
|
elif model_management.pytorch_attention_enabled():
|
||||||
print("Using pytorch attention in VAE")
|
logging.info("Using pytorch attention in VAE")
|
||||||
self.optimized_attention = pytorch_attention
|
self.optimized_attention = pytorch_attention
|
||||||
else:
|
else:
|
||||||
print("Using split attention in VAE")
|
logging.info("Using split attention in VAE")
|
||||||
self.optimized_attention = normal_attention
|
self.optimized_attention = normal_attention
|
||||||
|
|
||||||
def forward(self, x):
|
def forward(self, x):
|
||||||
@ -562,7 +563,7 @@ class Decoder(nn.Module):
|
|||||||
block_in = ch*ch_mult[self.num_resolutions-1]
|
block_in = ch*ch_mult[self.num_resolutions-1]
|
||||||
curr_res = resolution // 2**(self.num_resolutions-1)
|
curr_res = resolution // 2**(self.num_resolutions-1)
|
||||||
self.z_shape = (1,z_channels,curr_res,curr_res)
|
self.z_shape = (1,z_channels,curr_res,curr_res)
|
||||||
print("Working with z of shape {} = {} dimensions.".format(
|
logging.debug("Working with z of shape {} = {} dimensions.".format(
|
||||||
self.z_shape, np.prod(self.z_shape)))
|
self.z_shape, np.prod(self.z_shape)))
|
||||||
|
|
||||||
# z to block_in
|
# z to block_in
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import torch as th
|
|||||||
import torch.nn as nn
|
import torch.nn as nn
|
||||||
import torch.nn.functional as F
|
import torch.nn.functional as F
|
||||||
from einops import rearrange
|
from einops import rearrange
|
||||||
|
import logging
|
||||||
|
|
||||||
from .util import (
|
from .util import (
|
||||||
checkpoint,
|
checkpoint,
|
||||||
@ -359,7 +360,7 @@ def apply_control(h, control, name):
|
|||||||
try:
|
try:
|
||||||
h += ctrl
|
h += ctrl
|
||||||
except:
|
except:
|
||||||
print("warning control could not be applied", h.shape, ctrl.shape)
|
logging.warning("warning control could not be applied {} {}".format(h.shape, ctrl.shape))
|
||||||
return h
|
return h
|
||||||
|
|
||||||
class UNetModel(nn.Module):
|
class UNetModel(nn.Module):
|
||||||
@ -496,7 +497,7 @@ class UNetModel(nn.Module):
|
|||||||
if isinstance(self.num_classes, int):
|
if isinstance(self.num_classes, int):
|
||||||
self.label_emb = nn.Embedding(num_classes, time_embed_dim, dtype=self.dtype, device=device)
|
self.label_emb = nn.Embedding(num_classes, time_embed_dim, dtype=self.dtype, device=device)
|
||||||
elif self.num_classes == "continuous":
|
elif self.num_classes == "continuous":
|
||||||
print("setting up linear c_adm embedding layer")
|
logging.debug("setting up linear c_adm embedding layer")
|
||||||
self.label_emb = nn.Linear(1, time_embed_dim)
|
self.label_emb = nn.Linear(1, time_embed_dim)
|
||||||
elif self.num_classes == "sequential":
|
elif self.num_classes == "sequential":
|
||||||
assert adm_in_channels is not None
|
assert adm_in_channels is not None
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import torch
|
|||||||
from torch import Tensor
|
from torch import Tensor
|
||||||
from torch.utils.checkpoint import checkpoint
|
from torch.utils.checkpoint import checkpoint
|
||||||
import math
|
import math
|
||||||
|
import logging
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from typing import Optional, NamedTuple, List, Protocol
|
from typing import Optional, NamedTuple, List, Protocol
|
||||||
@ -170,7 +171,7 @@ def _get_attention_scores_no_kv_chunking(
|
|||||||
attn_probs = attn_scores.softmax(dim=-1)
|
attn_probs = attn_scores.softmax(dim=-1)
|
||||||
del attn_scores
|
del attn_scores
|
||||||
except model_management.OOM_EXCEPTION:
|
except model_management.OOM_EXCEPTION:
|
||||||
print("ran out of memory while running softmax in _get_attention_scores_no_kv_chunking, trying slower in place softmax instead")
|
logging.warning("ran out of memory while running softmax in _get_attention_scores_no_kv_chunking, trying slower in place softmax instead")
|
||||||
attn_scores -= attn_scores.max(dim=-1, keepdim=True).values
|
attn_scores -= attn_scores.max(dim=-1, keepdim=True).values
|
||||||
torch.exp(attn_scores, out=attn_scores)
|
torch.exp(attn_scores, out=attn_scores)
|
||||||
summed = torch.sum(attn_scores, dim=-1, keepdim=True)
|
summed = torch.sum(attn_scores, dim=-1, keepdim=True)
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import comfy.utils
|
import comfy.utils
|
||||||
|
import logging
|
||||||
|
|
||||||
LORA_CLIP_MAP = {
|
LORA_CLIP_MAP = {
|
||||||
"mlp.fc1": "mlp_fc1",
|
"mlp.fc1": "mlp_fc1",
|
||||||
@ -20,6 +21,12 @@ def load_lora(lora, to_load):
|
|||||||
alpha = lora[alpha_name].item()
|
alpha = lora[alpha_name].item()
|
||||||
loaded_keys.add(alpha_name)
|
loaded_keys.add(alpha_name)
|
||||||
|
|
||||||
|
dora_scale_name = "{}.dora_scale".format(x)
|
||||||
|
dora_scale = None
|
||||||
|
if dora_scale_name in lora.keys():
|
||||||
|
dora_scale = lora[dora_scale_name]
|
||||||
|
loaded_keys.add(dora_scale_name)
|
||||||
|
|
||||||
regular_lora = "{}.lora_up.weight".format(x)
|
regular_lora = "{}.lora_up.weight".format(x)
|
||||||
diffusers_lora = "{}_lora.up.weight".format(x)
|
diffusers_lora = "{}_lora.up.weight".format(x)
|
||||||
transformers_lora = "{}.lora_linear_layer.up.weight".format(x)
|
transformers_lora = "{}.lora_linear_layer.up.weight".format(x)
|
||||||
@ -43,7 +50,7 @@ def load_lora(lora, to_load):
|
|||||||
if mid_name is not None and mid_name in lora.keys():
|
if mid_name is not None and mid_name in lora.keys():
|
||||||
mid = lora[mid_name]
|
mid = lora[mid_name]
|
||||||
loaded_keys.add(mid_name)
|
loaded_keys.add(mid_name)
|
||||||
patch_dict[to_load[x]] = ("lora", (lora[A_name], lora[B_name], alpha, mid))
|
patch_dict[to_load[x]] = ("lora", (lora[A_name], lora[B_name], alpha, mid, dora_scale))
|
||||||
loaded_keys.add(A_name)
|
loaded_keys.add(A_name)
|
||||||
loaded_keys.add(B_name)
|
loaded_keys.add(B_name)
|
||||||
|
|
||||||
@ -64,7 +71,7 @@ def load_lora(lora, to_load):
|
|||||||
loaded_keys.add(hada_t1_name)
|
loaded_keys.add(hada_t1_name)
|
||||||
loaded_keys.add(hada_t2_name)
|
loaded_keys.add(hada_t2_name)
|
||||||
|
|
||||||
patch_dict[to_load[x]] = ("loha", (lora[hada_w1_a_name], lora[hada_w1_b_name], alpha, lora[hada_w2_a_name], lora[hada_w2_b_name], hada_t1, hada_t2))
|
patch_dict[to_load[x]] = ("loha", (lora[hada_w1_a_name], lora[hada_w1_b_name], alpha, lora[hada_w2_a_name], lora[hada_w2_b_name], hada_t1, hada_t2, dora_scale))
|
||||||
loaded_keys.add(hada_w1_a_name)
|
loaded_keys.add(hada_w1_a_name)
|
||||||
loaded_keys.add(hada_w1_b_name)
|
loaded_keys.add(hada_w1_b_name)
|
||||||
loaded_keys.add(hada_w2_a_name)
|
loaded_keys.add(hada_w2_a_name)
|
||||||
@ -116,7 +123,7 @@ def load_lora(lora, to_load):
|
|||||||
loaded_keys.add(lokr_t2_name)
|
loaded_keys.add(lokr_t2_name)
|
||||||
|
|
||||||
if (lokr_w1 is not None) or (lokr_w2 is not None) or (lokr_w1_a is not None) or (lokr_w2_a is not None):
|
if (lokr_w1 is not None) or (lokr_w2 is not None) or (lokr_w1_a is not None) or (lokr_w2_a is not None):
|
||||||
patch_dict[to_load[x]] = ("lokr", (lokr_w1, lokr_w2, alpha, lokr_w1_a, lokr_w1_b, lokr_w2_a, lokr_w2_b, lokr_t2))
|
patch_dict[to_load[x]] = ("lokr", (lokr_w1, lokr_w2, alpha, lokr_w1_a, lokr_w1_b, lokr_w2_a, lokr_w2_b, lokr_t2, dora_scale))
|
||||||
|
|
||||||
#glora
|
#glora
|
||||||
a1_name = "{}.a1.weight".format(x)
|
a1_name = "{}.a1.weight".format(x)
|
||||||
@ -124,7 +131,7 @@ def load_lora(lora, to_load):
|
|||||||
b1_name = "{}.b1.weight".format(x)
|
b1_name = "{}.b1.weight".format(x)
|
||||||
b2_name = "{}.b2.weight".format(x)
|
b2_name = "{}.b2.weight".format(x)
|
||||||
if a1_name in lora:
|
if a1_name in lora:
|
||||||
patch_dict[to_load[x]] = ("glora", (lora[a1_name], lora[a2_name], lora[b1_name], lora[b2_name], alpha))
|
patch_dict[to_load[x]] = ("glora", (lora[a1_name], lora[a2_name], lora[b1_name], lora[b2_name], alpha, dora_scale))
|
||||||
loaded_keys.add(a1_name)
|
loaded_keys.add(a1_name)
|
||||||
loaded_keys.add(a2_name)
|
loaded_keys.add(a2_name)
|
||||||
loaded_keys.add(b1_name)
|
loaded_keys.add(b1_name)
|
||||||
@ -156,7 +163,7 @@ def load_lora(lora, to_load):
|
|||||||
|
|
||||||
for x in lora.keys():
|
for x in lora.keys():
|
||||||
if x not in loaded_keys:
|
if x not in loaded_keys:
|
||||||
print("lora key not loaded", x)
|
logging.warning("lora key not loaded: {}".format(x))
|
||||||
return patch_dict
|
return patch_dict
|
||||||
|
|
||||||
def model_lora_keys_clip(model, key_map={}):
|
def model_lora_keys_clip(model, key_map={}):
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import torch
|
import torch
|
||||||
|
import logging
|
||||||
from comfy.ldm.modules.diffusionmodules.openaimodel import UNetModel, Timestep
|
from comfy.ldm.modules.diffusionmodules.openaimodel import UNetModel, Timestep
|
||||||
from comfy.ldm.cascade.stage_c import StageC
|
from comfy.ldm.cascade.stage_c import StageC
|
||||||
from comfy.ldm.cascade.stage_b import StageB
|
from comfy.ldm.cascade.stage_b import StageB
|
||||||
@ -65,9 +66,10 @@ class BaseModel(torch.nn.Module):
|
|||||||
self.adm_channels = unet_config.get("adm_in_channels", None)
|
self.adm_channels = unet_config.get("adm_in_channels", None)
|
||||||
if self.adm_channels is None:
|
if self.adm_channels is None:
|
||||||
self.adm_channels = 0
|
self.adm_channels = 0
|
||||||
self.inpaint_model = False
|
|
||||||
print("model_type", model_type.name)
|
self.concat_keys = ()
|
||||||
print("adm", self.adm_channels)
|
logging.info("model_type {}".format(model_type.name))
|
||||||
|
logging.debug("adm {}".format(self.adm_channels))
|
||||||
|
|
||||||
def apply_model(self, x, t, c_concat=None, c_crossattn=None, control=None, transformer_options={}, **kwargs):
|
def apply_model(self, x, t, c_concat=None, c_crossattn=None, control=None, transformer_options={}, **kwargs):
|
||||||
sigma = t
|
sigma = t
|
||||||
@ -106,8 +108,7 @@ class BaseModel(torch.nn.Module):
|
|||||||
|
|
||||||
def extra_conds(self, **kwargs):
|
def extra_conds(self, **kwargs):
|
||||||
out = {}
|
out = {}
|
||||||
if self.inpaint_model:
|
if len(self.concat_keys) > 0:
|
||||||
concat_keys = ("mask", "masked_image")
|
|
||||||
cond_concat = []
|
cond_concat = []
|
||||||
denoise_mask = kwargs.get("concat_mask", kwargs.get("denoise_mask", None))
|
denoise_mask = kwargs.get("concat_mask", kwargs.get("denoise_mask", None))
|
||||||
concat_latent_image = kwargs.get("concat_latent_image", None)
|
concat_latent_image = kwargs.get("concat_latent_image", None)
|
||||||
@ -124,24 +125,16 @@ class BaseModel(torch.nn.Module):
|
|||||||
|
|
||||||
concat_latent_image = utils.resize_to_batch_size(concat_latent_image, noise.shape[0])
|
concat_latent_image = utils.resize_to_batch_size(concat_latent_image, noise.shape[0])
|
||||||
|
|
||||||
if len(denoise_mask.shape) == len(noise.shape):
|
if denoise_mask is not None:
|
||||||
denoise_mask = denoise_mask[:,:1]
|
if len(denoise_mask.shape) == len(noise.shape):
|
||||||
|
denoise_mask = denoise_mask[:,:1]
|
||||||
|
|
||||||
denoise_mask = denoise_mask.reshape((-1, 1, denoise_mask.shape[-2], denoise_mask.shape[-1]))
|
denoise_mask = denoise_mask.reshape((-1, 1, denoise_mask.shape[-2], denoise_mask.shape[-1]))
|
||||||
if denoise_mask.shape[-2:] != noise.shape[-2:]:
|
if denoise_mask.shape[-2:] != noise.shape[-2:]:
|
||||||
denoise_mask = utils.common_upscale(denoise_mask, noise.shape[-1], noise.shape[-2], "bilinear", "center")
|
denoise_mask = utils.common_upscale(denoise_mask, noise.shape[-1], noise.shape[-2], "bilinear", "center")
|
||||||
denoise_mask = utils.resize_to_batch_size(denoise_mask.round(), noise.shape[0])
|
denoise_mask = utils.resize_to_batch_size(denoise_mask.round(), noise.shape[0])
|
||||||
|
|
||||||
def blank_inpaint_image_like(latent_image):
|
for ck in self.concat_keys:
|
||||||
blank_image = torch.ones_like(latent_image)
|
|
||||||
# these are the values for "zero" in pixel space translated to latent space
|
|
||||||
blank_image[:,0] *= 0.8223
|
|
||||||
blank_image[:,1] *= -0.6876
|
|
||||||
blank_image[:,2] *= 0.6364
|
|
||||||
blank_image[:,3] *= 0.1380
|
|
||||||
return blank_image
|
|
||||||
|
|
||||||
for ck in concat_keys:
|
|
||||||
if denoise_mask is not None:
|
if denoise_mask is not None:
|
||||||
if ck == "mask":
|
if ck == "mask":
|
||||||
cond_concat.append(denoise_mask.to(device))
|
cond_concat.append(denoise_mask.to(device))
|
||||||
@ -151,7 +144,7 @@ class BaseModel(torch.nn.Module):
|
|||||||
if ck == "mask":
|
if ck == "mask":
|
||||||
cond_concat.append(torch.ones_like(noise)[:,:1])
|
cond_concat.append(torch.ones_like(noise)[:,:1])
|
||||||
elif ck == "masked_image":
|
elif ck == "masked_image":
|
||||||
cond_concat.append(blank_inpaint_image_like(noise))
|
cond_concat.append(self.blank_inpaint_image_like(noise))
|
||||||
data = torch.cat(cond_concat, dim=1)
|
data = torch.cat(cond_concat, dim=1)
|
||||||
out['c_concat'] = comfy.conds.CONDNoiseShape(data)
|
out['c_concat'] = comfy.conds.CONDNoiseShape(data)
|
||||||
|
|
||||||
@ -169,7 +162,7 @@ class BaseModel(torch.nn.Module):
|
|||||||
|
|
||||||
c_concat = kwargs.get("noise_concat", None)
|
c_concat = kwargs.get("noise_concat", None)
|
||||||
if c_concat is not None:
|
if c_concat is not None:
|
||||||
out['c_concat'] = comfy.conds.CONDNoiseShape(data)
|
out['c_concat'] = comfy.conds.CONDNoiseShape(c_concat)
|
||||||
|
|
||||||
return out
|
return out
|
||||||
|
|
||||||
@ -183,10 +176,10 @@ class BaseModel(torch.nn.Module):
|
|||||||
to_load = self.model_config.process_unet_state_dict(to_load)
|
to_load = self.model_config.process_unet_state_dict(to_load)
|
||||||
m, u = self.diffusion_model.load_state_dict(to_load, strict=False)
|
m, u = self.diffusion_model.load_state_dict(to_load, strict=False)
|
||||||
if len(m) > 0:
|
if len(m) > 0:
|
||||||
print("unet missing:", m)
|
logging.warning("unet missing: {}".format(m))
|
||||||
|
|
||||||
if len(u) > 0:
|
if len(u) > 0:
|
||||||
print("unet unexpected:", u)
|
logging.warning("unet unexpected: {}".format(u))
|
||||||
del to_load
|
del to_load
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@ -220,7 +213,16 @@ class BaseModel(torch.nn.Module):
|
|||||||
return unet_state_dict
|
return unet_state_dict
|
||||||
|
|
||||||
def set_inpaint(self):
|
def set_inpaint(self):
|
||||||
self.inpaint_model = True
|
self.concat_keys = ("mask", "masked_image")
|
||||||
|
def blank_inpaint_image_like(latent_image):
|
||||||
|
blank_image = torch.ones_like(latent_image)
|
||||||
|
# these are the values for "zero" in pixel space translated to latent space
|
||||||
|
blank_image[:,0] *= 0.8223
|
||||||
|
blank_image[:,1] *= -0.6876
|
||||||
|
blank_image[:,2] *= 0.6364
|
||||||
|
blank_image[:,3] *= 0.1380
|
||||||
|
return blank_image
|
||||||
|
self.blank_inpaint_image_like = blank_inpaint_image_like
|
||||||
|
|
||||||
def memory_required(self, input_shape):
|
def memory_required(self, input_shape):
|
||||||
if comfy.model_management.xformers_enabled() or comfy.model_management.pytorch_attention_flash_attention():
|
if comfy.model_management.xformers_enabled() or comfy.model_management.pytorch_attention_flash_attention():
|
||||||
@ -379,6 +381,36 @@ class SVD_img2vid(BaseModel):
|
|||||||
out['num_video_frames'] = comfy.conds.CONDConstant(noise.shape[0])
|
out['num_video_frames'] = comfy.conds.CONDConstant(noise.shape[0])
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
class SV3D_u(SVD_img2vid):
|
||||||
|
def encode_adm(self, **kwargs):
|
||||||
|
augmentation = kwargs.get("augmentation_level", 0)
|
||||||
|
|
||||||
|
out = []
|
||||||
|
out.append(self.embedder(torch.flatten(torch.Tensor([augmentation]))))
|
||||||
|
|
||||||
|
flat = torch.flatten(torch.cat(out)).unsqueeze(dim=0)
|
||||||
|
return flat
|
||||||
|
|
||||||
|
class SV3D_p(SVD_img2vid):
|
||||||
|
def __init__(self, model_config, model_type=ModelType.V_PREDICTION_EDM, device=None):
|
||||||
|
super().__init__(model_config, model_type, device=device)
|
||||||
|
self.embedder_512 = Timestep(512)
|
||||||
|
|
||||||
|
def encode_adm(self, **kwargs):
|
||||||
|
augmentation = kwargs.get("augmentation_level", 0)
|
||||||
|
elevation = kwargs.get("elevation", 0) #elevation and azimuth are in degrees here
|
||||||
|
azimuth = kwargs.get("azimuth", 0)
|
||||||
|
noise = kwargs.get("noise", None)
|
||||||
|
|
||||||
|
out = []
|
||||||
|
out.append(self.embedder(torch.flatten(torch.Tensor([augmentation]))))
|
||||||
|
out.append(self.embedder_512(torch.deg2rad(torch.fmod(torch.flatten(90 - torch.Tensor([elevation])), 360.0))))
|
||||||
|
out.append(self.embedder_512(torch.deg2rad(torch.fmod(torch.flatten(torch.Tensor([azimuth])), 360.0))))
|
||||||
|
|
||||||
|
out = list(map(lambda a: utils.resize_to_batch_size(a, noise.shape[0]), out))
|
||||||
|
return torch.cat(out, dim=1)
|
||||||
|
|
||||||
|
|
||||||
class Stable_Zero123(BaseModel):
|
class Stable_Zero123(BaseModel):
|
||||||
def __init__(self, model_config, model_type=ModelType.EPS, device=None, cc_projection_weight=None, cc_projection_bias=None):
|
def __init__(self, model_config, model_type=ModelType.EPS, device=None, cc_projection_weight=None, cc_projection_bias=None):
|
||||||
super().__init__(model_config, model_type, device=device)
|
super().__init__(model_config, model_type, device=device)
|
||||||
@ -441,6 +473,42 @@ class SD_X4Upscaler(BaseModel):
|
|||||||
out['y'] = comfy.conds.CONDRegular(noise_level)
|
out['y'] = comfy.conds.CONDRegular(noise_level)
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
class IP2P:
|
||||||
|
def extra_conds(self, **kwargs):
|
||||||
|
out = {}
|
||||||
|
|
||||||
|
image = kwargs.get("concat_latent_image", None)
|
||||||
|
noise = kwargs.get("noise", None)
|
||||||
|
device = kwargs["device"]
|
||||||
|
|
||||||
|
if image is None:
|
||||||
|
image = torch.zeros_like(noise)
|
||||||
|
|
||||||
|
if image.shape[1:] != noise.shape[1:]:
|
||||||
|
image = utils.common_upscale(image.to(device), noise.shape[-1], noise.shape[-2], "bilinear", "center")
|
||||||
|
|
||||||
|
image = utils.resize_to_batch_size(image, noise.shape[0])
|
||||||
|
|
||||||
|
out['c_concat'] = comfy.conds.CONDNoiseShape(self.process_ip2p_image_in(image))
|
||||||
|
adm = self.encode_adm(**kwargs)
|
||||||
|
if adm is not None:
|
||||||
|
out['y'] = comfy.conds.CONDRegular(adm)
|
||||||
|
return out
|
||||||
|
|
||||||
|
class SD15_instructpix2pix(IP2P, BaseModel):
|
||||||
|
def __init__(self, model_config, model_type=ModelType.EPS, device=None):
|
||||||
|
super().__init__(model_config, model_type, device=device)
|
||||||
|
self.process_ip2p_image_in = lambda image: image
|
||||||
|
|
||||||
|
class SDXL_instructpix2pix(IP2P, SDXL):
|
||||||
|
def __init__(self, model_config, model_type=ModelType.EPS, device=None):
|
||||||
|
super().__init__(model_config, model_type, device=device)
|
||||||
|
if model_type == ModelType.V_PREDICTION_EDM:
|
||||||
|
self.process_ip2p_image_in = lambda image: comfy.latent_formats.SDXL().process_in(image) #cosxl ip2p
|
||||||
|
else:
|
||||||
|
self.process_ip2p_image_in = lambda image: image #diffusers ip2p
|
||||||
|
|
||||||
|
|
||||||
class StableCascade_C(BaseModel):
|
class StableCascade_C(BaseModel):
|
||||||
def __init__(self, model_config, model_type=ModelType.STABLE_CASCADE, device=None):
|
def __init__(self, model_config, model_type=ModelType.STABLE_CASCADE, device=None):
|
||||||
super().__init__(model_config, model_type, device=device, unet_model=StageC)
|
super().__init__(model_config, model_type, device=device, unet_model=StageC)
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import comfy.supported_models
|
import comfy.supported_models
|
||||||
import comfy.supported_models_base
|
import comfy.supported_models_base
|
||||||
|
import logging
|
||||||
|
|
||||||
def count_blocks(state_dict_keys, prefix_string):
|
def count_blocks(state_dict_keys, prefix_string):
|
||||||
count = 0
|
count = 0
|
||||||
@ -181,17 +182,17 @@ def detect_unet_config(state_dict, key_prefix):
|
|||||||
|
|
||||||
return unet_config
|
return unet_config
|
||||||
|
|
||||||
def model_config_from_unet_config(unet_config):
|
def model_config_from_unet_config(unet_config, state_dict=None):
|
||||||
for model_config in comfy.supported_models.models:
|
for model_config in comfy.supported_models.models:
|
||||||
if model_config.matches(unet_config):
|
if model_config.matches(unet_config, state_dict):
|
||||||
return model_config(unet_config)
|
return model_config(unet_config)
|
||||||
|
|
||||||
print("no match", unet_config)
|
logging.error("no match {}".format(unet_config))
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def model_config_from_unet(state_dict, unet_key_prefix, use_base_if_no_match=False):
|
def model_config_from_unet(state_dict, unet_key_prefix, use_base_if_no_match=False):
|
||||||
unet_config = detect_unet_config(state_dict, unet_key_prefix)
|
unet_config = detect_unet_config(state_dict, unet_key_prefix)
|
||||||
model_config = model_config_from_unet_config(unet_config)
|
model_config = model_config_from_unet_config(unet_config, state_dict)
|
||||||
if model_config is None and use_base_if_no_match:
|
if model_config is None and use_base_if_no_match:
|
||||||
return comfy.supported_models_base.BASE(unet_config)
|
return comfy.supported_models_base.BASE(unet_config)
|
||||||
else:
|
else:
|
||||||
@ -320,6 +321,12 @@ def unet_config_from_diffusers_unet(state_dict, dtype=None):
|
|||||||
'use_linear_in_transformer': True, 'context_dim': 2048, 'num_head_channels': 64, 'transformer_depth_output': [0, 0, 0, 2, 2, 2, 10, 10, 10],
|
'use_linear_in_transformer': True, 'context_dim': 2048, 'num_head_channels': 64, 'transformer_depth_output': [0, 0, 0, 2, 2, 2, 10, 10, 10],
|
||||||
'use_temporal_attention': False, 'use_temporal_resblock': False}
|
'use_temporal_attention': False, 'use_temporal_resblock': False}
|
||||||
|
|
||||||
|
SDXL_diffusers_ip2p = {'use_checkpoint': False, 'image_size': 32, 'out_channels': 4, 'use_spatial_transformer': True, 'legacy': False,
|
||||||
|
'num_classes': 'sequential', 'adm_in_channels': 2816, 'dtype': dtype, 'in_channels': 8, 'model_channels': 320,
|
||||||
|
'num_res_blocks': [2, 2, 2], 'transformer_depth': [0, 0, 2, 2, 10, 10], 'channel_mult': [1, 2, 4], 'transformer_depth_middle': 10,
|
||||||
|
'use_linear_in_transformer': True, 'context_dim': 2048, 'num_head_channels': 64, 'transformer_depth_output': [0, 0, 0, 2, 2, 2, 10, 10, 10],
|
||||||
|
'use_temporal_attention': False, 'use_temporal_resblock': False}
|
||||||
|
|
||||||
SSD_1B = {'use_checkpoint': False, 'image_size': 32, 'out_channels': 4, 'use_spatial_transformer': True, 'legacy': False,
|
SSD_1B = {'use_checkpoint': False, 'image_size': 32, 'out_channels': 4, 'use_spatial_transformer': True, 'legacy': False,
|
||||||
'num_classes': 'sequential', 'adm_in_channels': 2816, 'dtype': dtype, 'in_channels': 4, 'model_channels': 320,
|
'num_classes': 'sequential', 'adm_in_channels': 2816, 'dtype': dtype, 'in_channels': 4, 'model_channels': 320,
|
||||||
'num_res_blocks': [2, 2, 2], 'transformer_depth': [0, 0, 2, 2, 4, 4], 'transformer_depth_output': [0, 0, 0, 1, 1, 2, 10, 4, 4],
|
'num_res_blocks': [2, 2, 2], 'transformer_depth': [0, 0, 2, 2, 4, 4], 'transformer_depth_output': [0, 0, 0, 1, 1, 2, 10, 4, 4],
|
||||||
@ -344,7 +351,20 @@ def unet_config_from_diffusers_unet(state_dict, dtype=None):
|
|||||||
'channel_mult': [1, 2, 4], 'transformer_depth_middle': 6, 'use_linear_in_transformer': True, 'context_dim': 2048, 'num_head_channels': 64,
|
'channel_mult': [1, 2, 4], 'transformer_depth_middle': 6, 'use_linear_in_transformer': True, 'context_dim': 2048, 'num_head_channels': 64,
|
||||||
'use_temporal_attention': False, 'use_temporal_resblock': False}
|
'use_temporal_attention': False, 'use_temporal_resblock': False}
|
||||||
|
|
||||||
supported_models = [SDXL, SDXL_refiner, SD21, SD15, SD21_uncliph, SD21_unclipl, SDXL_mid_cnet, SDXL_small_cnet, SDXL_diffusers_inpaint, SSD_1B, Segmind_Vega, KOALA_700M, KOALA_1B]
|
SD09_XS = {'use_checkpoint': False, 'image_size': 32, 'out_channels': 4, 'use_spatial_transformer': True, 'legacy': False,
|
||||||
|
'adm_in_channels': None, 'dtype': dtype, 'in_channels': 4, 'model_channels': 320, 'num_res_blocks': [1, 1, 1],
|
||||||
|
'transformer_depth': [1, 1, 1], 'channel_mult': [1, 2, 4], 'transformer_depth_middle': -2, 'use_linear_in_transformer': True,
|
||||||
|
'context_dim': 1024, 'num_head_channels': 64, 'transformer_depth_output': [1, 1, 1, 1, 1, 1],
|
||||||
|
'use_temporal_attention': False, 'use_temporal_resblock': False, 'disable_self_attentions': [True, False, False]}
|
||||||
|
|
||||||
|
SD_XS = {'use_checkpoint': False, 'image_size': 32, 'out_channels': 4, 'use_spatial_transformer': True, 'legacy': False,
|
||||||
|
'adm_in_channels': None, 'dtype': dtype, 'in_channels': 4, 'model_channels': 320, 'num_res_blocks': [1, 1, 1],
|
||||||
|
'transformer_depth': [0, 1, 1], 'channel_mult': [1, 2, 4], 'transformer_depth_middle': -2, 'use_linear_in_transformer': False,
|
||||||
|
'context_dim': 768, 'num_head_channels': 64, 'transformer_depth_output': [0, 0, 1, 1, 1, 1],
|
||||||
|
'use_temporal_attention': False, 'use_temporal_resblock': False}
|
||||||
|
|
||||||
|
|
||||||
|
supported_models = [SDXL, SDXL_refiner, SD21, SD15, SD21_uncliph, SD21_unclipl, SDXL_mid_cnet, SDXL_small_cnet, SDXL_diffusers_inpaint, SSD_1B, Segmind_Vega, KOALA_700M, KOALA_1B, SD09_XS, SD_XS, SDXL_diffusers_ip2p]
|
||||||
|
|
||||||
for unet_config in supported_models:
|
for unet_config in supported_models:
|
||||||
matches = True
|
matches = True
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import psutil
|
import psutil
|
||||||
|
import logging
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from comfy.cli_args import args
|
from comfy.cli_args import args
|
||||||
import comfy.utils
|
import comfy.utils
|
||||||
@ -29,7 +30,7 @@ lowvram_available = True
|
|||||||
xpu_available = False
|
xpu_available = False
|
||||||
|
|
||||||
if args.deterministic:
|
if args.deterministic:
|
||||||
print("Using deterministic algorithms for pytorch")
|
logging.info("Using deterministic algorithms for pytorch")
|
||||||
torch.use_deterministic_algorithms(True, warn_only=True)
|
torch.use_deterministic_algorithms(True, warn_only=True)
|
||||||
|
|
||||||
directml_enabled = False
|
directml_enabled = False
|
||||||
@ -41,7 +42,7 @@ if args.directml is not None:
|
|||||||
directml_device = torch_directml.device()
|
directml_device = torch_directml.device()
|
||||||
else:
|
else:
|
||||||
directml_device = torch_directml.device(device_index)
|
directml_device = torch_directml.device(device_index)
|
||||||
print("Using directml with device:", torch_directml.device_name(device_index))
|
logging.info("Using directml with device: {}".format(torch_directml.device_name(device_index)))
|
||||||
# torch_directml.disable_tiled_resources(True)
|
# torch_directml.disable_tiled_resources(True)
|
||||||
lowvram_available = False #TODO: need to find a way to get free memory in directml before this can be enabled by default.
|
lowvram_available = False #TODO: need to find a way to get free memory in directml before this can be enabled by default.
|
||||||
|
|
||||||
@ -82,7 +83,7 @@ def get_torch_device():
|
|||||||
return torch.device("cpu")
|
return torch.device("cpu")
|
||||||
else:
|
else:
|
||||||
if is_intel_xpu():
|
if is_intel_xpu():
|
||||||
return torch.device("xpu")
|
return torch.device("xpu", torch.xpu.current_device())
|
||||||
else:
|
else:
|
||||||
return torch.device(torch.cuda.current_device())
|
return torch.device(torch.cuda.current_device())
|
||||||
|
|
||||||
@ -117,10 +118,10 @@ def get_total_memory(dev=None, torch_total_too=False):
|
|||||||
|
|
||||||
total_vram = get_total_memory(get_torch_device()) / (1024 * 1024)
|
total_vram = get_total_memory(get_torch_device()) / (1024 * 1024)
|
||||||
total_ram = psutil.virtual_memory().total / (1024 * 1024)
|
total_ram = psutil.virtual_memory().total / (1024 * 1024)
|
||||||
print("Total VRAM {:0.0f} MB, total RAM {:0.0f} MB".format(total_vram, total_ram))
|
logging.info("Total VRAM {:0.0f} MB, total RAM {:0.0f} MB".format(total_vram, total_ram))
|
||||||
if not args.normalvram and not args.cpu:
|
if not args.normalvram and not args.cpu:
|
||||||
if lowvram_available and total_vram <= 4096:
|
if lowvram_available and total_vram <= 4096:
|
||||||
print("Trying to enable lowvram mode because your GPU seems to have 4GB or less. If you don't want this use: --normalvram")
|
logging.warning("Trying to enable lowvram mode because your GPU seems to have 4GB or less. If you don't want this use: --normalvram")
|
||||||
set_vram_to = VRAMState.LOW_VRAM
|
set_vram_to = VRAMState.LOW_VRAM
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -143,12 +144,10 @@ else:
|
|||||||
pass
|
pass
|
||||||
try:
|
try:
|
||||||
XFORMERS_VERSION = xformers.version.__version__
|
XFORMERS_VERSION = xformers.version.__version__
|
||||||
print("xformers version:", XFORMERS_VERSION)
|
logging.info("xformers version: {}".format(XFORMERS_VERSION))
|
||||||
if XFORMERS_VERSION.startswith("0.0.18"):
|
if XFORMERS_VERSION.startswith("0.0.18"):
|
||||||
print()
|
logging.warning("\nWARNING: This version of xformers has a major bug where you will get black images when generating high resolution images.")
|
||||||
print("WARNING: This version of xformers has a major bug where you will get black images when generating high resolution images.")
|
logging.warning("Please downgrade or upgrade xformers to a different version.\n")
|
||||||
print("Please downgrade or upgrade xformers to a different version.")
|
|
||||||
print()
|
|
||||||
XFORMERS_ENABLED_VAE = False
|
XFORMERS_ENABLED_VAE = False
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
@ -213,11 +212,11 @@ elif args.highvram or args.gpu_only:
|
|||||||
FORCE_FP32 = False
|
FORCE_FP32 = False
|
||||||
FORCE_FP16 = False
|
FORCE_FP16 = False
|
||||||
if args.force_fp32:
|
if args.force_fp32:
|
||||||
print("Forcing FP32, if this improves things please report it.")
|
logging.info("Forcing FP32, if this improves things please report it.")
|
||||||
FORCE_FP32 = True
|
FORCE_FP32 = True
|
||||||
|
|
||||||
if args.force_fp16:
|
if args.force_fp16:
|
||||||
print("Forcing FP16.")
|
logging.info("Forcing FP16.")
|
||||||
FORCE_FP16 = True
|
FORCE_FP16 = True
|
||||||
|
|
||||||
if lowvram_available:
|
if lowvram_available:
|
||||||
@ -231,12 +230,12 @@ if cpu_state != CPUState.GPU:
|
|||||||
if cpu_state == CPUState.MPS:
|
if cpu_state == CPUState.MPS:
|
||||||
vram_state = VRAMState.SHARED
|
vram_state = VRAMState.SHARED
|
||||||
|
|
||||||
print(f"Set vram state to: {vram_state.name}")
|
logging.info(f"Set vram state to: {vram_state.name}")
|
||||||
|
|
||||||
DISABLE_SMART_MEMORY = args.disable_smart_memory
|
DISABLE_SMART_MEMORY = args.disable_smart_memory
|
||||||
|
|
||||||
if DISABLE_SMART_MEMORY:
|
if DISABLE_SMART_MEMORY:
|
||||||
print("Disabling smart memory management")
|
logging.info("Disabling smart memory management")
|
||||||
|
|
||||||
def get_torch_device_name(device):
|
def get_torch_device_name(device):
|
||||||
if hasattr(device, 'type'):
|
if hasattr(device, 'type'):
|
||||||
@ -254,6 +253,7 @@ def get_torch_device_name(device):
|
|||||||
return "CUDA {}: {}".format(device, torch.cuda.get_device_name(device))
|
return "CUDA {}: {}".format(device, torch.cuda.get_device_name(device))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
<<<<<<< HEAD
|
||||||
torch_device_name = get_torch_device_name(get_torch_device())
|
torch_device_name = get_torch_device_name(get_torch_device())
|
||||||
|
|
||||||
if "[ZLUDA]" in torch_device_name:
|
if "[ZLUDA]" in torch_device_name:
|
||||||
@ -268,10 +268,13 @@ try:
|
|||||||
torch.backends.cuda.enable_mem_efficient_sdp(False)
|
torch.backends.cuda.enable_mem_efficient_sdp(False)
|
||||||
|
|
||||||
print("Device:", torch_device_name)
|
print("Device:", torch_device_name)
|
||||||
|
=======
|
||||||
|
logging.info("Device: {}".format(get_torch_device_name(get_torch_device())))
|
||||||
|
>>>>>>> upstream/master
|
||||||
except:
|
except:
|
||||||
print("Could not pick default device.")
|
logging.warning("Could not pick default device.")
|
||||||
|
|
||||||
print("VAE dtype:", VAE_DTYPE)
|
logging.info("VAE dtype: {}".format(VAE_DTYPE))
|
||||||
|
|
||||||
current_loaded_models = []
|
current_loaded_models = []
|
||||||
|
|
||||||
@ -286,8 +289,9 @@ def module_size(module):
|
|||||||
class LoadedModel:
|
class LoadedModel:
|
||||||
def __init__(self, model):
|
def __init__(self, model):
|
||||||
self.model = model
|
self.model = model
|
||||||
self.model_accelerated = False
|
|
||||||
self.device = model.load_device
|
self.device = model.load_device
|
||||||
|
self.weights_loaded = False
|
||||||
|
self.real_model = None
|
||||||
|
|
||||||
def model_memory(self):
|
def model_memory(self):
|
||||||
return self.model.model_size()
|
return self.model.model_size()
|
||||||
@ -299,54 +303,34 @@ class LoadedModel:
|
|||||||
return self.model_memory()
|
return self.model_memory()
|
||||||
|
|
||||||
def model_load(self, lowvram_model_memory=0):
|
def model_load(self, lowvram_model_memory=0):
|
||||||
patch_model_to = None
|
patch_model_to = self.device
|
||||||
if lowvram_model_memory == 0:
|
|
||||||
patch_model_to = self.device
|
|
||||||
|
|
||||||
self.model.model_patches_to(self.device)
|
self.model.model_patches_to(self.device)
|
||||||
self.model.model_patches_to(self.model.model_dtype())
|
self.model.model_patches_to(self.model.model_dtype())
|
||||||
|
|
||||||
|
load_weights = not self.weights_loaded
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.real_model = self.model.patch_model(device_to=patch_model_to) #TODO: do something with loras and offloading to CPU
|
if lowvram_model_memory > 0 and load_weights:
|
||||||
|
self.real_model = self.model.patch_model_lowvram(device_to=patch_model_to, lowvram_model_memory=lowvram_model_memory)
|
||||||
|
else:
|
||||||
|
self.real_model = self.model.patch_model(device_to=patch_model_to, patch_weights=load_weights)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.model.unpatch_model(self.model.offload_device)
|
self.model.unpatch_model(self.model.offload_device)
|
||||||
self.model_unload()
|
self.model_unload()
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
if lowvram_model_memory > 0:
|
|
||||||
print("loading in lowvram mode", lowvram_model_memory/(1024 * 1024))
|
|
||||||
mem_counter = 0
|
|
||||||
for m in self.real_model.modules():
|
|
||||||
if hasattr(m, "comfy_cast_weights"):
|
|
||||||
m.prev_comfy_cast_weights = m.comfy_cast_weights
|
|
||||||
m.comfy_cast_weights = True
|
|
||||||
module_mem = module_size(m)
|
|
||||||
if mem_counter + module_mem < lowvram_model_memory:
|
|
||||||
m.to(self.device)
|
|
||||||
mem_counter += module_mem
|
|
||||||
elif hasattr(m, "weight"): #only modules with comfy_cast_weights can be set to lowvram mode
|
|
||||||
m.to(self.device)
|
|
||||||
mem_counter += module_size(m)
|
|
||||||
print("lowvram: loaded module regularly", m)
|
|
||||||
|
|
||||||
self.model_accelerated = True
|
|
||||||
|
|
||||||
if is_intel_xpu() and not args.disable_ipex_optimize:
|
if is_intel_xpu() and not args.disable_ipex_optimize:
|
||||||
self.real_model = torch.xpu.optimize(self.real_model.eval(), inplace=True, auto_kernel_selection=True, graph_mode=True)
|
self.real_model = ipex.optimize(self.real_model.eval(), graph_mode=True, concat_linear=True)
|
||||||
|
|
||||||
|
self.weights_loaded = True
|
||||||
return self.real_model
|
return self.real_model
|
||||||
|
|
||||||
def model_unload(self):
|
def model_unload(self, unpatch_weights=True):
|
||||||
if self.model_accelerated:
|
self.model.unpatch_model(self.model.offload_device, unpatch_weights=unpatch_weights)
|
||||||
for m in self.real_model.modules():
|
|
||||||
if hasattr(m, "prev_comfy_cast_weights"):
|
|
||||||
m.comfy_cast_weights = m.prev_comfy_cast_weights
|
|
||||||
del m.prev_comfy_cast_weights
|
|
||||||
|
|
||||||
self.model_accelerated = False
|
|
||||||
|
|
||||||
self.model.unpatch_model(self.model.offload_device)
|
|
||||||
self.model.model_patches_to(self.model.offload_device)
|
self.model.model_patches_to(self.model.offload_device)
|
||||||
|
self.weights_loaded = self.weights_loaded and not unpatch_weights
|
||||||
|
self.real_model = None
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
return self.model is other.model
|
return self.model is other.model
|
||||||
@ -354,31 +338,57 @@ class LoadedModel:
|
|||||||
def minimum_inference_memory():
|
def minimum_inference_memory():
|
||||||
return (1024 * 1024 * 1024)
|
return (1024 * 1024 * 1024)
|
||||||
|
|
||||||
def unload_model_clones(model):
|
def unload_model_clones(model, unload_weights_only=True, force_unload=True):
|
||||||
to_unload = []
|
to_unload = []
|
||||||
for i in range(len(current_loaded_models)):
|
for i in range(len(current_loaded_models)):
|
||||||
if model.is_clone(current_loaded_models[i].model):
|
if model.is_clone(current_loaded_models[i].model):
|
||||||
to_unload = [i] + to_unload
|
to_unload = [i] + to_unload
|
||||||
|
|
||||||
|
if len(to_unload) == 0:
|
||||||
|
return True
|
||||||
|
|
||||||
|
same_weights = 0
|
||||||
for i in to_unload:
|
for i in to_unload:
|
||||||
print("unload clone", i)
|
if model.clone_has_same_weights(current_loaded_models[i].model):
|
||||||
current_loaded_models.pop(i).model_unload()
|
same_weights += 1
|
||||||
|
|
||||||
|
if same_weights == len(to_unload):
|
||||||
|
unload_weight = False
|
||||||
|
else:
|
||||||
|
unload_weight = True
|
||||||
|
|
||||||
|
if not force_unload:
|
||||||
|
if unload_weights_only and unload_weight == False:
|
||||||
|
return None
|
||||||
|
|
||||||
|
for i in to_unload:
|
||||||
|
logging.debug("unload clone {} {}".format(i, unload_weight))
|
||||||
|
current_loaded_models.pop(i).model_unload(unpatch_weights=unload_weight)
|
||||||
|
|
||||||
|
return unload_weight
|
||||||
|
|
||||||
def free_memory(memory_required, device, keep_loaded=[]):
|
def free_memory(memory_required, device, keep_loaded=[]):
|
||||||
unloaded_model = False
|
unloaded_model = []
|
||||||
|
can_unload = []
|
||||||
|
|
||||||
for i in range(len(current_loaded_models) -1, -1, -1):
|
for i in range(len(current_loaded_models) -1, -1, -1):
|
||||||
if not DISABLE_SMART_MEMORY:
|
|
||||||
if get_free_memory(device) > memory_required:
|
|
||||||
break
|
|
||||||
shift_model = current_loaded_models[i]
|
shift_model = current_loaded_models[i]
|
||||||
if shift_model.device == device:
|
if shift_model.device == device:
|
||||||
if shift_model not in keep_loaded:
|
if shift_model not in keep_loaded:
|
||||||
m = current_loaded_models.pop(i)
|
can_unload.append((sys.getrefcount(shift_model.model), shift_model.model_memory(), i))
|
||||||
m.model_unload()
|
|
||||||
del m
|
|
||||||
unloaded_model = True
|
|
||||||
|
|
||||||
if unloaded_model:
|
for x in sorted(can_unload):
|
||||||
|
i = x[-1]
|
||||||
|
if not DISABLE_SMART_MEMORY:
|
||||||
|
if get_free_memory(device) > memory_required:
|
||||||
|
break
|
||||||
|
current_loaded_models[i].model_unload()
|
||||||
|
unloaded_model.append(i)
|
||||||
|
|
||||||
|
for i in sorted(unloaded_model, reverse=True):
|
||||||
|
current_loaded_models.pop(i)
|
||||||
|
|
||||||
|
if len(unloaded_model) > 0:
|
||||||
soft_empty_cache()
|
soft_empty_cache()
|
||||||
else:
|
else:
|
||||||
if vram_state != VRAMState.HIGH_VRAM:
|
if vram_state != VRAMState.HIGH_VRAM:
|
||||||
@ -392,6 +402,8 @@ def load_models_gpu(models, memory_required=0):
|
|||||||
inference_memory = minimum_inference_memory()
|
inference_memory = minimum_inference_memory()
|
||||||
extra_mem = max(inference_memory, memory_required)
|
extra_mem = max(inference_memory, memory_required)
|
||||||
|
|
||||||
|
models = set(models)
|
||||||
|
|
||||||
models_to_load = []
|
models_to_load = []
|
||||||
models_already_loaded = []
|
models_already_loaded = []
|
||||||
for x in models:
|
for x in models:
|
||||||
@ -403,7 +415,7 @@ def load_models_gpu(models, memory_required=0):
|
|||||||
models_already_loaded.append(loaded_model)
|
models_already_loaded.append(loaded_model)
|
||||||
else:
|
else:
|
||||||
if hasattr(x, "model"):
|
if hasattr(x, "model"):
|
||||||
print(f"Requested to load {x.model.__class__.__name__}")
|
logging.info(f"Requested to load {x.model.__class__.__name__}")
|
||||||
models_to_load.append(loaded_model)
|
models_to_load.append(loaded_model)
|
||||||
|
|
||||||
if len(models_to_load) == 0:
|
if len(models_to_load) == 0:
|
||||||
@ -413,17 +425,22 @@ def load_models_gpu(models, memory_required=0):
|
|||||||
free_memory(extra_mem, d, models_already_loaded)
|
free_memory(extra_mem, d, models_already_loaded)
|
||||||
return
|
return
|
||||||
|
|
||||||
print(f"Loading {len(models_to_load)} new model{'s' if len(models_to_load) > 1 else ''}")
|
logging.info(f"Loading {len(models_to_load)} new model{'s' if len(models_to_load) > 1 else ''}")
|
||||||
|
|
||||||
total_memory_required = {}
|
total_memory_required = {}
|
||||||
for loaded_model in models_to_load:
|
for loaded_model in models_to_load:
|
||||||
unload_model_clones(loaded_model.model)
|
if unload_model_clones(loaded_model.model, unload_weights_only=True, force_unload=False) == True:#unload clones where the weights are different
|
||||||
total_memory_required[loaded_model.device] = total_memory_required.get(loaded_model.device, 0) + loaded_model.model_memory_required(loaded_model.device)
|
total_memory_required[loaded_model.device] = total_memory_required.get(loaded_model.device, 0) + loaded_model.model_memory_required(loaded_model.device)
|
||||||
|
|
||||||
for device in total_memory_required:
|
for device in total_memory_required:
|
||||||
if device != torch.device("cpu"):
|
if device != torch.device("cpu"):
|
||||||
free_memory(total_memory_required[device] * 1.3 + extra_mem, device, models_already_loaded)
|
free_memory(total_memory_required[device] * 1.3 + extra_mem, device, models_already_loaded)
|
||||||
|
|
||||||
|
for loaded_model in models_to_load:
|
||||||
|
weights_unloaded = unload_model_clones(loaded_model.model, unload_weights_only=False, force_unload=False) #unload the rest of the clones where the weights can stay loaded
|
||||||
|
if weights_unloaded is not None:
|
||||||
|
loaded_model.weights_loaded = not weights_unloaded
|
||||||
|
|
||||||
for loaded_model in models_to_load:
|
for loaded_model in models_to_load:
|
||||||
model = loaded_model.model
|
model = loaded_model.model
|
||||||
torch_dev = model.load_device
|
torch_dev = model.load_device
|
||||||
@ -452,11 +469,15 @@ def load_models_gpu(models, memory_required=0):
|
|||||||
def load_model_gpu(model):
|
def load_model_gpu(model):
|
||||||
return load_models_gpu([model])
|
return load_models_gpu([model])
|
||||||
|
|
||||||
def cleanup_models():
|
def cleanup_models(keep_clone_weights_loaded=False):
|
||||||
to_delete = []
|
to_delete = []
|
||||||
for i in range(len(current_loaded_models)):
|
for i in range(len(current_loaded_models)):
|
||||||
if sys.getrefcount(current_loaded_models[i].model) <= 2:
|
if sys.getrefcount(current_loaded_models[i].model) <= 2:
|
||||||
to_delete = [i] + to_delete
|
if not keep_clone_weights_loaded:
|
||||||
|
to_delete = [i] + to_delete
|
||||||
|
#TODO: find a less fragile way to do this.
|
||||||
|
elif sys.getrefcount(current_loaded_models[i].real_model) <= 3: #references from .real_model + the .model
|
||||||
|
to_delete = [i] + to_delete
|
||||||
|
|
||||||
for i in to_delete:
|
for i in to_delete:
|
||||||
x = current_loaded_models.pop(i)
|
x = current_loaded_models.pop(i)
|
||||||
@ -548,8 +569,6 @@ def text_encoder_device():
|
|||||||
if args.gpu_only:
|
if args.gpu_only:
|
||||||
return get_torch_device()
|
return get_torch_device()
|
||||||
elif vram_state == VRAMState.HIGH_VRAM or vram_state == VRAMState.NORMAL_VRAM:
|
elif vram_state == VRAMState.HIGH_VRAM or vram_state == VRAMState.NORMAL_VRAM:
|
||||||
if is_intel_xpu():
|
|
||||||
return torch.device("cpu")
|
|
||||||
if should_use_fp16(prioritize_performance=False):
|
if should_use_fp16(prioritize_performance=False):
|
||||||
return get_torch_device()
|
return get_torch_device()
|
||||||
else:
|
else:
|
||||||
@ -613,7 +632,8 @@ def supports_dtype(device, dtype): #TODO
|
|||||||
def device_supports_non_blocking(device):
|
def device_supports_non_blocking(device):
|
||||||
if is_device_mps(device):
|
if is_device_mps(device):
|
||||||
return False #pytorch bug? mps doesn't support non blocking
|
return False #pytorch bug? mps doesn't support non blocking
|
||||||
return True
|
return False
|
||||||
|
# return True #TODO: figure out why this causes issues
|
||||||
|
|
||||||
def cast_to_device(tensor, device, dtype, copy=False):
|
def cast_to_device(tensor, device, dtype, copy=False):
|
||||||
device_supports_cast = False
|
device_supports_cast = False
|
||||||
|
|||||||
@ -1,10 +1,44 @@
|
|||||||
import torch
|
import torch
|
||||||
import copy
|
import copy
|
||||||
import inspect
|
import inspect
|
||||||
|
import logging
|
||||||
|
import uuid
|
||||||
|
|
||||||
import comfy.utils
|
import comfy.utils
|
||||||
import comfy.model_management
|
import comfy.model_management
|
||||||
|
|
||||||
|
def apply_weight_decompose(dora_scale, weight):
|
||||||
|
weight_norm = (
|
||||||
|
weight.transpose(0, 1)
|
||||||
|
.reshape(weight.shape[1], -1)
|
||||||
|
.norm(dim=1, keepdim=True)
|
||||||
|
.reshape(weight.shape[1], *[1] * (weight.dim() - 1))
|
||||||
|
.transpose(0, 1)
|
||||||
|
)
|
||||||
|
|
||||||
|
return weight * (dora_scale / weight_norm)
|
||||||
|
|
||||||
|
def set_model_options_patch_replace(model_options, patch, name, block_name, number, transformer_index=None):
|
||||||
|
to = model_options["transformer_options"].copy()
|
||||||
|
|
||||||
|
if "patches_replace" not in to:
|
||||||
|
to["patches_replace"] = {}
|
||||||
|
else:
|
||||||
|
to["patches_replace"] = to["patches_replace"].copy()
|
||||||
|
|
||||||
|
if name not in to["patches_replace"]:
|
||||||
|
to["patches_replace"][name] = {}
|
||||||
|
else:
|
||||||
|
to["patches_replace"][name] = to["patches_replace"][name].copy()
|
||||||
|
|
||||||
|
if transformer_index is not None:
|
||||||
|
block = (block_name, number, transformer_index)
|
||||||
|
else:
|
||||||
|
block = (block_name, number)
|
||||||
|
to["patches_replace"][name][block] = patch
|
||||||
|
model_options["transformer_options"] = to
|
||||||
|
return model_options
|
||||||
|
|
||||||
class ModelPatcher:
|
class ModelPatcher:
|
||||||
def __init__(self, model, load_device, offload_device, size=0, current_device=None, weight_inplace_update=False):
|
def __init__(self, model, load_device, offload_device, size=0, current_device=None, weight_inplace_update=False):
|
||||||
self.size = size
|
self.size = size
|
||||||
@ -23,6 +57,8 @@ class ModelPatcher:
|
|||||||
self.current_device = current_device
|
self.current_device = current_device
|
||||||
|
|
||||||
self.weight_inplace_update = weight_inplace_update
|
self.weight_inplace_update = weight_inplace_update
|
||||||
|
self.model_lowvram = False
|
||||||
|
self.patches_uuid = uuid.uuid4()
|
||||||
|
|
||||||
def model_size(self):
|
def model_size(self):
|
||||||
if self.size > 0:
|
if self.size > 0:
|
||||||
@ -37,10 +73,13 @@ class ModelPatcher:
|
|||||||
n.patches = {}
|
n.patches = {}
|
||||||
for k in self.patches:
|
for k in self.patches:
|
||||||
n.patches[k] = self.patches[k][:]
|
n.patches[k] = self.patches[k][:]
|
||||||
|
n.patches_uuid = self.patches_uuid
|
||||||
|
|
||||||
n.object_patches = self.object_patches.copy()
|
n.object_patches = self.object_patches.copy()
|
||||||
n.model_options = copy.deepcopy(self.model_options)
|
n.model_options = copy.deepcopy(self.model_options)
|
||||||
n.model_keys = self.model_keys
|
n.model_keys = self.model_keys
|
||||||
|
n.backup = self.backup
|
||||||
|
n.object_patches_backup = self.object_patches_backup
|
||||||
return n
|
return n
|
||||||
|
|
||||||
def is_clone(self, other):
|
def is_clone(self, other):
|
||||||
@ -48,6 +87,19 @@ class ModelPatcher:
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def clone_has_same_weights(self, clone):
|
||||||
|
if not self.is_clone(clone):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if len(self.patches) == 0 and len(clone.patches) == 0:
|
||||||
|
return True
|
||||||
|
|
||||||
|
if self.patches_uuid == clone.patches_uuid:
|
||||||
|
if len(self.patches) != len(clone.patches):
|
||||||
|
logging.warning("WARNING: something went wrong, same patch uuid but different length of patches.")
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
def memory_required(self, input_shape):
|
def memory_required(self, input_shape):
|
||||||
return self.model.memory_required(input_shape=input_shape)
|
return self.model.memory_required(input_shape=input_shape)
|
||||||
|
|
||||||
@ -77,16 +129,7 @@ class ModelPatcher:
|
|||||||
to["patches"][name] = to["patches"].get(name, []) + [patch]
|
to["patches"][name] = to["patches"].get(name, []) + [patch]
|
||||||
|
|
||||||
def set_model_patch_replace(self, patch, name, block_name, number, transformer_index=None):
|
def set_model_patch_replace(self, patch, name, block_name, number, transformer_index=None):
|
||||||
to = self.model_options["transformer_options"]
|
self.model_options = set_model_options_patch_replace(self.model_options, patch, name, block_name, number, transformer_index=transformer_index)
|
||||||
if "patches_replace" not in to:
|
|
||||||
to["patches_replace"] = {}
|
|
||||||
if name not in to["patches_replace"]:
|
|
||||||
to["patches_replace"][name] = {}
|
|
||||||
if transformer_index is not None:
|
|
||||||
block = (block_name, number, transformer_index)
|
|
||||||
else:
|
|
||||||
block = (block_name, number)
|
|
||||||
to["patches_replace"][name][block] = patch
|
|
||||||
|
|
||||||
def set_model_attn1_patch(self, patch):
|
def set_model_attn1_patch(self, patch):
|
||||||
self.set_model_patch(patch, "attn1_patch")
|
self.set_model_patch(patch, "attn1_patch")
|
||||||
@ -118,6 +161,15 @@ class ModelPatcher:
|
|||||||
def add_object_patch(self, name, obj):
|
def add_object_patch(self, name, obj):
|
||||||
self.object_patches[name] = obj
|
self.object_patches[name] = obj
|
||||||
|
|
||||||
|
def get_model_object(self, name):
|
||||||
|
if name in self.object_patches:
|
||||||
|
return self.object_patches[name]
|
||||||
|
else:
|
||||||
|
if name in self.object_patches_backup:
|
||||||
|
return self.object_patches_backup[name]
|
||||||
|
else:
|
||||||
|
return comfy.utils.get_attr(self.model, name)
|
||||||
|
|
||||||
def model_patches_to(self, device):
|
def model_patches_to(self, device):
|
||||||
to = self.model_options["transformer_options"]
|
to = self.model_options["transformer_options"]
|
||||||
if "patches" in to:
|
if "patches" in to:
|
||||||
@ -152,6 +204,7 @@ class ModelPatcher:
|
|||||||
current_patches.append((strength_patch, patches[k], strength_model))
|
current_patches.append((strength_patch, patches[k], strength_model))
|
||||||
self.patches[k] = current_patches
|
self.patches[k] = current_patches
|
||||||
|
|
||||||
|
self.patches_uuid = uuid.uuid4()
|
||||||
return list(p)
|
return list(p)
|
||||||
|
|
||||||
def get_key_patches(self, filter_prefix=None):
|
def get_key_patches(self, filter_prefix=None):
|
||||||
@ -177,6 +230,27 @@ class ModelPatcher:
|
|||||||
sd.pop(k)
|
sd.pop(k)
|
||||||
return sd
|
return sd
|
||||||
|
|
||||||
|
def patch_weight_to_device(self, key, device_to=None):
|
||||||
|
if key not in self.patches:
|
||||||
|
return
|
||||||
|
|
||||||
|
weight = comfy.utils.get_attr(self.model, key)
|
||||||
|
|
||||||
|
inplace_update = self.weight_inplace_update
|
||||||
|
|
||||||
|
if key not in self.backup:
|
||||||
|
self.backup[key] = weight.to(device=self.offload_device, copy=inplace_update)
|
||||||
|
|
||||||
|
if device_to is not None:
|
||||||
|
temp_weight = comfy.model_management.cast_to_device(weight, device_to, torch.float32, copy=True)
|
||||||
|
else:
|
||||||
|
temp_weight = weight.to(torch.float32, copy=True)
|
||||||
|
out_weight = self.calculate_weight(self.patches[key], temp_weight, key).to(weight.dtype)
|
||||||
|
if inplace_update:
|
||||||
|
comfy.utils.copy_to_param(self.model, key, out_weight)
|
||||||
|
else:
|
||||||
|
comfy.utils.set_attr_param(self.model, key, out_weight)
|
||||||
|
|
||||||
def patch_model(self, device_to=None, patch_weights=True):
|
def patch_model(self, device_to=None, patch_weights=True):
|
||||||
for k in self.object_patches:
|
for k in self.object_patches:
|
||||||
old = comfy.utils.set_attr(self.model, k, self.object_patches[k])
|
old = comfy.utils.set_attr(self.model, k, self.object_patches[k])
|
||||||
@ -187,26 +261,10 @@ class ModelPatcher:
|
|||||||
model_sd = self.model_state_dict()
|
model_sd = self.model_state_dict()
|
||||||
for key in self.patches:
|
for key in self.patches:
|
||||||
if key not in model_sd:
|
if key not in model_sd:
|
||||||
print("could not patch. key doesn't exist in model:", key)
|
logging.warning("could not patch. key doesn't exist in model: {}".format(key))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
weight = model_sd[key]
|
self.patch_weight_to_device(key, device_to)
|
||||||
|
|
||||||
inplace_update = self.weight_inplace_update
|
|
||||||
|
|
||||||
if key not in self.backup:
|
|
||||||
self.backup[key] = weight.to(device=self.offload_device, copy=inplace_update)
|
|
||||||
|
|
||||||
if device_to is not None:
|
|
||||||
temp_weight = comfy.model_management.cast_to_device(weight, device_to, torch.float32, copy=True)
|
|
||||||
else:
|
|
||||||
temp_weight = weight.to(torch.float32, copy=True)
|
|
||||||
out_weight = self.calculate_weight(self.patches[key], temp_weight, key).to(weight.dtype)
|
|
||||||
if inplace_update:
|
|
||||||
comfy.utils.copy_to_param(self.model, key, out_weight)
|
|
||||||
else:
|
|
||||||
comfy.utils.set_attr_param(self.model, key, out_weight)
|
|
||||||
del temp_weight
|
|
||||||
|
|
||||||
if device_to is not None:
|
if device_to is not None:
|
||||||
self.model.to(device_to)
|
self.model.to(device_to)
|
||||||
@ -214,6 +272,47 @@ class ModelPatcher:
|
|||||||
|
|
||||||
return self.model
|
return self.model
|
||||||
|
|
||||||
|
def patch_model_lowvram(self, device_to=None, lowvram_model_memory=0):
|
||||||
|
self.patch_model(device_to, patch_weights=False)
|
||||||
|
|
||||||
|
logging.info("loading in lowvram mode {}".format(lowvram_model_memory/(1024 * 1024)))
|
||||||
|
class LowVramPatch:
|
||||||
|
def __init__(self, key, model_patcher):
|
||||||
|
self.key = key
|
||||||
|
self.model_patcher = model_patcher
|
||||||
|
def __call__(self, weight):
|
||||||
|
return self.model_patcher.calculate_weight(self.model_patcher.patches[self.key], weight, self.key)
|
||||||
|
|
||||||
|
mem_counter = 0
|
||||||
|
for n, m in self.model.named_modules():
|
||||||
|
lowvram_weight = False
|
||||||
|
if hasattr(m, "comfy_cast_weights"):
|
||||||
|
module_mem = comfy.model_management.module_size(m)
|
||||||
|
if mem_counter + module_mem >= lowvram_model_memory:
|
||||||
|
lowvram_weight = True
|
||||||
|
|
||||||
|
weight_key = "{}.weight".format(n)
|
||||||
|
bias_key = "{}.bias".format(n)
|
||||||
|
|
||||||
|
if lowvram_weight:
|
||||||
|
if weight_key in self.patches:
|
||||||
|
m.weight_function = LowVramPatch(weight_key, self)
|
||||||
|
if bias_key in self.patches:
|
||||||
|
m.bias_function = LowVramPatch(bias_key, self)
|
||||||
|
|
||||||
|
m.prev_comfy_cast_weights = m.comfy_cast_weights
|
||||||
|
m.comfy_cast_weights = True
|
||||||
|
else:
|
||||||
|
if hasattr(m, "weight"):
|
||||||
|
self.patch_weight_to_device(weight_key, device_to)
|
||||||
|
self.patch_weight_to_device(bias_key, device_to)
|
||||||
|
m.to(device_to)
|
||||||
|
mem_counter += comfy.model_management.module_size(m)
|
||||||
|
logging.debug("lowvram: loaded module regularly {}".format(m))
|
||||||
|
|
||||||
|
self.model_lowvram = True
|
||||||
|
return self.model
|
||||||
|
|
||||||
def calculate_weight(self, patches, weight, key):
|
def calculate_weight(self, patches, weight, key):
|
||||||
for p in patches:
|
for p in patches:
|
||||||
alpha = p[0]
|
alpha = p[0]
|
||||||
@ -236,12 +335,13 @@ class ModelPatcher:
|
|||||||
w1 = v[0]
|
w1 = v[0]
|
||||||
if alpha != 0.0:
|
if alpha != 0.0:
|
||||||
if w1.shape != weight.shape:
|
if w1.shape != weight.shape:
|
||||||
print("WARNING SHAPE MISMATCH {} WEIGHT NOT MERGED {} != {}".format(key, w1.shape, weight.shape))
|
logging.warning("WARNING SHAPE MISMATCH {} WEIGHT NOT MERGED {} != {}".format(key, w1.shape, weight.shape))
|
||||||
else:
|
else:
|
||||||
weight += alpha * comfy.model_management.cast_to_device(w1, weight.device, weight.dtype)
|
weight += alpha * comfy.model_management.cast_to_device(w1, weight.device, weight.dtype)
|
||||||
elif patch_type == "lora": #lora/locon
|
elif patch_type == "lora": #lora/locon
|
||||||
mat1 = comfy.model_management.cast_to_device(v[0], weight.device, torch.float32)
|
mat1 = comfy.model_management.cast_to_device(v[0], weight.device, torch.float32)
|
||||||
mat2 = comfy.model_management.cast_to_device(v[1], weight.device, torch.float32)
|
mat2 = comfy.model_management.cast_to_device(v[1], weight.device, torch.float32)
|
||||||
|
dora_scale = v[4]
|
||||||
if v[2] is not None:
|
if v[2] is not None:
|
||||||
alpha *= v[2] / mat2.shape[0]
|
alpha *= v[2] / mat2.shape[0]
|
||||||
if v[3] is not None:
|
if v[3] is not None:
|
||||||
@ -251,8 +351,10 @@ class ModelPatcher:
|
|||||||
mat2 = torch.mm(mat2.transpose(0, 1).flatten(start_dim=1), mat3.transpose(0, 1).flatten(start_dim=1)).reshape(final_shape).transpose(0, 1)
|
mat2 = torch.mm(mat2.transpose(0, 1).flatten(start_dim=1), mat3.transpose(0, 1).flatten(start_dim=1)).reshape(final_shape).transpose(0, 1)
|
||||||
try:
|
try:
|
||||||
weight += (alpha * torch.mm(mat1.flatten(start_dim=1), mat2.flatten(start_dim=1))).reshape(weight.shape).type(weight.dtype)
|
weight += (alpha * torch.mm(mat1.flatten(start_dim=1), mat2.flatten(start_dim=1))).reshape(weight.shape).type(weight.dtype)
|
||||||
|
if dora_scale is not None:
|
||||||
|
weight = apply_weight_decompose(comfy.model_management.cast_to_device(dora_scale, weight.device, torch.float32), weight)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("ERROR", key, e)
|
logging.error("ERROR {} {} {}".format(patch_type, key, e))
|
||||||
elif patch_type == "lokr":
|
elif patch_type == "lokr":
|
||||||
w1 = v[0]
|
w1 = v[0]
|
||||||
w2 = v[1]
|
w2 = v[1]
|
||||||
@ -261,6 +363,7 @@ class ModelPatcher:
|
|||||||
w2_a = v[5]
|
w2_a = v[5]
|
||||||
w2_b = v[6]
|
w2_b = v[6]
|
||||||
t2 = v[7]
|
t2 = v[7]
|
||||||
|
dora_scale = v[8]
|
||||||
dim = None
|
dim = None
|
||||||
|
|
||||||
if w1 is None:
|
if w1 is None:
|
||||||
@ -290,8 +393,10 @@ class ModelPatcher:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
weight += alpha * torch.kron(w1, w2).reshape(weight.shape).type(weight.dtype)
|
weight += alpha * torch.kron(w1, w2).reshape(weight.shape).type(weight.dtype)
|
||||||
|
if dora_scale is not None:
|
||||||
|
weight = apply_weight_decompose(comfy.model_management.cast_to_device(dora_scale, weight.device, torch.float32), weight)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("ERROR", key, e)
|
logging.error("ERROR {} {} {}".format(patch_type, key, e))
|
||||||
elif patch_type == "loha":
|
elif patch_type == "loha":
|
||||||
w1a = v[0]
|
w1a = v[0]
|
||||||
w1b = v[1]
|
w1b = v[1]
|
||||||
@ -299,6 +404,7 @@ class ModelPatcher:
|
|||||||
alpha *= v[2] / w1b.shape[0]
|
alpha *= v[2] / w1b.shape[0]
|
||||||
w2a = v[3]
|
w2a = v[3]
|
||||||
w2b = v[4]
|
w2b = v[4]
|
||||||
|
dora_scale = v[7]
|
||||||
if v[5] is not None: #cp decomposition
|
if v[5] is not None: #cp decomposition
|
||||||
t1 = v[5]
|
t1 = v[5]
|
||||||
t2 = v[6]
|
t2 = v[6]
|
||||||
@ -319,41 +425,61 @@ class ModelPatcher:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
weight += (alpha * m1 * m2).reshape(weight.shape).type(weight.dtype)
|
weight += (alpha * m1 * m2).reshape(weight.shape).type(weight.dtype)
|
||||||
|
if dora_scale is not None:
|
||||||
|
weight = apply_weight_decompose(comfy.model_management.cast_to_device(dora_scale, weight.device, torch.float32), weight)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("ERROR", key, e)
|
logging.error("ERROR {} {} {}".format(patch_type, key, e))
|
||||||
elif patch_type == "glora":
|
elif patch_type == "glora":
|
||||||
if v[4] is not None:
|
if v[4] is not None:
|
||||||
alpha *= v[4] / v[0].shape[0]
|
alpha *= v[4] / v[0].shape[0]
|
||||||
|
|
||||||
|
dora_scale = v[5]
|
||||||
|
|
||||||
a1 = comfy.model_management.cast_to_device(v[0].flatten(start_dim=1), weight.device, torch.float32)
|
a1 = comfy.model_management.cast_to_device(v[0].flatten(start_dim=1), weight.device, torch.float32)
|
||||||
a2 = comfy.model_management.cast_to_device(v[1].flatten(start_dim=1), weight.device, torch.float32)
|
a2 = comfy.model_management.cast_to_device(v[1].flatten(start_dim=1), weight.device, torch.float32)
|
||||||
b1 = comfy.model_management.cast_to_device(v[2].flatten(start_dim=1), weight.device, torch.float32)
|
b1 = comfy.model_management.cast_to_device(v[2].flatten(start_dim=1), weight.device, torch.float32)
|
||||||
b2 = comfy.model_management.cast_to_device(v[3].flatten(start_dim=1), weight.device, torch.float32)
|
b2 = comfy.model_management.cast_to_device(v[3].flatten(start_dim=1), weight.device, torch.float32)
|
||||||
|
|
||||||
weight += ((torch.mm(b2, b1) + torch.mm(torch.mm(weight.flatten(start_dim=1), a2), a1)) * alpha).reshape(weight.shape).type(weight.dtype)
|
try:
|
||||||
|
weight += ((torch.mm(b2, b1) + torch.mm(torch.mm(weight.flatten(start_dim=1), a2), a1)) * alpha).reshape(weight.shape).type(weight.dtype)
|
||||||
|
if dora_scale is not None:
|
||||||
|
weight = apply_weight_decompose(comfy.model_management.cast_to_device(dora_scale, weight.device, torch.float32), weight)
|
||||||
|
except Exception as e:
|
||||||
|
logging.error("ERROR {} {} {}".format(patch_type, key, e))
|
||||||
else:
|
else:
|
||||||
print("patch type not recognized", patch_type, key)
|
logging.warning("patch type not recognized {} {}".format(patch_type, key))
|
||||||
|
|
||||||
return weight
|
return weight
|
||||||
|
|
||||||
def unpatch_model(self, device_to=None):
|
def unpatch_model(self, device_to=None, unpatch_weights=True):
|
||||||
keys = list(self.backup.keys())
|
if unpatch_weights:
|
||||||
|
if self.model_lowvram:
|
||||||
|
for m in self.model.modules():
|
||||||
|
if hasattr(m, "prev_comfy_cast_weights"):
|
||||||
|
m.comfy_cast_weights = m.prev_comfy_cast_weights
|
||||||
|
del m.prev_comfy_cast_weights
|
||||||
|
m.weight_function = None
|
||||||
|
m.bias_function = None
|
||||||
|
|
||||||
if self.weight_inplace_update:
|
self.model_lowvram = False
|
||||||
for k in keys:
|
|
||||||
comfy.utils.copy_to_param(self.model, k, self.backup[k])
|
|
||||||
else:
|
|
||||||
for k in keys:
|
|
||||||
comfy.utils.set_attr_param(self.model, k, self.backup[k])
|
|
||||||
|
|
||||||
self.backup = {}
|
keys = list(self.backup.keys())
|
||||||
|
|
||||||
if device_to is not None:
|
if self.weight_inplace_update:
|
||||||
self.model.to(device_to)
|
for k in keys:
|
||||||
self.current_device = device_to
|
comfy.utils.copy_to_param(self.model, k, self.backup[k])
|
||||||
|
else:
|
||||||
|
for k in keys:
|
||||||
|
comfy.utils.set_attr_param(self.model, k, self.backup[k])
|
||||||
|
|
||||||
|
self.backup.clear()
|
||||||
|
|
||||||
|
if device_to is not None:
|
||||||
|
self.model.to(device_to)
|
||||||
|
self.current_device = device_to
|
||||||
|
|
||||||
keys = list(self.object_patches_backup.keys())
|
keys = list(self.object_patches_backup.keys())
|
||||||
for k in keys:
|
for k in keys:
|
||||||
comfy.utils.set_attr(self.model, k, self.object_patches_backup[k])
|
comfy.utils.set_attr(self.model, k, self.object_patches_backup[k])
|
||||||
|
|
||||||
self.object_patches_backup = {}
|
self.object_patches_backup.clear()
|
||||||
|
|||||||
@ -20,6 +20,9 @@ class EPS:
|
|||||||
noise += latent_image
|
noise += latent_image
|
||||||
return noise
|
return noise
|
||||||
|
|
||||||
|
def inverse_noise_scaling(self, sigma, latent):
|
||||||
|
return latent
|
||||||
|
|
||||||
class V_PREDICTION(EPS):
|
class V_PREDICTION(EPS):
|
||||||
def calculate_denoised(self, sigma, model_output, model_input):
|
def calculate_denoised(self, sigma, model_output, model_input):
|
||||||
sigma = sigma.view(sigma.shape[:1] + (1,) * (model_output.ndim - 1))
|
sigma = sigma.view(sigma.shape[:1] + (1,) * (model_output.ndim - 1))
|
||||||
|
|||||||
26
comfy/ops.py
26
comfy/ops.py
@ -24,13 +24,20 @@ def cast_bias_weight(s, input):
|
|||||||
non_blocking = comfy.model_management.device_supports_non_blocking(input.device)
|
non_blocking = comfy.model_management.device_supports_non_blocking(input.device)
|
||||||
if s.bias is not None:
|
if s.bias is not None:
|
||||||
bias = s.bias.to(device=input.device, dtype=input.dtype, non_blocking=non_blocking)
|
bias = s.bias.to(device=input.device, dtype=input.dtype, non_blocking=non_blocking)
|
||||||
|
if s.bias_function is not None:
|
||||||
|
bias = s.bias_function(bias)
|
||||||
weight = s.weight.to(device=input.device, dtype=input.dtype, non_blocking=non_blocking)
|
weight = s.weight.to(device=input.device, dtype=input.dtype, non_blocking=non_blocking)
|
||||||
|
if s.weight_function is not None:
|
||||||
|
weight = s.weight_function(weight)
|
||||||
return weight, bias
|
return weight, bias
|
||||||
|
|
||||||
|
class CastWeightBiasOp:
|
||||||
|
comfy_cast_weights = False
|
||||||
|
weight_function = None
|
||||||
|
bias_function = None
|
||||||
|
|
||||||
class disable_weight_init:
|
class disable_weight_init:
|
||||||
class Linear(torch.nn.Linear):
|
class Linear(torch.nn.Linear, CastWeightBiasOp):
|
||||||
comfy_cast_weights = False
|
|
||||||
def reset_parameters(self):
|
def reset_parameters(self):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -44,8 +51,7 @@ class disable_weight_init:
|
|||||||
else:
|
else:
|
||||||
return super().forward(*args, **kwargs)
|
return super().forward(*args, **kwargs)
|
||||||
|
|
||||||
class Conv2d(torch.nn.Conv2d):
|
class Conv2d(torch.nn.Conv2d, CastWeightBiasOp):
|
||||||
comfy_cast_weights = False
|
|
||||||
def reset_parameters(self):
|
def reset_parameters(self):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -59,8 +65,7 @@ class disable_weight_init:
|
|||||||
else:
|
else:
|
||||||
return super().forward(*args, **kwargs)
|
return super().forward(*args, **kwargs)
|
||||||
|
|
||||||
class Conv3d(torch.nn.Conv3d):
|
class Conv3d(torch.nn.Conv3d, CastWeightBiasOp):
|
||||||
comfy_cast_weights = False
|
|
||||||
def reset_parameters(self):
|
def reset_parameters(self):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -74,8 +79,7 @@ class disable_weight_init:
|
|||||||
else:
|
else:
|
||||||
return super().forward(*args, **kwargs)
|
return super().forward(*args, **kwargs)
|
||||||
|
|
||||||
class GroupNorm(torch.nn.GroupNorm):
|
class GroupNorm(torch.nn.GroupNorm, CastWeightBiasOp):
|
||||||
comfy_cast_weights = False
|
|
||||||
def reset_parameters(self):
|
def reset_parameters(self):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -90,8 +94,7 @@ class disable_weight_init:
|
|||||||
return super().forward(*args, **kwargs)
|
return super().forward(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class LayerNorm(torch.nn.LayerNorm):
|
class LayerNorm(torch.nn.LayerNorm, CastWeightBiasOp):
|
||||||
comfy_cast_weights = False
|
|
||||||
def reset_parameters(self):
|
def reset_parameters(self):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -109,8 +112,7 @@ class disable_weight_init:
|
|||||||
else:
|
else:
|
||||||
return super().forward(*args, **kwargs)
|
return super().forward(*args, **kwargs)
|
||||||
|
|
||||||
class ConvTranspose2d(torch.nn.ConvTranspose2d):
|
class ConvTranspose2d(torch.nn.ConvTranspose2d, CastWeightBiasOp):
|
||||||
comfy_cast_weights = False
|
|
||||||
def reset_parameters(self):
|
def reset_parameters(self):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
import torch
|
import torch
|
||||||
import comfy.model_management
|
import comfy.model_management
|
||||||
import comfy.samplers
|
import comfy.samplers
|
||||||
import comfy.conds
|
|
||||||
import comfy.utils
|
import comfy.utils
|
||||||
import math
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
import logging
|
||||||
|
|
||||||
def prepare_noise(latent_image, seed, noise_inds=None):
|
def prepare_noise(latent_image, seed, noise_inds=None):
|
||||||
"""
|
"""
|
||||||
@ -25,94 +24,21 @@ def prepare_noise(latent_image, seed, noise_inds=None):
|
|||||||
noises = torch.cat(noises, axis=0)
|
noises = torch.cat(noises, axis=0)
|
||||||
return noises
|
return noises
|
||||||
|
|
||||||
def prepare_mask(noise_mask, shape, device):
|
def prepare_sampling(model, noise_shape, positive, negative, noise_mask):
|
||||||
"""ensures noise mask is of proper dimensions"""
|
logging.warning("Warning: comfy.sample.prepare_sampling isn't used anymore and can be removed")
|
||||||
noise_mask = torch.nn.functional.interpolate(noise_mask.reshape((-1, 1, noise_mask.shape[-2], noise_mask.shape[-1])), size=(shape[2], shape[3]), mode="bilinear")
|
return model, positive, negative, noise_mask, []
|
||||||
noise_mask = torch.cat([noise_mask] * shape[1], dim=1)
|
|
||||||
noise_mask = comfy.utils.repeat_to_batch_size(noise_mask, shape[0])
|
|
||||||
noise_mask = noise_mask.to(device)
|
|
||||||
return noise_mask
|
|
||||||
|
|
||||||
def get_models_from_cond(cond, model_type):
|
|
||||||
models = []
|
|
||||||
for c in cond:
|
|
||||||
if model_type in c:
|
|
||||||
models += [c[model_type]]
|
|
||||||
return models
|
|
||||||
|
|
||||||
def convert_cond(cond):
|
|
||||||
out = []
|
|
||||||
for c in cond:
|
|
||||||
temp = c[1].copy()
|
|
||||||
model_conds = temp.get("model_conds", {})
|
|
||||||
if c[0] is not None:
|
|
||||||
model_conds["c_crossattn"] = comfy.conds.CONDCrossAttn(c[0]) #TODO: remove
|
|
||||||
temp["cross_attn"] = c[0]
|
|
||||||
temp["model_conds"] = model_conds
|
|
||||||
out.append(temp)
|
|
||||||
return out
|
|
||||||
|
|
||||||
def get_additional_models(positive, negative, dtype):
|
|
||||||
"""loads additional models in positive and negative conditioning"""
|
|
||||||
control_nets = set(get_models_from_cond(positive, "control") + get_models_from_cond(negative, "control"))
|
|
||||||
|
|
||||||
inference_memory = 0
|
|
||||||
control_models = []
|
|
||||||
for m in control_nets:
|
|
||||||
control_models += m.get_models()
|
|
||||||
inference_memory += m.inference_memory_requirements(dtype)
|
|
||||||
|
|
||||||
gligen = get_models_from_cond(positive, "gligen") + get_models_from_cond(negative, "gligen")
|
|
||||||
gligen = [x[1] for x in gligen]
|
|
||||||
models = control_models + gligen
|
|
||||||
return models, inference_memory
|
|
||||||
|
|
||||||
def cleanup_additional_models(models):
|
def cleanup_additional_models(models):
|
||||||
"""cleanup additional models that were loaded"""
|
logging.warning("Warning: comfy.sample.cleanup_additional_models isn't used anymore and can be removed")
|
||||||
for m in models:
|
|
||||||
if hasattr(m, 'cleanup'):
|
|
||||||
m.cleanup()
|
|
||||||
|
|
||||||
def prepare_sampling(model, noise_shape, positive, negative, noise_mask):
|
|
||||||
device = model.load_device
|
|
||||||
positive = convert_cond(positive)
|
|
||||||
negative = convert_cond(negative)
|
|
||||||
|
|
||||||
if noise_mask is not None:
|
|
||||||
noise_mask = prepare_mask(noise_mask, noise_shape, device)
|
|
||||||
|
|
||||||
real_model = None
|
|
||||||
models, inference_memory = get_additional_models(positive, negative, model.model_dtype())
|
|
||||||
comfy.model_management.load_models_gpu([model] + models, model.memory_required([noise_shape[0] * 2] + list(noise_shape[1:])) + inference_memory)
|
|
||||||
real_model = model.model
|
|
||||||
|
|
||||||
return real_model, positive, negative, noise_mask, models
|
|
||||||
|
|
||||||
|
|
||||||
def sample(model, noise, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, denoise=1.0, disable_noise=False, start_step=None, last_step=None, force_full_denoise=False, noise_mask=None, sigmas=None, callback=None, disable_pbar=False, seed=None):
|
def sample(model, noise, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, denoise=1.0, disable_noise=False, start_step=None, last_step=None, force_full_denoise=False, noise_mask=None, sigmas=None, callback=None, disable_pbar=False, seed=None):
|
||||||
real_model, positive_copy, negative_copy, noise_mask, models = prepare_sampling(model, noise.shape, positive, negative, noise_mask)
|
sampler = comfy.samplers.KSampler(model, steps=steps, device=model.load_device, sampler=sampler_name, scheduler=scheduler, denoise=denoise, model_options=model.model_options)
|
||||||
|
|
||||||
noise = noise.to(model.load_device)
|
samples = sampler.sample(noise, positive, negative, cfg=cfg, latent_image=latent_image, start_step=start_step, last_step=last_step, force_full_denoise=force_full_denoise, denoise_mask=noise_mask, sigmas=sigmas, callback=callback, disable_pbar=disable_pbar, seed=seed)
|
||||||
latent_image = latent_image.to(model.load_device)
|
|
||||||
|
|
||||||
sampler = comfy.samplers.KSampler(real_model, steps=steps, device=model.load_device, sampler=sampler_name, scheduler=scheduler, denoise=denoise, model_options=model.model_options)
|
|
||||||
|
|
||||||
samples = sampler.sample(noise, positive_copy, negative_copy, cfg=cfg, latent_image=latent_image, start_step=start_step, last_step=last_step, force_full_denoise=force_full_denoise, denoise_mask=noise_mask, sigmas=sigmas, callback=callback, disable_pbar=disable_pbar, seed=seed)
|
|
||||||
samples = samples.to(comfy.model_management.intermediate_device())
|
samples = samples.to(comfy.model_management.intermediate_device())
|
||||||
|
|
||||||
cleanup_additional_models(models)
|
|
||||||
cleanup_additional_models(set(get_models_from_cond(positive_copy, "control") + get_models_from_cond(negative_copy, "control")))
|
|
||||||
return samples
|
return samples
|
||||||
|
|
||||||
def sample_custom(model, noise, cfg, sampler, sigmas, positive, negative, latent_image, noise_mask=None, callback=None, disable_pbar=False, seed=None):
|
def sample_custom(model, noise, cfg, sampler, sigmas, positive, negative, latent_image, noise_mask=None, callback=None, disable_pbar=False, seed=None):
|
||||||
real_model, positive_copy, negative_copy, noise_mask, models = prepare_sampling(model, noise.shape, positive, negative, noise_mask)
|
samples = comfy.samplers.sample(model, noise, positive, negative, cfg, model.load_device, sampler, sigmas, model_options=model.model_options, latent_image=latent_image, denoise_mask=noise_mask, callback=callback, disable_pbar=disable_pbar, seed=seed)
|
||||||
noise = noise.to(model.load_device)
|
|
||||||
latent_image = latent_image.to(model.load_device)
|
|
||||||
sigmas = sigmas.to(model.load_device)
|
|
||||||
|
|
||||||
samples = comfy.samplers.sample(real_model, noise, positive_copy, negative_copy, cfg, model.load_device, sampler, sigmas, model_options=model.model_options, latent_image=latent_image, denoise_mask=noise_mask, callback=callback, disable_pbar=disable_pbar, seed=seed)
|
|
||||||
samples = samples.to(comfy.model_management.intermediate_device())
|
samples = samples.to(comfy.model_management.intermediate_device())
|
||||||
cleanup_additional_models(models)
|
|
||||||
cleanup_additional_models(set(get_models_from_cond(positive_copy, "control") + get_models_from_cond(negative_copy, "control")))
|
|
||||||
return samples
|
return samples
|
||||||
|
|
||||||
|
|||||||
76
comfy/sampler_helpers.py
Normal file
76
comfy/sampler_helpers.py
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import torch
|
||||||
|
import comfy.model_management
|
||||||
|
import comfy.conds
|
||||||
|
|
||||||
|
def prepare_mask(noise_mask, shape, device):
|
||||||
|
"""ensures noise mask is of proper dimensions"""
|
||||||
|
noise_mask = torch.nn.functional.interpolate(noise_mask.reshape((-1, 1, noise_mask.shape[-2], noise_mask.shape[-1])), size=(shape[2], shape[3]), mode="bilinear")
|
||||||
|
noise_mask = torch.cat([noise_mask] * shape[1], dim=1)
|
||||||
|
noise_mask = comfy.utils.repeat_to_batch_size(noise_mask, shape[0])
|
||||||
|
noise_mask = noise_mask.to(device)
|
||||||
|
return noise_mask
|
||||||
|
|
||||||
|
def get_models_from_cond(cond, model_type):
|
||||||
|
models = []
|
||||||
|
for c in cond:
|
||||||
|
if model_type in c:
|
||||||
|
models += [c[model_type]]
|
||||||
|
return models
|
||||||
|
|
||||||
|
def convert_cond(cond):
|
||||||
|
out = []
|
||||||
|
for c in cond:
|
||||||
|
temp = c[1].copy()
|
||||||
|
model_conds = temp.get("model_conds", {})
|
||||||
|
if c[0] is not None:
|
||||||
|
model_conds["c_crossattn"] = comfy.conds.CONDCrossAttn(c[0]) #TODO: remove
|
||||||
|
temp["cross_attn"] = c[0]
|
||||||
|
temp["model_conds"] = model_conds
|
||||||
|
out.append(temp)
|
||||||
|
return out
|
||||||
|
|
||||||
|
def get_additional_models(conds, dtype):
|
||||||
|
"""loads additional models in conditioning"""
|
||||||
|
cnets = []
|
||||||
|
gligen = []
|
||||||
|
|
||||||
|
for k in conds:
|
||||||
|
cnets += get_models_from_cond(conds[k], "control")
|
||||||
|
gligen += get_models_from_cond(conds[k], "gligen")
|
||||||
|
|
||||||
|
control_nets = set(cnets)
|
||||||
|
|
||||||
|
inference_memory = 0
|
||||||
|
control_models = []
|
||||||
|
for m in control_nets:
|
||||||
|
control_models += m.get_models()
|
||||||
|
inference_memory += m.inference_memory_requirements(dtype)
|
||||||
|
|
||||||
|
gligen = [x[1] for x in gligen]
|
||||||
|
models = control_models + gligen
|
||||||
|
return models, inference_memory
|
||||||
|
|
||||||
|
def cleanup_additional_models(models):
|
||||||
|
"""cleanup additional models that were loaded"""
|
||||||
|
for m in models:
|
||||||
|
if hasattr(m, 'cleanup'):
|
||||||
|
m.cleanup()
|
||||||
|
|
||||||
|
|
||||||
|
def prepare_sampling(model, noise_shape, conds):
|
||||||
|
device = model.load_device
|
||||||
|
real_model = None
|
||||||
|
models, inference_memory = get_additional_models(conds, model.model_dtype())
|
||||||
|
comfy.model_management.load_models_gpu([model] + models, model.memory_required([noise_shape[0] * 2] + list(noise_shape[1:])) + inference_memory)
|
||||||
|
real_model = model.model
|
||||||
|
|
||||||
|
return real_model, conds, models
|
||||||
|
|
||||||
|
def cleanup_models(conds, models):
|
||||||
|
cleanup_additional_models(models)
|
||||||
|
|
||||||
|
control_cleanup = []
|
||||||
|
for k in conds:
|
||||||
|
control_cleanup += get_models_from_cond(conds[k], "control")
|
||||||
|
|
||||||
|
cleanup_additional_models(set(control_cleanup))
|
||||||
@ -4,6 +4,8 @@ import torch
|
|||||||
import collections
|
import collections
|
||||||
from comfy import model_management
|
from comfy import model_management
|
||||||
import math
|
import math
|
||||||
|
import logging
|
||||||
|
import comfy.sampler_helpers
|
||||||
|
|
||||||
def get_area_and_mult(conds, x_in, timestep_in):
|
def get_area_and_mult(conds, x_in, timestep_in):
|
||||||
area = (x_in.shape[2], x_in.shape[3], 0, 0)
|
area = (x_in.shape[2], x_in.shape[3], 0, 0)
|
||||||
@ -32,7 +34,7 @@ def get_area_and_mult(conds, x_in, timestep_in):
|
|||||||
mask = conds['mask']
|
mask = conds['mask']
|
||||||
assert(mask.shape[1] == x_in.shape[2])
|
assert(mask.shape[1] == x_in.shape[2])
|
||||||
assert(mask.shape[2] == x_in.shape[3])
|
assert(mask.shape[2] == x_in.shape[3])
|
||||||
mask = mask[:,area[2]:area[0] + area[2],area[3]:area[1] + area[3]] * mask_strength
|
mask = mask[:input_x.shape[0],area[2]:area[0] + area[2],area[3]:area[1] + area[3]] * mask_strength
|
||||||
mask = mask.unsqueeze(1).repeat(input_x.shape[0] // mask.shape[0], input_x.shape[1], 1, 1)
|
mask = mask.unsqueeze(1).repeat(input_x.shape[0] // mask.shape[0], input_x.shape[1], 1, 1)
|
||||||
else:
|
else:
|
||||||
mask = torch.ones_like(input_x)
|
mask = torch.ones_like(input_x)
|
||||||
@ -126,30 +128,23 @@ def cond_cat(c_list):
|
|||||||
|
|
||||||
return out
|
return out
|
||||||
|
|
||||||
def calc_cond_uncond_batch(model, cond, uncond, x_in, timestep, model_options):
|
def calc_cond_batch(model, conds, x_in, timestep, model_options):
|
||||||
out_cond = torch.zeros_like(x_in)
|
out_conds = []
|
||||||
out_count = torch.ones_like(x_in) * 1e-37
|
out_counts = []
|
||||||
|
|
||||||
out_uncond = torch.zeros_like(x_in)
|
|
||||||
out_uncond_count = torch.ones_like(x_in) * 1e-37
|
|
||||||
|
|
||||||
COND = 0
|
|
||||||
UNCOND = 1
|
|
||||||
|
|
||||||
to_run = []
|
to_run = []
|
||||||
for x in cond:
|
|
||||||
p = get_area_and_mult(x, x_in, timestep)
|
|
||||||
if p is None:
|
|
||||||
continue
|
|
||||||
|
|
||||||
to_run += [(p, COND)]
|
for i in range(len(conds)):
|
||||||
if uncond is not None:
|
out_conds.append(torch.zeros_like(x_in))
|
||||||
for x in uncond:
|
out_counts.append(torch.ones_like(x_in) * 1e-37)
|
||||||
p = get_area_and_mult(x, x_in, timestep)
|
|
||||||
if p is None:
|
|
||||||
continue
|
|
||||||
|
|
||||||
to_run += [(p, UNCOND)]
|
cond = conds[i]
|
||||||
|
if cond is not None:
|
||||||
|
for x in cond:
|
||||||
|
p = get_area_and_mult(x, x_in, timestep)
|
||||||
|
if p is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
to_run += [(p, i)]
|
||||||
|
|
||||||
while len(to_run) > 0:
|
while len(to_run) > 0:
|
||||||
first = to_run[0]
|
first = to_run[0]
|
||||||
@ -221,74 +216,66 @@ def calc_cond_uncond_batch(model, cond, uncond, x_in, timestep, model_options):
|
|||||||
output = model_options['model_function_wrapper'](model.apply_model, {"input": input_x, "timestep": timestep_, "c": c, "cond_or_uncond": cond_or_uncond}).chunk(batch_chunks)
|
output = model_options['model_function_wrapper'](model.apply_model, {"input": input_x, "timestep": timestep_, "c": c, "cond_or_uncond": cond_or_uncond}).chunk(batch_chunks)
|
||||||
else:
|
else:
|
||||||
output = model.apply_model(input_x, timestep_, **c).chunk(batch_chunks)
|
output = model.apply_model(input_x, timestep_, **c).chunk(batch_chunks)
|
||||||
del input_x
|
|
||||||
|
|
||||||
for o in range(batch_chunks):
|
for o in range(batch_chunks):
|
||||||
if cond_or_uncond[o] == COND:
|
cond_index = cond_or_uncond[o]
|
||||||
out_cond[:,:,area[o][2]:area[o][0] + area[o][2],area[o][3]:area[o][1] + area[o][3]] += output[o] * mult[o]
|
out_conds[cond_index][:,:,area[o][2]:area[o][0] + area[o][2],area[o][3]:area[o][1] + area[o][3]] += output[o] * mult[o]
|
||||||
out_count[:,:,area[o][2]:area[o][0] + area[o][2],area[o][3]:area[o][1] + area[o][3]] += mult[o]
|
out_counts[cond_index][:,:,area[o][2]:area[o][0] + area[o][2],area[o][3]:area[o][1] + area[o][3]] += mult[o]
|
||||||
else:
|
|
||||||
out_uncond[:,:,area[o][2]:area[o][0] + area[o][2],area[o][3]:area[o][1] + area[o][3]] += output[o] * mult[o]
|
|
||||||
out_uncond_count[:,:,area[o][2]:area[o][0] + area[o][2],area[o][3]:area[o][1] + area[o][3]] += mult[o]
|
|
||||||
del mult
|
|
||||||
|
|
||||||
out_cond /= out_count
|
for i in range(len(out_conds)):
|
||||||
del out_count
|
out_conds[i] /= out_counts[i]
|
||||||
out_uncond /= out_uncond_count
|
|
||||||
del out_uncond_count
|
return out_conds
|
||||||
return out_cond, out_uncond
|
|
||||||
|
def calc_cond_uncond_batch(model, cond, uncond, x_in, timestep, model_options): #TODO: remove
|
||||||
|
logging.warning("WARNING: The comfy.samplers.calc_cond_uncond_batch function is deprecated please use the calc_cond_batch one instead.")
|
||||||
|
return tuple(calc_cond_batch(model, [cond, uncond], x_in, timestep, model_options))
|
||||||
|
|
||||||
|
def cfg_function(model, cond_pred, uncond_pred, cond_scale, x, timestep, model_options={}, cond=None, uncond=None):
|
||||||
|
if "sampler_cfg_function" in model_options:
|
||||||
|
args = {"cond": x - cond_pred, "uncond": x - uncond_pred, "cond_scale": cond_scale, "timestep": timestep, "input": x, "sigma": timestep,
|
||||||
|
"cond_denoised": cond_pred, "uncond_denoised": uncond_pred, "model": model, "model_options": model_options}
|
||||||
|
cfg_result = x - model_options["sampler_cfg_function"](args)
|
||||||
|
else:
|
||||||
|
cfg_result = uncond_pred + (cond_pred - uncond_pred) * cond_scale
|
||||||
|
|
||||||
|
for fn in model_options.get("sampler_post_cfg_function", []):
|
||||||
|
args = {"denoised": cfg_result, "cond": cond, "uncond": uncond, "model": model, "uncond_denoised": uncond_pred, "cond_denoised": cond_pred,
|
||||||
|
"sigma": timestep, "model_options": model_options, "input": x}
|
||||||
|
cfg_result = fn(args)
|
||||||
|
|
||||||
|
return cfg_result
|
||||||
|
|
||||||
#The main sampling function shared by all the samplers
|
#The main sampling function shared by all the samplers
|
||||||
#Returns denoised
|
#Returns denoised
|
||||||
def sampling_function(model, x, timestep, uncond, cond, cond_scale, model_options={}, seed=None):
|
def sampling_function(model, x, timestep, uncond, cond, cond_scale, model_options={}, seed=None):
|
||||||
if math.isclose(cond_scale, 1.0) and model_options.get("disable_cfg1_optimization", False) == False:
|
if math.isclose(cond_scale, 1.0) and model_options.get("disable_cfg1_optimization", False) == False:
|
||||||
uncond_ = None
|
uncond_ = None
|
||||||
else:
|
else:
|
||||||
uncond_ = uncond
|
uncond_ = uncond
|
||||||
|
|
||||||
cond_pred, uncond_pred = calc_cond_uncond_batch(model, cond, uncond_, x, timestep, model_options)
|
conds = [cond, uncond_]
|
||||||
if "sampler_cfg_function" in model_options:
|
out = calc_cond_batch(model, conds, x, timestep, model_options)
|
||||||
args = {"cond": x - cond_pred, "uncond": x - uncond_pred, "cond_scale": cond_scale, "timestep": timestep, "input": x, "sigma": timestep,
|
return cfg_function(model, out[0], out[1], cond_scale, x, timestep, model_options=model_options, cond=cond, uncond=uncond_)
|
||||||
"cond_denoised": cond_pred, "uncond_denoised": uncond_pred, "model": model, "model_options": model_options}
|
|
||||||
cfg_result = x - model_options["sampler_cfg_function"](args)
|
|
||||||
else:
|
|
||||||
cfg_result = uncond_pred + (cond_pred - uncond_pred) * cond_scale
|
|
||||||
|
|
||||||
for fn in model_options.get("sampler_post_cfg_function", []):
|
|
||||||
args = {"denoised": cfg_result, "cond": cond, "uncond": uncond, "model": model, "uncond_denoised": uncond_pred, "cond_denoised": cond_pred,
|
|
||||||
"sigma": timestep, "model_options": model_options, "input": x}
|
|
||||||
cfg_result = fn(args)
|
|
||||||
|
|
||||||
return cfg_result
|
class KSamplerX0Inpaint:
|
||||||
|
|
||||||
class CFGNoisePredictor(torch.nn.Module):
|
|
||||||
def __init__(self, model):
|
|
||||||
super().__init__()
|
|
||||||
self.inner_model = model
|
|
||||||
def apply_model(self, x, timestep, cond, uncond, cond_scale, model_options={}, seed=None):
|
|
||||||
out = sampling_function(self.inner_model, x, timestep, uncond, cond, cond_scale, model_options=model_options, seed=seed)
|
|
||||||
return out
|
|
||||||
def forward(self, *args, **kwargs):
|
|
||||||
return self.apply_model(*args, **kwargs)
|
|
||||||
|
|
||||||
class KSamplerX0Inpaint(torch.nn.Module):
|
|
||||||
def __init__(self, model, sigmas):
|
def __init__(self, model, sigmas):
|
||||||
super().__init__()
|
|
||||||
self.inner_model = model
|
self.inner_model = model
|
||||||
self.sigmas = sigmas
|
self.sigmas = sigmas
|
||||||
def forward(self, x, sigma, uncond, cond, cond_scale, denoise_mask, model_options={}, seed=None):
|
def __call__(self, x, sigma, denoise_mask, model_options={}, seed=None):
|
||||||
if denoise_mask is not None:
|
if denoise_mask is not None:
|
||||||
if "denoise_mask_function" in model_options:
|
if "denoise_mask_function" in model_options:
|
||||||
denoise_mask = model_options["denoise_mask_function"](sigma, denoise_mask, extra_options={"model": self.inner_model, "sigmas": self.sigmas})
|
denoise_mask = model_options["denoise_mask_function"](sigma, denoise_mask, extra_options={"model": self.inner_model, "sigmas": self.sigmas})
|
||||||
latent_mask = 1. - denoise_mask
|
latent_mask = 1. - denoise_mask
|
||||||
x = x * denoise_mask + self.inner_model.inner_model.model_sampling.noise_scaling(sigma.reshape([sigma.shape[0]] + [1] * (len(self.noise.shape) - 1)), self.noise, self.latent_image) * latent_mask
|
x = x * denoise_mask + self.inner_model.inner_model.model_sampling.noise_scaling(sigma.reshape([sigma.shape[0]] + [1] * (len(self.noise.shape) - 1)), self.noise, self.latent_image) * latent_mask
|
||||||
out = self.inner_model(x, sigma, cond=cond, uncond=uncond, cond_scale=cond_scale, model_options=model_options, seed=seed)
|
out = self.inner_model(x, sigma, model_options=model_options, seed=seed)
|
||||||
if denoise_mask is not None:
|
if denoise_mask is not None:
|
||||||
out = out * denoise_mask + self.latent_image * latent_mask
|
out = out * denoise_mask + self.latent_image * latent_mask
|
||||||
return out
|
return out
|
||||||
|
|
||||||
def simple_scheduler(model, steps):
|
def simple_scheduler(model_sampling, steps):
|
||||||
s = model.model_sampling
|
s = model_sampling
|
||||||
sigs = []
|
sigs = []
|
||||||
ss = len(s.sigmas) / steps
|
ss = len(s.sigmas) / steps
|
||||||
for x in range(steps):
|
for x in range(steps):
|
||||||
@ -296,8 +283,8 @@ def simple_scheduler(model, steps):
|
|||||||
sigs += [0.0]
|
sigs += [0.0]
|
||||||
return torch.FloatTensor(sigs)
|
return torch.FloatTensor(sigs)
|
||||||
|
|
||||||
def ddim_scheduler(model, steps):
|
def ddim_scheduler(model_sampling, steps):
|
||||||
s = model.model_sampling
|
s = model_sampling
|
||||||
sigs = []
|
sigs = []
|
||||||
ss = max(len(s.sigmas) // steps, 1)
|
ss = max(len(s.sigmas) // steps, 1)
|
||||||
x = 1
|
x = 1
|
||||||
@ -308,8 +295,8 @@ def ddim_scheduler(model, steps):
|
|||||||
sigs += [0.0]
|
sigs += [0.0]
|
||||||
return torch.FloatTensor(sigs)
|
return torch.FloatTensor(sigs)
|
||||||
|
|
||||||
def normal_scheduler(model, steps, sgm=False, floor=False):
|
def normal_scheduler(model_sampling, steps, sgm=False, floor=False):
|
||||||
s = model.model_sampling
|
s = model_sampling
|
||||||
start = s.timestep(s.sigma_max)
|
start = s.timestep(s.sigma_max)
|
||||||
end = s.timestep(s.sigma_min)
|
end = s.timestep(s.sigma_min)
|
||||||
|
|
||||||
@ -545,12 +532,16 @@ class KSAMPLER(Sampler):
|
|||||||
k_callback = lambda x: callback(x["i"], x["denoised"], x["x"], total_steps)
|
k_callback = lambda x: callback(x["i"], x["denoised"], x["x"], total_steps)
|
||||||
|
|
||||||
samples = self.sampler_function(model_k, noise, sigmas, extra_args=extra_args, callback=k_callback, disable=disable_pbar, **self.extra_options)
|
samples = self.sampler_function(model_k, noise, sigmas, 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
|
return samples
|
||||||
|
|
||||||
|
|
||||||
def ksampler(sampler_name, extra_options={}, inpaint_options={}):
|
def ksampler(sampler_name, extra_options={}, inpaint_options={}):
|
||||||
if sampler_name == "dpm_fast":
|
if sampler_name == "dpm_fast":
|
||||||
def dpm_fast_function(model, noise, sigmas, extra_args, callback, disable):
|
def dpm_fast_function(model, noise, sigmas, extra_args, callback, disable):
|
||||||
|
if len(sigmas) <= 1:
|
||||||
|
return noise
|
||||||
|
|
||||||
sigma_min = sigmas[-1]
|
sigma_min = sigmas[-1]
|
||||||
if sigma_min == 0:
|
if sigma_min == 0:
|
||||||
sigma_min = sigmas[-2]
|
sigma_min = sigmas[-2]
|
||||||
@ -558,74 +549,138 @@ def ksampler(sampler_name, extra_options={}, inpaint_options={}):
|
|||||||
return k_diffusion_sampling.sample_dpm_fast(model, noise, sigma_min, sigmas[0], total_steps, extra_args=extra_args, callback=callback, disable=disable)
|
return k_diffusion_sampling.sample_dpm_fast(model, noise, sigma_min, sigmas[0], total_steps, extra_args=extra_args, callback=callback, disable=disable)
|
||||||
sampler_function = dpm_fast_function
|
sampler_function = dpm_fast_function
|
||||||
elif sampler_name == "dpm_adaptive":
|
elif sampler_name == "dpm_adaptive":
|
||||||
def dpm_adaptive_function(model, noise, sigmas, extra_args, callback, disable):
|
def dpm_adaptive_function(model, noise, sigmas, extra_args, callback, disable, **extra_options):
|
||||||
|
if len(sigmas) <= 1:
|
||||||
|
return noise
|
||||||
|
|
||||||
sigma_min = sigmas[-1]
|
sigma_min = sigmas[-1]
|
||||||
if sigma_min == 0:
|
if sigma_min == 0:
|
||||||
sigma_min = sigmas[-2]
|
sigma_min = sigmas[-2]
|
||||||
return k_diffusion_sampling.sample_dpm_adaptive(model, noise, sigma_min, sigmas[0], extra_args=extra_args, callback=callback, disable=disable)
|
return k_diffusion_sampling.sample_dpm_adaptive(model, noise, sigma_min, sigmas[0], extra_args=extra_args, callback=callback, disable=disable, **extra_options)
|
||||||
sampler_function = dpm_adaptive_function
|
sampler_function = dpm_adaptive_function
|
||||||
else:
|
else:
|
||||||
sampler_function = getattr(k_diffusion_sampling, "sample_{}".format(sampler_name))
|
sampler_function = getattr(k_diffusion_sampling, "sample_{}".format(sampler_name))
|
||||||
|
|
||||||
return KSAMPLER(sampler_function, extra_options, inpaint_options)
|
return KSAMPLER(sampler_function, extra_options, inpaint_options)
|
||||||
|
|
||||||
def wrap_model(model):
|
|
||||||
model_denoise = CFGNoisePredictor(model)
|
|
||||||
return model_denoise
|
|
||||||
|
|
||||||
def sample(model, noise, positive, negative, cfg, device, sampler, sigmas, model_options={}, latent_image=None, denoise_mask=None, callback=None, disable_pbar=False, seed=None):
|
def process_conds(model, noise, conds, device, latent_image=None, denoise_mask=None, seed=None):
|
||||||
positive = positive[:]
|
for k in conds:
|
||||||
negative = negative[:]
|
conds[k] = conds[k][:]
|
||||||
|
resolve_areas_and_cond_masks(conds[k], noise.shape[2], noise.shape[3], device)
|
||||||
|
|
||||||
resolve_areas_and_cond_masks(positive, noise.shape[2], noise.shape[3], device)
|
for k in conds:
|
||||||
resolve_areas_and_cond_masks(negative, noise.shape[2], noise.shape[3], device)
|
calculate_start_end_timesteps(model, conds[k])
|
||||||
|
|
||||||
model_wrap = wrap_model(model)
|
|
||||||
|
|
||||||
calculate_start_end_timesteps(model, negative)
|
|
||||||
calculate_start_end_timesteps(model, positive)
|
|
||||||
|
|
||||||
if latent_image is not None and torch.count_nonzero(latent_image) > 0: #Don't shift the empty latent image.
|
|
||||||
latent_image = model.process_latent_in(latent_image)
|
|
||||||
|
|
||||||
if hasattr(model, 'extra_conds'):
|
if hasattr(model, 'extra_conds'):
|
||||||
positive = encode_model_conds(model.extra_conds, positive, noise, device, "positive", latent_image=latent_image, denoise_mask=denoise_mask, seed=seed)
|
for k in conds:
|
||||||
negative = encode_model_conds(model.extra_conds, negative, noise, device, "negative", latent_image=latent_image, denoise_mask=denoise_mask, seed=seed)
|
conds[k] = encode_model_conds(model.extra_conds, conds[k], noise, device, k, latent_image=latent_image, denoise_mask=denoise_mask, seed=seed)
|
||||||
|
|
||||||
#make sure each cond area has an opposite one with the same area
|
#make sure each cond area has an opposite one with the same area
|
||||||
for c in positive:
|
for k in conds:
|
||||||
create_cond_with_same_area_if_none(negative, c)
|
for c in conds[k]:
|
||||||
for c in negative:
|
for kk in conds:
|
||||||
create_cond_with_same_area_if_none(positive, c)
|
if k != kk:
|
||||||
|
create_cond_with_same_area_if_none(conds[kk], c)
|
||||||
|
|
||||||
pre_run_control(model, negative + positive)
|
for k in conds:
|
||||||
|
pre_run_control(model, conds[k])
|
||||||
|
|
||||||
apply_empty_x_to_equal_area(list(filter(lambda c: c.get('control_apply_to_uncond', False) == True, positive)), negative, 'control', lambda cond_cnets, x: cond_cnets[x])
|
if "positive" in conds:
|
||||||
apply_empty_x_to_equal_area(positive, negative, 'gligen', lambda cond_cnets, x: cond_cnets[x])
|
positive = conds["positive"]
|
||||||
|
for k in conds:
|
||||||
|
if k != "positive":
|
||||||
|
apply_empty_x_to_equal_area(list(filter(lambda c: c.get('control_apply_to_uncond', False) == True, positive)), conds[k], 'control', lambda cond_cnets, x: cond_cnets[x])
|
||||||
|
apply_empty_x_to_equal_area(positive, conds[k], 'gligen', lambda cond_cnets, x: cond_cnets[x])
|
||||||
|
|
||||||
extra_args = {"cond":positive, "uncond":negative, "cond_scale": cfg, "model_options": model_options, "seed":seed}
|
return conds
|
||||||
|
|
||||||
|
class CFGGuider:
|
||||||
|
def __init__(self, model_patcher):
|
||||||
|
self.model_patcher = model_patcher
|
||||||
|
self.model_options = model_patcher.model_options
|
||||||
|
self.original_conds = {}
|
||||||
|
self.cfg = 1.0
|
||||||
|
|
||||||
|
def set_conds(self, positive, negative):
|
||||||
|
self.inner_set_conds({"positive": positive, "negative": negative})
|
||||||
|
|
||||||
|
def set_cfg(self, cfg):
|
||||||
|
self.cfg = cfg
|
||||||
|
|
||||||
|
def inner_set_conds(self, conds):
|
||||||
|
for k in conds:
|
||||||
|
self.original_conds[k] = comfy.sampler_helpers.convert_cond(conds[k])
|
||||||
|
|
||||||
|
def __call__(self, *args, **kwargs):
|
||||||
|
return self.predict_noise(*args, **kwargs)
|
||||||
|
|
||||||
|
def predict_noise(self, x, timestep, model_options={}, seed=None):
|
||||||
|
return sampling_function(self.inner_model, x, timestep, self.conds.get("negative", None), self.conds.get("positive", None), self.cfg, model_options=model_options, seed=seed)
|
||||||
|
|
||||||
|
def inner_sample(self, noise, latent_image, device, sampler, sigmas, denoise_mask, callback, disable_pbar, seed):
|
||||||
|
if latent_image is not None and torch.count_nonzero(latent_image) > 0: #Don't shift the empty latent image.
|
||||||
|
latent_image = self.inner_model.process_latent_in(latent_image)
|
||||||
|
|
||||||
|
self.conds = process_conds(self.inner_model, noise, self.conds, device, latent_image, denoise_mask, seed)
|
||||||
|
|
||||||
|
extra_args = {"model_options": self.model_options, "seed":seed}
|
||||||
|
|
||||||
|
samples = sampler.sample(self, sigmas, extra_args, callback, noise, latent_image, denoise_mask, disable_pbar)
|
||||||
|
return self.inner_model.process_latent_out(samples.to(torch.float32))
|
||||||
|
|
||||||
|
def sample(self, noise, latent_image, sampler, sigmas, denoise_mask=None, callback=None, disable_pbar=False, seed=None):
|
||||||
|
if sigmas.shape[-1] == 0:
|
||||||
|
return latent_image
|
||||||
|
|
||||||
|
self.conds = {}
|
||||||
|
for k in self.original_conds:
|
||||||
|
self.conds[k] = list(map(lambda a: a.copy(), self.original_conds[k]))
|
||||||
|
|
||||||
|
self.inner_model, self.conds, self.loaded_models = comfy.sampler_helpers.prepare_sampling(self.model_patcher, noise.shape, self.conds)
|
||||||
|
device = self.model_patcher.load_device
|
||||||
|
|
||||||
|
if denoise_mask is not None:
|
||||||
|
denoise_mask = comfy.sampler_helpers.prepare_mask(denoise_mask, noise.shape, device)
|
||||||
|
|
||||||
|
noise = noise.to(device)
|
||||||
|
latent_image = latent_image.to(device)
|
||||||
|
sigmas = sigmas.to(device)
|
||||||
|
|
||||||
|
output = self.inner_sample(noise, latent_image, device, sampler, sigmas, denoise_mask, callback, disable_pbar, seed)
|
||||||
|
|
||||||
|
comfy.sampler_helpers.cleanup_models(self.conds, self.loaded_models)
|
||||||
|
del self.inner_model
|
||||||
|
del self.conds
|
||||||
|
del self.loaded_models
|
||||||
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
def sample(model, noise, positive, negative, cfg, device, sampler, sigmas, model_options={}, latent_image=None, denoise_mask=None, callback=None, disable_pbar=False, seed=None):
|
||||||
|
cfg_guider = CFGGuider(model)
|
||||||
|
cfg_guider.set_conds(positive, negative)
|
||||||
|
cfg_guider.set_cfg(cfg)
|
||||||
|
return cfg_guider.sample(noise, latent_image, sampler, sigmas, denoise_mask, callback, disable_pbar, seed)
|
||||||
|
|
||||||
samples = sampler.sample(model_wrap, sigmas, extra_args, callback, noise, latent_image, denoise_mask, disable_pbar)
|
|
||||||
return model.process_latent_out(samples.to(torch.float32))
|
|
||||||
|
|
||||||
SCHEDULER_NAMES = ["normal", "karras", "exponential", "sgm_uniform", "simple", "ddim_uniform"]
|
SCHEDULER_NAMES = ["normal", "karras", "exponential", "sgm_uniform", "simple", "ddim_uniform"]
|
||||||
SAMPLER_NAMES = KSAMPLER_NAMES + ["ddim", "uni_pc", "uni_pc_bh2"]
|
SAMPLER_NAMES = KSAMPLER_NAMES + ["ddim", "uni_pc", "uni_pc_bh2"]
|
||||||
|
|
||||||
def calculate_sigmas_scheduler(model, scheduler_name, steps):
|
def calculate_sigmas(model_sampling, scheduler_name, steps):
|
||||||
if scheduler_name == "karras":
|
if scheduler_name == "karras":
|
||||||
sigmas = k_diffusion_sampling.get_sigmas_karras(n=steps, sigma_min=float(model.model_sampling.sigma_min), sigma_max=float(model.model_sampling.sigma_max))
|
sigmas = k_diffusion_sampling.get_sigmas_karras(n=steps, sigma_min=float(model_sampling.sigma_min), sigma_max=float(model_sampling.sigma_max))
|
||||||
elif scheduler_name == "exponential":
|
elif scheduler_name == "exponential":
|
||||||
sigmas = k_diffusion_sampling.get_sigmas_exponential(n=steps, sigma_min=float(model.model_sampling.sigma_min), sigma_max=float(model.model_sampling.sigma_max))
|
sigmas = k_diffusion_sampling.get_sigmas_exponential(n=steps, sigma_min=float(model_sampling.sigma_min), sigma_max=float(model_sampling.sigma_max))
|
||||||
elif scheduler_name == "normal":
|
elif scheduler_name == "normal":
|
||||||
sigmas = normal_scheduler(model, steps)
|
sigmas = normal_scheduler(model_sampling, steps)
|
||||||
elif scheduler_name == "simple":
|
elif scheduler_name == "simple":
|
||||||
sigmas = simple_scheduler(model, steps)
|
sigmas = simple_scheduler(model_sampling, steps)
|
||||||
elif scheduler_name == "ddim_uniform":
|
elif scheduler_name == "ddim_uniform":
|
||||||
sigmas = ddim_scheduler(model, steps)
|
sigmas = ddim_scheduler(model_sampling, steps)
|
||||||
elif scheduler_name == "sgm_uniform":
|
elif scheduler_name == "sgm_uniform":
|
||||||
sigmas = normal_scheduler(model, steps, sgm=True)
|
sigmas = normal_scheduler(model_sampling, steps, sgm=True)
|
||||||
else:
|
else:
|
||||||
print("error invalid scheduler", scheduler_name)
|
logging.error("error invalid scheduler {}".format(scheduler_name))
|
||||||
return sigmas
|
return sigmas
|
||||||
|
|
||||||
def sampler_object(name):
|
def sampler_object(name):
|
||||||
@ -665,7 +720,7 @@ class KSampler:
|
|||||||
steps += 1
|
steps += 1
|
||||||
discard_penultimate_sigma = True
|
discard_penultimate_sigma = True
|
||||||
|
|
||||||
sigmas = calculate_sigmas_scheduler(self.model, self.scheduler, steps)
|
sigmas = calculate_sigmas(self.model.get_model_object("model_sampling"), self.scheduler, steps)
|
||||||
|
|
||||||
if discard_penultimate_sigma:
|
if discard_penultimate_sigma:
|
||||||
sigmas = torch.cat([sigmas[:-2], sigmas[-1:]])
|
sigmas = torch.cat([sigmas[:-2], sigmas[-1:]])
|
||||||
@ -676,9 +731,12 @@ class KSampler:
|
|||||||
if denoise is None or denoise > 0.9999:
|
if denoise is None or denoise > 0.9999:
|
||||||
self.sigmas = self.calculate_sigmas(steps).to(self.device)
|
self.sigmas = self.calculate_sigmas(steps).to(self.device)
|
||||||
else:
|
else:
|
||||||
new_steps = int(steps/denoise)
|
if denoise <= 0.0:
|
||||||
sigmas = self.calculate_sigmas(new_steps).to(self.device)
|
self.sigmas = torch.FloatTensor([])
|
||||||
self.sigmas = sigmas[-(steps + 1):]
|
else:
|
||||||
|
new_steps = int(steps/denoise)
|
||||||
|
sigmas = self.calculate_sigmas(new_steps).to(self.device)
|
||||||
|
self.sigmas = sigmas[-(steps + 1):]
|
||||||
|
|
||||||
def sample(self, noise, positive, negative, cfg, latent_image=None, start_step=None, last_step=None, force_full_denoise=False, denoise_mask=None, sigmas=None, callback=None, disable_pbar=False, seed=None):
|
def sample(self, noise, positive, negative, cfg, latent_image=None, start_step=None, last_step=None, force_full_denoise=False, denoise_mask=None, sigmas=None, callback=None, disable_pbar=False, seed=None):
|
||||||
if sigmas is None:
|
if sigmas is None:
|
||||||
|
|||||||
139
comfy/sd.py
139
comfy/sd.py
@ -1,5 +1,6 @@
|
|||||||
import torch
|
import torch
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
import logging
|
||||||
|
|
||||||
from comfy import model_management
|
from comfy import model_management
|
||||||
from .ldm.models.autoencoder import AutoencoderKL, AutoencodingEngine
|
from .ldm.models.autoencoder import AutoencoderKL, AutoencodingEngine
|
||||||
@ -37,7 +38,7 @@ def load_model_weights(model, sd):
|
|||||||
w = sd.pop(x)
|
w = sd.pop(x)
|
||||||
del w
|
del w
|
||||||
if len(m) > 0:
|
if len(m) > 0:
|
||||||
print("missing", m)
|
logging.warning("missing {}".format(m))
|
||||||
return model
|
return model
|
||||||
|
|
||||||
def load_clip_weights(model, sd):
|
def load_clip_weights(model, sd):
|
||||||
@ -81,7 +82,7 @@ def load_lora_for_models(model, clip, lora, strength_model, strength_clip):
|
|||||||
k1 = set(k1)
|
k1 = set(k1)
|
||||||
for x in loaded:
|
for x in loaded:
|
||||||
if (x not in k) and (x not in k1):
|
if (x not in k) and (x not in k1):
|
||||||
print("NOT LOADED", x)
|
logging.warning("NOT LOADED {}".format(x))
|
||||||
|
|
||||||
return (new_modelpatcher, new_clip)
|
return (new_modelpatcher, new_clip)
|
||||||
|
|
||||||
@ -209,26 +210,36 @@ class VAE:
|
|||||||
self.first_stage_model = StageC_coder()
|
self.first_stage_model = StageC_coder()
|
||||||
self.downscale_ratio = 32
|
self.downscale_ratio = 32
|
||||||
self.latent_channels = 16
|
self.latent_channels = 16
|
||||||
else:
|
elif "decoder.conv_in.weight" in sd:
|
||||||
#default SD1.x/SD2.x VAE parameters
|
#default SD1.x/SD2.x VAE parameters
|
||||||
ddconfig = {'double_z': True, 'z_channels': 4, 'resolution': 256, 'in_channels': 3, 'out_ch': 3, 'ch': 128, 'ch_mult': [1, 2, 4, 4], 'num_res_blocks': 2, 'attn_resolutions': [], 'dropout': 0.0}
|
ddconfig = {'double_z': True, 'z_channels': 4, 'resolution': 256, 'in_channels': 3, 'out_ch': 3, 'ch': 128, 'ch_mult': [1, 2, 4, 4], 'num_res_blocks': 2, 'attn_resolutions': [], 'dropout': 0.0}
|
||||||
|
|
||||||
if 'encoder.down.2.downsample.conv.weight' not in sd: #Stable diffusion x4 upscaler VAE
|
if 'encoder.down.2.downsample.conv.weight' not in sd and 'decoder.up.3.upsample.conv.weight' not in sd: #Stable diffusion x4 upscaler VAE
|
||||||
ddconfig['ch_mult'] = [1, 2, 4]
|
ddconfig['ch_mult'] = [1, 2, 4]
|
||||||
self.downscale_ratio = 4
|
self.downscale_ratio = 4
|
||||||
self.upscale_ratio = 4
|
self.upscale_ratio = 4
|
||||||
|
|
||||||
self.first_stage_model = AutoencoderKL(ddconfig=ddconfig, embed_dim=4)
|
self.latent_channels = ddconfig['z_channels'] = sd["decoder.conv_in.weight"].shape[1]
|
||||||
|
if 'quant_conv.weight' in sd:
|
||||||
|
self.first_stage_model = AutoencoderKL(ddconfig=ddconfig, embed_dim=4)
|
||||||
|
else:
|
||||||
|
self.first_stage_model = AutoencodingEngine(regularizer_config={'target': "comfy.ldm.models.autoencoder.DiagonalGaussianRegularizer"},
|
||||||
|
encoder_config={'target': "comfy.ldm.modules.diffusionmodules.model.Encoder", 'params': ddconfig},
|
||||||
|
decoder_config={'target': "comfy.ldm.modules.diffusionmodules.model.Decoder", 'params': ddconfig})
|
||||||
|
else:
|
||||||
|
logging.warning("WARNING: No VAE weights detected, VAE not initalized.")
|
||||||
|
self.first_stage_model = None
|
||||||
|
return
|
||||||
else:
|
else:
|
||||||
self.first_stage_model = AutoencoderKL(**(config['params']))
|
self.first_stage_model = AutoencoderKL(**(config['params']))
|
||||||
self.first_stage_model = self.first_stage_model.eval()
|
self.first_stage_model = self.first_stage_model.eval()
|
||||||
|
|
||||||
m, u = self.first_stage_model.load_state_dict(sd, strict=False)
|
m, u = self.first_stage_model.load_state_dict(sd, strict=False)
|
||||||
if len(m) > 0:
|
if len(m) > 0:
|
||||||
print("Missing VAE keys", m)
|
logging.warning("Missing VAE keys {}".format(m))
|
||||||
|
|
||||||
if len(u) > 0:
|
if len(u) > 0:
|
||||||
print("Leftover VAE keys", u)
|
logging.debug("Leftover VAE keys {}".format(u))
|
||||||
|
|
||||||
if device is None:
|
if device is None:
|
||||||
device = model_management.vae_device()
|
device = model_management.vae_device()
|
||||||
@ -291,7 +302,7 @@ class VAE:
|
|||||||
samples = samples_in[x:x+batch_number].to(self.vae_dtype).to(self.device)
|
samples = samples_in[x:x+batch_number].to(self.vae_dtype).to(self.device)
|
||||||
pixel_samples[x:x+batch_number] = self.process_output(self.first_stage_model.decode(samples).to(self.output_device).float())
|
pixel_samples[x:x+batch_number] = self.process_output(self.first_stage_model.decode(samples).to(self.output_device).float())
|
||||||
except model_management.OOM_EXCEPTION as e:
|
except model_management.OOM_EXCEPTION as e:
|
||||||
print("Warning: Ran out of memory when regular VAE decoding, retrying with tiled VAE decoding.")
|
logging.warning("Warning: Ran out of memory when regular VAE decoding, retrying with tiled VAE decoding.")
|
||||||
pixel_samples = self.decode_tiled_(samples_in)
|
pixel_samples = self.decode_tiled_(samples_in)
|
||||||
|
|
||||||
pixel_samples = pixel_samples.to(self.output_device).movedim(1,-1)
|
pixel_samples = pixel_samples.to(self.output_device).movedim(1,-1)
|
||||||
@ -317,7 +328,7 @@ class VAE:
|
|||||||
samples[x:x+batch_number] = self.first_stage_model.encode(pixels_in).to(self.output_device).float()
|
samples[x:x+batch_number] = self.first_stage_model.encode(pixels_in).to(self.output_device).float()
|
||||||
|
|
||||||
except model_management.OOM_EXCEPTION as e:
|
except model_management.OOM_EXCEPTION as e:
|
||||||
print("Warning: Ran out of memory when regular VAE encoding, retrying with tiled VAE encoding.")
|
logging.warning("Warning: Ran out of memory when regular VAE encoding, retrying with tiled VAE encoding.")
|
||||||
samples = self.encode_tiled_(pixel_samples)
|
samples = self.encode_tiled_(pixel_samples)
|
||||||
|
|
||||||
return samples
|
return samples
|
||||||
@ -393,10 +404,10 @@ def load_clip(ckpt_paths, embedding_directory=None, clip_type=CLIPType.STABLE_DI
|
|||||||
for c in clip_data:
|
for c in clip_data:
|
||||||
m, u = clip.load_sd(c)
|
m, u = clip.load_sd(c)
|
||||||
if len(m) > 0:
|
if len(m) > 0:
|
||||||
print("clip missing:", m)
|
logging.warning("clip missing: {}".format(m))
|
||||||
|
|
||||||
if len(u) > 0:
|
if len(u) > 0:
|
||||||
print("clip unexpected:", u)
|
logging.debug("clip unexpected: {}".format(u))
|
||||||
return clip
|
return clip
|
||||||
|
|
||||||
def load_gligen(ckpt_path):
|
def load_gligen(ckpt_path):
|
||||||
@ -407,6 +418,8 @@ def load_gligen(ckpt_path):
|
|||||||
return comfy.model_patcher.ModelPatcher(model, load_device=model_management.get_torch_device(), offload_device=model_management.unet_offload_device())
|
return comfy.model_patcher.ModelPatcher(model, load_device=model_management.get_torch_device(), offload_device=model_management.unet_offload_device())
|
||||||
|
|
||||||
def load_checkpoint(config_path=None, ckpt_path=None, output_vae=True, output_clip=True, embedding_directory=None, state_dict=None, config=None):
|
def load_checkpoint(config_path=None, ckpt_path=None, output_vae=True, output_clip=True, embedding_directory=None, state_dict=None, config=None):
|
||||||
|
logging.warning("Warning: The load checkpoint with config function is deprecated and will eventually be removed, please use the other one.")
|
||||||
|
model, clip, vae, _ = load_checkpoint_guess_config(ckpt_path, output_vae=output_vae, output_clip=output_clip, output_clipvision=False, embedding_directory=embedding_directory, output_model=True)
|
||||||
#TODO: this function is a mess and should be removed eventually
|
#TODO: this function is a mess and should be removed eventually
|
||||||
if config is None:
|
if config is None:
|
||||||
with open(config_path, 'r') as stream:
|
with open(config_path, 'r') as stream:
|
||||||
@ -414,81 +427,20 @@ def load_checkpoint(config_path=None, ckpt_path=None, output_vae=True, output_cl
|
|||||||
model_config_params = config['model']['params']
|
model_config_params = config['model']['params']
|
||||||
clip_config = model_config_params['cond_stage_config']
|
clip_config = model_config_params['cond_stage_config']
|
||||||
scale_factor = model_config_params['scale_factor']
|
scale_factor = model_config_params['scale_factor']
|
||||||
vae_config = model_config_params['first_stage_config']
|
|
||||||
|
|
||||||
fp16 = False
|
|
||||||
if "unet_config" in model_config_params:
|
|
||||||
if "params" in model_config_params["unet_config"]:
|
|
||||||
unet_config = model_config_params["unet_config"]["params"]
|
|
||||||
if "use_fp16" in unet_config:
|
|
||||||
fp16 = unet_config.pop("use_fp16")
|
|
||||||
if fp16:
|
|
||||||
unet_config["dtype"] = torch.float16
|
|
||||||
|
|
||||||
noise_aug_config = None
|
|
||||||
if "noise_aug_config" in model_config_params:
|
|
||||||
noise_aug_config = model_config_params["noise_aug_config"]
|
|
||||||
|
|
||||||
model_type = model_base.ModelType.EPS
|
|
||||||
|
|
||||||
if "parameterization" in model_config_params:
|
if "parameterization" in model_config_params:
|
||||||
if model_config_params["parameterization"] == "v":
|
if model_config_params["parameterization"] == "v":
|
||||||
model_type = model_base.ModelType.V_PREDICTION
|
m = model.clone()
|
||||||
|
class ModelSamplingAdvanced(comfy.model_sampling.ModelSamplingDiscrete, comfy.model_sampling.V_PREDICTION):
|
||||||
|
pass
|
||||||
|
m.add_object_patch("model_sampling", ModelSamplingAdvanced(model.model.model_config))
|
||||||
|
model = m
|
||||||
|
|
||||||
clip = None
|
layer_idx = clip_config.get("params", {}).get("layer_idx", None)
|
||||||
vae = None
|
if layer_idx is not None:
|
||||||
|
clip.clip_layer(layer_idx)
|
||||||
|
|
||||||
class WeightsLoader(torch.nn.Module):
|
return (model, clip, vae)
|
||||||
pass
|
|
||||||
|
|
||||||
if state_dict is None:
|
|
||||||
state_dict = comfy.utils.load_torch_file(ckpt_path)
|
|
||||||
|
|
||||||
class EmptyClass:
|
|
||||||
pass
|
|
||||||
|
|
||||||
model_config = comfy.supported_models_base.BASE({})
|
|
||||||
|
|
||||||
from . import latent_formats
|
|
||||||
model_config.latent_format = latent_formats.SD15(scale_factor=scale_factor)
|
|
||||||
model_config.unet_config = model_detection.convert_config(unet_config)
|
|
||||||
|
|
||||||
if config['model']["target"].endswith("ImageEmbeddingConditionedLatentDiffusion"):
|
|
||||||
model = model_base.SD21UNCLIP(model_config, noise_aug_config["params"], model_type=model_type)
|
|
||||||
else:
|
|
||||||
model = model_base.BaseModel(model_config, model_type=model_type)
|
|
||||||
|
|
||||||
if config['model']["target"].endswith("LatentInpaintDiffusion"):
|
|
||||||
model.set_inpaint()
|
|
||||||
|
|
||||||
if fp16:
|
|
||||||
model = model.half()
|
|
||||||
|
|
||||||
offload_device = model_management.unet_offload_device()
|
|
||||||
model = model.to(offload_device)
|
|
||||||
model.load_model_weights(state_dict, "model.diffusion_model.")
|
|
||||||
|
|
||||||
if output_vae:
|
|
||||||
vae_sd = comfy.utils.state_dict_prefix_replace(state_dict, {"first_stage_model.": ""}, filter_keys=True)
|
|
||||||
vae = VAE(sd=vae_sd, config=vae_config)
|
|
||||||
|
|
||||||
if output_clip:
|
|
||||||
w = WeightsLoader()
|
|
||||||
clip_target = EmptyClass()
|
|
||||||
clip_target.params = clip_config.get("params", {})
|
|
||||||
if clip_config["target"].endswith("FrozenOpenCLIPEmbedder"):
|
|
||||||
clip_target.clip = sd2_clip.SD2ClipModel
|
|
||||||
clip_target.tokenizer = sd2_clip.SD2Tokenizer
|
|
||||||
clip = CLIP(clip_target, embedding_directory=embedding_directory)
|
|
||||||
w.cond_stage_model = clip.cond_stage_model.clip_h
|
|
||||||
elif clip_config["target"].endswith("FrozenCLIPEmbedder"):
|
|
||||||
clip_target.clip = sd1_clip.SD1ClipModel
|
|
||||||
clip_target.tokenizer = sd1_clip.SD1Tokenizer
|
|
||||||
clip = CLIP(clip_target, embedding_directory=embedding_directory)
|
|
||||||
w.cond_stage_model = clip.cond_stage_model.clip_l
|
|
||||||
load_clip_weights(w, state_dict)
|
|
||||||
|
|
||||||
return (comfy.model_patcher.ModelPatcher(model, load_device=model_management.get_torch_device(), offload_device=offload_device), clip, vae)
|
|
||||||
|
|
||||||
def load_checkpoint_guess_config(ckpt_path, output_vae=True, output_clip=True, output_clipvision=False, embedding_directory=None, output_model=True):
|
def load_checkpoint_guess_config(ckpt_path, output_vae=True, output_clip=True, output_clipvision=False, embedding_directory=None, output_model=True):
|
||||||
sd = comfy.utils.load_torch_file(ckpt_path)
|
sd = comfy.utils.load_torch_file(ckpt_path)
|
||||||
@ -534,21 +486,25 @@ def load_checkpoint_guess_config(ckpt_path, output_vae=True, output_clip=True, o
|
|||||||
clip = CLIP(clip_target, embedding_directory=embedding_directory)
|
clip = CLIP(clip_target, embedding_directory=embedding_directory)
|
||||||
m, u = clip.load_sd(clip_sd, full_model=True)
|
m, u = clip.load_sd(clip_sd, full_model=True)
|
||||||
if len(m) > 0:
|
if len(m) > 0:
|
||||||
print("clip missing:", m)
|
m_filter = list(filter(lambda a: ".logit_scale" not in a and ".transformer.text_projection.weight" not in a, m))
|
||||||
|
if len(m_filter) > 0:
|
||||||
|
logging.warning("clip missing: {}".format(m))
|
||||||
|
else:
|
||||||
|
logging.debug("clip missing: {}".format(m))
|
||||||
|
|
||||||
if len(u) > 0:
|
if len(u) > 0:
|
||||||
print("clip unexpected:", u)
|
logging.debug("clip unexpected {}:".format(u))
|
||||||
else:
|
else:
|
||||||
print("no CLIP/text encoder weights in checkpoint, the text encoder model will not be loaded.")
|
logging.warning("no CLIP/text encoder weights in checkpoint, the text encoder model will not be loaded.")
|
||||||
|
|
||||||
left_over = sd.keys()
|
left_over = sd.keys()
|
||||||
if len(left_over) > 0:
|
if len(left_over) > 0:
|
||||||
print("left over keys:", left_over)
|
logging.debug("left over keys: {}".format(left_over))
|
||||||
|
|
||||||
if output_model:
|
if output_model:
|
||||||
model_patcher = comfy.model_patcher.ModelPatcher(model, load_device=load_device, offload_device=model_management.unet_offload_device(), current_device=inital_load_device)
|
model_patcher = comfy.model_patcher.ModelPatcher(model, load_device=load_device, offload_device=model_management.unet_offload_device(), current_device=inital_load_device)
|
||||||
if inital_load_device != torch.device("cpu"):
|
if inital_load_device != torch.device("cpu"):
|
||||||
print("loaded straight to GPU")
|
logging.info("loaded straight to GPU")
|
||||||
model_management.load_model_gpu(model_patcher)
|
model_management.load_model_gpu(model_patcher)
|
||||||
|
|
||||||
return (model_patcher, clip, vae, clipvision)
|
return (model_patcher, clip, vae, clipvision)
|
||||||
@ -577,7 +533,7 @@ def load_unet_state_dict(sd): #load unet in diffusers format
|
|||||||
if k in sd:
|
if k in sd:
|
||||||
new_sd[diffusers_keys[k]] = sd.pop(k)
|
new_sd[diffusers_keys[k]] = sd.pop(k)
|
||||||
else:
|
else:
|
||||||
print(diffusers_keys[k], k)
|
logging.warning("{} {}".format(diffusers_keys[k], k))
|
||||||
|
|
||||||
offload_device = model_management.unet_offload_device()
|
offload_device = model_management.unet_offload_device()
|
||||||
unet_dtype = model_management.unet_dtype(model_params=parameters, supported_dtypes=model_config.supported_inference_dtypes)
|
unet_dtype = model_management.unet_dtype(model_params=parameters, supported_dtypes=model_config.supported_inference_dtypes)
|
||||||
@ -588,18 +544,18 @@ def load_unet_state_dict(sd): #load unet in diffusers format
|
|||||||
model.load_model_weights(new_sd, "")
|
model.load_model_weights(new_sd, "")
|
||||||
left_over = sd.keys()
|
left_over = sd.keys()
|
||||||
if len(left_over) > 0:
|
if len(left_over) > 0:
|
||||||
print("left over keys in unet:", left_over)
|
logging.info("left over keys in unet: {}".format(left_over))
|
||||||
return comfy.model_patcher.ModelPatcher(model, load_device=load_device, offload_device=offload_device)
|
return comfy.model_patcher.ModelPatcher(model, load_device=load_device, offload_device=offload_device)
|
||||||
|
|
||||||
def load_unet(unet_path):
|
def load_unet(unet_path):
|
||||||
sd = comfy.utils.load_torch_file(unet_path)
|
sd = comfy.utils.load_torch_file(unet_path)
|
||||||
model = load_unet_state_dict(sd)
|
model = load_unet_state_dict(sd)
|
||||||
if model is None:
|
if model is None:
|
||||||
print("ERROR UNSUPPORTED UNET", unet_path)
|
logging.error("ERROR UNSUPPORTED UNET {}".format(unet_path))
|
||||||
raise RuntimeError("ERROR: Could not detect model type of: {}".format(unet_path))
|
raise RuntimeError("ERROR: Could not detect model type of: {}".format(unet_path))
|
||||||
return model
|
return model
|
||||||
|
|
||||||
def save_checkpoint(output_path, model, clip=None, vae=None, clip_vision=None, metadata=None):
|
def save_checkpoint(output_path, model, clip=None, vae=None, clip_vision=None, metadata=None, extra_keys={}):
|
||||||
clip_sd = None
|
clip_sd = None
|
||||||
load_models = [model]
|
load_models = [model]
|
||||||
if clip is not None:
|
if clip is not None:
|
||||||
@ -609,4 +565,7 @@ def save_checkpoint(output_path, model, clip=None, vae=None, clip_vision=None, m
|
|||||||
model_management.load_models_gpu(load_models)
|
model_management.load_models_gpu(load_models)
|
||||||
clip_vision_sd = clip_vision.get_sd() if clip_vision is not None else None
|
clip_vision_sd = clip_vision.get_sd() if clip_vision is not None else None
|
||||||
sd = model.model.state_dict_for_saving(clip_sd, vae.get_sd(), clip_vision_sd)
|
sd = model.model.state_dict_for_saving(clip_sd, vae.get_sd(), clip_vision_sd)
|
||||||
|
for k in extra_keys:
|
||||||
|
sd[k] = extra_keys[k]
|
||||||
|
|
||||||
comfy.utils.save_torch_file(sd, output_path, metadata=metadata)
|
comfy.utils.save_torch_file(sd, output_path, metadata=metadata)
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import zipfile
|
|||||||
from . import model_management
|
from . import model_management
|
||||||
import comfy.clip_model
|
import comfy.clip_model
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
|
|
||||||
def gen_empty_tokens(special_tokens, length):
|
def gen_empty_tokens(special_tokens, length):
|
||||||
start_token = special_tokens.get("start", None)
|
start_token = special_tokens.get("start", None)
|
||||||
@ -137,7 +138,7 @@ class SDClipModel(torch.nn.Module, ClipTokenWeightEncoder):
|
|||||||
tokens_temp += [next_new_token]
|
tokens_temp += [next_new_token]
|
||||||
next_new_token += 1
|
next_new_token += 1
|
||||||
else:
|
else:
|
||||||
print("WARNING: shape mismatch when trying to apply embedding, embedding will be ignored", y.shape[0], current_embeds.weight.shape[1])
|
logging.warning("WARNING: shape mismatch when trying to apply embedding, embedding will be ignored {} != {}".format(y.shape[0], current_embeds.weight.shape[1]))
|
||||||
while len(tokens_temp) < len(x):
|
while len(tokens_temp) < len(x):
|
||||||
tokens_temp += [self.special_tokens["pad"]]
|
tokens_temp += [self.special_tokens["pad"]]
|
||||||
out_tokens += [tokens_temp]
|
out_tokens += [tokens_temp]
|
||||||
@ -329,9 +330,7 @@ def load_embed(embedding_name, embedding_directory, embedding_size, embed_key=No
|
|||||||
else:
|
else:
|
||||||
embed = torch.load(embed_path, map_location="cpu")
|
embed = torch.load(embed_path, map_location="cpu")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(traceback.format_exc())
|
logging.warning("{}\n\nerror loading embedding, skipping loading: {}".format(traceback.format_exc(), embedding_name))
|
||||||
print()
|
|
||||||
print("error loading embedding, skipping loading:", embedding_name)
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if embed_out is None:
|
if embed_out is None:
|
||||||
@ -422,7 +421,7 @@ class SDTokenizer:
|
|||||||
embedding_name = word[len(self.embedding_identifier):].strip('\n')
|
embedding_name = word[len(self.embedding_identifier):].strip('\n')
|
||||||
embed, leftover = self._try_get_embedding(embedding_name)
|
embed, leftover = self._try_get_embedding(embedding_name)
|
||||||
if embed is None:
|
if embed is None:
|
||||||
print(f"warning, embedding:{embedding_name} does not exist, ignoring")
|
logging.warning(f"warning, embedding:{embedding_name} does not exist, ignoring")
|
||||||
else:
|
else:
|
||||||
if len(embed.shape) == 1:
|
if len(embed.shape) == 1:
|
||||||
tokens.append([(embed, weight)])
|
tokens.append([(embed, weight)])
|
||||||
|
|||||||
@ -45,6 +45,11 @@ class SD15(supported_models_base.BASE):
|
|||||||
return state_dict
|
return state_dict
|
||||||
|
|
||||||
def process_clip_state_dict_for_saving(self, state_dict):
|
def process_clip_state_dict_for_saving(self, state_dict):
|
||||||
|
pop_keys = ["clip_l.transformer.text_projection.weight", "clip_l.logit_scale"]
|
||||||
|
for p in pop_keys:
|
||||||
|
if p in state_dict:
|
||||||
|
state_dict.pop(p)
|
||||||
|
|
||||||
replace_prefix = {"clip_l.": "cond_stage_model."}
|
replace_prefix = {"clip_l.": "cond_stage_model."}
|
||||||
return utils.state_dict_prefix_replace(state_dict, replace_prefix)
|
return utils.state_dict_prefix_replace(state_dict, replace_prefix)
|
||||||
|
|
||||||
@ -65,8 +70,8 @@ class SD20(supported_models_base.BASE):
|
|||||||
def model_type(self, state_dict, prefix=""):
|
def model_type(self, state_dict, prefix=""):
|
||||||
if self.unet_config["in_channels"] == 4: #SD2.0 inpainting models are not v prediction
|
if self.unet_config["in_channels"] == 4: #SD2.0 inpainting models are not v prediction
|
||||||
k = "{}output_blocks.11.1.transformer_blocks.0.norm1.bias".format(prefix)
|
k = "{}output_blocks.11.1.transformer_blocks.0.norm1.bias".format(prefix)
|
||||||
out = state_dict[k]
|
out = state_dict.get(k, None)
|
||||||
if torch.std(out, unbiased=False) > 0.09: # not sure how well this will actually work. I guess we will find out.
|
if out is not None and torch.std(out, unbiased=False) > 0.09: # not sure how well this will actually work. I guess we will find out.
|
||||||
return model_base.ModelType.V_PREDICTION
|
return model_base.ModelType.V_PREDICTION
|
||||||
return model_base.ModelType.EPS
|
return model_base.ModelType.EPS
|
||||||
|
|
||||||
@ -169,6 +174,11 @@ class SDXL(supported_models_base.BASE):
|
|||||||
self.sampling_settings["sigma_max"] = 80.0
|
self.sampling_settings["sigma_max"] = 80.0
|
||||||
self.sampling_settings["sigma_min"] = 0.002
|
self.sampling_settings["sigma_min"] = 0.002
|
||||||
return model_base.ModelType.EDM
|
return model_base.ModelType.EDM
|
||||||
|
elif "edm_vpred.sigma_max" in state_dict:
|
||||||
|
self.sampling_settings["sigma_max"] = float(state_dict["edm_vpred.sigma_max"].item())
|
||||||
|
if "edm_vpred.sigma_min" in state_dict:
|
||||||
|
self.sampling_settings["sigma_min"] = float(state_dict["edm_vpred.sigma_min"].item())
|
||||||
|
return model_base.ModelType.V_PREDICTION_EDM
|
||||||
elif "v_pred" in state_dict:
|
elif "v_pred" in state_dict:
|
||||||
return model_base.ModelType.V_PREDICTION
|
return model_base.ModelType.V_PREDICTION
|
||||||
else:
|
else:
|
||||||
@ -279,6 +289,41 @@ class SVD_img2vid(supported_models_base.BASE):
|
|||||||
def clip_target(self):
|
def clip_target(self):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
class SV3D_u(SVD_img2vid):
|
||||||
|
unet_config = {
|
||||||
|
"model_channels": 320,
|
||||||
|
"in_channels": 8,
|
||||||
|
"use_linear_in_transformer": True,
|
||||||
|
"transformer_depth": [1, 1, 1, 1, 1, 1, 0, 0],
|
||||||
|
"context_dim": 1024,
|
||||||
|
"adm_in_channels": 256,
|
||||||
|
"use_temporal_attention": True,
|
||||||
|
"use_temporal_resblock": True
|
||||||
|
}
|
||||||
|
|
||||||
|
vae_key_prefix = ["conditioner.embedders.1.encoder."]
|
||||||
|
|
||||||
|
def get_model(self, state_dict, prefix="", device=None):
|
||||||
|
out = model_base.SV3D_u(self, device=device)
|
||||||
|
return out
|
||||||
|
|
||||||
|
class SV3D_p(SV3D_u):
|
||||||
|
unet_config = {
|
||||||
|
"model_channels": 320,
|
||||||
|
"in_channels": 8,
|
||||||
|
"use_linear_in_transformer": True,
|
||||||
|
"transformer_depth": [1, 1, 1, 1, 1, 1, 0, 0],
|
||||||
|
"context_dim": 1024,
|
||||||
|
"adm_in_channels": 1280,
|
||||||
|
"use_temporal_attention": True,
|
||||||
|
"use_temporal_resblock": True
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_model(self, state_dict, prefix="", device=None):
|
||||||
|
out = model_base.SV3D_p(self, device=device)
|
||||||
|
return out
|
||||||
|
|
||||||
class Stable_Zero123(supported_models_base.BASE):
|
class Stable_Zero123(supported_models_base.BASE):
|
||||||
unet_config = {
|
unet_config = {
|
||||||
"context_dim": 768,
|
"context_dim": 768,
|
||||||
@ -294,6 +339,11 @@ class Stable_Zero123(supported_models_base.BASE):
|
|||||||
"num_head_channels": -1,
|
"num_head_channels": -1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
required_keys = {
|
||||||
|
"cc_projection.weight": None,
|
||||||
|
"cc_projection.bias": None,
|
||||||
|
}
|
||||||
|
|
||||||
clip_vision_prefix = "cond_stage_model.model.visual."
|
clip_vision_prefix = "cond_stage_model.model.visual."
|
||||||
|
|
||||||
latent_format = latent_formats.SD15
|
latent_format = latent_formats.SD15
|
||||||
@ -399,6 +449,33 @@ class Stable_Cascade_B(Stable_Cascade_C):
|
|||||||
out = model_base.StableCascade_B(self, device=device)
|
out = model_base.StableCascade_B(self, device=device)
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
class SD15_instructpix2pix(SD15):
|
||||||
|
unet_config = {
|
||||||
|
"context_dim": 768,
|
||||||
|
"model_channels": 320,
|
||||||
|
"use_linear_in_transformer": False,
|
||||||
|
"adm_in_channels": None,
|
||||||
|
"use_temporal_attention": False,
|
||||||
|
"in_channels": 8,
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_model(self, state_dict, prefix="", device=None):
|
||||||
|
return model_base.SD15_instructpix2pix(self, device=device)
|
||||||
|
|
||||||
|
class SDXL_instructpix2pix(SDXL):
|
||||||
|
unet_config = {
|
||||||
|
"model_channels": 320,
|
||||||
|
"use_linear_in_transformer": True,
|
||||||
|
"transformer_depth": [0, 0, 2, 2, 10, 10],
|
||||||
|
"context_dim": 2048,
|
||||||
|
"adm_in_channels": 2816,
|
||||||
|
"use_temporal_attention": False,
|
||||||
|
"in_channels": 8,
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_model(self, state_dict, prefix="", device=None):
|
||||||
|
return model_base.SDXL_instructpix2pix(self, model_type=self.model_type(state_dict, prefix), device=device)
|
||||||
|
|
||||||
|
models = [Stable_Zero123, SD15_instructpix2pix, SD15, SD20, SD21UnclipL, SD21UnclipH, SDXL_instructpix2pix, SDXLRefiner, SDXL, SSD1B, KOALA_700M, KOALA_1B, Segmind_Vega, SD_X4Upscaler, Stable_Cascade_C, Stable_Cascade_B, SV3D_u, SV3D_p]
|
||||||
|
|
||||||
models = [Stable_Zero123, SD15, SD20, SD21UnclipL, SD21UnclipH, SDXLRefiner, SDXL, SSD1B, KOALA_700M, KOALA_1B, Segmind_Vega, SD_X4Upscaler, Stable_Cascade_C, Stable_Cascade_B]
|
|
||||||
models += [SVD_img2vid]
|
models += [SVD_img2vid]
|
||||||
|
|||||||
@ -16,6 +16,8 @@ class BASE:
|
|||||||
"num_head_channels": 64,
|
"num_head_channels": 64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
required_keys = {}
|
||||||
|
|
||||||
clip_prefix = []
|
clip_prefix = []
|
||||||
clip_vision_prefix = None
|
clip_vision_prefix = None
|
||||||
noise_aug_config = None
|
noise_aug_config = None
|
||||||
@ -28,10 +30,14 @@ class BASE:
|
|||||||
manual_cast_dtype = None
|
manual_cast_dtype = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def matches(s, unet_config):
|
def matches(s, unet_config, state_dict=None):
|
||||||
for k in s.unet_config:
|
for k in s.unet_config:
|
||||||
if k not in unet_config or s.unet_config[k] != unet_config[k]:
|
if k not in unet_config or s.unet_config[k] != unet_config[k]:
|
||||||
return False
|
return False
|
||||||
|
if state_dict is not None:
|
||||||
|
for k in s.required_keys:
|
||||||
|
if k not in state_dict:
|
||||||
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def model_type(self, state_dict, prefix=""):
|
def model_type(self, state_dict, prefix=""):
|
||||||
@ -41,7 +47,8 @@ class BASE:
|
|||||||
return self.unet_config["in_channels"] > 4
|
return self.unet_config["in_channels"] > 4
|
||||||
|
|
||||||
def __init__(self, unet_config):
|
def __init__(self, unet_config):
|
||||||
self.unet_config = unet_config
|
self.unet_config = unet_config.copy()
|
||||||
|
self.sampling_settings = self.sampling_settings.copy()
|
||||||
self.latent_format = self.latent_format()
|
self.latent_format = self.latent_format()
|
||||||
for x in self.unet_extra_config:
|
for x in self.unet_extra_config:
|
||||||
self.unet_config[x] = self.unet_extra_config[x]
|
self.unet_config[x] = self.unet_extra_config[x]
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import comfy.checkpoint_pickle
|
|||||||
import safetensors.torch
|
import safetensors.torch
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
import logging
|
||||||
|
|
||||||
def load_torch_file(ckpt, safe_load=False, device=None):
|
def load_torch_file(ckpt, safe_load=False, device=None):
|
||||||
if device is None:
|
if device is None:
|
||||||
@ -14,14 +15,14 @@ def load_torch_file(ckpt, safe_load=False, device=None):
|
|||||||
else:
|
else:
|
||||||
if safe_load:
|
if safe_load:
|
||||||
if not 'weights_only' in torch.load.__code__.co_varnames:
|
if not 'weights_only' in torch.load.__code__.co_varnames:
|
||||||
print("Warning torch.load doesn't support weights_only on this pytorch version, loading unsafely.")
|
logging.warning("Warning torch.load doesn't support weights_only on this pytorch version, loading unsafely.")
|
||||||
safe_load = False
|
safe_load = False
|
||||||
if safe_load:
|
if safe_load:
|
||||||
pl_sd = torch.load(ckpt, map_location=device, weights_only=True)
|
pl_sd = torch.load(ckpt, map_location=device, weights_only=True)
|
||||||
else:
|
else:
|
||||||
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 "global_step" in pl_sd:
|
if "global_step" in pl_sd:
|
||||||
print(f"Global Step: {pl_sd['global_step']}")
|
logging.debug(f"Global Step: {pl_sd['global_step']}")
|
||||||
if "state_dict" in pl_sd:
|
if "state_dict" in pl_sd:
|
||||||
sd = pl_sd["state_dict"]
|
sd = pl_sd["state_dict"]
|
||||||
else:
|
else:
|
||||||
|
|||||||
61
comfy_extras/nodes_advanced_samplers.py
Normal file
61
comfy_extras/nodes_advanced_samplers.py
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import comfy.samplers
|
||||||
|
import comfy.utils
|
||||||
|
import torch
|
||||||
|
import numpy as np
|
||||||
|
from tqdm.auto import trange, tqdm
|
||||||
|
import math
|
||||||
|
|
||||||
|
|
||||||
|
@torch.no_grad()
|
||||||
|
def sample_lcm_upscale(model, x, sigmas, extra_args=None, callback=None, disable=None, total_upscale=2.0, upscale_method="bislerp", upscale_steps=None):
|
||||||
|
extra_args = {} if extra_args is None else extra_args
|
||||||
|
|
||||||
|
if upscale_steps is None:
|
||||||
|
upscale_steps = max(len(sigmas) // 2 + 1, 2)
|
||||||
|
else:
|
||||||
|
upscale_steps += 1
|
||||||
|
upscale_steps = min(upscale_steps, len(sigmas) + 1)
|
||||||
|
|
||||||
|
upscales = np.linspace(1.0, total_upscale, upscale_steps)[1:]
|
||||||
|
|
||||||
|
orig_shape = x.size()
|
||||||
|
s_in = x.new_ones([x.shape[0]])
|
||||||
|
for i in trange(len(sigmas) - 1, disable=disable):
|
||||||
|
denoised = 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})
|
||||||
|
|
||||||
|
x = denoised
|
||||||
|
if i < len(upscales):
|
||||||
|
x = comfy.utils.common_upscale(x, round(orig_shape[-1] * upscales[i]), round(orig_shape[-2] * upscales[i]), upscale_method, "disabled")
|
||||||
|
|
||||||
|
if sigmas[i + 1] > 0:
|
||||||
|
x += sigmas[i + 1] * torch.randn_like(x)
|
||||||
|
return x
|
||||||
|
|
||||||
|
|
||||||
|
class SamplerLCMUpscale:
|
||||||
|
upscale_methods = ["bislerp", "nearest-exact", "bilinear", "area", "bicubic"]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(s):
|
||||||
|
return {"required":
|
||||||
|
{"scale_ratio": ("FLOAT", {"default": 1.0, "min": 0.1, "max": 20.0, "step": 0.01}),
|
||||||
|
"scale_steps": ("INT", {"default": -1, "min": -1, "max": 1000, "step": 1}),
|
||||||
|
"upscale_method": (s.upscale_methods,),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RETURN_TYPES = ("SAMPLER",)
|
||||||
|
CATEGORY = "sampling/custom_sampling/samplers"
|
||||||
|
|
||||||
|
FUNCTION = "get_sampler"
|
||||||
|
|
||||||
|
def get_sampler(self, scale_ratio, scale_steps, upscale_method):
|
||||||
|
if scale_steps < 0:
|
||||||
|
scale_steps = None
|
||||||
|
sampler = comfy.samplers.KSAMPLER(sample_lcm_upscale, extra_options={"total_upscale": scale_ratio, "upscale_steps": scale_steps, "upscale_method": upscale_method})
|
||||||
|
return (sampler, )
|
||||||
|
|
||||||
|
NODE_CLASS_MAPPINGS = {
|
||||||
|
"SamplerLCMUpscale": SamplerLCMUpscale,
|
||||||
|
}
|
||||||
53
comfy_extras/nodes_align_your_steps.py
Normal file
53
comfy_extras/nodes_align_your_steps.py
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
#from: https://research.nvidia.com/labs/toronto-ai/AlignYourSteps/howto.html
|
||||||
|
import numpy as np
|
||||||
|
import torch
|
||||||
|
|
||||||
|
def loglinear_interp(t_steps, num_steps):
|
||||||
|
"""
|
||||||
|
Performs log-linear interpolation of a given array of decreasing numbers.
|
||||||
|
"""
|
||||||
|
xs = np.linspace(0, 1, len(t_steps))
|
||||||
|
ys = np.log(t_steps[::-1])
|
||||||
|
|
||||||
|
new_xs = np.linspace(0, 1, num_steps)
|
||||||
|
new_ys = np.interp(new_xs, xs, ys)
|
||||||
|
|
||||||
|
interped_ys = np.exp(new_ys)[::-1].copy()
|
||||||
|
return interped_ys
|
||||||
|
|
||||||
|
NOISE_LEVELS = {"SD1": [14.6146412293, 6.4745760956, 3.8636745985, 2.6946151520, 1.8841921177, 1.3943805092, 0.9642583904, 0.6523686016, 0.3977456272, 0.1515232662, 0.0291671582],
|
||||||
|
"SDXL":[14.6146412293, 6.3184485287, 3.7681790315, 2.1811480769, 1.3405244945, 0.8620721141, 0.5550693289, 0.3798540708, 0.2332364134, 0.1114188177, 0.0291671582],
|
||||||
|
"SVD": [700.00, 54.5, 15.886, 7.977, 4.248, 1.789, 0.981, 0.403, 0.173, 0.034, 0.002]}
|
||||||
|
|
||||||
|
class AlignYourStepsScheduler:
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(s):
|
||||||
|
return {"required":
|
||||||
|
{"model_type": (["SD1", "SDXL", "SVD"], ),
|
||||||
|
"steps": ("INT", {"default": 10, "min": 10, "max": 10000}),
|
||||||
|
"denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RETURN_TYPES = ("SIGMAS",)
|
||||||
|
CATEGORY = "sampling/custom_sampling/schedulers"
|
||||||
|
|
||||||
|
FUNCTION = "get_sigmas"
|
||||||
|
|
||||||
|
def get_sigmas(self, model_type, steps, denoise):
|
||||||
|
total_steps = steps
|
||||||
|
if denoise < 1.0:
|
||||||
|
if denoise <= 0.0:
|
||||||
|
return (torch.FloatTensor([]),)
|
||||||
|
total_steps = round(steps * denoise)
|
||||||
|
|
||||||
|
sigmas = NOISE_LEVELS[model_type][:]
|
||||||
|
if (steps + 1) != len(sigmas):
|
||||||
|
sigmas = loglinear_interp(sigmas, steps + 1)
|
||||||
|
|
||||||
|
sigmas = sigmas[-(total_steps + 1):]
|
||||||
|
sigmas[-1] = 0
|
||||||
|
return (torch.FloatTensor(sigmas), )
|
||||||
|
|
||||||
|
NODE_CLASS_MAPPINGS = {
|
||||||
|
"AlignYourStepsScheduler": AlignYourStepsScheduler,
|
||||||
|
}
|
||||||
120
comfy_extras/nodes_attention_multiply.py
Normal file
120
comfy_extras/nodes_attention_multiply.py
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
|
||||||
|
def attention_multiply(attn, model, q, k, v, out):
|
||||||
|
m = model.clone()
|
||||||
|
sd = model.model_state_dict()
|
||||||
|
|
||||||
|
for key in sd:
|
||||||
|
if key.endswith("{}.to_q.bias".format(attn)) or key.endswith("{}.to_q.weight".format(attn)):
|
||||||
|
m.add_patches({key: (None,)}, 0.0, q)
|
||||||
|
if key.endswith("{}.to_k.bias".format(attn)) or key.endswith("{}.to_k.weight".format(attn)):
|
||||||
|
m.add_patches({key: (None,)}, 0.0, k)
|
||||||
|
if key.endswith("{}.to_v.bias".format(attn)) or key.endswith("{}.to_v.weight".format(attn)):
|
||||||
|
m.add_patches({key: (None,)}, 0.0, v)
|
||||||
|
if key.endswith("{}.to_out.0.bias".format(attn)) or key.endswith("{}.to_out.0.weight".format(attn)):
|
||||||
|
m.add_patches({key: (None,)}, 0.0, out)
|
||||||
|
|
||||||
|
return m
|
||||||
|
|
||||||
|
|
||||||
|
class UNetSelfAttentionMultiply:
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(s):
|
||||||
|
return {"required": { "model": ("MODEL",),
|
||||||
|
"q": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
|
||||||
|
"k": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
|
||||||
|
"v": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
|
||||||
|
"out": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
|
||||||
|
}}
|
||||||
|
RETURN_TYPES = ("MODEL",)
|
||||||
|
FUNCTION = "patch"
|
||||||
|
|
||||||
|
CATEGORY = "_for_testing/attention_experiments"
|
||||||
|
|
||||||
|
def patch(self, model, q, k, v, out):
|
||||||
|
m = attention_multiply("attn1", model, q, k, v, out)
|
||||||
|
return (m, )
|
||||||
|
|
||||||
|
class UNetCrossAttentionMultiply:
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(s):
|
||||||
|
return {"required": { "model": ("MODEL",),
|
||||||
|
"q": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
|
||||||
|
"k": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
|
||||||
|
"v": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
|
||||||
|
"out": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
|
||||||
|
}}
|
||||||
|
RETURN_TYPES = ("MODEL",)
|
||||||
|
FUNCTION = "patch"
|
||||||
|
|
||||||
|
CATEGORY = "_for_testing/attention_experiments"
|
||||||
|
|
||||||
|
def patch(self, model, q, k, v, out):
|
||||||
|
m = attention_multiply("attn2", model, q, k, v, out)
|
||||||
|
return (m, )
|
||||||
|
|
||||||
|
class CLIPAttentionMultiply:
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(s):
|
||||||
|
return {"required": { "clip": ("CLIP",),
|
||||||
|
"q": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
|
||||||
|
"k": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
|
||||||
|
"v": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
|
||||||
|
"out": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
|
||||||
|
}}
|
||||||
|
RETURN_TYPES = ("CLIP",)
|
||||||
|
FUNCTION = "patch"
|
||||||
|
|
||||||
|
CATEGORY = "_for_testing/attention_experiments"
|
||||||
|
|
||||||
|
def patch(self, clip, q, k, v, out):
|
||||||
|
m = clip.clone()
|
||||||
|
sd = m.patcher.model_state_dict()
|
||||||
|
|
||||||
|
for key in sd:
|
||||||
|
if key.endswith("self_attn.q_proj.weight") or key.endswith("self_attn.q_proj.bias"):
|
||||||
|
m.add_patches({key: (None,)}, 0.0, q)
|
||||||
|
if key.endswith("self_attn.k_proj.weight") or key.endswith("self_attn.k_proj.bias"):
|
||||||
|
m.add_patches({key: (None,)}, 0.0, k)
|
||||||
|
if key.endswith("self_attn.v_proj.weight") or key.endswith("self_attn.v_proj.bias"):
|
||||||
|
m.add_patches({key: (None,)}, 0.0, v)
|
||||||
|
if key.endswith("self_attn.out_proj.weight") or key.endswith("self_attn.out_proj.bias"):
|
||||||
|
m.add_patches({key: (None,)}, 0.0, out)
|
||||||
|
return (m, )
|
||||||
|
|
||||||
|
class UNetTemporalAttentionMultiply:
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(s):
|
||||||
|
return {"required": { "model": ("MODEL",),
|
||||||
|
"self_structural": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
|
||||||
|
"self_temporal": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
|
||||||
|
"cross_structural": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
|
||||||
|
"cross_temporal": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
|
||||||
|
}}
|
||||||
|
RETURN_TYPES = ("MODEL",)
|
||||||
|
FUNCTION = "patch"
|
||||||
|
|
||||||
|
CATEGORY = "_for_testing/attention_experiments"
|
||||||
|
|
||||||
|
def patch(self, model, self_structural, self_temporal, cross_structural, cross_temporal):
|
||||||
|
m = model.clone()
|
||||||
|
sd = model.model_state_dict()
|
||||||
|
|
||||||
|
for k in sd:
|
||||||
|
if (k.endswith("attn1.to_out.0.bias") or k.endswith("attn1.to_out.0.weight")):
|
||||||
|
if '.time_stack.' in k:
|
||||||
|
m.add_patches({k: (None,)}, 0.0, self_temporal)
|
||||||
|
else:
|
||||||
|
m.add_patches({k: (None,)}, 0.0, self_structural)
|
||||||
|
elif (k.endswith("attn2.to_out.0.bias") or k.endswith("attn2.to_out.0.weight")):
|
||||||
|
if '.time_stack.' in k:
|
||||||
|
m.add_patches({k: (None,)}, 0.0, cross_temporal)
|
||||||
|
else:
|
||||||
|
m.add_patches({k: (None,)}, 0.0, cross_structural)
|
||||||
|
return (m, )
|
||||||
|
|
||||||
|
NODE_CLASS_MAPPINGS = {
|
||||||
|
"UNetSelfAttentionMultiply": UNetSelfAttentionMultiply,
|
||||||
|
"UNetCrossAttentionMultiply": UNetCrossAttentionMultiply,
|
||||||
|
"CLIPAttentionMultiply": CLIPAttentionMultiply,
|
||||||
|
"UNetTemporalAttentionMultiply": UNetTemporalAttentionMultiply,
|
||||||
|
}
|
||||||
@ -1,4 +1,3 @@
|
|||||||
#From https://github.com/kornia/kornia
|
|
||||||
import math
|
import math
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
|
|||||||
@ -8,7 +8,7 @@ class CLIPTextEncodeSDXLRefiner:
|
|||||||
"ascore": ("FLOAT", {"default": 6.0, "min": 0.0, "max": 1000.0, "step": 0.01}),
|
"ascore": ("FLOAT", {"default": 6.0, "min": 0.0, "max": 1000.0, "step": 0.01}),
|
||||||
"width": ("INT", {"default": 1024.0, "min": 0, "max": MAX_RESOLUTION}),
|
"width": ("INT", {"default": 1024.0, "min": 0, "max": MAX_RESOLUTION}),
|
||||||
"height": ("INT", {"default": 1024.0, "min": 0, "max": MAX_RESOLUTION}),
|
"height": ("INT", {"default": 1024.0, "min": 0, "max": MAX_RESOLUTION}),
|
||||||
"text": ("STRING", {"multiline": True}), "clip": ("CLIP", ),
|
"text": ("STRING", {"multiline": True, "dynamicPrompts": True}), "clip": ("CLIP", ),
|
||||||
}}
|
}}
|
||||||
RETURN_TYPES = ("CONDITIONING",)
|
RETURN_TYPES = ("CONDITIONING",)
|
||||||
FUNCTION = "encode"
|
FUNCTION = "encode"
|
||||||
@ -30,8 +30,8 @@ class CLIPTextEncodeSDXL:
|
|||||||
"crop_h": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION}),
|
"crop_h": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION}),
|
||||||
"target_width": ("INT", {"default": 1024.0, "min": 0, "max": MAX_RESOLUTION}),
|
"target_width": ("INT", {"default": 1024.0, "min": 0, "max": MAX_RESOLUTION}),
|
||||||
"target_height": ("INT", {"default": 1024.0, "min": 0, "max": MAX_RESOLUTION}),
|
"target_height": ("INT", {"default": 1024.0, "min": 0, "max": MAX_RESOLUTION}),
|
||||||
"text_g": ("STRING", {"multiline": True, "default": "CLIP_G"}), "clip": ("CLIP", ),
|
"text_g": ("STRING", {"multiline": True, "dynamicPrompts": True}), "clip": ("CLIP", ),
|
||||||
"text_l": ("STRING", {"multiline": True, "default": "CLIP_L"}), "clip": ("CLIP", ),
|
"text_l": ("STRING", {"multiline": True, "dynamicPrompts": True}), "clip": ("CLIP", ),
|
||||||
}}
|
}}
|
||||||
RETURN_TYPES = ("CONDITIONING",)
|
RETURN_TYPES = ("CONDITIONING",)
|
||||||
FUNCTION = "encode"
|
FUNCTION = "encode"
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
class CLIPTextEncodeControlnet:
|
class CLIPTextEncodeControlnet:
|
||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(s):
|
def INPUT_TYPES(s):
|
||||||
return {"required": {"clip": ("CLIP", ), "conditioning": ("CONDITIONING", ), "text": ("STRING", {"multiline": True})}}
|
return {"required": {"clip": ("CLIP", ), "conditioning": ("CONDITIONING", ), "text": ("STRING", {"multiline": True, "dynamicPrompts": True})}}
|
||||||
RETURN_TYPES = ("CONDITIONING",)
|
RETURN_TYPES = ("CONDITIONING",)
|
||||||
FUNCTION = "encode"
|
FUNCTION = "encode"
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@ from comfy.k_diffusion import sampling as k_diffusion_sampling
|
|||||||
import latent_preview
|
import latent_preview
|
||||||
import torch
|
import torch
|
||||||
import comfy.utils
|
import comfy.utils
|
||||||
|
import node_helpers
|
||||||
|
|
||||||
|
|
||||||
class BasicScheduler:
|
class BasicScheduler:
|
||||||
@ -24,10 +25,11 @@ class BasicScheduler:
|
|||||||
def get_sigmas(self, model, scheduler, steps, denoise):
|
def get_sigmas(self, model, scheduler, steps, denoise):
|
||||||
total_steps = steps
|
total_steps = steps
|
||||||
if denoise < 1.0:
|
if denoise < 1.0:
|
||||||
|
if denoise <= 0.0:
|
||||||
|
return (torch.FloatTensor([]),)
|
||||||
total_steps = int(steps/denoise)
|
total_steps = int(steps/denoise)
|
||||||
|
|
||||||
comfy.model_management.load_models_gpu([model])
|
sigmas = comfy.samplers.calculate_sigmas(model.get_model_object("model_sampling"), scheduler, total_steps).cpu()
|
||||||
sigmas = comfy.samplers.calculate_sigmas_scheduler(model.model, scheduler, total_steps).cpu()
|
|
||||||
sigmas = sigmas[-(steps + 1):]
|
sigmas = sigmas[-(steps + 1):]
|
||||||
return (sigmas, )
|
return (sigmas, )
|
||||||
|
|
||||||
@ -37,8 +39,8 @@ class KarrasScheduler:
|
|||||||
def INPUT_TYPES(s):
|
def INPUT_TYPES(s):
|
||||||
return {"required":
|
return {"required":
|
||||||
{"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
|
{"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
|
||||||
"sigma_max": ("FLOAT", {"default": 14.614642, "min": 0.0, "max": 1000.0, "step":0.01, "round": False}),
|
"sigma_max": ("FLOAT", {"default": 14.614642, "min": 0.0, "max": 5000.0, "step":0.01, "round": False}),
|
||||||
"sigma_min": ("FLOAT", {"default": 0.0291675, "min": 0.0, "max": 1000.0, "step":0.01, "round": False}),
|
"sigma_min": ("FLOAT", {"default": 0.0291675, "min": 0.0, "max": 5000.0, "step":0.01, "round": False}),
|
||||||
"rho": ("FLOAT", {"default": 7.0, "min": 0.0, "max": 100.0, "step":0.01, "round": False}),
|
"rho": ("FLOAT", {"default": 7.0, "min": 0.0, "max": 100.0, "step":0.01, "round": False}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -56,8 +58,8 @@ class ExponentialScheduler:
|
|||||||
def INPUT_TYPES(s):
|
def INPUT_TYPES(s):
|
||||||
return {"required":
|
return {"required":
|
||||||
{"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
|
{"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
|
||||||
"sigma_max": ("FLOAT", {"default": 14.614642, "min": 0.0, "max": 1000.0, "step":0.01, "round": False}),
|
"sigma_max": ("FLOAT", {"default": 14.614642, "min": 0.0, "max": 5000.0, "step":0.01, "round": False}),
|
||||||
"sigma_min": ("FLOAT", {"default": 0.0291675, "min": 0.0, "max": 1000.0, "step":0.01, "round": False}),
|
"sigma_min": ("FLOAT", {"default": 0.0291675, "min": 0.0, "max": 5000.0, "step":0.01, "round": False}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RETURN_TYPES = ("SIGMAS",)
|
RETURN_TYPES = ("SIGMAS",)
|
||||||
@ -74,8 +76,8 @@ class PolyexponentialScheduler:
|
|||||||
def INPUT_TYPES(s):
|
def INPUT_TYPES(s):
|
||||||
return {"required":
|
return {"required":
|
||||||
{"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
|
{"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
|
||||||
"sigma_max": ("FLOAT", {"default": 14.614642, "min": 0.0, "max": 1000.0, "step":0.01, "round": False}),
|
"sigma_max": ("FLOAT", {"default": 14.614642, "min": 0.0, "max": 5000.0, "step":0.01, "round": False}),
|
||||||
"sigma_min": ("FLOAT", {"default": 0.0291675, "min": 0.0, "max": 1000.0, "step":0.01, "round": False}),
|
"sigma_min": ("FLOAT", {"default": 0.0291675, "min": 0.0, "max": 5000.0, "step":0.01, "round": False}),
|
||||||
"rho": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step":0.01, "round": False}),
|
"rho": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step":0.01, "round": False}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -105,8 +107,7 @@ class SDTurboScheduler:
|
|||||||
def get_sigmas(self, model, steps, denoise):
|
def get_sigmas(self, model, steps, denoise):
|
||||||
start_step = 10 - int(10 * denoise)
|
start_step = 10 - int(10 * denoise)
|
||||||
timesteps = torch.flip(torch.arange(1, 11) * 100 - 1, (0,))[start_step:start_step + steps]
|
timesteps = torch.flip(torch.arange(1, 11) * 100 - 1, (0,))[start_step:start_step + steps]
|
||||||
comfy.model_management.load_models_gpu([model])
|
sigmas = model.get_model_object("model_sampling").sigma(timesteps)
|
||||||
sigmas = model.model.model_sampling.sigma(timesteps)
|
|
||||||
sigmas = torch.cat([sigmas, sigmas.new_zeros([1])])
|
sigmas = torch.cat([sigmas, sigmas.new_zeros([1])])
|
||||||
return (sigmas, )
|
return (sigmas, )
|
||||||
|
|
||||||
@ -115,8 +116,8 @@ class VPScheduler:
|
|||||||
def INPUT_TYPES(s):
|
def INPUT_TYPES(s):
|
||||||
return {"required":
|
return {"required":
|
||||||
{"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
|
{"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
|
||||||
"beta_d": ("FLOAT", {"default": 19.9, "min": 0.0, "max": 1000.0, "step":0.01, "round": False}), #TODO: fix default values
|
"beta_d": ("FLOAT", {"default": 19.9, "min": 0.0, "max": 5000.0, "step":0.01, "round": False}), #TODO: fix default values
|
||||||
"beta_min": ("FLOAT", {"default": 0.1, "min": 0.0, "max": 1000.0, "step":0.01, "round": False}),
|
"beta_min": ("FLOAT", {"default": 0.1, "min": 0.0, "max": 5000.0, "step":0.01, "round": False}),
|
||||||
"eps_s": ("FLOAT", {"default": 0.001, "min": 0.0, "max": 1.0, "step":0.0001, "round": False}),
|
"eps_s": ("FLOAT", {"default": 0.001, "min": 0.0, "max": 1.0, "step":0.0001, "round": False}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -138,6 +139,7 @@ class SplitSigmas:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
RETURN_TYPES = ("SIGMAS","SIGMAS")
|
RETURN_TYPES = ("SIGMAS","SIGMAS")
|
||||||
|
RETURN_NAMES = ("high_sigmas", "low_sigmas")
|
||||||
CATEGORY = "sampling/custom_sampling/sigmas"
|
CATEGORY = "sampling/custom_sampling/sigmas"
|
||||||
|
|
||||||
FUNCTION = "get_sigmas"
|
FUNCTION = "get_sigmas"
|
||||||
@ -147,6 +149,27 @@ class SplitSigmas:
|
|||||||
sigmas2 = sigmas[step:]
|
sigmas2 = sigmas[step:]
|
||||||
return (sigmas1, sigmas2)
|
return (sigmas1, sigmas2)
|
||||||
|
|
||||||
|
class SplitSigmasDenoise:
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(s):
|
||||||
|
return {"required":
|
||||||
|
{"sigmas": ("SIGMAS", ),
|
||||||
|
"denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RETURN_TYPES = ("SIGMAS","SIGMAS")
|
||||||
|
RETURN_NAMES = ("high_sigmas", "low_sigmas")
|
||||||
|
CATEGORY = "sampling/custom_sampling/sigmas"
|
||||||
|
|
||||||
|
FUNCTION = "get_sigmas"
|
||||||
|
|
||||||
|
def get_sigmas(self, sigmas, denoise):
|
||||||
|
steps = max(sigmas.shape[-1] - 1, 0)
|
||||||
|
total_steps = round(steps * denoise)
|
||||||
|
sigmas1 = sigmas[:-(total_steps)]
|
||||||
|
sigmas2 = sigmas[-(total_steps + 1):]
|
||||||
|
return (sigmas1, sigmas2)
|
||||||
|
|
||||||
class FlipSigmas:
|
class FlipSigmas:
|
||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(s):
|
def INPUT_TYPES(s):
|
||||||
@ -160,6 +183,9 @@ class FlipSigmas:
|
|||||||
FUNCTION = "get_sigmas"
|
FUNCTION = "get_sigmas"
|
||||||
|
|
||||||
def get_sigmas(self, sigmas):
|
def get_sigmas(self, sigmas):
|
||||||
|
if len(sigmas) == 0:
|
||||||
|
return (sigmas,)
|
||||||
|
|
||||||
sigmas = sigmas.flip(0)
|
sigmas = sigmas.flip(0)
|
||||||
if sigmas[0] == 0:
|
if sigmas[0] == 0:
|
||||||
sigmas[0] = 0.0001
|
sigmas[0] = 0.0001
|
||||||
@ -181,6 +207,28 @@ class KSamplerSelect:
|
|||||||
sampler = comfy.samplers.sampler_object(sampler_name)
|
sampler = comfy.samplers.sampler_object(sampler_name)
|
||||||
return (sampler, )
|
return (sampler, )
|
||||||
|
|
||||||
|
class SamplerDPMPP_3M_SDE:
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(s):
|
||||||
|
return {"required":
|
||||||
|
{"eta": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step":0.01, "round": False}),
|
||||||
|
"s_noise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step":0.01, "round": False}),
|
||||||
|
"noise_device": (['gpu', 'cpu'], ),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RETURN_TYPES = ("SAMPLER",)
|
||||||
|
CATEGORY = "sampling/custom_sampling/samplers"
|
||||||
|
|
||||||
|
FUNCTION = "get_sampler"
|
||||||
|
|
||||||
|
def get_sampler(self, eta, s_noise, noise_device):
|
||||||
|
if noise_device == 'cpu':
|
||||||
|
sampler_name = "dpmpp_3m_sde"
|
||||||
|
else:
|
||||||
|
sampler_name = "dpmpp_3m_sde_gpu"
|
||||||
|
sampler = comfy.samplers.ksampler(sampler_name, {"eta": eta, "s_noise": s_noise})
|
||||||
|
return (sampler, )
|
||||||
|
|
||||||
class SamplerDPMPP_2M_SDE:
|
class SamplerDPMPP_2M_SDE:
|
||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(s):
|
def INPUT_TYPES(s):
|
||||||
@ -228,6 +276,84 @@ class SamplerDPMPP_SDE:
|
|||||||
sampler = comfy.samplers.ksampler(sampler_name, {"eta": eta, "s_noise": s_noise, "r": r})
|
sampler = comfy.samplers.ksampler(sampler_name, {"eta": eta, "s_noise": s_noise, "r": r})
|
||||||
return (sampler, )
|
return (sampler, )
|
||||||
|
|
||||||
|
class SamplerEulerAncestral:
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(s):
|
||||||
|
return {"required":
|
||||||
|
{"eta": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step":0.01, "round": False}),
|
||||||
|
"s_noise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step":0.01, "round": False}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RETURN_TYPES = ("SAMPLER",)
|
||||||
|
CATEGORY = "sampling/custom_sampling/samplers"
|
||||||
|
|
||||||
|
FUNCTION = "get_sampler"
|
||||||
|
|
||||||
|
def get_sampler(self, eta, s_noise):
|
||||||
|
sampler = comfy.samplers.ksampler("euler_ancestral", {"eta": eta, "s_noise": s_noise})
|
||||||
|
return (sampler, )
|
||||||
|
|
||||||
|
class SamplerLMS:
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(s):
|
||||||
|
return {"required":
|
||||||
|
{"order": ("INT", {"default": 4, "min": 1, "max": 100}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RETURN_TYPES = ("SAMPLER",)
|
||||||
|
CATEGORY = "sampling/custom_sampling/samplers"
|
||||||
|
|
||||||
|
FUNCTION = "get_sampler"
|
||||||
|
|
||||||
|
def get_sampler(self, order):
|
||||||
|
sampler = comfy.samplers.ksampler("lms", {"order": order})
|
||||||
|
return (sampler, )
|
||||||
|
|
||||||
|
class SamplerDPMAdaptative:
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(s):
|
||||||
|
return {"required":
|
||||||
|
{"order": ("INT", {"default": 3, "min": 2, "max": 3}),
|
||||||
|
"rtol": ("FLOAT", {"default": 0.05, "min": 0.0, "max": 100.0, "step":0.01, "round": False}),
|
||||||
|
"atol": ("FLOAT", {"default": 0.0078, "min": 0.0, "max": 100.0, "step":0.01, "round": False}),
|
||||||
|
"h_init": ("FLOAT", {"default": 0.05, "min": 0.0, "max": 100.0, "step":0.01, "round": False}),
|
||||||
|
"pcoeff": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 100.0, "step":0.01, "round": False}),
|
||||||
|
"icoeff": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step":0.01, "round": False}),
|
||||||
|
"dcoeff": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 100.0, "step":0.01, "round": False}),
|
||||||
|
"accept_safety": ("FLOAT", {"default": 0.81, "min": 0.0, "max": 100.0, "step":0.01, "round": False}),
|
||||||
|
"eta": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 100.0, "step":0.01, "round": False}),
|
||||||
|
"s_noise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step":0.01, "round": False}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RETURN_TYPES = ("SAMPLER",)
|
||||||
|
CATEGORY = "sampling/custom_sampling/samplers"
|
||||||
|
|
||||||
|
FUNCTION = "get_sampler"
|
||||||
|
|
||||||
|
def get_sampler(self, order, rtol, atol, h_init, pcoeff, icoeff, dcoeff, accept_safety, eta, s_noise):
|
||||||
|
sampler = comfy.samplers.ksampler("dpm_adaptive", {"order": order, "rtol": rtol, "atol": atol, "h_init": h_init, "pcoeff": pcoeff,
|
||||||
|
"icoeff": icoeff, "dcoeff": dcoeff, "accept_safety": accept_safety, "eta": eta,
|
||||||
|
"s_noise":s_noise })
|
||||||
|
return (sampler, )
|
||||||
|
|
||||||
|
class Noise_EmptyNoise:
|
||||||
|
def __init__(self):
|
||||||
|
self.seed = 0
|
||||||
|
|
||||||
|
def generate_noise(self, input_latent):
|
||||||
|
latent_image = input_latent["samples"]
|
||||||
|
return torch.zeros(latent_image.shape, dtype=latent_image.dtype, layout=latent_image.layout, device="cpu")
|
||||||
|
|
||||||
|
|
||||||
|
class Noise_RandomNoise:
|
||||||
|
def __init__(self, seed):
|
||||||
|
self.seed = seed
|
||||||
|
|
||||||
|
def generate_noise(self, input_latent):
|
||||||
|
latent_image = input_latent["samples"]
|
||||||
|
batch_inds = input_latent["batch_index"] if "batch_index" in input_latent else None
|
||||||
|
return comfy.sample.prepare_noise(latent_image, self.seed, batch_inds)
|
||||||
|
|
||||||
class SamplerCustom:
|
class SamplerCustom:
|
||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(s):
|
def INPUT_TYPES(s):
|
||||||
@ -255,10 +381,9 @@ class SamplerCustom:
|
|||||||
latent = latent_image
|
latent = latent_image
|
||||||
latent_image = latent["samples"]
|
latent_image = latent["samples"]
|
||||||
if not add_noise:
|
if not add_noise:
|
||||||
noise = torch.zeros(latent_image.size(), dtype=latent_image.dtype, layout=latent_image.layout, device="cpu")
|
noise = Noise_EmptyNoise().generate_noise(latent)
|
||||||
else:
|
else:
|
||||||
batch_inds = latent["batch_index"] if "batch_index" in latent else None
|
noise = Noise_RandomNoise(noise_seed).generate_noise(latent)
|
||||||
noise = comfy.sample.prepare_noise(latent_image, noise_seed, batch_inds)
|
|
||||||
|
|
||||||
noise_mask = None
|
noise_mask = None
|
||||||
if "noise_mask" in latent:
|
if "noise_mask" in latent:
|
||||||
@ -279,6 +404,207 @@ class SamplerCustom:
|
|||||||
out_denoised = out
|
out_denoised = out
|
||||||
return (out, out_denoised)
|
return (out, out_denoised)
|
||||||
|
|
||||||
|
class Guider_Basic(comfy.samplers.CFGGuider):
|
||||||
|
def set_conds(self, positive):
|
||||||
|
self.inner_set_conds({"positive": positive})
|
||||||
|
|
||||||
|
class BasicGuider:
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(s):
|
||||||
|
return {"required":
|
||||||
|
{"model": ("MODEL",),
|
||||||
|
"conditioning": ("CONDITIONING", ),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_TYPES = ("GUIDER",)
|
||||||
|
|
||||||
|
FUNCTION = "get_guider"
|
||||||
|
CATEGORY = "sampling/custom_sampling/guiders"
|
||||||
|
|
||||||
|
def get_guider(self, model, conditioning):
|
||||||
|
guider = Guider_Basic(model)
|
||||||
|
guider.set_conds(conditioning)
|
||||||
|
return (guider,)
|
||||||
|
|
||||||
|
class CFGGuider:
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(s):
|
||||||
|
return {"required":
|
||||||
|
{"model": ("MODEL",),
|
||||||
|
"positive": ("CONDITIONING", ),
|
||||||
|
"negative": ("CONDITIONING", ),
|
||||||
|
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0, "step":0.1, "round": 0.01}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_TYPES = ("GUIDER",)
|
||||||
|
|
||||||
|
FUNCTION = "get_guider"
|
||||||
|
CATEGORY = "sampling/custom_sampling/guiders"
|
||||||
|
|
||||||
|
def get_guider(self, model, positive, negative, cfg):
|
||||||
|
guider = comfy.samplers.CFGGuider(model)
|
||||||
|
guider.set_conds(positive, negative)
|
||||||
|
guider.set_cfg(cfg)
|
||||||
|
return (guider,)
|
||||||
|
|
||||||
|
class Guider_DualCFG(comfy.samplers.CFGGuider):
|
||||||
|
def set_cfg(self, cfg1, cfg2):
|
||||||
|
self.cfg1 = cfg1
|
||||||
|
self.cfg2 = cfg2
|
||||||
|
|
||||||
|
def set_conds(self, positive, middle, negative):
|
||||||
|
middle = node_helpers.conditioning_set_values(middle, {"prompt_type": "negative"})
|
||||||
|
self.inner_set_conds({"positive": positive, "middle": middle, "negative": negative})
|
||||||
|
|
||||||
|
def predict_noise(self, x, timestep, model_options={}, seed=None):
|
||||||
|
negative_cond = self.conds.get("negative", None)
|
||||||
|
middle_cond = self.conds.get("middle", None)
|
||||||
|
|
||||||
|
out = comfy.samplers.calc_cond_batch(self.inner_model, [negative_cond, middle_cond, self.conds.get("positive", None)], x, timestep, model_options)
|
||||||
|
return comfy.samplers.cfg_function(self.inner_model, out[1], out[0], self.cfg2, x, timestep, model_options=model_options, cond=middle_cond, uncond=negative_cond) + (out[2] - out[1]) * self.cfg1
|
||||||
|
|
||||||
|
class DualCFGGuider:
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(s):
|
||||||
|
return {"required":
|
||||||
|
{"model": ("MODEL",),
|
||||||
|
"cond1": ("CONDITIONING", ),
|
||||||
|
"cond2": ("CONDITIONING", ),
|
||||||
|
"negative": ("CONDITIONING", ),
|
||||||
|
"cfg_conds": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0, "step":0.1, "round": 0.01}),
|
||||||
|
"cfg_cond2_negative": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0, "step":0.1, "round": 0.01}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_TYPES = ("GUIDER",)
|
||||||
|
|
||||||
|
FUNCTION = "get_guider"
|
||||||
|
CATEGORY = "sampling/custom_sampling/guiders"
|
||||||
|
|
||||||
|
def get_guider(self, model, cond1, cond2, negative, cfg_conds, cfg_cond2_negative):
|
||||||
|
guider = Guider_DualCFG(model)
|
||||||
|
guider.set_conds(cond1, cond2, negative)
|
||||||
|
guider.set_cfg(cfg_conds, cfg_cond2_negative)
|
||||||
|
return (guider,)
|
||||||
|
|
||||||
|
class DisableNoise:
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(s):
|
||||||
|
return {"required":{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_TYPES = ("NOISE",)
|
||||||
|
FUNCTION = "get_noise"
|
||||||
|
CATEGORY = "sampling/custom_sampling/noise"
|
||||||
|
|
||||||
|
def get_noise(self):
|
||||||
|
return (Noise_EmptyNoise(),)
|
||||||
|
|
||||||
|
|
||||||
|
class RandomNoise(DisableNoise):
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(s):
|
||||||
|
return {"required":{
|
||||||
|
"noise_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_noise(self, noise_seed):
|
||||||
|
return (Noise_RandomNoise(noise_seed),)
|
||||||
|
|
||||||
|
|
||||||
|
class SamplerCustomAdvanced:
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(s):
|
||||||
|
return {"required":
|
||||||
|
{"noise": ("NOISE", ),
|
||||||
|
"guider": ("GUIDER", ),
|
||||||
|
"sampler": ("SAMPLER", ),
|
||||||
|
"sigmas": ("SIGMAS", ),
|
||||||
|
"latent_image": ("LATENT", ),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_TYPES = ("LATENT","LATENT")
|
||||||
|
RETURN_NAMES = ("output", "denoised_output")
|
||||||
|
|
||||||
|
FUNCTION = "sample"
|
||||||
|
|
||||||
|
CATEGORY = "sampling/custom_sampling"
|
||||||
|
|
||||||
|
def sample(self, noise, guider, sampler, sigmas, latent_image):
|
||||||
|
latent = latent_image
|
||||||
|
latent_image = latent["samples"]
|
||||||
|
|
||||||
|
noise_mask = None
|
||||||
|
if "noise_mask" in latent:
|
||||||
|
noise_mask = latent["noise_mask"]
|
||||||
|
|
||||||
|
x0_output = {}
|
||||||
|
callback = latent_preview.prepare_callback(guider.model_patcher, sigmas.shape[-1] - 1, x0_output)
|
||||||
|
|
||||||
|
disable_pbar = not comfy.utils.PROGRESS_BAR_ENABLED
|
||||||
|
samples = guider.sample(noise.generate_noise(latent), latent_image, sampler, sigmas, denoise_mask=noise_mask, callback=callback, disable_pbar=disable_pbar, seed=noise.seed)
|
||||||
|
samples = samples.to(comfy.model_management.intermediate_device())
|
||||||
|
|
||||||
|
out = latent.copy()
|
||||||
|
out["samples"] = samples
|
||||||
|
if "x0" in x0_output:
|
||||||
|
out_denoised = latent.copy()
|
||||||
|
out_denoised["samples"] = guider.model_patcher.model.process_latent_out(x0_output["x0"].cpu())
|
||||||
|
else:
|
||||||
|
out_denoised = out
|
||||||
|
return (out, out_denoised)
|
||||||
|
|
||||||
|
class AddNoise:
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(s):
|
||||||
|
return {"required":
|
||||||
|
{"model": ("MODEL",),
|
||||||
|
"noise": ("NOISE", ),
|
||||||
|
"sigmas": ("SIGMAS", ),
|
||||||
|
"latent_image": ("LATENT", ),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_TYPES = ("LATENT",)
|
||||||
|
|
||||||
|
FUNCTION = "add_noise"
|
||||||
|
|
||||||
|
CATEGORY = "_for_testing/custom_sampling/noise"
|
||||||
|
|
||||||
|
def add_noise(self, model, noise, sigmas, latent_image):
|
||||||
|
if len(sigmas) == 0:
|
||||||
|
return latent_image
|
||||||
|
|
||||||
|
latent = latent_image
|
||||||
|
latent_image = latent["samples"]
|
||||||
|
|
||||||
|
noisy = noise.generate_noise(latent)
|
||||||
|
|
||||||
|
model_sampling = model.get_model_object("model_sampling")
|
||||||
|
process_latent_out = model.get_model_object("process_latent_out")
|
||||||
|
process_latent_in = model.get_model_object("process_latent_in")
|
||||||
|
|
||||||
|
if len(sigmas) > 1:
|
||||||
|
scale = torch.abs(sigmas[0] - sigmas[-1])
|
||||||
|
else:
|
||||||
|
scale = sigmas[0]
|
||||||
|
|
||||||
|
if torch.count_nonzero(latent_image) > 0: #Don't shift the empty latent image.
|
||||||
|
latent_image = process_latent_in(latent_image)
|
||||||
|
noisy = model_sampling.noise_scaling(scale, noisy, latent_image)
|
||||||
|
noisy = process_latent_out(noisy)
|
||||||
|
noisy = torch.nan_to_num(noisy, nan=0.0, posinf=0.0, neginf=0.0)
|
||||||
|
|
||||||
|
out = latent.copy()
|
||||||
|
out["samples"] = noisy
|
||||||
|
return (out,)
|
||||||
|
|
||||||
|
|
||||||
NODE_CLASS_MAPPINGS = {
|
NODE_CLASS_MAPPINGS = {
|
||||||
"SamplerCustom": SamplerCustom,
|
"SamplerCustom": SamplerCustom,
|
||||||
"BasicScheduler": BasicScheduler,
|
"BasicScheduler": BasicScheduler,
|
||||||
@ -288,8 +614,21 @@ NODE_CLASS_MAPPINGS = {
|
|||||||
"VPScheduler": VPScheduler,
|
"VPScheduler": VPScheduler,
|
||||||
"SDTurboScheduler": SDTurboScheduler,
|
"SDTurboScheduler": SDTurboScheduler,
|
||||||
"KSamplerSelect": KSamplerSelect,
|
"KSamplerSelect": KSamplerSelect,
|
||||||
|
"SamplerEulerAncestral": SamplerEulerAncestral,
|
||||||
|
"SamplerLMS": SamplerLMS,
|
||||||
|
"SamplerDPMPP_3M_SDE": SamplerDPMPP_3M_SDE,
|
||||||
"SamplerDPMPP_2M_SDE": SamplerDPMPP_2M_SDE,
|
"SamplerDPMPP_2M_SDE": SamplerDPMPP_2M_SDE,
|
||||||
"SamplerDPMPP_SDE": SamplerDPMPP_SDE,
|
"SamplerDPMPP_SDE": SamplerDPMPP_SDE,
|
||||||
|
"SamplerDPMAdaptative": SamplerDPMAdaptative,
|
||||||
"SplitSigmas": SplitSigmas,
|
"SplitSigmas": SplitSigmas,
|
||||||
|
"SplitSigmasDenoise": SplitSigmasDenoise,
|
||||||
"FlipSigmas": FlipSigmas,
|
"FlipSigmas": FlipSigmas,
|
||||||
|
|
||||||
|
"CFGGuider": CFGGuider,
|
||||||
|
"DualCFGGuider": DualCFGGuider,
|
||||||
|
"BasicGuider": BasicGuider,
|
||||||
|
"RandomNoise": RandomNoise,
|
||||||
|
"DisableNoise": DisableNoise,
|
||||||
|
"AddNoise": AddNoise,
|
||||||
|
"SamplerCustomAdvanced": SamplerCustomAdvanced,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
#code originally taken from: https://github.com/ChenyangSi/FreeU (under MIT License)
|
#code originally taken from: https://github.com/ChenyangSi/FreeU (under MIT License)
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
|
import logging
|
||||||
|
|
||||||
def Fourier_filter(x, threshold, scale):
|
def Fourier_filter(x, threshold, scale):
|
||||||
# FFT
|
# FFT
|
||||||
@ -49,7 +49,7 @@ class FreeU:
|
|||||||
try:
|
try:
|
||||||
hsp = Fourier_filter(hsp, threshold=1, scale=scale[1])
|
hsp = Fourier_filter(hsp, threshold=1, scale=scale[1])
|
||||||
except:
|
except:
|
||||||
print("Device", hsp.device, "does not support the torch.fft functions used in the FreeU node, switching to CPU.")
|
logging.warning("Device {} does not support the torch.fft functions used in the FreeU node, switching to CPU.".format(hsp.device))
|
||||||
on_cpu_devices[hsp.device] = True
|
on_cpu_devices[hsp.device] = True
|
||||||
hsp = Fourier_filter(hsp.cpu(), threshold=1, scale=scale[1]).to(hsp.device)
|
hsp = Fourier_filter(hsp.cpu(), threshold=1, scale=scale[1]).to(hsp.device)
|
||||||
else:
|
else:
|
||||||
@ -95,7 +95,7 @@ class FreeU_V2:
|
|||||||
try:
|
try:
|
||||||
hsp = Fourier_filter(hsp, threshold=1, scale=scale[1])
|
hsp = Fourier_filter(hsp, threshold=1, scale=scale[1])
|
||||||
except:
|
except:
|
||||||
print("Device", hsp.device, "does not support the torch.fft functions used in the FreeU node, switching to CPU.")
|
logging.warning("Device {} does not support the torch.fft functions used in the FreeU node, switching to CPU.".format(hsp.device))
|
||||||
on_cpu_devices[hsp.device] = True
|
on_cpu_devices[hsp.device] = True
|
||||||
hsp = Fourier_filter(hsp.cpu(), threshold=1, scale=scale[1]).to(hsp.device)
|
hsp = Fourier_filter(hsp.cpu(), threshold=1, scale=scale[1]).to(hsp.device)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import comfy.utils
|
import comfy.utils
|
||||||
import folder_paths
|
import folder_paths
|
||||||
import torch
|
import torch
|
||||||
|
import logging
|
||||||
|
|
||||||
def load_hypernetwork_patch(path, strength):
|
def load_hypernetwork_patch(path, strength):
|
||||||
sd = comfy.utils.load_torch_file(path, safe_load=True)
|
sd = comfy.utils.load_torch_file(path, safe_load=True)
|
||||||
@ -23,7 +24,7 @@ def load_hypernetwork_patch(path, strength):
|
|||||||
}
|
}
|
||||||
|
|
||||||
if activation_func not in valid_activation:
|
if activation_func not in valid_activation:
|
||||||
print("Unsupported Hypernetwork format, if you report it I might implement it.", path, " ", activation_func, is_layer_norm, use_dropout, activate_output, last_layer_dropout)
|
logging.error("Unsupported Hypernetwork format, if you report it I might implement it. {} {} {} {} {} {}".format(path, activation_func, is_layer_norm, use_dropout, activate_output, last_layer_dropout))
|
||||||
return None
|
return None
|
||||||
|
|
||||||
out = {}
|
out = {}
|
||||||
|
|||||||
@ -37,7 +37,7 @@ class RepeatImageBatch:
|
|||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(s):
|
def INPUT_TYPES(s):
|
||||||
return {"required": { "image": ("IMAGE",),
|
return {"required": { "image": ("IMAGE",),
|
||||||
"amount": ("INT", {"default": 1, "min": 1, "max": 64}),
|
"amount": ("INT", {"default": 1, "min": 1, "max": 4096}),
|
||||||
}}
|
}}
|
||||||
RETURN_TYPES = ("IMAGE",)
|
RETURN_TYPES = ("IMAGE",)
|
||||||
FUNCTION = "repeat"
|
FUNCTION = "repeat"
|
||||||
@ -52,8 +52,8 @@ class ImageFromBatch:
|
|||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(s):
|
def INPUT_TYPES(s):
|
||||||
return {"required": { "image": ("IMAGE",),
|
return {"required": { "image": ("IMAGE",),
|
||||||
"batch_index": ("INT", {"default": 0, "min": 0, "max": 63}),
|
"batch_index": ("INT", {"default": 0, "min": 0, "max": 4095}),
|
||||||
"length": ("INT", {"default": 1, "min": 1, "max": 64}),
|
"length": ("INT", {"default": 1, "min": 1, "max": 4096}),
|
||||||
}}
|
}}
|
||||||
RETURN_TYPES = ("IMAGE",)
|
RETURN_TYPES = ("IMAGE",)
|
||||||
FUNCTION = "frombatch"
|
FUNCTION = "frombatch"
|
||||||
|
|||||||
45
comfy_extras/nodes_ip2p.py
Normal file
45
comfy_extras/nodes_ip2p.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import torch
|
||||||
|
|
||||||
|
class InstructPixToPixConditioning:
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(s):
|
||||||
|
return {"required": {"positive": ("CONDITIONING", ),
|
||||||
|
"negative": ("CONDITIONING", ),
|
||||||
|
"vae": ("VAE", ),
|
||||||
|
"pixels": ("IMAGE", ),
|
||||||
|
}}
|
||||||
|
|
||||||
|
RETURN_TYPES = ("CONDITIONING","CONDITIONING","LATENT")
|
||||||
|
RETURN_NAMES = ("positive", "negative", "latent")
|
||||||
|
FUNCTION = "encode"
|
||||||
|
|
||||||
|
CATEGORY = "conditioning/instructpix2pix"
|
||||||
|
|
||||||
|
def encode(self, positive, negative, pixels, vae):
|
||||||
|
x = (pixels.shape[1] // 8) * 8
|
||||||
|
y = (pixels.shape[2] // 8) * 8
|
||||||
|
|
||||||
|
if pixels.shape[1] != x or pixels.shape[2] != y:
|
||||||
|
x_offset = (pixels.shape[1] % 8) // 2
|
||||||
|
y_offset = (pixels.shape[2] % 8) // 2
|
||||||
|
pixels = pixels[:,x_offset:x + x_offset, y_offset:y + y_offset,:]
|
||||||
|
|
||||||
|
concat_latent = vae.encode(pixels)
|
||||||
|
|
||||||
|
out_latent = {}
|
||||||
|
out_latent["samples"] = torch.zeros_like(concat_latent)
|
||||||
|
|
||||||
|
out = []
|
||||||
|
for conditioning in [positive, negative]:
|
||||||
|
c = []
|
||||||
|
for t in conditioning:
|
||||||
|
d = t[1].copy()
|
||||||
|
d["concat_latent_image"] = concat_latent
|
||||||
|
n = [t[0], d]
|
||||||
|
c.append(n)
|
||||||
|
out.append(c)
|
||||||
|
return (out[0], out[1], out_latent)
|
||||||
|
|
||||||
|
NODE_CLASS_MAPPINGS = {
|
||||||
|
"InstructPixToPixConditioning": InstructPixToPixConditioning,
|
||||||
|
}
|
||||||
@ -20,8 +20,9 @@ class PatchModelAddDownscale:
|
|||||||
CATEGORY = "_for_testing"
|
CATEGORY = "_for_testing"
|
||||||
|
|
||||||
def patch(self, model, block_number, downscale_factor, start_percent, end_percent, downscale_after_skip, downscale_method, upscale_method):
|
def patch(self, model, block_number, downscale_factor, start_percent, end_percent, downscale_after_skip, downscale_method, upscale_method):
|
||||||
sigma_start = model.model.model_sampling.percent_to_sigma(start_percent)
|
model_sampling = model.get_model_object("model_sampling")
|
||||||
sigma_end = model.model.model_sampling.percent_to_sigma(end_percent)
|
sigma_start = model_sampling.percent_to_sigma(start_percent)
|
||||||
|
sigma_end = model_sampling.percent_to_sigma(end_percent)
|
||||||
|
|
||||||
def input_block_patch(h, transformer_options):
|
def input_block_patch(h, transformer_options):
|
||||||
if transformer_options["block"][1] == block_number:
|
if transformer_options["block"][1] == block_number:
|
||||||
|
|||||||
@ -2,7 +2,9 @@ import comfy.sd
|
|||||||
import comfy.utils
|
import comfy.utils
|
||||||
import comfy.model_base
|
import comfy.model_base
|
||||||
import comfy.model_management
|
import comfy.model_management
|
||||||
|
import comfy.model_sampling
|
||||||
|
|
||||||
|
import torch
|
||||||
import folder_paths
|
import folder_paths
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
@ -87,6 +89,50 @@ class CLIPMergeSimple:
|
|||||||
m.add_patches({k: kp[k]}, 1.0 - ratio, ratio)
|
m.add_patches({k: kp[k]}, 1.0 - ratio, ratio)
|
||||||
return (m, )
|
return (m, )
|
||||||
|
|
||||||
|
|
||||||
|
class CLIPSubtract:
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(s):
|
||||||
|
return {"required": { "clip1": ("CLIP",),
|
||||||
|
"clip2": ("CLIP",),
|
||||||
|
"multiplier": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
|
||||||
|
}}
|
||||||
|
RETURN_TYPES = ("CLIP",)
|
||||||
|
FUNCTION = "merge"
|
||||||
|
|
||||||
|
CATEGORY = "advanced/model_merging"
|
||||||
|
|
||||||
|
def merge(self, clip1, clip2, multiplier):
|
||||||
|
m = clip1.clone()
|
||||||
|
kp = clip2.get_key_patches()
|
||||||
|
for k in kp:
|
||||||
|
if k.endswith(".position_ids") or k.endswith(".logit_scale"):
|
||||||
|
continue
|
||||||
|
m.add_patches({k: kp[k]}, - multiplier, multiplier)
|
||||||
|
return (m, )
|
||||||
|
|
||||||
|
|
||||||
|
class CLIPAdd:
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(s):
|
||||||
|
return {"required": { "clip1": ("CLIP",),
|
||||||
|
"clip2": ("CLIP",),
|
||||||
|
}}
|
||||||
|
RETURN_TYPES = ("CLIP",)
|
||||||
|
FUNCTION = "merge"
|
||||||
|
|
||||||
|
CATEGORY = "advanced/model_merging"
|
||||||
|
|
||||||
|
def merge(self, clip1, clip2):
|
||||||
|
m = clip1.clone()
|
||||||
|
kp = clip2.get_key_patches()
|
||||||
|
for k in kp:
|
||||||
|
if k.endswith(".position_ids") or k.endswith(".logit_scale"):
|
||||||
|
continue
|
||||||
|
m.add_patches({k: kp[k]}, 1.0, 1.0)
|
||||||
|
return (m, )
|
||||||
|
|
||||||
|
|
||||||
class ModelMergeBlocks:
|
class ModelMergeBlocks:
|
||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(s):
|
def INPUT_TYPES(s):
|
||||||
@ -145,6 +191,13 @@ def save_checkpoint(model, clip=None, vae=None, clip_vision=None, filename_prefi
|
|||||||
# "stable-diffusion-v2-768-v", "stable-diffusion-v2-unclip-l", "stable-diffusion-v2-unclip-h",
|
# "stable-diffusion-v2-768-v", "stable-diffusion-v2-unclip-l", "stable-diffusion-v2-unclip-h",
|
||||||
# "v2-inpainting"
|
# "v2-inpainting"
|
||||||
|
|
||||||
|
extra_keys = {}
|
||||||
|
model_sampling = model.get_model_object("model_sampling")
|
||||||
|
if isinstance(model_sampling, comfy.model_sampling.ModelSamplingContinuousEDM):
|
||||||
|
if isinstance(model_sampling, comfy.model_sampling.V_PREDICTION):
|
||||||
|
extra_keys["edm_vpred.sigma_max"] = torch.tensor(model_sampling.sigma_max).float()
|
||||||
|
extra_keys["edm_vpred.sigma_min"] = torch.tensor(model_sampling.sigma_min).float()
|
||||||
|
|
||||||
if model.model.model_type == comfy.model_base.ModelType.EPS:
|
if model.model.model_type == comfy.model_base.ModelType.EPS:
|
||||||
metadata["modelspec.predict_key"] = "epsilon"
|
metadata["modelspec.predict_key"] = "epsilon"
|
||||||
elif model.model.model_type == comfy.model_base.ModelType.V_PREDICTION:
|
elif model.model.model_type == comfy.model_base.ModelType.V_PREDICTION:
|
||||||
@ -159,7 +212,7 @@ def save_checkpoint(model, clip=None, vae=None, clip_vision=None, filename_prefi
|
|||||||
output_checkpoint = f"{filename}_{counter:05}_.safetensors"
|
output_checkpoint = f"{filename}_{counter:05}_.safetensors"
|
||||||
output_checkpoint = os.path.join(full_output_folder, output_checkpoint)
|
output_checkpoint = os.path.join(full_output_folder, output_checkpoint)
|
||||||
|
|
||||||
comfy.sd.save_checkpoint(output_checkpoint, model, clip, vae, clip_vision, metadata=metadata)
|
comfy.sd.save_checkpoint(output_checkpoint, model, clip, vae, clip_vision, metadata=metadata, extra_keys=extra_keys)
|
||||||
|
|
||||||
class CheckpointSave:
|
class CheckpointSave:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@ -279,6 +332,8 @@ NODE_CLASS_MAPPINGS = {
|
|||||||
"ModelMergeAdd": ModelAdd,
|
"ModelMergeAdd": ModelAdd,
|
||||||
"CheckpointSave": CheckpointSave,
|
"CheckpointSave": CheckpointSave,
|
||||||
"CLIPMergeSimple": CLIPMergeSimple,
|
"CLIPMergeSimple": CLIPMergeSimple,
|
||||||
|
"CLIPMergeSubtract": CLIPSubtract,
|
||||||
|
"CLIPMergeAdd": CLIPAdd,
|
||||||
"CLIPSave": CLIPSave,
|
"CLIPSave": CLIPSave,
|
||||||
"VAESave": VAESave,
|
"VAESave": VAESave,
|
||||||
}
|
}
|
||||||
|
|||||||
60
comfy_extras/nodes_model_merging_model_specific.py
Normal file
60
comfy_extras/nodes_model_merging_model_specific.py
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import comfy_extras.nodes_model_merging
|
||||||
|
|
||||||
|
class ModelMergeSD1(comfy_extras.nodes_model_merging.ModelMergeBlocks):
|
||||||
|
CATEGORY = "advanced/model_merging/model_specific"
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(s):
|
||||||
|
arg_dict = { "model1": ("MODEL",),
|
||||||
|
"model2": ("MODEL",)}
|
||||||
|
|
||||||
|
argument = ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01})
|
||||||
|
|
||||||
|
arg_dict["time_embed."] = argument
|
||||||
|
arg_dict["label_emb."] = argument
|
||||||
|
|
||||||
|
for i in range(12):
|
||||||
|
arg_dict["input_blocks.{}.".format(i)] = argument
|
||||||
|
|
||||||
|
for i in range(3):
|
||||||
|
arg_dict["middle_block.{}.".format(i)] = argument
|
||||||
|
|
||||||
|
for i in range(12):
|
||||||
|
arg_dict["output_blocks.{}.".format(i)] = argument
|
||||||
|
|
||||||
|
arg_dict["out."] = argument
|
||||||
|
|
||||||
|
return {"required": arg_dict}
|
||||||
|
|
||||||
|
|
||||||
|
class ModelMergeSDXL(comfy_extras.nodes_model_merging.ModelMergeBlocks):
|
||||||
|
CATEGORY = "advanced/model_merging/model_specific"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(s):
|
||||||
|
arg_dict = { "model1": ("MODEL",),
|
||||||
|
"model2": ("MODEL",)}
|
||||||
|
|
||||||
|
argument = ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01})
|
||||||
|
|
||||||
|
arg_dict["time_embed."] = argument
|
||||||
|
arg_dict["label_emb."] = argument
|
||||||
|
|
||||||
|
for i in range(9):
|
||||||
|
arg_dict["input_blocks.{}".format(i)] = argument
|
||||||
|
|
||||||
|
for i in range(3):
|
||||||
|
arg_dict["middle_block.{}".format(i)] = argument
|
||||||
|
|
||||||
|
for i in range(9):
|
||||||
|
arg_dict["output_blocks.{}".format(i)] = argument
|
||||||
|
|
||||||
|
arg_dict["out."] = argument
|
||||||
|
|
||||||
|
return {"required": arg_dict}
|
||||||
|
|
||||||
|
|
||||||
|
NODE_CLASS_MAPPINGS = {
|
||||||
|
"ModelMergeSD1": ModelMergeSD1,
|
||||||
|
"ModelMergeSD2": ModelMergeSD1, #SD1 and SD2 have the same blocks
|
||||||
|
"ModelMergeSDXL": ModelMergeSDXL,
|
||||||
|
}
|
||||||
56
comfy_extras/nodes_pag.py
Normal file
56
comfy_extras/nodes_pag.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
#Modified/simplified version of the node from: https://github.com/pamparamm/sd-perturbed-attention
|
||||||
|
#If you want the one with more options see the above repo.
|
||||||
|
|
||||||
|
#My modified one here is more basic but has less chances of breaking with ComfyUI updates.
|
||||||
|
|
||||||
|
import comfy.model_patcher
|
||||||
|
import comfy.samplers
|
||||||
|
|
||||||
|
class PerturbedAttentionGuidance:
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(s):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"model": ("MODEL",),
|
||||||
|
"scale": ("FLOAT", {"default": 3.0, "min": 0.0, "max": 100.0, "step": 0.1, "round": 0.01}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_TYPES = ("MODEL",)
|
||||||
|
FUNCTION = "patch"
|
||||||
|
|
||||||
|
CATEGORY = "_for_testing"
|
||||||
|
|
||||||
|
def patch(self, model, scale):
|
||||||
|
unet_block = "middle"
|
||||||
|
unet_block_id = 0
|
||||||
|
m = model.clone()
|
||||||
|
|
||||||
|
def perturbed_attention(q, k, v, extra_options, mask=None):
|
||||||
|
return v
|
||||||
|
|
||||||
|
def post_cfg_function(args):
|
||||||
|
model = args["model"]
|
||||||
|
cond_pred = args["cond_denoised"]
|
||||||
|
cond = args["cond"]
|
||||||
|
cfg_result = args["denoised"]
|
||||||
|
sigma = args["sigma"]
|
||||||
|
model_options = args["model_options"].copy()
|
||||||
|
x = args["input"]
|
||||||
|
|
||||||
|
if scale == 0:
|
||||||
|
return cfg_result
|
||||||
|
|
||||||
|
# Replace Self-attention with PAG
|
||||||
|
model_options = comfy.model_patcher.set_model_options_patch_replace(model_options, perturbed_attention, "attn1", unet_block, unet_block_id)
|
||||||
|
(pag,) = comfy.samplers.calc_cond_batch(model, [cond], x, sigma, model_options)
|
||||||
|
|
||||||
|
return cfg_result + (cond_pred - pag) * scale
|
||||||
|
|
||||||
|
m.set_model_sampler_post_cfg_function(post_cfg_function)
|
||||||
|
|
||||||
|
return (m,)
|
||||||
|
|
||||||
|
NODE_CLASS_MAPPINGS = {
|
||||||
|
"PerturbedAttentionGuidance": PerturbedAttentionGuidance,
|
||||||
|
}
|
||||||
@ -1,16 +1,26 @@
|
|||||||
import torch
|
import torch
|
||||||
import comfy.model_management
|
import comfy.model_management
|
||||||
import comfy.sample
|
import comfy.sampler_helpers
|
||||||
import comfy.samplers
|
import comfy.samplers
|
||||||
import comfy.utils
|
import comfy.utils
|
||||||
|
import node_helpers
|
||||||
|
|
||||||
|
def perp_neg(x, noise_pred_pos, noise_pred_neg, noise_pred_nocond, neg_scale, cond_scale):
|
||||||
|
pos = noise_pred_pos - noise_pred_nocond
|
||||||
|
neg = noise_pred_neg - noise_pred_nocond
|
||||||
|
|
||||||
|
perp = neg - ((torch.mul(neg, pos).sum())/(torch.norm(pos)**2)) * pos
|
||||||
|
perp_neg = perp * neg_scale
|
||||||
|
cfg_result = noise_pred_nocond + cond_scale*(pos - perp_neg)
|
||||||
|
return cfg_result
|
||||||
|
|
||||||
|
#TODO: This node should be removed, it has been replaced with PerpNegGuider
|
||||||
class PerpNeg:
|
class PerpNeg:
|
||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(s):
|
def INPUT_TYPES(s):
|
||||||
return {"required": {"model": ("MODEL", ),
|
return {"required": {"model": ("MODEL", ),
|
||||||
"empty_conditioning": ("CONDITIONING", ),
|
"empty_conditioning": ("CONDITIONING", ),
|
||||||
"neg_scale": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0}),
|
"neg_scale": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step": 0.01}),
|
||||||
}}
|
}}
|
||||||
RETURN_TYPES = ("MODEL",)
|
RETURN_TYPES = ("MODEL",)
|
||||||
FUNCTION = "patch"
|
FUNCTION = "patch"
|
||||||
@ -19,7 +29,7 @@ class PerpNeg:
|
|||||||
|
|
||||||
def patch(self, model, empty_conditioning, neg_scale):
|
def patch(self, model, empty_conditioning, neg_scale):
|
||||||
m = model.clone()
|
m = model.clone()
|
||||||
nocond = comfy.sample.convert_cond(empty_conditioning)
|
nocond = comfy.sampler_helpers.convert_cond(empty_conditioning)
|
||||||
|
|
||||||
def cfg_function(args):
|
def cfg_function(args):
|
||||||
model = args["model"]
|
model = args["model"]
|
||||||
@ -31,14 +41,9 @@ class PerpNeg:
|
|||||||
model_options = args["model_options"]
|
model_options = args["model_options"]
|
||||||
nocond_processed = comfy.samplers.encode_model_conds(model.extra_conds, nocond, x, x.device, "negative")
|
nocond_processed = comfy.samplers.encode_model_conds(model.extra_conds, nocond, x, x.device, "negative")
|
||||||
|
|
||||||
(noise_pred_nocond, _) = comfy.samplers.calc_cond_uncond_batch(model, nocond_processed, None, x, sigma, model_options)
|
(noise_pred_nocond,) = comfy.samplers.calc_cond_batch(model, [nocond_processed], x, sigma, model_options)
|
||||||
|
|
||||||
pos = noise_pred_pos - noise_pred_nocond
|
cfg_result = x - perp_neg(x, noise_pred_pos, noise_pred_neg, noise_pred_nocond, neg_scale, cond_scale)
|
||||||
neg = noise_pred_neg - noise_pred_nocond
|
|
||||||
perp = neg - ((torch.mul(neg, pos).sum())/(torch.norm(pos)**2)) * pos
|
|
||||||
perp_neg = perp * neg_scale
|
|
||||||
cfg_result = noise_pred_nocond + cond_scale*(pos - perp_neg)
|
|
||||||
cfg_result = x - cfg_result
|
|
||||||
return cfg_result
|
return cfg_result
|
||||||
|
|
||||||
m.set_model_sampler_cfg_function(cfg_function)
|
m.set_model_sampler_cfg_function(cfg_function)
|
||||||
@ -46,10 +51,52 @@ class PerpNeg:
|
|||||||
return (m, )
|
return (m, )
|
||||||
|
|
||||||
|
|
||||||
|
class Guider_PerpNeg(comfy.samplers.CFGGuider):
|
||||||
|
def set_conds(self, positive, negative, empty_negative_prompt):
|
||||||
|
empty_negative_prompt = node_helpers.conditioning_set_values(empty_negative_prompt, {"prompt_type": "negative"})
|
||||||
|
self.inner_set_conds({"positive": positive, "empty_negative_prompt": empty_negative_prompt, "negative": negative})
|
||||||
|
|
||||||
|
def set_cfg(self, cfg, neg_scale):
|
||||||
|
self.cfg = cfg
|
||||||
|
self.neg_scale = neg_scale
|
||||||
|
|
||||||
|
def predict_noise(self, x, timestep, model_options={}, seed=None):
|
||||||
|
positive_cond = self.conds.get("positive", None)
|
||||||
|
negative_cond = self.conds.get("negative", None)
|
||||||
|
empty_cond = self.conds.get("empty_negative_prompt", None)
|
||||||
|
|
||||||
|
out = comfy.samplers.calc_cond_batch(self.inner_model, [negative_cond, positive_cond, empty_cond], x, timestep, model_options)
|
||||||
|
return perp_neg(x, out[1], out[0], out[2], self.neg_scale, self.cfg)
|
||||||
|
|
||||||
|
class PerpNegGuider:
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(s):
|
||||||
|
return {"required":
|
||||||
|
{"model": ("MODEL",),
|
||||||
|
"positive": ("CONDITIONING", ),
|
||||||
|
"negative": ("CONDITIONING", ),
|
||||||
|
"empty_conditioning": ("CONDITIONING", ),
|
||||||
|
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0, "step":0.1, "round": 0.01}),
|
||||||
|
"neg_scale": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step": 0.01}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_TYPES = ("GUIDER",)
|
||||||
|
|
||||||
|
FUNCTION = "get_guider"
|
||||||
|
CATEGORY = "_for_testing"
|
||||||
|
|
||||||
|
def get_guider(self, model, positive, negative, empty_conditioning, cfg, neg_scale):
|
||||||
|
guider = Guider_PerpNeg(model)
|
||||||
|
guider.set_conds(positive, negative, empty_conditioning)
|
||||||
|
guider.set_cfg(cfg, neg_scale)
|
||||||
|
return (guider,)
|
||||||
|
|
||||||
NODE_CLASS_MAPPINGS = {
|
NODE_CLASS_MAPPINGS = {
|
||||||
"PerpNeg": PerpNeg,
|
"PerpNeg": PerpNeg,
|
||||||
|
"PerpNegGuider": PerpNegGuider,
|
||||||
}
|
}
|
||||||
|
|
||||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||||
"PerpNeg": "Perp-Neg",
|
"PerpNeg": "Perp-Neg (DEPRECATED by PerpNegGuider)",
|
||||||
}
|
}
|
||||||
|
|||||||
@ -141,7 +141,7 @@ class PhotoMakerEncode:
|
|||||||
return {"required": { "photomaker": ("PHOTOMAKER",),
|
return {"required": { "photomaker": ("PHOTOMAKER",),
|
||||||
"image": ("IMAGE",),
|
"image": ("IMAGE",),
|
||||||
"clip": ("CLIP", ),
|
"clip": ("CLIP", ),
|
||||||
"text": ("STRING", {"multiline": True, "default": "photograph of photomaker"}),
|
"text": ("STRING", {"multiline": True, "dynamicPrompts": True, "default": "photograph of photomaker"}),
|
||||||
}}
|
}}
|
||||||
|
|
||||||
RETURN_TYPES = ("CONDITIONING",)
|
RETURN_TYPES = ("CONDITIONING",)
|
||||||
|
|||||||
@ -5,6 +5,7 @@ from PIL import Image
|
|||||||
import math
|
import math
|
||||||
|
|
||||||
import comfy.utils
|
import comfy.utils
|
||||||
|
import comfy.model_management
|
||||||
|
|
||||||
|
|
||||||
class Blend:
|
class Blend:
|
||||||
@ -102,6 +103,7 @@ class Blur:
|
|||||||
if blur_radius == 0:
|
if blur_radius == 0:
|
||||||
return (image,)
|
return (image,)
|
||||||
|
|
||||||
|
image = image.to(comfy.model_management.get_torch_device())
|
||||||
batch_size, height, width, channels = image.shape
|
batch_size, height, width, channels = image.shape
|
||||||
|
|
||||||
kernel_size = blur_radius * 2 + 1
|
kernel_size = blur_radius * 2 + 1
|
||||||
@ -112,7 +114,7 @@ class Blur:
|
|||||||
blurred = F.conv2d(padded_image, kernel, padding=kernel_size // 2, groups=channels)[:,:,blur_radius:-blur_radius, blur_radius:-blur_radius]
|
blurred = F.conv2d(padded_image, kernel, padding=kernel_size // 2, groups=channels)[:,:,blur_radius:-blur_radius, blur_radius:-blur_radius]
|
||||||
blurred = blurred.permute(0, 2, 3, 1)
|
blurred = blurred.permute(0, 2, 3, 1)
|
||||||
|
|
||||||
return (blurred,)
|
return (blurred.to(comfy.model_management.intermediate_device()),)
|
||||||
|
|
||||||
class Quantize:
|
class Quantize:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@ -204,13 +206,13 @@ class Sharpen:
|
|||||||
"default": 1.0,
|
"default": 1.0,
|
||||||
"min": 0.1,
|
"min": 0.1,
|
||||||
"max": 10.0,
|
"max": 10.0,
|
||||||
"step": 0.1
|
"step": 0.01
|
||||||
}),
|
}),
|
||||||
"alpha": ("FLOAT", {
|
"alpha": ("FLOAT", {
|
||||||
"default": 1.0,
|
"default": 1.0,
|
||||||
"min": 0.0,
|
"min": 0.0,
|
||||||
"max": 5.0,
|
"max": 5.0,
|
||||||
"step": 0.1
|
"step": 0.01
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -225,6 +227,7 @@ class Sharpen:
|
|||||||
return (image,)
|
return (image,)
|
||||||
|
|
||||||
batch_size, height, width, channels = image.shape
|
batch_size, height, width, channels = image.shape
|
||||||
|
image = image.to(comfy.model_management.get_torch_device())
|
||||||
|
|
||||||
kernel_size = sharpen_radius * 2 + 1
|
kernel_size = sharpen_radius * 2 + 1
|
||||||
kernel = gaussian_kernel(kernel_size, sigma, device=image.device) * -(alpha*10)
|
kernel = gaussian_kernel(kernel_size, sigma, device=image.device) * -(alpha*10)
|
||||||
@ -239,7 +242,7 @@ class Sharpen:
|
|||||||
|
|
||||||
result = torch.clamp(sharpened, 0, 1)
|
result = torch.clamp(sharpened, 0, 1)
|
||||||
|
|
||||||
return (result,)
|
return (result.to(comfy.model_management.intermediate_device()),)
|
||||||
|
|
||||||
class ImageScaleToTotalPixels:
|
class ImageScaleToTotalPixels:
|
||||||
upscale_methods = ["nearest-exact", "bilinear", "area", "bicubic", "lanczos"]
|
upscale_methods = ["nearest-exact", "bilinear", "area", "bicubic", "lanczos"]
|
||||||
|
|||||||
@ -150,7 +150,7 @@ class SelfAttentionGuidance:
|
|||||||
degraded = create_blur_map(uncond_pred, uncond_attn, sag_sigma, sag_threshold)
|
degraded = create_blur_map(uncond_pred, uncond_attn, sag_sigma, sag_threshold)
|
||||||
degraded_noised = degraded + x - uncond_pred
|
degraded_noised = degraded + x - uncond_pred
|
||||||
# call into the UNet
|
# call into the UNet
|
||||||
(sag, _) = comfy.samplers.calc_cond_uncond_batch(model, uncond, None, degraded_noised, sigma, model_options)
|
(sag,) = comfy.samplers.calc_cond_batch(model, [uncond], degraded_noised, sigma, model_options)
|
||||||
return cfg_result + (degraded - sag) * sag_scale
|
return cfg_result + (degraded - sag) * sag_scale
|
||||||
|
|
||||||
m.set_model_sampler_post_cfg_function(post_cfg_function, disable_cfg1_optimization=True)
|
m.set_model_sampler_post_cfg_function(post_cfg_function, disable_cfg1_optimization=True)
|
||||||
|
|||||||
@ -29,8 +29,8 @@ class StableZero123_Conditioning:
|
|||||||
"width": ("INT", {"default": 256, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 8}),
|
"width": ("INT", {"default": 256, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 8}),
|
||||||
"height": ("INT", {"default": 256, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 8}),
|
"height": ("INT", {"default": 256, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 8}),
|
||||||
"batch_size": ("INT", {"default": 1, "min": 1, "max": 4096}),
|
"batch_size": ("INT", {"default": 1, "min": 1, "max": 4096}),
|
||||||
"elevation": ("FLOAT", {"default": 0.0, "min": -180.0, "max": 180.0}),
|
"elevation": ("FLOAT", {"default": 0.0, "min": -180.0, "max": 180.0, "step": 0.1, "round": False}),
|
||||||
"azimuth": ("FLOAT", {"default": 0.0, "min": -180.0, "max": 180.0}),
|
"azimuth": ("FLOAT", {"default": 0.0, "min": -180.0, "max": 180.0, "step": 0.1, "round": False}),
|
||||||
}}
|
}}
|
||||||
RETURN_TYPES = ("CONDITIONING", "CONDITIONING", "LATENT")
|
RETURN_TYPES = ("CONDITIONING", "CONDITIONING", "LATENT")
|
||||||
RETURN_NAMES = ("positive", "negative", "latent")
|
RETURN_NAMES = ("positive", "negative", "latent")
|
||||||
@ -62,10 +62,10 @@ class StableZero123_Conditioning_Batched:
|
|||||||
"width": ("INT", {"default": 256, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 8}),
|
"width": ("INT", {"default": 256, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 8}),
|
||||||
"height": ("INT", {"default": 256, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 8}),
|
"height": ("INT", {"default": 256, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 8}),
|
||||||
"batch_size": ("INT", {"default": 1, "min": 1, "max": 4096}),
|
"batch_size": ("INT", {"default": 1, "min": 1, "max": 4096}),
|
||||||
"elevation": ("FLOAT", {"default": 0.0, "min": -180.0, "max": 180.0}),
|
"elevation": ("FLOAT", {"default": 0.0, "min": -180.0, "max": 180.0, "step": 0.1, "round": False}),
|
||||||
"azimuth": ("FLOAT", {"default": 0.0, "min": -180.0, "max": 180.0}),
|
"azimuth": ("FLOAT", {"default": 0.0, "min": -180.0, "max": 180.0, "step": 0.1, "round": False}),
|
||||||
"elevation_batch_increment": ("FLOAT", {"default": 0.0, "min": -180.0, "max": 180.0}),
|
"elevation_batch_increment": ("FLOAT", {"default": 0.0, "min": -180.0, "max": 180.0, "step": 0.1, "round": False}),
|
||||||
"azimuth_batch_increment": ("FLOAT", {"default": 0.0, "min": -180.0, "max": 180.0}),
|
"azimuth_batch_increment": ("FLOAT", {"default": 0.0, "min": -180.0, "max": 180.0, "step": 0.1, "round": False}),
|
||||||
}}
|
}}
|
||||||
RETURN_TYPES = ("CONDITIONING", "CONDITIONING", "LATENT")
|
RETURN_TYPES = ("CONDITIONING", "CONDITIONING", "LATENT")
|
||||||
RETURN_NAMES = ("positive", "negative", "latent")
|
RETURN_NAMES = ("positive", "negative", "latent")
|
||||||
@ -95,8 +95,49 @@ class StableZero123_Conditioning_Batched:
|
|||||||
latent = torch.zeros([batch_size, 4, height // 8, width // 8])
|
latent = torch.zeros([batch_size, 4, height // 8, width // 8])
|
||||||
return (positive, negative, {"samples":latent, "batch_index": [0] * batch_size})
|
return (positive, negative, {"samples":latent, "batch_index": [0] * batch_size})
|
||||||
|
|
||||||
|
class SV3D_Conditioning:
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(s):
|
||||||
|
return {"required": { "clip_vision": ("CLIP_VISION",),
|
||||||
|
"init_image": ("IMAGE",),
|
||||||
|
"vae": ("VAE",),
|
||||||
|
"width": ("INT", {"default": 576, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 8}),
|
||||||
|
"height": ("INT", {"default": 576, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 8}),
|
||||||
|
"video_frames": ("INT", {"default": 21, "min": 1, "max": 4096}),
|
||||||
|
"elevation": ("FLOAT", {"default": 0.0, "min": -90.0, "max": 90.0, "step": 0.1, "round": False}),
|
||||||
|
}}
|
||||||
|
RETURN_TYPES = ("CONDITIONING", "CONDITIONING", "LATENT")
|
||||||
|
RETURN_NAMES = ("positive", "negative", "latent")
|
||||||
|
|
||||||
|
FUNCTION = "encode"
|
||||||
|
|
||||||
|
CATEGORY = "conditioning/3d_models"
|
||||||
|
|
||||||
|
def encode(self, clip_vision, init_image, vae, width, height, video_frames, elevation):
|
||||||
|
output = clip_vision.encode_image(init_image)
|
||||||
|
pooled = output.image_embeds.unsqueeze(0)
|
||||||
|
pixels = comfy.utils.common_upscale(init_image.movedim(-1,1), width, height, "bilinear", "center").movedim(1,-1)
|
||||||
|
encode_pixels = pixels[:,:,:,:3]
|
||||||
|
t = vae.encode(encode_pixels)
|
||||||
|
|
||||||
|
azimuth = 0
|
||||||
|
azimuth_increment = 360 / (max(video_frames, 2) - 1)
|
||||||
|
|
||||||
|
elevations = []
|
||||||
|
azimuths = []
|
||||||
|
for i in range(video_frames):
|
||||||
|
elevations.append(elevation)
|
||||||
|
azimuths.append(azimuth)
|
||||||
|
azimuth += azimuth_increment
|
||||||
|
|
||||||
|
positive = [[pooled, {"concat_latent_image": t, "elevation": elevations, "azimuth": azimuths}]]
|
||||||
|
negative = [[torch.zeros_like(pooled), {"concat_latent_image": torch.zeros_like(t), "elevation": elevations, "azimuth": azimuths}]]
|
||||||
|
latent = torch.zeros([video_frames, 4, height // 8, width // 8])
|
||||||
|
return (positive, negative, {"samples":latent})
|
||||||
|
|
||||||
|
|
||||||
NODE_CLASS_MAPPINGS = {
|
NODE_CLASS_MAPPINGS = {
|
||||||
"StableZero123_Conditioning": StableZero123_Conditioning,
|
"StableZero123_Conditioning": StableZero123_Conditioning,
|
||||||
"StableZero123_Conditioning_Batched": StableZero123_Conditioning_Batched,
|
"StableZero123_Conditioning_Batched": StableZero123_Conditioning_Batched,
|
||||||
|
"SV3D_Conditioning": SV3D_Conditioning,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -74,7 +74,7 @@ class StableCascade_StageC_VAEEncode:
|
|||||||
s = comfy.utils.common_upscale(image.movedim(-1,1), out_width, out_height, "bicubic", "center").movedim(1,-1)
|
s = comfy.utils.common_upscale(image.movedim(-1,1), out_width, out_height, "bicubic", "center").movedim(1,-1)
|
||||||
|
|
||||||
c_latent = vae.encode(s[:,:,:,:3])
|
c_latent = vae.encode(s[:,:,:,:3])
|
||||||
b_latent = torch.zeros([c_latent.shape[0], 4, height // 4, width // 4])
|
b_latent = torch.zeros([c_latent.shape[0], 4, (height // 8) * 2, (width // 8) * 2])
|
||||||
return ({
|
return ({
|
||||||
"samples": c_latent,
|
"samples": c_latent,
|
||||||
}, {
|
}, {
|
||||||
|
|||||||
@ -37,9 +37,14 @@ class ImageUpscaleWithModel:
|
|||||||
|
|
||||||
def upscale(self, upscale_model, image):
|
def upscale(self, upscale_model, image):
|
||||||
device = model_management.get_torch_device()
|
device = model_management.get_torch_device()
|
||||||
|
|
||||||
|
memory_required = model_management.module_size(upscale_model)
|
||||||
|
memory_required += (512 * 512 * 3) * image.element_size() * max(upscale_model.scale, 1.0) * 384.0 #The 384.0 is an estimate of how much some of these models take, TODO: make it more accurate
|
||||||
|
memory_required += image.nelement() * image.element_size()
|
||||||
|
model_management.free_memory(memory_required, device)
|
||||||
|
|
||||||
upscale_model.to(device)
|
upscale_model.to(device)
|
||||||
in_img = image.movedim(-1,-3).to(device)
|
in_img = image.movedim(-1,-3).to(device)
|
||||||
free_memory = model_management.get_free_memory(device)
|
|
||||||
|
|
||||||
tile = 512
|
tile = 512
|
||||||
overlap = 32
|
overlap = 32
|
||||||
|
|||||||
@ -79,6 +79,33 @@ class VideoLinearCFGGuidance:
|
|||||||
m.set_model_sampler_cfg_function(linear_cfg)
|
m.set_model_sampler_cfg_function(linear_cfg)
|
||||||
return (m, )
|
return (m, )
|
||||||
|
|
||||||
|
class VideoTriangleCFGGuidance:
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(s):
|
||||||
|
return {"required": { "model": ("MODEL",),
|
||||||
|
"min_cfg": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step":0.5, "round": 0.01}),
|
||||||
|
}}
|
||||||
|
RETURN_TYPES = ("MODEL",)
|
||||||
|
FUNCTION = "patch"
|
||||||
|
|
||||||
|
CATEGORY = "sampling/video_models"
|
||||||
|
|
||||||
|
def patch(self, model, min_cfg):
|
||||||
|
def linear_cfg(args):
|
||||||
|
cond = args["cond"]
|
||||||
|
uncond = args["uncond"]
|
||||||
|
cond_scale = args["cond_scale"]
|
||||||
|
period = 1.0
|
||||||
|
values = torch.linspace(0, 1, cond.shape[0], device=cond.device)
|
||||||
|
values = 2 * (values / period - torch.floor(values / period + 0.5)).abs()
|
||||||
|
scale = (values * (cond_scale - min_cfg) + min_cfg).reshape((cond.shape[0], 1, 1, 1))
|
||||||
|
|
||||||
|
return uncond + scale * (cond - uncond)
|
||||||
|
|
||||||
|
m = model.clone()
|
||||||
|
m.set_model_sampler_cfg_function(linear_cfg)
|
||||||
|
return (m, )
|
||||||
|
|
||||||
class ImageOnlyCheckpointSave(comfy_extras.nodes_model_merging.CheckpointSave):
|
class ImageOnlyCheckpointSave(comfy_extras.nodes_model_merging.CheckpointSave):
|
||||||
CATEGORY = "_for_testing"
|
CATEGORY = "_for_testing"
|
||||||
|
|
||||||
@ -98,6 +125,7 @@ NODE_CLASS_MAPPINGS = {
|
|||||||
"ImageOnlyCheckpointLoader": ImageOnlyCheckpointLoader,
|
"ImageOnlyCheckpointLoader": ImageOnlyCheckpointLoader,
|
||||||
"SVD_img2vid_Conditioning": SVD_img2vid_Conditioning,
|
"SVD_img2vid_Conditioning": SVD_img2vid_Conditioning,
|
||||||
"VideoLinearCFGGuidance": VideoLinearCFGGuidance,
|
"VideoLinearCFGGuidance": VideoLinearCFGGuidance,
|
||||||
|
"VideoTriangleCFGGuidance": VideoTriangleCFGGuidance,
|
||||||
"ImageOnlyCheckpointSave": ImageOnlyCheckpointSave,
|
"ImageOnlyCheckpointSave": ImageOnlyCheckpointSave,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -47,7 +47,7 @@ blacklist = {"GeForce GTX TITAN X", "GeForce GTX 980", "GeForce GTX 970", "GeFor
|
|||||||
"Quadro K1200", "Quadro K2200", "Quadro M500", "Quadro M520", "Quadro M600", "Quadro M620", "Quadro M1000",
|
"Quadro K1200", "Quadro K2200", "Quadro M500", "Quadro M520", "Quadro M600", "Quadro M620", "Quadro M1000",
|
||||||
"Quadro M1200", "Quadro M2000", "Quadro M2200", "Quadro M3000", "Quadro M4000", "Quadro M5000", "Quadro M5500", "Quadro M6000",
|
"Quadro M1200", "Quadro M2000", "Quadro M2200", "Quadro M3000", "Quadro M4000", "Quadro M5000", "Quadro M5500", "Quadro M6000",
|
||||||
"GeForce MX110", "GeForce MX130", "GeForce 830M", "GeForce 840M", "GeForce GTX 850M", "GeForce GTX 860M",
|
"GeForce MX110", "GeForce MX130", "GeForce 830M", "GeForce 840M", "GeForce GTX 850M", "GeForce GTX 860M",
|
||||||
"GeForce GTX 1650", "GeForce GTX 1630"
|
"GeForce GTX 1650", "GeForce GTX 1630", "Tesla M4", "Tesla M6", "Tesla M10", "Tesla M40", "Tesla M60"
|
||||||
}
|
}
|
||||||
|
|
||||||
def cuda_malloc_supported():
|
def cuda_malloc_supported():
|
||||||
|
|||||||
@ -10,10 +10,6 @@ import time
|
|||||||
#binary images on the websocket with a 8 byte header indicating the type
|
#binary images on the websocket with a 8 byte header indicating the type
|
||||||
#of binary message (first 4 bytes) and the image format (next 4 bytes).
|
#of binary message (first 4 bytes) and the image format (next 4 bytes).
|
||||||
|
|
||||||
#The reason this node is disabled by default is because there is a small
|
|
||||||
#issue when using it with the default ComfyUI web interface: When generating
|
|
||||||
#batches only the last image will be shown in the UI.
|
|
||||||
|
|
||||||
#Note that no metadata will be put in the images saved with this node.
|
#Note that no metadata will be put in the images saved with this node.
|
||||||
|
|
||||||
class SaveImageWebsocket:
|
class SaveImageWebsocket:
|
||||||
@ -28,7 +24,7 @@ class SaveImageWebsocket:
|
|||||||
|
|
||||||
OUTPUT_NODE = True
|
OUTPUT_NODE = True
|
||||||
|
|
||||||
CATEGORY = "image"
|
CATEGORY = "api/image"
|
||||||
|
|
||||||
def save_images(self, images):
|
def save_images(self, images):
|
||||||
pbar = comfy.utils.ProgressBar(images.shape[0])
|
pbar = comfy.utils.ProgressBar(images.shape[0])
|
||||||
@ -35,8 +35,7 @@ def get_input_data(inputs, class_def, unique_id, outputs={}, prompt={}, extra_da
|
|||||||
if h[x] == "PROMPT":
|
if h[x] == "PROMPT":
|
||||||
input_data_all[x] = [prompt]
|
input_data_all[x] = [prompt]
|
||||||
if h[x] == "EXTRA_PNGINFO":
|
if h[x] == "EXTRA_PNGINFO":
|
||||||
if "extra_pnginfo" in extra_data:
|
input_data_all[x] = [extra_data.get('extra_pnginfo', None)]
|
||||||
input_data_all[x] = [extra_data['extra_pnginfo']]
|
|
||||||
if h[x] == "UNIQUE_ID":
|
if h[x] == "UNIQUE_ID":
|
||||||
input_data_all[x] = [unique_id]
|
input_data_all[x] = [unique_id]
|
||||||
return input_data_all
|
return input_data_all
|
||||||
@ -177,7 +176,7 @@ def recursive_execute(server, prompt, outputs, current_item, extra_data, execute
|
|||||||
for node_id, node_outputs in outputs.items():
|
for node_id, node_outputs in outputs.items():
|
||||||
output_data_formatted[node_id] = [[format_value(x) for x in l] for l in node_outputs]
|
output_data_formatted[node_id] = [[format_value(x) for x in l] for l in node_outputs]
|
||||||
|
|
||||||
logging.error("!!! Exception during processing !!!")
|
logging.error(f"!!! Exception during processing!!! {ex}")
|
||||||
logging.error(traceback.format_exc())
|
logging.error(traceback.format_exc())
|
||||||
|
|
||||||
error_details = {
|
error_details = {
|
||||||
@ -369,7 +368,7 @@ class PromptExecutor:
|
|||||||
d = self.outputs_ui.pop(x)
|
d = self.outputs_ui.pop(x)
|
||||||
del d
|
del d
|
||||||
|
|
||||||
comfy.model_management.cleanup_models()
|
comfy.model_management.cleanup_models(keep_clone_weights_loaded=True)
|
||||||
self.add_message("execution_cached",
|
self.add_message("execution_cached",
|
||||||
{ "nodes": list(current_outputs) , "prompt_id": prompt_id},
|
{ "nodes": list(current_outputs) , "prompt_id": prompt_id},
|
||||||
broadcast=False)
|
broadcast=False)
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
|
import logging
|
||||||
|
|
||||||
supported_pt_extensions = set(['.ckpt', '.pt', '.bin', '.pth', '.safetensors'])
|
supported_pt_extensions = set(['.ckpt', '.pt', '.bin', '.pth', '.safetensors', '.pkl'])
|
||||||
|
|
||||||
folder_names_and_paths = {}
|
folder_names_and_paths = {}
|
||||||
|
|
||||||
@ -44,7 +45,7 @@ if not os.path.exists(input_directory):
|
|||||||
try:
|
try:
|
||||||
os.makedirs(input_directory)
|
os.makedirs(input_directory)
|
||||||
except:
|
except:
|
||||||
print("Failed to create input directory")
|
logging.error("Failed to create input directory")
|
||||||
|
|
||||||
def set_output_directory(output_dir):
|
def set_output_directory(output_dir):
|
||||||
global output_directory
|
global output_directory
|
||||||
@ -146,8 +147,9 @@ def recursive_search(directory, excluded_dir_names=None):
|
|||||||
try:
|
try:
|
||||||
dirs[directory] = os.path.getmtime(directory)
|
dirs[directory] = os.path.getmtime(directory)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
print(f"Warning: Unable to access {directory}. Skipping this path.")
|
logging.warning(f"Warning: Unable to access {directory}. Skipping this path.")
|
||||||
|
|
||||||
|
logging.debug("recursive file list on directory {}".format(directory))
|
||||||
for dirpath, subdirs, filenames in os.walk(directory, followlinks=True, topdown=True):
|
for dirpath, subdirs, filenames in os.walk(directory, followlinks=True, topdown=True):
|
||||||
subdirs[:] = [d for d in subdirs if d not in excluded_dir_names]
|
subdirs[:] = [d for d in subdirs if d not in excluded_dir_names]
|
||||||
for file_name in filenames:
|
for file_name in filenames:
|
||||||
@ -159,8 +161,9 @@ def recursive_search(directory, excluded_dir_names=None):
|
|||||||
try:
|
try:
|
||||||
dirs[path] = os.path.getmtime(path)
|
dirs[path] = os.path.getmtime(path)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
print(f"Warning: Unable to access {path}. Skipping this path.")
|
logging.warning(f"Warning: Unable to access {path}. Skipping this path.")
|
||||||
continue
|
continue
|
||||||
|
logging.debug("found {} files".format(len(result)))
|
||||||
return result, dirs
|
return result, dirs
|
||||||
|
|
||||||
def filter_files_extensions(files, extensions):
|
def filter_files_extensions(files, extensions):
|
||||||
@ -178,6 +181,8 @@ def get_full_path(folder_name, filename):
|
|||||||
full_path = os.path.join(x, filename)
|
full_path = os.path.join(x, filename)
|
||||||
if os.path.isfile(full_path):
|
if os.path.isfile(full_path):
|
||||||
return full_path
|
return full_path
|
||||||
|
elif os.path.islink(full_path):
|
||||||
|
logging.warning("WARNING path {} exists but doesn't link anywhere, skipping.".format(full_path))
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -249,7 +254,7 @@ def get_save_image_path(filename_prefix, output_dir, image_width=0, image_height
|
|||||||
"\n full_output_folder: " + os.path.abspath(full_output_folder) + \
|
"\n full_output_folder: " + os.path.abspath(full_output_folder) + \
|
||||||
"\n output_dir: " + output_dir + \
|
"\n output_dir: " + output_dir + \
|
||||||
"\n commonpath: " + os.path.commonpath((output_dir, os.path.abspath(full_output_folder)))
|
"\n commonpath: " + os.path.commonpath((output_dir, os.path.abspath(full_output_folder)))
|
||||||
print(err)
|
logging.error(err)
|
||||||
raise Exception(err)
|
raise Exception(err)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@ -6,6 +6,7 @@ from comfy.cli_args import args, LatentPreviewMethod
|
|||||||
from comfy.taesd.taesd import TAESD
|
from comfy.taesd.taesd import TAESD
|
||||||
import folder_paths
|
import folder_paths
|
||||||
import comfy.utils
|
import comfy.utils
|
||||||
|
import logging
|
||||||
|
|
||||||
MAX_PREVIEW_RESOLUTION = 512
|
MAX_PREVIEW_RESOLUTION = 512
|
||||||
|
|
||||||
@ -70,7 +71,7 @@ def get_previewer(device, latent_format):
|
|||||||
taesd = TAESD(None, taesd_decoder_path).to(device)
|
taesd = TAESD(None, taesd_decoder_path).to(device)
|
||||||
previewer = TAESDPreviewerImpl(taesd)
|
previewer = TAESDPreviewerImpl(taesd)
|
||||||
else:
|
else:
|
||||||
print("Warning: TAESD previews enabled, but could not find models/vae_approx/{}".format(latent_format.taesd_decoder_name))
|
logging.warning("Warning: TAESD previews enabled, but could not find models/vae_approx/{}".format(latent_format.taesd_decoder_name))
|
||||||
|
|
||||||
if previewer is None:
|
if previewer is None:
|
||||||
if latent_format.latent_rgb_factors is not None:
|
if latent_format.latent_rgb_factors is not None:
|
||||||
|
|||||||
23
main.py
23
main.py
@ -54,15 +54,15 @@ import threading
|
|||||||
import gc
|
import gc
|
||||||
|
|
||||||
from comfy.cli_args import args
|
from comfy.cli_args import args
|
||||||
|
import logging
|
||||||
|
|
||||||
if os.name == "nt":
|
if os.name == "nt":
|
||||||
import logging
|
|
||||||
logging.getLogger("xformers").addFilter(lambda record: 'A matching Triton is not available' not in record.getMessage())
|
logging.getLogger("xformers").addFilter(lambda record: 'A matching Triton is not available' not in record.getMessage())
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
if args.cuda_device is not None:
|
if args.cuda_device is not None:
|
||||||
os.environ['CUDA_VISIBLE_DEVICES'] = str(args.cuda_device)
|
os.environ['CUDA_VISIBLE_DEVICES'] = str(args.cuda_device)
|
||||||
print("Set cuda device to:", args.cuda_device)
|
logging.info("Set cuda device to: {}".format(args.cuda_device))
|
||||||
|
|
||||||
if args.deterministic:
|
if args.deterministic:
|
||||||
if 'CUBLAS_WORKSPACE_CONFIG' not in os.environ:
|
if 'CUBLAS_WORKSPACE_CONFIG' not in os.environ:
|
||||||
@ -88,7 +88,7 @@ def cuda_malloc_warning():
|
|||||||
if b in device_name:
|
if b in device_name:
|
||||||
cuda_malloc_warning = True
|
cuda_malloc_warning = True
|
||||||
if cuda_malloc_warning:
|
if cuda_malloc_warning:
|
||||||
print("\nWARNING: this card most likely does not support cuda-malloc, if you get \"CUDA error\" please run ComfyUI with: --disable-cuda-malloc\n")
|
logging.warning("\nWARNING: this card most likely does not support cuda-malloc, if you get \"CUDA error\" please run ComfyUI with: --disable-cuda-malloc\n")
|
||||||
|
|
||||||
def prompt_worker(q, server):
|
def prompt_worker(q, server):
|
||||||
e = execution.PromptExecutor(server)
|
e = execution.PromptExecutor(server)
|
||||||
@ -121,7 +121,7 @@ def prompt_worker(q, server):
|
|||||||
|
|
||||||
current_time = time.perf_counter()
|
current_time = time.perf_counter()
|
||||||
execution_time = current_time - execution_start_time
|
execution_time = current_time - execution_start_time
|
||||||
print("Prompt executed in {:.2f} seconds".format(execution_time))
|
logging.info("Prompt executed in {:.2f} seconds".format(execution_time))
|
||||||
|
|
||||||
flags = q.get_flags()
|
flags = q.get_flags()
|
||||||
free_memory = flags.get("free_memory", False)
|
free_memory = flags.get("free_memory", False)
|
||||||
@ -139,6 +139,7 @@ def prompt_worker(q, server):
|
|||||||
if need_gc:
|
if need_gc:
|
||||||
current_time = time.perf_counter()
|
current_time = time.perf_counter()
|
||||||
if (current_time - last_gc_collect) > gc_collect_interval:
|
if (current_time - last_gc_collect) > gc_collect_interval:
|
||||||
|
comfy.model_management.cleanup_models()
|
||||||
gc.collect()
|
gc.collect()
|
||||||
comfy.model_management.soft_empty_cache()
|
comfy.model_management.soft_empty_cache()
|
||||||
last_gc_collect = current_time
|
last_gc_collect = current_time
|
||||||
@ -182,14 +183,14 @@ def load_extra_path_config(yaml_path):
|
|||||||
full_path = y
|
full_path = y
|
||||||
if base_path is not None:
|
if base_path is not None:
|
||||||
full_path = os.path.join(base_path, full_path)
|
full_path = os.path.join(base_path, full_path)
|
||||||
print("Adding extra search path", x, full_path)
|
logging.info("Adding extra search path {} {}".format(x, full_path))
|
||||||
folder_paths.add_model_folder_path(x, full_path)
|
folder_paths.add_model_folder_path(x, full_path)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
if args.temp_directory:
|
if args.temp_directory:
|
||||||
temp_dir = os.path.join(os.path.abspath(args.temp_directory), "temp")
|
temp_dir = os.path.join(os.path.abspath(args.temp_directory), "temp")
|
||||||
print(f"Setting temp directory to: {temp_dir}")
|
logging.info(f"Setting temp directory to: {temp_dir}")
|
||||||
folder_paths.set_temp_directory(temp_dir)
|
folder_paths.set_temp_directory(temp_dir)
|
||||||
cleanup_temp()
|
cleanup_temp()
|
||||||
|
|
||||||
@ -224,7 +225,7 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
if args.output_directory:
|
if args.output_directory:
|
||||||
output_dir = os.path.abspath(args.output_directory)
|
output_dir = os.path.abspath(args.output_directory)
|
||||||
print(f"Setting output directory to: {output_dir}")
|
logging.info(f"Setting output directory to: {output_dir}")
|
||||||
folder_paths.set_output_directory(output_dir)
|
folder_paths.set_output_directory(output_dir)
|
||||||
|
|
||||||
#These are the default folders that checkpoints, clip and vae models will be saved to when using CheckpointSave, etc.. nodes
|
#These are the default folders that checkpoints, clip and vae models will be saved to when using CheckpointSave, etc.. nodes
|
||||||
@ -234,7 +235,7 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
if args.input_directory:
|
if args.input_directory:
|
||||||
input_dir = os.path.abspath(args.input_directory)
|
input_dir = os.path.abspath(args.input_directory)
|
||||||
print(f"Setting input directory to: {input_dir}")
|
logging.info(f"Setting input directory to: {input_dir}")
|
||||||
folder_paths.set_input_directory(input_dir)
|
folder_paths.set_input_directory(input_dir)
|
||||||
|
|
||||||
if args.quick_test_for_ci:
|
if args.quick_test_for_ci:
|
||||||
@ -242,16 +243,16 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
call_on_start = None
|
call_on_start = None
|
||||||
if args.auto_launch:
|
if args.auto_launch:
|
||||||
def startup_server(address, port):
|
def startup_server(scheme, address, port):
|
||||||
import webbrowser
|
import webbrowser
|
||||||
if os.name == 'nt' and address == '0.0.0.0':
|
if os.name == 'nt' and address == '0.0.0.0':
|
||||||
address = '127.0.0.1'
|
address = '127.0.0.1'
|
||||||
webbrowser.open(f"http://{address}:{port}")
|
webbrowser.open(f"{scheme}://{address}:{port}")
|
||||||
call_on_start = startup_server
|
call_on_start = startup_server
|
||||||
|
|
||||||
try:
|
try:
|
||||||
loop.run_until_complete(run(server, address=args.listen, port=args.port, verbose=not args.dont_print_server, call_on_start=call_on_start))
|
loop.run_until_complete(run(server, address=args.listen, port=args.port, verbose=not args.dont_print_server, call_on_start=call_on_start))
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("\nStopped server")
|
logging.info("\nStopped server")
|
||||||
|
|
||||||
cleanup_temp()
|
cleanup_temp()
|
||||||
|
|||||||
24
node_helpers.py
Normal file
24
node_helpers.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
from PIL import ImageFile, UnidentifiedImageError
|
||||||
|
|
||||||
|
def conditioning_set_values(conditioning, values={}):
|
||||||
|
c = []
|
||||||
|
for t in conditioning:
|
||||||
|
n = [t[0], t[1].copy()]
|
||||||
|
for k in values:
|
||||||
|
n[1][k] = values[k]
|
||||||
|
c.append(n)
|
||||||
|
|
||||||
|
return c
|
||||||
|
|
||||||
|
def pillow(fn, arg):
|
||||||
|
prev_value = None
|
||||||
|
try:
|
||||||
|
x = fn(arg)
|
||||||
|
except (OSError, UnidentifiedImageError, ValueError): #PIL issues #4472 and #2445, also fixes ComfyUI issue #3416
|
||||||
|
prev_value = ImageFile.LOAD_TRUNCATED_IMAGES
|
||||||
|
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
||||||
|
x = fn(arg)
|
||||||
|
finally:
|
||||||
|
if prev_value is not None:
|
||||||
|
ImageFile.LOAD_TRUNCATED_IMAGES = prev_value
|
||||||
|
return x
|
||||||
120
nodes.py
120
nodes.py
@ -8,15 +8,16 @@ import traceback
|
|||||||
import math
|
import math
|
||||||
import time
|
import time
|
||||||
import random
|
import random
|
||||||
|
import logging
|
||||||
|
|
||||||
from PIL import Image, ImageOps, ImageSequence
|
from PIL import Image, ImageOps, ImageSequence, ImageFile
|
||||||
from PIL.PngImagePlugin import PngInfo
|
from PIL.PngImagePlugin import PngInfo
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import safetensors.torch
|
import safetensors.torch
|
||||||
|
|
||||||
sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)), "comfy"))
|
sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)), "comfy"))
|
||||||
|
|
||||||
|
|
||||||
import comfy.diffusers_load
|
import comfy.diffusers_load
|
||||||
import comfy.samplers
|
import comfy.samplers
|
||||||
import comfy.sample
|
import comfy.sample
|
||||||
@ -33,6 +34,7 @@ import importlib
|
|||||||
|
|
||||||
import folder_paths
|
import folder_paths
|
||||||
import latent_preview
|
import latent_preview
|
||||||
|
import node_helpers
|
||||||
|
|
||||||
def before_node_execution():
|
def before_node_execution():
|
||||||
comfy.model_management.throw_exception_if_processing_interrupted()
|
comfy.model_management.throw_exception_if_processing_interrupted()
|
||||||
@ -40,12 +42,12 @@ def before_node_execution():
|
|||||||
def interrupt_processing(value=True):
|
def interrupt_processing(value=True):
|
||||||
comfy.model_management.interrupt_current_processing(value)
|
comfy.model_management.interrupt_current_processing(value)
|
||||||
|
|
||||||
MAX_RESOLUTION=8192
|
MAX_RESOLUTION=16384
|
||||||
|
|
||||||
class CLIPTextEncode:
|
class CLIPTextEncode:
|
||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(s):
|
def INPUT_TYPES(s):
|
||||||
return {"required": {"text": ("STRING", {"multiline": True}), "clip": ("CLIP", )}}
|
return {"required": {"text": ("STRING", {"multiline": True, "dynamicPrompts": True}), "clip": ("CLIP", )}}
|
||||||
RETURN_TYPES = ("CONDITIONING",)
|
RETURN_TYPES = ("CONDITIONING",)
|
||||||
FUNCTION = "encode"
|
FUNCTION = "encode"
|
||||||
|
|
||||||
@ -83,7 +85,7 @@ class ConditioningAverage :
|
|||||||
out = []
|
out = []
|
||||||
|
|
||||||
if len(conditioning_from) > 1:
|
if len(conditioning_from) > 1:
|
||||||
print("Warning: ConditioningAverage conditioning_from contains more than 1 cond, only the first one will actually be applied to conditioning_to.")
|
logging.warning("Warning: ConditioningAverage conditioning_from contains more than 1 cond, only the first one will actually be applied to conditioning_to.")
|
||||||
|
|
||||||
cond_from = conditioning_from[0][0]
|
cond_from = conditioning_from[0][0]
|
||||||
pooled_output_from = conditioning_from[0][1].get("pooled_output", None)
|
pooled_output_from = conditioning_from[0][1].get("pooled_output", None)
|
||||||
@ -122,7 +124,7 @@ class ConditioningConcat:
|
|||||||
out = []
|
out = []
|
||||||
|
|
||||||
if len(conditioning_from) > 1:
|
if len(conditioning_from) > 1:
|
||||||
print("Warning: ConditioningConcat conditioning_from contains more than 1 cond, only the first one will actually be applied to conditioning_to.")
|
logging.warning("Warning: ConditioningConcat conditioning_from contains more than 1 cond, only the first one will actually be applied to conditioning_to.")
|
||||||
|
|
||||||
cond_from = conditioning_from[0][0]
|
cond_from = conditioning_from[0][0]
|
||||||
|
|
||||||
@ -150,13 +152,9 @@ class ConditioningSetArea:
|
|||||||
CATEGORY = "conditioning"
|
CATEGORY = "conditioning"
|
||||||
|
|
||||||
def append(self, conditioning, width, height, x, y, strength):
|
def append(self, conditioning, width, height, x, y, strength):
|
||||||
c = []
|
c = node_helpers.conditioning_set_values(conditioning, {"area": (height // 8, width // 8, y // 8, x // 8),
|
||||||
for t in conditioning:
|
"strength": strength,
|
||||||
n = [t[0], t[1].copy()]
|
"set_area_to_bounds": False})
|
||||||
n[1]['area'] = (height // 8, width // 8, y // 8, x // 8)
|
|
||||||
n[1]['strength'] = strength
|
|
||||||
n[1]['set_area_to_bounds'] = False
|
|
||||||
c.append(n)
|
|
||||||
return (c, )
|
return (c, )
|
||||||
|
|
||||||
class ConditioningSetAreaPercentage:
|
class ConditioningSetAreaPercentage:
|
||||||
@ -175,13 +173,9 @@ class ConditioningSetAreaPercentage:
|
|||||||
CATEGORY = "conditioning"
|
CATEGORY = "conditioning"
|
||||||
|
|
||||||
def append(self, conditioning, width, height, x, y, strength):
|
def append(self, conditioning, width, height, x, y, strength):
|
||||||
c = []
|
c = node_helpers.conditioning_set_values(conditioning, {"area": ("percentage", height, width, y, x),
|
||||||
for t in conditioning:
|
"strength": strength,
|
||||||
n = [t[0], t[1].copy()]
|
"set_area_to_bounds": False})
|
||||||
n[1]['area'] = ("percentage", height, width, y, x)
|
|
||||||
n[1]['strength'] = strength
|
|
||||||
n[1]['set_area_to_bounds'] = False
|
|
||||||
c.append(n)
|
|
||||||
return (c, )
|
return (c, )
|
||||||
|
|
||||||
class ConditioningSetAreaStrength:
|
class ConditioningSetAreaStrength:
|
||||||
@ -196,11 +190,7 @@ class ConditioningSetAreaStrength:
|
|||||||
CATEGORY = "conditioning"
|
CATEGORY = "conditioning"
|
||||||
|
|
||||||
def append(self, conditioning, strength):
|
def append(self, conditioning, strength):
|
||||||
c = []
|
c = node_helpers.conditioning_set_values(conditioning, {"strength": strength})
|
||||||
for t in conditioning:
|
|
||||||
n = [t[0], t[1].copy()]
|
|
||||||
n[1]['strength'] = strength
|
|
||||||
c.append(n)
|
|
||||||
return (c, )
|
return (c, )
|
||||||
|
|
||||||
|
|
||||||
@ -218,19 +208,15 @@ class ConditioningSetMask:
|
|||||||
CATEGORY = "conditioning"
|
CATEGORY = "conditioning"
|
||||||
|
|
||||||
def append(self, conditioning, mask, set_cond_area, strength):
|
def append(self, conditioning, mask, set_cond_area, strength):
|
||||||
c = []
|
|
||||||
set_area_to_bounds = False
|
set_area_to_bounds = False
|
||||||
if set_cond_area != "default":
|
if set_cond_area != "default":
|
||||||
set_area_to_bounds = True
|
set_area_to_bounds = True
|
||||||
if len(mask.shape) < 3:
|
if len(mask.shape) < 3:
|
||||||
mask = mask.unsqueeze(0)
|
mask = mask.unsqueeze(0)
|
||||||
for t in conditioning:
|
|
||||||
n = [t[0], t[1].copy()]
|
c = node_helpers.conditioning_set_values(conditioning, {"mask": mask,
|
||||||
_, h, w = mask.shape
|
"set_area_to_bounds": set_area_to_bounds,
|
||||||
n[1]['mask'] = mask
|
"mask_strength": strength})
|
||||||
n[1]['set_area_to_bounds'] = set_area_to_bounds
|
|
||||||
n[1]['mask_strength'] = strength
|
|
||||||
c.append(n)
|
|
||||||
return (c, )
|
return (c, )
|
||||||
|
|
||||||
class ConditioningZeroOut:
|
class ConditioningZeroOut:
|
||||||
@ -265,13 +251,8 @@ class ConditioningSetTimestepRange:
|
|||||||
CATEGORY = "advanced/conditioning"
|
CATEGORY = "advanced/conditioning"
|
||||||
|
|
||||||
def set_range(self, conditioning, start, end):
|
def set_range(self, conditioning, start, end):
|
||||||
c = []
|
c = node_helpers.conditioning_set_values(conditioning, {"start_percent": start,
|
||||||
for t in conditioning:
|
"end_percent": end})
|
||||||
d = t[1].copy()
|
|
||||||
d['start_percent'] = start
|
|
||||||
d['end_percent'] = end
|
|
||||||
n = [t[0], d]
|
|
||||||
c.append(n)
|
|
||||||
return (c, )
|
return (c, )
|
||||||
|
|
||||||
class VAEDecode:
|
class VAEDecode:
|
||||||
@ -412,13 +393,8 @@ class InpaintModelConditioning:
|
|||||||
|
|
||||||
out = []
|
out = []
|
||||||
for conditioning in [positive, negative]:
|
for conditioning in [positive, negative]:
|
||||||
c = []
|
c = node_helpers.conditioning_set_values(conditioning, {"concat_latent_image": concat_latent,
|
||||||
for t in conditioning:
|
"concat_mask": mask})
|
||||||
d = t[1].copy()
|
|
||||||
d["concat_latent_image"] = concat_latent
|
|
||||||
d["concat_mask"] = mask
|
|
||||||
n = [t[0], d]
|
|
||||||
c.append(n)
|
|
||||||
out.append(c)
|
out.append(c)
|
||||||
return (out[0], out[1], out_latent)
|
return (out[0], out[1], out_latent)
|
||||||
|
|
||||||
@ -607,8 +583,8 @@ class LoraLoader:
|
|||||||
return {"required": { "model": ("MODEL",),
|
return {"required": { "model": ("MODEL",),
|
||||||
"clip": ("CLIP", ),
|
"clip": ("CLIP", ),
|
||||||
"lora_name": (folder_paths.get_filename_list("loras"), ),
|
"lora_name": (folder_paths.get_filename_list("loras"), ),
|
||||||
"strength_model": ("FLOAT", {"default": 1.0, "min": -20.0, "max": 20.0, "step": 0.01}),
|
"strength_model": ("FLOAT", {"default": 1.0, "min": -100.0, "max": 100.0, "step": 0.01}),
|
||||||
"strength_clip": ("FLOAT", {"default": 1.0, "min": -20.0, "max": 20.0, "step": 0.01}),
|
"strength_clip": ("FLOAT", {"default": 1.0, "min": -100.0, "max": 100.0, "step": 0.01}),
|
||||||
}}
|
}}
|
||||||
RETURN_TYPES = ("MODEL", "CLIP")
|
RETURN_TYPES = ("MODEL", "CLIP")
|
||||||
FUNCTION = "load_lora"
|
FUNCTION = "load_lora"
|
||||||
@ -641,7 +617,7 @@ class LoraLoaderModelOnly(LoraLoader):
|
|||||||
def INPUT_TYPES(s):
|
def INPUT_TYPES(s):
|
||||||
return {"required": { "model": ("MODEL",),
|
return {"required": { "model": ("MODEL",),
|
||||||
"lora_name": (folder_paths.get_filename_list("loras"), ),
|
"lora_name": (folder_paths.get_filename_list("loras"), ),
|
||||||
"strength_model": ("FLOAT", {"default": 1.0, "min": -20.0, "max": 20.0, "step": 0.01}),
|
"strength_model": ("FLOAT", {"default": 1.0, "min": -100.0, "max": 100.0, "step": 0.01}),
|
||||||
}}
|
}}
|
||||||
RETURN_TYPES = ("MODEL",)
|
RETURN_TYPES = ("MODEL",)
|
||||||
FUNCTION = "load_lora_model_only"
|
FUNCTION = "load_lora_model_only"
|
||||||
@ -990,7 +966,7 @@ class GLIGENTextBoxApply:
|
|||||||
return {"required": {"conditioning_to": ("CONDITIONING", ),
|
return {"required": {"conditioning_to": ("CONDITIONING", ),
|
||||||
"clip": ("CLIP", ),
|
"clip": ("CLIP", ),
|
||||||
"gligen_textbox_model": ("GLIGEN", ),
|
"gligen_textbox_model": ("GLIGEN", ),
|
||||||
"text": ("STRING", {"multiline": True}),
|
"text": ("STRING", {"multiline": True, "dynamicPrompts": True}),
|
||||||
"width": ("INT", {"default": 64, "min": 8, "max": MAX_RESOLUTION, "step": 8}),
|
"width": ("INT", {"default": 64, "min": 8, "max": MAX_RESOLUTION, "step": 8}),
|
||||||
"height": ("INT", {"default": 64, "min": 8, "max": MAX_RESOLUTION, "step": 8}),
|
"height": ("INT", {"default": 64, "min": 8, "max": MAX_RESOLUTION, "step": 8}),
|
||||||
"x": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 8}),
|
"x": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 8}),
|
||||||
@ -1480,11 +1456,14 @@ class LoadImage:
|
|||||||
FUNCTION = "load_image"
|
FUNCTION = "load_image"
|
||||||
def load_image(self, image):
|
def load_image(self, image):
|
||||||
image_path = folder_paths.get_annotated_filepath(image)
|
image_path = folder_paths.get_annotated_filepath(image)
|
||||||
img = Image.open(image_path)
|
|
||||||
|
img = node_helpers.pillow(Image.open, image_path)
|
||||||
|
|
||||||
output_images = []
|
output_images = []
|
||||||
output_masks = []
|
output_masks = []
|
||||||
for i in ImageSequence.Iterator(img):
|
for i in ImageSequence.Iterator(img):
|
||||||
i = ImageOps.exif_transpose(i)
|
i = node_helpers.pillow(ImageOps.exif_transpose, i)
|
||||||
|
|
||||||
if i.mode == 'I':
|
if i.mode == 'I':
|
||||||
i = i.point(lambda i: i * (1 / 255))
|
i = i.point(lambda i: i * (1 / 255))
|
||||||
image = i.convert("RGB")
|
image = i.convert("RGB")
|
||||||
@ -1539,8 +1518,8 @@ class LoadImageMask:
|
|||||||
FUNCTION = "load_image"
|
FUNCTION = "load_image"
|
||||||
def load_image(self, image, channel):
|
def load_image(self, image, channel):
|
||||||
image_path = folder_paths.get_annotated_filepath(image)
|
image_path = folder_paths.get_annotated_filepath(image)
|
||||||
i = Image.open(image_path)
|
i = node_helpers.pillow(Image.open, image_path)
|
||||||
i = ImageOps.exif_transpose(i)
|
i = node_helpers.pillow(ImageOps.exif_transpose, i)
|
||||||
if i.getbands() != ("R", "G", "B", "A"):
|
if i.getbands() != ("R", "G", "B", "A"):
|
||||||
if i.mode == 'I':
|
if i.mode == 'I':
|
||||||
i = i.point(lambda i: i * (1 / 255))
|
i = i.point(lambda i: i * (1 / 255))
|
||||||
@ -1875,6 +1854,7 @@ def load_custom_node(module_path, ignore=set()):
|
|||||||
sp = os.path.splitext(module_path)
|
sp = os.path.splitext(module_path)
|
||||||
module_name = sp[0]
|
module_name = sp[0]
|
||||||
try:
|
try:
|
||||||
|
logging.debug("Trying to load custom node {}".format(module_path))
|
||||||
if os.path.isfile(module_path):
|
if os.path.isfile(module_path):
|
||||||
module_spec = importlib.util.spec_from_file_location(module_name, module_path)
|
module_spec = importlib.util.spec_from_file_location(module_name, module_path)
|
||||||
module_dir = os.path.split(module_path)[0]
|
module_dir = os.path.split(module_path)[0]
|
||||||
@ -1899,11 +1879,11 @@ def load_custom_node(module_path, ignore=set()):
|
|||||||
NODE_DISPLAY_NAME_MAPPINGS.update(module.NODE_DISPLAY_NAME_MAPPINGS)
|
NODE_DISPLAY_NAME_MAPPINGS.update(module.NODE_DISPLAY_NAME_MAPPINGS)
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
print(f"Skip {module_path} module for custom nodes due to the lack of NODE_CLASS_MAPPINGS.")
|
logging.warning(f"Skip {module_path} module for custom nodes due to the lack of NODE_CLASS_MAPPINGS.")
|
||||||
return False
|
return False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(traceback.format_exc())
|
logging.warning(traceback.format_exc())
|
||||||
print(f"Cannot import {module_path} module for custom nodes:", e)
|
logging.warning(f"Cannot import {module_path} module for custom nodes: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def load_custom_nodes():
|
def load_custom_nodes():
|
||||||
@ -1924,14 +1904,14 @@ def load_custom_nodes():
|
|||||||
node_import_times.append((time.perf_counter() - time_before, module_path, success))
|
node_import_times.append((time.perf_counter() - time_before, module_path, success))
|
||||||
|
|
||||||
if len(node_import_times) > 0:
|
if len(node_import_times) > 0:
|
||||||
print("\nImport times for custom nodes:")
|
logging.info("\nImport times for custom nodes:")
|
||||||
for n in sorted(node_import_times):
|
for n in sorted(node_import_times):
|
||||||
if n[2]:
|
if n[2]:
|
||||||
import_message = ""
|
import_message = ""
|
||||||
else:
|
else:
|
||||||
import_message = " (IMPORT FAILED)"
|
import_message = " (IMPORT FAILED)"
|
||||||
print("{:6.1f} seconds{}:".format(n[0], import_message), n[1])
|
logging.info("{:6.1f} seconds{}: {}".format(n[0], import_message, n[1]))
|
||||||
print()
|
logging.info("")
|
||||||
|
|
||||||
def init_custom_nodes():
|
def init_custom_nodes():
|
||||||
extras_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "comfy_extras")
|
extras_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "comfy_extras")
|
||||||
@ -1963,6 +1943,12 @@ def init_custom_nodes():
|
|||||||
"nodes_morphology.py",
|
"nodes_morphology.py",
|
||||||
"nodes_stable_cascade.py",
|
"nodes_stable_cascade.py",
|
||||||
"nodes_differential_diffusion.py",
|
"nodes_differential_diffusion.py",
|
||||||
|
"nodes_ip2p.py",
|
||||||
|
"nodes_model_merging_model_specific.py",
|
||||||
|
"nodes_pag.py",
|
||||||
|
"nodes_align_your_steps.py",
|
||||||
|
"nodes_attention_multiply.py",
|
||||||
|
"nodes_advanced_samplers.py",
|
||||||
]
|
]
|
||||||
|
|
||||||
import_failed = []
|
import_failed = []
|
||||||
@ -1973,12 +1959,12 @@ def init_custom_nodes():
|
|||||||
load_custom_nodes()
|
load_custom_nodes()
|
||||||
|
|
||||||
if len(import_failed) > 0:
|
if len(import_failed) > 0:
|
||||||
print("WARNING: some comfy_extras/ nodes did not import correctly. This may be because they are missing some dependencies.\n")
|
logging.warning("WARNING: some comfy_extras/ nodes did not import correctly. This may be because they are missing some dependencies.\n")
|
||||||
for node in import_failed:
|
for node in import_failed:
|
||||||
print("IMPORT FAILED: {}".format(node))
|
logging.warning("IMPORT FAILED: {}".format(node))
|
||||||
print("\nThis issue might be caused by missing dependencies.")
|
logging.warning("\nThis issue might be caused by new missing dependencies added the last time you updated ComfyUI.")
|
||||||
if args.windows_standalone_build:
|
if args.windows_standalone_build:
|
||||||
print("Please run the update script: update/update_comfyui.bat")
|
logging.warning("Please run the update script: update/update_comfyui.bat")
|
||||||
else:
|
else:
|
||||||
print("Please do a: pip install -r requirements.txt")
|
logging.warning("Please do a: pip install -r requirements.txt")
|
||||||
print()
|
logging.warning("")
|
||||||
|
|||||||
159
script_examples/websockets_api_example_ws_images.py
Normal file
159
script_examples/websockets_api_example_ws_images.py
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
#This is an example that uses the websockets api and the SaveImageWebsocket node to get images directly without
|
||||||
|
#them being saved to disk
|
||||||
|
|
||||||
|
import websocket #NOTE: websocket-client (https://github.com/websocket-client/websocket-client)
|
||||||
|
import uuid
|
||||||
|
import json
|
||||||
|
import urllib.request
|
||||||
|
import urllib.parse
|
||||||
|
|
||||||
|
server_address = "127.0.0.1:8188"
|
||||||
|
client_id = str(uuid.uuid4())
|
||||||
|
|
||||||
|
def queue_prompt(prompt):
|
||||||
|
p = {"prompt": prompt, "client_id": client_id}
|
||||||
|
data = json.dumps(p).encode('utf-8')
|
||||||
|
req = urllib.request.Request("http://{}/prompt".format(server_address), data=data)
|
||||||
|
return json.loads(urllib.request.urlopen(req).read())
|
||||||
|
|
||||||
|
def get_image(filename, subfolder, folder_type):
|
||||||
|
data = {"filename": filename, "subfolder": subfolder, "type": folder_type}
|
||||||
|
url_values = urllib.parse.urlencode(data)
|
||||||
|
with urllib.request.urlopen("http://{}/view?{}".format(server_address, url_values)) as response:
|
||||||
|
return response.read()
|
||||||
|
|
||||||
|
def get_history(prompt_id):
|
||||||
|
with urllib.request.urlopen("http://{}/history/{}".format(server_address, prompt_id)) as response:
|
||||||
|
return json.loads(response.read())
|
||||||
|
|
||||||
|
def get_images(ws, prompt):
|
||||||
|
prompt_id = queue_prompt(prompt)['prompt_id']
|
||||||
|
output_images = {}
|
||||||
|
current_node = ""
|
||||||
|
while True:
|
||||||
|
out = ws.recv()
|
||||||
|
if isinstance(out, str):
|
||||||
|
message = json.loads(out)
|
||||||
|
if message['type'] == 'executing':
|
||||||
|
data = message['data']
|
||||||
|
if data['prompt_id'] == prompt_id:
|
||||||
|
if data['node'] is None:
|
||||||
|
break #Execution is done
|
||||||
|
else:
|
||||||
|
current_node = data['node']
|
||||||
|
else:
|
||||||
|
if current_node == 'save_image_websocket_node':
|
||||||
|
images_output = output_images.get(current_node, [])
|
||||||
|
images_output.append(out[8:])
|
||||||
|
output_images[current_node] = images_output
|
||||||
|
|
||||||
|
return output_images
|
||||||
|
|
||||||
|
prompt_text = """
|
||||||
|
{
|
||||||
|
"3": {
|
||||||
|
"class_type": "KSampler",
|
||||||
|
"inputs": {
|
||||||
|
"cfg": 8,
|
||||||
|
"denoise": 1,
|
||||||
|
"latent_image": [
|
||||||
|
"5",
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"model": [
|
||||||
|
"4",
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"negative": [
|
||||||
|
"7",
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"positive": [
|
||||||
|
"6",
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"sampler_name": "euler",
|
||||||
|
"scheduler": "normal",
|
||||||
|
"seed": 8566257,
|
||||||
|
"steps": 20
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"4": {
|
||||||
|
"class_type": "CheckpointLoaderSimple",
|
||||||
|
"inputs": {
|
||||||
|
"ckpt_name": "v1-5-pruned-emaonly.ckpt"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"5": {
|
||||||
|
"class_type": "EmptyLatentImage",
|
||||||
|
"inputs": {
|
||||||
|
"batch_size": 1,
|
||||||
|
"height": 512,
|
||||||
|
"width": 512
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"6": {
|
||||||
|
"class_type": "CLIPTextEncode",
|
||||||
|
"inputs": {
|
||||||
|
"clip": [
|
||||||
|
"4",
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"text": "masterpiece best quality girl"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"7": {
|
||||||
|
"class_type": "CLIPTextEncode",
|
||||||
|
"inputs": {
|
||||||
|
"clip": [
|
||||||
|
"4",
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"text": "bad hands"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"8": {
|
||||||
|
"class_type": "VAEDecode",
|
||||||
|
"inputs": {
|
||||||
|
"samples": [
|
||||||
|
"3",
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"vae": [
|
||||||
|
"4",
|
||||||
|
2
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"save_image_websocket_node": {
|
||||||
|
"class_type": "SaveImageWebsocket",
|
||||||
|
"inputs": {
|
||||||
|
"images": [
|
||||||
|
"8",
|
||||||
|
0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
prompt = json.loads(prompt_text)
|
||||||
|
#set the text prompt for our positive CLIPTextEncode
|
||||||
|
prompt["6"]["inputs"]["text"] = "masterpiece best quality man"
|
||||||
|
|
||||||
|
#set the seed for our KSampler node
|
||||||
|
prompt["3"]["inputs"]["seed"] = 5
|
||||||
|
|
||||||
|
ws = websocket.WebSocket()
|
||||||
|
ws.connect("ws://{}/ws?clientId={}".format(server_address, client_id))
|
||||||
|
images = get_images(ws, prompt)
|
||||||
|
|
||||||
|
#Commented out code to display the output images:
|
||||||
|
|
||||||
|
# for node_id in images:
|
||||||
|
# for image_data in images[node_id]:
|
||||||
|
# from PIL import Image
|
||||||
|
# import io
|
||||||
|
# image = Image.open(io.BytesIO(image_data))
|
||||||
|
# image.show()
|
||||||
|
|
||||||
45
server.py
45
server.py
@ -11,19 +11,14 @@ import urllib
|
|||||||
import json
|
import json
|
||||||
import glob
|
import glob
|
||||||
import struct
|
import struct
|
||||||
|
import ssl
|
||||||
from PIL import Image, ImageOps
|
from PIL import Image, ImageOps
|
||||||
from PIL.PngImagePlugin import PngInfo
|
from PIL.PngImagePlugin import PngInfo
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
try:
|
import aiohttp
|
||||||
import aiohttp
|
from aiohttp import web
|
||||||
from aiohttp import web
|
import logging
|
||||||
except ImportError:
|
|
||||||
print("Module 'aiohttp' not installed. Please install it via:")
|
|
||||||
print("pip install aiohttp")
|
|
||||||
print("or")
|
|
||||||
print("pip install -r requirements.txt")
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
import mimetypes
|
import mimetypes
|
||||||
from comfy.cli_args import args
|
from comfy.cli_args import args
|
||||||
@ -40,7 +35,7 @@ async def send_socket_catch_exception(function, message):
|
|||||||
try:
|
try:
|
||||||
await function(message)
|
await function(message)
|
||||||
except (aiohttp.ClientError, aiohttp.ClientPayloadError, ConnectionResetError) as err:
|
except (aiohttp.ClientError, aiohttp.ClientPayloadError, ConnectionResetError) as err:
|
||||||
print("send error:", err)
|
logging.warning("send error: {}".format(err))
|
||||||
|
|
||||||
@web.middleware
|
@web.middleware
|
||||||
async def cache_control(request: web.Request, handler):
|
async def cache_control(request: web.Request, handler):
|
||||||
@ -118,7 +113,7 @@ class PromptServer():
|
|||||||
|
|
||||||
async for msg in ws:
|
async for msg in ws:
|
||||||
if msg.type == aiohttp.WSMsgType.ERROR:
|
if msg.type == aiohttp.WSMsgType.ERROR:
|
||||||
print('ws connection closed with exception %s' % ws.exception())
|
logging.warning('ws connection closed with exception %s' % ws.exception())
|
||||||
finally:
|
finally:
|
||||||
self.sockets.pop(sid, None)
|
self.sockets.pop(sid, None)
|
||||||
return ws
|
return ws
|
||||||
@ -419,8 +414,8 @@ class PromptServer():
|
|||||||
try:
|
try:
|
||||||
out[x] = node_info(x)
|
out[x] = node_info(x)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[ERROR] An error occurred while retrieving information for the '{x}' node.", file=sys.stderr)
|
logging.error(f"[ERROR] An error occurred while retrieving information for the '{x}' node.")
|
||||||
traceback.print_exc()
|
logging.error(traceback.format_exc())
|
||||||
return web.json_response(out)
|
return web.json_response(out)
|
||||||
|
|
||||||
@routes.get("/object_info/{node_class}")
|
@routes.get("/object_info/{node_class}")
|
||||||
@ -453,7 +448,7 @@ class PromptServer():
|
|||||||
|
|
||||||
@routes.post("/prompt")
|
@routes.post("/prompt")
|
||||||
async def post_prompt(request):
|
async def post_prompt(request):
|
||||||
print("got prompt")
|
logging.info("got prompt")
|
||||||
resp_code = 200
|
resp_code = 200
|
||||||
out_string = ""
|
out_string = ""
|
||||||
json_data = await request.json()
|
json_data = await request.json()
|
||||||
@ -485,7 +480,7 @@ class PromptServer():
|
|||||||
response = {"prompt_id": prompt_id, "number": number, "node_errors": valid[3]}
|
response = {"prompt_id": prompt_id, "number": number, "node_errors": valid[3]}
|
||||||
return web.json_response(response)
|
return web.json_response(response)
|
||||||
else:
|
else:
|
||||||
print("invalid prompt:", valid[1])
|
logging.warning("invalid prompt: {}".format(valid[1]))
|
||||||
return web.json_response({"error": valid[1], "node_errors": valid[3]}, status=400)
|
return web.json_response({"error": valid[1], "node_errors": valid[3]}, status=400)
|
||||||
else:
|
else:
|
||||||
return web.json_response({"error": "no prompt", "node_errors": []}, status=400)
|
return web.json_response({"error": "no prompt", "node_errors": []}, status=400)
|
||||||
@ -629,14 +624,22 @@ class PromptServer():
|
|||||||
async def start(self, address, port, verbose=True, call_on_start=None):
|
async def start(self, address, port, verbose=True, call_on_start=None):
|
||||||
runner = web.AppRunner(self.app, access_log=None)
|
runner = web.AppRunner(self.app, access_log=None)
|
||||||
await runner.setup()
|
await runner.setup()
|
||||||
site = web.TCPSite(runner, address, port)
|
ssl_ctx = None
|
||||||
|
scheme = "http"
|
||||||
|
if args.tls_keyfile and args.tls_certfile:
|
||||||
|
ssl_ctx = ssl.SSLContext(protocol=ssl.PROTOCOL_TLS_SERVER, verify_mode=ssl.CERT_NONE)
|
||||||
|
ssl_ctx.load_cert_chain(certfile=args.tls_certfile,
|
||||||
|
keyfile=args.tls_keyfile)
|
||||||
|
scheme = "https"
|
||||||
|
|
||||||
|
site = web.TCPSite(runner, address, port, ssl_context=ssl_ctx)
|
||||||
await site.start()
|
await site.start()
|
||||||
|
|
||||||
if verbose:
|
if verbose:
|
||||||
print("Starting server\n")
|
logging.info("Starting server\n")
|
||||||
print("To see the GUI go to: http://{}:{}".format(address, port))
|
logging.info("To see the GUI go to: {}://{}:{}".format(scheme, address, port))
|
||||||
if call_on_start is not None:
|
if call_on_start is not None:
|
||||||
call_on_start(address, port)
|
call_on_start(scheme, address, port)
|
||||||
|
|
||||||
def add_on_prompt_handler(self, handler):
|
def add_on_prompt_handler(self, handler):
|
||||||
self.on_prompt_handlers.append(handler)
|
self.on_prompt_handlers.append(handler)
|
||||||
@ -646,7 +649,7 @@ class PromptServer():
|
|||||||
try:
|
try:
|
||||||
json_data = handler(json_data)
|
json_data = handler(json_data)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[ERROR] An error occurred during the on_prompt_handler processing")
|
logging.warning(f"[ERROR] An error occurred during the on_prompt_handler processing")
|
||||||
traceback.print_exc()
|
logging.warning(traceback.format_exc())
|
||||||
|
|
||||||
return json_data
|
return json_data
|
||||||
|
|||||||
@ -947,7 +947,7 @@ describe("group node", () => {
|
|||||||
expect(p1.widgets.value.widget.options?.step).toBe(80); // width/height step * 10
|
expect(p1.widgets.value.widget.options?.step).toBe(80); // width/height step * 10
|
||||||
|
|
||||||
expect(p2.widgets.value.widget.options?.min).toBe(16); // width/height min
|
expect(p2.widgets.value.widget.options?.min).toBe(16); // width/height min
|
||||||
expect(p2.widgets.value.widget.options?.max).toBe(8192); // width/height max
|
expect(p2.widgets.value.widget.options?.max).toBe(16384); // width/height max
|
||||||
expect(p2.widgets.value.widget.options?.step).toBe(80); // width/height step * 10
|
expect(p2.widgets.value.widget.options?.step).toBe(80); // width/height step * 10
|
||||||
|
|
||||||
expect(p1.widgets.value.value).toBe(128);
|
expect(p1.widgets.value.value).toBe(128);
|
||||||
|
|||||||
@ -204,13 +204,17 @@ export class EzWidget {
|
|||||||
convertToWidget() {
|
convertToWidget() {
|
||||||
if (!this.isConvertedToInput)
|
if (!this.isConvertedToInput)
|
||||||
throw new Error(`Widget ${this.widget.name} cannot be converted as it is already a widget.`);
|
throw new Error(`Widget ${this.widget.name} cannot be converted as it is already a widget.`);
|
||||||
this.node.menu[`Convert ${this.widget.name} to widget`].call();
|
var menu = this.node.menu["Convert Input to Widget"].item.submenu.options;
|
||||||
|
var index = menu.findIndex(a => a.content == `Convert ${this.widget.name} to widget`);
|
||||||
|
menu[index].callback.call();
|
||||||
}
|
}
|
||||||
|
|
||||||
convertToInput() {
|
convertToInput() {
|
||||||
if (this.isConvertedToInput)
|
if (this.isConvertedToInput)
|
||||||
throw new Error(`Widget ${this.widget.name} cannot be converted as it is already an input.`);
|
throw new Error(`Widget ${this.widget.name} cannot be converted as it is already an input.`);
|
||||||
this.node.menu[`Convert ${this.widget.name} to input`].call();
|
var menu = this.node.menu["Convert Widget to Input"].item.submenu.options;
|
||||||
|
var index = menu.findIndex(a => a.content == `Convert ${this.widget.name} to input`);
|
||||||
|
menu[index].callback.call();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -20,6 +20,10 @@ const colorPalettes = {
|
|||||||
"MODEL": "#B39DDB", // light lavender-purple
|
"MODEL": "#B39DDB", // light lavender-purple
|
||||||
"STYLE_MODEL": "#C2FFAE", // light green-yellow
|
"STYLE_MODEL": "#C2FFAE", // light green-yellow
|
||||||
"VAE": "#FF6E6E", // bright red
|
"VAE": "#FF6E6E", // bright red
|
||||||
|
"NOISE": "#B0B0B0", // gray
|
||||||
|
"GUIDER": "#66FFFF", // cyan
|
||||||
|
"SAMPLER": "#ECB4B4", // very soft red
|
||||||
|
"SIGMAS": "#CDFFCD", // soft lime green
|
||||||
"TAESD": "#DCC274", // cheesecake
|
"TAESD": "#DCC274", // cheesecake
|
||||||
},
|
},
|
||||||
"litegraph_base": {
|
"litegraph_base": {
|
||||||
|
|||||||
@ -17,7 +17,7 @@ app.registerExtension({
|
|||||||
// Locate dynamic prompt text widgets
|
// Locate dynamic prompt text widgets
|
||||||
// Include any widgets with dynamicPrompts set to true, and customtext
|
// Include any widgets with dynamicPrompts set to true, and customtext
|
||||||
const widgets = node.widgets.filter(
|
const widgets = node.widgets.filter(
|
||||||
(n) => (n.type === "customtext" && n.dynamicPrompts !== false) || n.dynamicPrompts
|
(n) => n.dynamicPrompts
|
||||||
);
|
);
|
||||||
for (const widget of widgets) {
|
for (const widget of widgets) {
|
||||||
// Override the serialization of the value to resolve dynamic prompts for all widgets supporting it in this node
|
// Override the serialization of the value to resolve dynamic prompts for all widgets supporting it in this node
|
||||||
|
|||||||
@ -164,6 +164,41 @@ class MaskEditorDialog extends ComfyDialog {
|
|||||||
return divElement;
|
return divElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createOpacitySlider(self, name, callback) {
|
||||||
|
const divElement = document.createElement('div');
|
||||||
|
divElement.id = "maskeditor-opacity-slider";
|
||||||
|
divElement.style.cssFloat = "left";
|
||||||
|
divElement.style.fontFamily = "sans-serif";
|
||||||
|
divElement.style.marginRight = "4px";
|
||||||
|
divElement.style.color = "var(--input-text)";
|
||||||
|
divElement.style.backgroundColor = "var(--comfy-input-bg)";
|
||||||
|
divElement.style.borderRadius = "8px";
|
||||||
|
divElement.style.borderColor = "var(--border-color)";
|
||||||
|
divElement.style.borderStyle = "solid";
|
||||||
|
divElement.style.fontSize = "15px";
|
||||||
|
divElement.style.height = "21px";
|
||||||
|
divElement.style.padding = "1px 6px";
|
||||||
|
divElement.style.display = "flex";
|
||||||
|
divElement.style.position = "relative";
|
||||||
|
divElement.style.top = "2px";
|
||||||
|
divElement.style.pointerEvents = "auto";
|
||||||
|
self.opacity_slider_input = document.createElement('input');
|
||||||
|
self.opacity_slider_input.setAttribute('type', 'range');
|
||||||
|
self.opacity_slider_input.setAttribute('min', '0.1');
|
||||||
|
self.opacity_slider_input.setAttribute('max', '1.0');
|
||||||
|
self.opacity_slider_input.setAttribute('step', '0.01')
|
||||||
|
self.opacity_slider_input.setAttribute('value', '0.7');
|
||||||
|
const labelElement = document.createElement("label");
|
||||||
|
labelElement.textContent = name;
|
||||||
|
|
||||||
|
divElement.appendChild(labelElement);
|
||||||
|
divElement.appendChild(self.opacity_slider_input);
|
||||||
|
|
||||||
|
self.opacity_slider_input.addEventListener("input", callback);
|
||||||
|
|
||||||
|
return divElement;
|
||||||
|
}
|
||||||
|
|
||||||
setlayout(imgCanvas, maskCanvas) {
|
setlayout(imgCanvas, maskCanvas) {
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
@ -203,6 +238,13 @@ class MaskEditorDialog extends ComfyDialog {
|
|||||||
self.updateBrushPreview(self, null, null);
|
self.updateBrushPreview(self, null, null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.brush_opacity_slider = this.createOpacitySlider(self, "Opacity", (event) => {
|
||||||
|
self.brush_opacity = event.target.value;
|
||||||
|
if (self.brush_color_mode !== "negative") {
|
||||||
|
self.maskCanvas.style.opacity = self.brush_opacity;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this.colorButton = this.createLeftButton(this.getColorButtonText(), () => {
|
this.colorButton = this.createLeftButton(this.getColorButtonText(), () => {
|
||||||
if (self.brush_color_mode === "black") {
|
if (self.brush_color_mode === "black") {
|
||||||
self.brush_color_mode = "white";
|
self.brush_color_mode = "white";
|
||||||
@ -237,6 +279,7 @@ class MaskEditorDialog extends ComfyDialog {
|
|||||||
bottom_panel.appendChild(this.saveButton);
|
bottom_panel.appendChild(this.saveButton);
|
||||||
bottom_panel.appendChild(cancelButton);
|
bottom_panel.appendChild(cancelButton);
|
||||||
bottom_panel.appendChild(this.brush_size_slider);
|
bottom_panel.appendChild(this.brush_size_slider);
|
||||||
|
bottom_panel.appendChild(this.brush_opacity_slider);
|
||||||
bottom_panel.appendChild(this.colorButton);
|
bottom_panel.appendChild(this.colorButton);
|
||||||
|
|
||||||
imgCanvas.style.position = "absolute";
|
imgCanvas.style.position = "absolute";
|
||||||
@ -472,7 +515,7 @@ class MaskEditorDialog extends ComfyDialog {
|
|||||||
else {
|
else {
|
||||||
return {
|
return {
|
||||||
mixBlendMode: "initial",
|
mixBlendMode: "initial",
|
||||||
opacity: "0.7",
|
opacity: this.brush_opacity,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -538,6 +581,7 @@ class MaskEditorDialog extends ComfyDialog {
|
|||||||
this.maskCtx.putImageData(maskData, 0, 0);
|
this.maskCtx.putImageData(maskData, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
brush_opacity = 0.7;
|
||||||
brush_size = 10;
|
brush_size = 10;
|
||||||
brush_color_mode = "black";
|
brush_color_mode = "black";
|
||||||
drawing_mode = false;
|
drawing_mode = false;
|
||||||
|
|||||||
@ -256,8 +256,18 @@ export function mergeIfValid(output, config2, forceUpdate, recreateWidget, confi
|
|||||||
return { customConfig };
|
return { customConfig };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let useConversionSubmenusSetting;
|
||||||
app.registerExtension({
|
app.registerExtension({
|
||||||
name: "Comfy.WidgetInputs",
|
name: "Comfy.WidgetInputs",
|
||||||
|
init() {
|
||||||
|
useConversionSubmenusSetting = app.ui.settings.addSetting({
|
||||||
|
id: "Comfy.NodeInputConversionSubmenus",
|
||||||
|
name: "Node widget/input conversion sub-menus",
|
||||||
|
tooltip: "In the node context menu, place the entries that convert between input/widget in sub-menus.",
|
||||||
|
type: "boolean",
|
||||||
|
defaultValue: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||||
// Add menu options to conver to/from widgets
|
// Add menu options to conver to/from widgets
|
||||||
const origGetExtraMenuOptions = nodeType.prototype.getExtraMenuOptions;
|
const origGetExtraMenuOptions = nodeType.prototype.getExtraMenuOptions;
|
||||||
@ -292,12 +302,31 @@ app.registerExtension({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (toInput.length) {
|
|
||||||
options.push(...toInput, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
//Convert.. main menu
|
||||||
|
if (toInput.length) {
|
||||||
|
if (useConversionSubmenusSetting.value) {
|
||||||
|
options.push({
|
||||||
|
content: "Convert Widget to Input",
|
||||||
|
submenu: {
|
||||||
|
options: toInput,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
options.push(...toInput, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (toWidget.length) {
|
if (toWidget.length) {
|
||||||
options.push(...toWidget, null);
|
if (useConversionSubmenusSetting.value) {
|
||||||
|
options.push({
|
||||||
|
content: "Convert Input to Widget",
|
||||||
|
submenu: {
|
||||||
|
options: toWidget,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
options.push(...toWidget, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7247,7 +7247,7 @@ LGraphNode.prototype.executeAction = function(action)
|
|||||||
//create links
|
//create links
|
||||||
for (var i = 0; i < clipboard_info.links.length; ++i) {
|
for (var i = 0; i < clipboard_info.links.length; ++i) {
|
||||||
var link_info = clipboard_info.links[i];
|
var link_info = clipboard_info.links[i];
|
||||||
var origin_node;
|
var origin_node = undefined;
|
||||||
var origin_node_relative_id = link_info[0];
|
var origin_node_relative_id = link_info[0];
|
||||||
if (origin_node_relative_id != null) {
|
if (origin_node_relative_id != null) {
|
||||||
origin_node = nodes[origin_node_relative_id];
|
origin_node = nodes[origin_node_relative_id];
|
||||||
|
|||||||
@ -263,6 +263,36 @@ export class ComfyApp {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#addRestoreWorkflowView() {
|
||||||
|
const serialize = LGraph.prototype.serialize;
|
||||||
|
const self = this;
|
||||||
|
LGraph.prototype.serialize = function() {
|
||||||
|
const workflow = serialize.apply(this, arguments);
|
||||||
|
|
||||||
|
// Store the drag & scale info in the serialized workflow if the setting is enabled
|
||||||
|
if (self.enableWorkflowViewRestore.value) {
|
||||||
|
if (!workflow.extra) {
|
||||||
|
workflow.extra = {};
|
||||||
|
}
|
||||||
|
workflow.extra.ds = {
|
||||||
|
scale: self.canvas.ds.scale,
|
||||||
|
offset: self.canvas.ds.offset,
|
||||||
|
};
|
||||||
|
} else if (workflow.extra?.ds) {
|
||||||
|
// Clear any old view data
|
||||||
|
delete workflow.extra.ds;
|
||||||
|
}
|
||||||
|
|
||||||
|
return workflow;
|
||||||
|
}
|
||||||
|
this.enableWorkflowViewRestore = this.ui.settings.addSetting({
|
||||||
|
id: "Comfy.EnableWorkflowViewRestore",
|
||||||
|
name: "Save and restore canvas position and zoom level in workflows",
|
||||||
|
type: "boolean",
|
||||||
|
defaultValue: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds special context menu handling for nodes
|
* Adds special context menu handling for nodes
|
||||||
* e.g. this adds Open Image functionality for nodes that show images
|
* e.g. this adds Open Image functionality for nodes that show images
|
||||||
@ -953,6 +983,12 @@ export class ComfyApp {
|
|||||||
|
|
||||||
const origProcessMouseDown = LGraphCanvas.prototype.processMouseDown;
|
const origProcessMouseDown = LGraphCanvas.prototype.processMouseDown;
|
||||||
LGraphCanvas.prototype.processMouseDown = function(e) {
|
LGraphCanvas.prototype.processMouseDown = function(e) {
|
||||||
|
// prepare for ctrl+shift drag: zoom start
|
||||||
|
if(e.ctrlKey && e.shiftKey && e.buttons) {
|
||||||
|
self.zoom_drag_start = [e.x, e.y, this.ds.scale];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const res = origProcessMouseDown.apply(this, arguments);
|
const res = origProcessMouseDown.apply(this, arguments);
|
||||||
|
|
||||||
this.selected_group_moving = false;
|
this.selected_group_moving = false;
|
||||||
@ -973,6 +1009,26 @@ export class ComfyApp {
|
|||||||
|
|
||||||
const origProcessMouseMove = LGraphCanvas.prototype.processMouseMove;
|
const origProcessMouseMove = LGraphCanvas.prototype.processMouseMove;
|
||||||
LGraphCanvas.prototype.processMouseMove = function(e) {
|
LGraphCanvas.prototype.processMouseMove = function(e) {
|
||||||
|
// handle ctrl+shift drag
|
||||||
|
if(e.ctrlKey && e.shiftKey && self.zoom_drag_start) {
|
||||||
|
// stop canvas zoom action
|
||||||
|
if(!e.buttons) {
|
||||||
|
self.zoom_drag_start = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate delta
|
||||||
|
let deltaY = e.y - self.zoom_drag_start[1];
|
||||||
|
let startScale = self.zoom_drag_start[2];
|
||||||
|
|
||||||
|
let scale = startScale - deltaY/100;
|
||||||
|
|
||||||
|
this.ds.changeScale(scale, [this.ds.element.width/2, this.ds.element.height/2]);
|
||||||
|
this.graph.change();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const orig_selected_group = this.selected_group;
|
const orig_selected_group = this.selected_group;
|
||||||
|
|
||||||
if (this.selected_group && !this.selected_group_resizing && !this.selected_group_moving) {
|
if (this.selected_group && !this.selected_group_resizing && !this.selected_group_moving) {
|
||||||
@ -1059,6 +1115,20 @@ export class ComfyApp {
|
|||||||
// Trigger onPaste
|
// Trigger onPaste
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if((e.key === '+') && e.altKey) {
|
||||||
|
block_default = true;
|
||||||
|
let scale = this.ds.scale * 1.1;
|
||||||
|
this.ds.changeScale(scale, [this.ds.element.width/2, this.ds.element.height/2]);
|
||||||
|
this.graph.change();
|
||||||
|
}
|
||||||
|
|
||||||
|
if((e.key === '-') && e.altKey) {
|
||||||
|
block_default = true;
|
||||||
|
let scale = this.ds.scale * 1 / 1.1;
|
||||||
|
this.ds.changeScale(scale, [this.ds.element.width/2, this.ds.element.height/2]);
|
||||||
|
this.graph.change();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.graph.change();
|
this.graph.change();
|
||||||
@ -1465,6 +1535,7 @@ export class ComfyApp {
|
|||||||
this.#addProcessKeyHandler();
|
this.#addProcessKeyHandler();
|
||||||
this.#addConfigureHandler();
|
this.#addConfigureHandler();
|
||||||
this.#addApiUpdateHandlers();
|
this.#addApiUpdateHandlers();
|
||||||
|
this.#addRestoreWorkflowView();
|
||||||
|
|
||||||
this.graph = new LGraph();
|
this.graph = new LGraph();
|
||||||
|
|
||||||
@ -1765,6 +1836,10 @@ export class ComfyApp {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
this.graph.configure(graphData);
|
this.graph.configure(graphData);
|
||||||
|
if (this.enableWorkflowViewRestore.value && graphData.extra?.ds) {
|
||||||
|
this.canvas.ds.offset = graphData.extra.ds.offset;
|
||||||
|
this.canvas.ds.scale = graphData.extra.ds.scale;
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
let errorHint = [];
|
let errorHint = [];
|
||||||
// Try extracting filename to see if it was caused by an extension script
|
// Try extracting filename to see if it was caused by an extension script
|
||||||
@ -2238,6 +2313,12 @@ export class ComfyApp {
|
|||||||
await this.#invokeExtensionsAsync("refreshComboInNodes", defs);
|
await this.#invokeExtensionsAsync("refreshComboInNodes", defs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resetView() {
|
||||||
|
app.canvas.ds.scale = 1;
|
||||||
|
app.canvas.ds.offset = [0, 0]
|
||||||
|
app.graph.setDirtyCanvas(true, true);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clean current state
|
* Clean current state
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -170,9 +170,12 @@ export async function importA1111(graph, parameters) {
|
|||||||
const opts = parameters
|
const opts = parameters
|
||||||
.substr(p)
|
.substr(p)
|
||||||
.split("\n")[1]
|
.split("\n")[1]
|
||||||
.split(",")
|
.match(new RegExp("\\s*([^:]+:\\s*([^\"\\{].*?|\".*?\"|\\{.*?\\}))\\s*(,|$)", "g"))
|
||||||
.reduce((p, n) => {
|
.reduce((p, n) => {
|
||||||
const s = n.split(":");
|
const s = n.split(":");
|
||||||
|
if (s[1].endsWith(',')) {
|
||||||
|
s[1] = s[1].substr(0, s[1].length -1);
|
||||||
|
}
|
||||||
p[s[0].trim().toLowerCase()] = s[1].trim();
|
p[s[0].trim().toLowerCase()] = s[1].trim();
|
||||||
return p;
|
return p;
|
||||||
}, {});
|
}, {});
|
||||||
@ -191,6 +194,7 @@ export async function importA1111(graph, parameters) {
|
|||||||
const vaeLoaderNode = LiteGraph.createNode("VAELoader");
|
const vaeLoaderNode = LiteGraph.createNode("VAELoader");
|
||||||
const saveNode = LiteGraph.createNode("SaveImage");
|
const saveNode = LiteGraph.createNode("SaveImage");
|
||||||
let hrSamplerNode = null;
|
let hrSamplerNode = null;
|
||||||
|
let hrSteps = null;
|
||||||
|
|
||||||
const ceil64 = (v) => Math.ceil(v / 64) * 64;
|
const ceil64 = (v) => Math.ceil(v / 64) * 64;
|
||||||
|
|
||||||
@ -290,6 +294,9 @@ export async function importA1111(graph, parameters) {
|
|||||||
model(v) {
|
model(v) {
|
||||||
setWidgetValue(ckptNode, "ckpt_name", v, true);
|
setWidgetValue(ckptNode, "ckpt_name", v, true);
|
||||||
},
|
},
|
||||||
|
"vae"(v) {
|
||||||
|
setWidgetValue(vaeLoaderNode, "vae_name", v, true);
|
||||||
|
},
|
||||||
"cfg scale"(v) {
|
"cfg scale"(v) {
|
||||||
setWidgetValue(samplerNode, "cfg", +v);
|
setWidgetValue(samplerNode, "cfg", +v);
|
||||||
},
|
},
|
||||||
@ -316,6 +323,7 @@ export async function importA1111(graph, parameters) {
|
|||||||
const h = ceil64(+wxh[1]);
|
const h = ceil64(+wxh[1]);
|
||||||
const hrUp = popOpt("hires upscale");
|
const hrUp = popOpt("hires upscale");
|
||||||
const hrSz = popOpt("hires resize");
|
const hrSz = popOpt("hires resize");
|
||||||
|
hrSteps = popOpt("hires steps");
|
||||||
let hrMethod = popOpt("hires upscaler");
|
let hrMethod = popOpt("hires upscaler");
|
||||||
|
|
||||||
setWidgetValue(imageNode, "width", w);
|
setWidgetValue(imageNode, "width", w);
|
||||||
@ -398,7 +406,7 @@ export async function importA1111(graph, parameters) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (hrSamplerNode) {
|
if (hrSamplerNode) {
|
||||||
setWidgetValue(hrSamplerNode, "steps", getWidget(samplerNode, "steps").value);
|
setWidgetValue(hrSamplerNode, "steps", hrSteps? +hrSteps : getWidget(samplerNode, "steps").value);
|
||||||
setWidgetValue(hrSamplerNode, "cfg", getWidget(samplerNode, "cfg").value);
|
setWidgetValue(hrSamplerNode, "cfg", getWidget(samplerNode, "cfg").value);
|
||||||
setWidgetValue(hrSamplerNode, "scheduler", getWidget(samplerNode, "scheduler").value);
|
setWidgetValue(hrSamplerNode, "scheduler", getWidget(samplerNode, "scheduler").value);
|
||||||
setWidgetValue(hrSamplerNode, "sampler_name", getWidget(samplerNode, "sampler_name").value);
|
setWidgetValue(hrSamplerNode, "sampler_name", getWidget(samplerNode, "sampler_name").value);
|
||||||
@ -415,7 +423,7 @@ export async function importA1111(graph, parameters) {
|
|||||||
|
|
||||||
graph.arrange();
|
graph.arrange();
|
||||||
|
|
||||||
for (const opt of ["model hash", "ensd"]) {
|
for (const opt of ["model hash", "ensd", "version", "vae hash", "ti hashes", "lora hashes", "hashes"]) {
|
||||||
delete opts[opt];
|
delete opts[opt];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -90,12 +90,15 @@ function dragElement(dragEl, settings) {
|
|||||||
}).observe(dragEl);
|
}).observe(dragEl);
|
||||||
|
|
||||||
function ensureInBounds() {
|
function ensureInBounds() {
|
||||||
if (dragEl.classList.contains("comfy-menu-manual-pos")) {
|
try {
|
||||||
newPosX = Math.min(document.body.clientWidth - dragEl.clientWidth, Math.max(0, dragEl.offsetLeft));
|
newPosX = Math.min(document.body.clientWidth - dragEl.clientWidth, Math.max(0, dragEl.offsetLeft));
|
||||||
newPosY = Math.min(document.body.clientHeight - dragEl.clientHeight, Math.max(0, dragEl.offsetTop));
|
newPosY = Math.min(document.body.clientHeight - dragEl.clientHeight, Math.max(0, dragEl.offsetTop));
|
||||||
|
|
||||||
positionElement();
|
positionElement();
|
||||||
}
|
}
|
||||||
|
catch(exception){
|
||||||
|
// robust
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function positionElement() {
|
function positionElement() {
|
||||||
@ -594,16 +597,23 @@ export class ComfyUI {
|
|||||||
if (!confirmClear.value || confirm("Clear workflow?")) {
|
if (!confirmClear.value || confirm("Clear workflow?")) {
|
||||||
app.clean();
|
app.clean();
|
||||||
app.graph.clear();
|
app.graph.clear();
|
||||||
|
app.resetView();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
$el("button", {
|
$el("button", {
|
||||||
id: "comfy-load-default-button", textContent: "Load Default", onclick: async () => {
|
id: "comfy-load-default-button", textContent: "Load Default", onclick: async () => {
|
||||||
if (!confirmClear.value || confirm("Load default workflow?")) {
|
if (!confirmClear.value || confirm("Load default workflow?")) {
|
||||||
|
app.resetView();
|
||||||
await app.loadGraphData()
|
await app.loadGraphData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
$el("button", {
|
||||||
|
id: "comfy-reset-view-button", textContent: "Reset View", onclick: async () => {
|
||||||
|
app.resetView();
|
||||||
|
}
|
||||||
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const devMode = this.settings.addSetting({
|
const devMode = this.settings.addSetting({
|
||||||
|
|||||||
@ -229,7 +229,11 @@ function createIntWidget(node, inputName, inputData, app, isSeedInput) {
|
|||||||
val,
|
val,
|
||||||
function (v) {
|
function (v) {
|
||||||
const s = this.options.step / 10;
|
const s = this.options.step / 10;
|
||||||
this.value = Math.round(v / s) * s;
|
let sh = this.options.min % s;
|
||||||
|
if (isNaN(sh)) {
|
||||||
|
sh = 0;
|
||||||
|
}
|
||||||
|
this.value = Math.round((v - sh) / s) * s + sh;
|
||||||
},
|
},
|
||||||
config
|
config
|
||||||
),
|
),
|
||||||
@ -307,7 +311,9 @@ export const ComfyWidgets = {
|
|||||||
return { widget: node.addWidget(widgetType, inputName, val,
|
return { widget: node.addWidget(widgetType, inputName, val,
|
||||||
function (v) {
|
function (v) {
|
||||||
if (config.round) {
|
if (config.round) {
|
||||||
this.value = Math.round(v/config.round)*config.round;
|
this.value = Math.round((v + Number.EPSILON)/config.round)*config.round;
|
||||||
|
if (this.value > config.max) this.value = config.max;
|
||||||
|
if (this.value < config.min) this.value = config.min;
|
||||||
} else {
|
} else {
|
||||||
this.value = v;
|
this.value = v;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user