mirror of
https://git.datalinker.icu/comfyanonymous/ComfyUI
synced 2026-11-12 03:20:03 +08:00
Merge branch 'comfyanonymous:master' into master
This commit is contained in:
commit
8748f818c9
@ -7,7 +7,7 @@ on:
|
|||||||
description: 'cuda version'
|
description: 'cuda version'
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
default: "121"
|
default: "124"
|
||||||
|
|
||||||
python_minor:
|
python_minor:
|
||||||
description: 'python minor version'
|
description: 'python minor version'
|
||||||
@ -19,7 +19,7 @@ on:
|
|||||||
description: 'python patch version'
|
description: 'python patch version'
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
default: "2"
|
default: "3"
|
||||||
# push:
|
# push:
|
||||||
# branches:
|
# branches:
|
||||||
# - master
|
# - master
|
||||||
@ -49,7 +49,7 @@ jobs:
|
|||||||
echo 'import site' >> ./python3${{ inputs.python_minor }}._pth
|
echo 'import site' >> ./python3${{ inputs.python_minor }}._pth
|
||||||
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
|
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
|
||||||
./python.exe get-pip.py
|
./python.exe get-pip.py
|
||||||
python -m pip wheel torch torchvision torchaudio mpmath==1.3.0 --pre --extra-index-url https://download.pytorch.org/whl/nightly/cu${{ inputs.cu }} -r ../ComfyUI/requirements.txt pygit2 -w ../temp_wheel_dir
|
python -m pip wheel torch torchvision torchaudio mpmath==1.3.0 numpy==1.26.4 --pre --extra-index-url https://download.pytorch.org/whl/nightly/cu${{ inputs.cu }} -r ../ComfyUI/requirements.txt pygit2 -w ../temp_wheel_dir
|
||||||
ls ../temp_wheel_dir
|
ls ../temp_wheel_dir
|
||||||
./python.exe -s -m pip install --pre ../temp_wheel_dir/*
|
./python.exe -s -m pip install --pre ../temp_wheel_dir/*
|
||||||
sed -i '1i../ComfyUI' ./python3${{ inputs.python_minor }}._pth
|
sed -i '1i../ComfyUI' ./python3${{ inputs.python_minor }}._pth
|
||||||
|
|||||||
78
README.md
78
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
|
||||||
|
|
||||||
@ -99,7 +102,7 @@ Put your VAE in: models/vae
|
|||||||
### 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:
|
||||||
|
|
||||||
@ -133,7 +136,16 @@ After this you should have everything installed and can proceed to running Comfy
|
|||||||
|
|
||||||
### Others:
|
### Others:
|
||||||
|
|
||||||
#### [Intel Arc](https://github.com/comfyanonymous/ComfyUI/discussions/476)
|
#### Intel GPUs
|
||||||
|
|
||||||
|
Intel GPU support is available for all Intel GPUs supported by Intel's Extension for Pytorch (IPEX) with the support requirements listed in the [Installation](https://intel.github.io/intel-extension-for-pytorch/index.html#installation?platform=gpu) page. Choose your platform and method of install and follow the instructions. The steps are as follows:
|
||||||
|
|
||||||
|
1. Start by installing the drivers or kernel listed or newer in the Installation page of IPEX linked above for Windows and Linux if needed.
|
||||||
|
1. Follow the instructions to install [Intel's oneAPI Basekit](https://www.intel.com/content/www/us/en/developer/tools/oneapi/base-toolkit-download.html) for your platform.
|
||||||
|
1. Install the packages for IPEX using the instructions provided in the Installation page for your platform.
|
||||||
|
1. Follow the [ComfyUI manual installation](#manual-install-windows-linux) instructions for Windows and Linux and run ComfyUI normally as described above after everything is installed.
|
||||||
|
|
||||||
|
Additional discussion and help can be found [here](https://github.com/comfyanonymous/ComfyUI/discussions/476).
|
||||||
|
|
||||||
#### Apple Mac silicon
|
#### Apple Mac silicon
|
||||||
|
|
||||||
@ -195,20 +207,20 @@ To use a textual inversion concepts/embeddings in a text prompt put them in the
|
|||||||
```embedding:embedding_filename.pt```
|
```embedding:embedding_filename.pt```
|
||||||
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
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```
|
|
||||||
|
|
||||||
## How to show high-quality previews?
|
## How to show high-quality previews?
|
||||||
|
|
||||||
Use ```--preview-method auto``` to enable previews.
|
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.")
|
||||||
|
|
||||||
@ -49,7 +51,6 @@ cm_group = parser.add_mutually_exclusive_group()
|
|||||||
cm_group.add_argument("--cuda-malloc", action="store_true", help="Enable cudaMallocAsync (enabled by default for torch 2.0 and up).")
|
cm_group.add_argument("--cuda-malloc", action="store_true", help="Enable cudaMallocAsync (enabled by default for torch 2.0 and up).")
|
||||||
cm_group.add_argument("--disable-cuda-malloc", action="store_true", help="Disable cudaMallocAsync.")
|
cm_group.add_argument("--disable-cuda-malloc", action="store_true", help="Disable cudaMallocAsync.")
|
||||||
|
|
||||||
parser.add_argument("--dont-upcast-attention", action="store_true", help="Disable upcasting of attention. Can boost speed but increase the chances of black images.")
|
|
||||||
|
|
||||||
fp_group = parser.add_mutually_exclusive_group()
|
fp_group = parser.add_mutually_exclusive_group()
|
||||||
fp_group.add_argument("--force-fp32", action="store_true", help="Force fp32 (If this makes your GPU work better please report it).")
|
fp_group.add_argument("--force-fp32", action="store_true", help="Force fp32 (If this makes your GPU work better please report it).")
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -19,14 +19,6 @@ from comfy.cli_args import args
|
|||||||
import comfy.ops
|
import comfy.ops
|
||||||
ops = comfy.ops.disable_weight_init
|
ops = comfy.ops.disable_weight_init
|
||||||
|
|
||||||
# CrossAttn precision handling
|
|
||||||
if args.dont_upcast_attention:
|
|
||||||
logging.info("disabling upcasting of attention")
|
|
||||||
_ATTN_PRECISION = "fp16"
|
|
||||||
else:
|
|
||||||
_ATTN_PRECISION = "fp32"
|
|
||||||
|
|
||||||
|
|
||||||
def exists(val):
|
def exists(val):
|
||||||
return val is not None
|
return val is not None
|
||||||
|
|
||||||
@ -85,7 +77,7 @@ class FeedForward(nn.Module):
|
|||||||
def Normalize(in_channels, dtype=None, device=None):
|
def Normalize(in_channels, dtype=None, device=None):
|
||||||
return torch.nn.GroupNorm(num_groups=32, num_channels=in_channels, eps=1e-6, affine=True, dtype=dtype, device=device)
|
return torch.nn.GroupNorm(num_groups=32, num_channels=in_channels, eps=1e-6, affine=True, dtype=dtype, device=device)
|
||||||
|
|
||||||
def attention_basic(q, k, v, heads, mask=None):
|
def attention_basic(q, k, v, heads, mask=None, attn_precision=None):
|
||||||
b, _, dim_head = q.shape
|
b, _, dim_head = q.shape
|
||||||
dim_head //= heads
|
dim_head //= heads
|
||||||
scale = dim_head ** -0.5
|
scale = dim_head ** -0.5
|
||||||
@ -101,7 +93,7 @@ def attention_basic(q, k, v, heads, mask=None):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# force cast to fp32 to avoid overflowing
|
# force cast to fp32 to avoid overflowing
|
||||||
if _ATTN_PRECISION =="fp32":
|
if attn_precision == torch.float32:
|
||||||
sim = einsum('b i d, b j d -> b i j', q.float(), k.float()) * scale
|
sim = einsum('b i d, b j d -> b i j', q.float(), k.float()) * scale
|
||||||
else:
|
else:
|
||||||
sim = einsum('b i d, b j d -> b i j', q, k) * scale
|
sim = einsum('b i d, b j d -> b i j', q, k) * scale
|
||||||
@ -135,7 +127,7 @@ def attention_basic(q, k, v, heads, mask=None):
|
|||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
def attention_sub_quad(query, key, value, heads, mask=None):
|
def attention_sub_quad(query, key, value, heads, mask=None, attn_precision=None):
|
||||||
b, _, dim_head = query.shape
|
b, _, dim_head = query.shape
|
||||||
dim_head //= heads
|
dim_head //= heads
|
||||||
|
|
||||||
@ -146,7 +138,7 @@ def attention_sub_quad(query, key, value, heads, mask=None):
|
|||||||
key = key.unsqueeze(3).reshape(b, -1, heads, dim_head).permute(0, 2, 3, 1).reshape(b * heads, dim_head, -1)
|
key = key.unsqueeze(3).reshape(b, -1, heads, dim_head).permute(0, 2, 3, 1).reshape(b * heads, dim_head, -1)
|
||||||
|
|
||||||
dtype = query.dtype
|
dtype = query.dtype
|
||||||
upcast_attention = _ATTN_PRECISION =="fp32" and query.dtype != torch.float32
|
upcast_attention = attn_precision == torch.float32 and query.dtype != torch.float32
|
||||||
if upcast_attention:
|
if upcast_attention:
|
||||||
bytes_per_token = torch.finfo(torch.float32).bits//8
|
bytes_per_token = torch.finfo(torch.float32).bits//8
|
||||||
else:
|
else:
|
||||||
@ -195,7 +187,7 @@ def attention_sub_quad(query, key, value, heads, mask=None):
|
|||||||
hidden_states = hidden_states.unflatten(0, (-1, heads)).transpose(1,2).flatten(start_dim=2)
|
hidden_states = hidden_states.unflatten(0, (-1, heads)).transpose(1,2).flatten(start_dim=2)
|
||||||
return hidden_states
|
return hidden_states
|
||||||
|
|
||||||
def attention_split(q, k, v, heads, mask=None):
|
def attention_split(q, k, v, heads, mask=None, attn_precision=None):
|
||||||
b, _, dim_head = q.shape
|
b, _, dim_head = q.shape
|
||||||
dim_head //= heads
|
dim_head //= heads
|
||||||
scale = dim_head ** -0.5
|
scale = dim_head ** -0.5
|
||||||
@ -214,10 +206,12 @@ def attention_split(q, k, v, heads, mask=None):
|
|||||||
|
|
||||||
mem_free_total = model_management.get_free_memory(q.device)
|
mem_free_total = model_management.get_free_memory(q.device)
|
||||||
|
|
||||||
if _ATTN_PRECISION =="fp32":
|
if attn_precision == torch.float32:
|
||||||
element_size = 4
|
element_size = 4
|
||||||
|
upcast = True
|
||||||
else:
|
else:
|
||||||
element_size = q.element_size()
|
element_size = q.element_size()
|
||||||
|
upcast = False
|
||||||
|
|
||||||
gb = 1024 ** 3
|
gb = 1024 ** 3
|
||||||
tensor_size = q.shape[0] * q.shape[1] * k.shape[1] * element_size
|
tensor_size = q.shape[0] * q.shape[1] * k.shape[1] * element_size
|
||||||
@ -251,7 +245,7 @@ def attention_split(q, k, v, heads, mask=None):
|
|||||||
slice_size = q.shape[1] // steps if (q.shape[1] % steps) == 0 else q.shape[1]
|
slice_size = q.shape[1] // steps if (q.shape[1] % steps) == 0 else q.shape[1]
|
||||||
for i in range(0, q.shape[1], slice_size):
|
for i in range(0, q.shape[1], slice_size):
|
||||||
end = i + slice_size
|
end = i + slice_size
|
||||||
if _ATTN_PRECISION =="fp32":
|
if upcast:
|
||||||
with torch.autocast(enabled=False, device_type = 'cuda'):
|
with torch.autocast(enabled=False, device_type = 'cuda'):
|
||||||
s1 = einsum('b i d, b j d -> b i j', q[:, i:end].float(), k.float()) * scale
|
s1 = einsum('b i d, b j d -> b i j', q[:, i:end].float(), k.float()) * scale
|
||||||
else:
|
else:
|
||||||
@ -297,12 +291,12 @@ 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
|
||||||
|
|
||||||
def attention_xformers(q, k, v, heads, mask=None):
|
def attention_xformers(q, k, v, heads, mask=None, attn_precision=None):
|
||||||
b, _, dim_head = q.shape
|
b, _, dim_head = q.shape
|
||||||
dim_head //= heads
|
dim_head //= heads
|
||||||
if BROKEN_XFORMERS:
|
if BROKEN_XFORMERS:
|
||||||
@ -334,7 +328,7 @@ def attention_xformers(q, k, v, heads, mask=None):
|
|||||||
)
|
)
|
||||||
return out
|
return out
|
||||||
|
|
||||||
def attention_pytorch(q, k, v, heads, mask=None):
|
def attention_pytorch(q, k, v, heads, mask=None, attn_precision=None):
|
||||||
b, _, dim_head = q.shape
|
b, _, dim_head = q.shape
|
||||||
dim_head //= heads
|
dim_head //= heads
|
||||||
q, k, v = map(
|
q, k, v = map(
|
||||||
@ -384,10 +378,11 @@ def optimized_attention_for_device(device, mask=False, small_input=False):
|
|||||||
|
|
||||||
|
|
||||||
class CrossAttention(nn.Module):
|
class CrossAttention(nn.Module):
|
||||||
def __init__(self, query_dim, context_dim=None, heads=8, dim_head=64, dropout=0., dtype=None, device=None, operations=ops):
|
def __init__(self, query_dim, context_dim=None, heads=8, dim_head=64, dropout=0., attn_precision=None, dtype=None, device=None, operations=ops):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
inner_dim = dim_head * heads
|
inner_dim = dim_head * heads
|
||||||
context_dim = default(context_dim, query_dim)
|
context_dim = default(context_dim, query_dim)
|
||||||
|
self.attn_precision = attn_precision
|
||||||
|
|
||||||
self.heads = heads
|
self.heads = heads
|
||||||
self.dim_head = dim_head
|
self.dim_head = dim_head
|
||||||
@ -409,15 +404,15 @@ class CrossAttention(nn.Module):
|
|||||||
v = self.to_v(context)
|
v = self.to_v(context)
|
||||||
|
|
||||||
if mask is None:
|
if mask is None:
|
||||||
out = optimized_attention(q, k, v, self.heads)
|
out = optimized_attention(q, k, v, self.heads, attn_precision=self.attn_precision)
|
||||||
else:
|
else:
|
||||||
out = optimized_attention_masked(q, k, v, self.heads, mask)
|
out = optimized_attention_masked(q, k, v, self.heads, mask, attn_precision=self.attn_precision)
|
||||||
return self.to_out(out)
|
return self.to_out(out)
|
||||||
|
|
||||||
|
|
||||||
class BasicTransformerBlock(nn.Module):
|
class BasicTransformerBlock(nn.Module):
|
||||||
def __init__(self, dim, n_heads, d_head, dropout=0., context_dim=None, gated_ff=True, checkpoint=True, ff_in=False, inner_dim=None,
|
def __init__(self, dim, n_heads, d_head, dropout=0., context_dim=None, gated_ff=True, checkpoint=True, ff_in=False, inner_dim=None,
|
||||||
disable_self_attn=False, disable_temporal_crossattention=False, switch_temporal_ca_to_sa=False, dtype=None, device=None, operations=ops):
|
disable_self_attn=False, disable_temporal_crossattention=False, switch_temporal_ca_to_sa=False, attn_precision=None, dtype=None, device=None, operations=ops):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
self.ff_in = ff_in or inner_dim is not None
|
self.ff_in = ff_in or inner_dim is not None
|
||||||
@ -425,6 +420,7 @@ class BasicTransformerBlock(nn.Module):
|
|||||||
inner_dim = dim
|
inner_dim = dim
|
||||||
|
|
||||||
self.is_res = inner_dim == dim
|
self.is_res = inner_dim == dim
|
||||||
|
self.attn_precision = attn_precision
|
||||||
|
|
||||||
if self.ff_in:
|
if self.ff_in:
|
||||||
self.norm_in = operations.LayerNorm(dim, dtype=dtype, device=device)
|
self.norm_in = operations.LayerNorm(dim, dtype=dtype, device=device)
|
||||||
@ -432,7 +428,7 @@ class BasicTransformerBlock(nn.Module):
|
|||||||
|
|
||||||
self.disable_self_attn = disable_self_attn
|
self.disable_self_attn = disable_self_attn
|
||||||
self.attn1 = CrossAttention(query_dim=inner_dim, heads=n_heads, dim_head=d_head, dropout=dropout,
|
self.attn1 = CrossAttention(query_dim=inner_dim, heads=n_heads, dim_head=d_head, dropout=dropout,
|
||||||
context_dim=context_dim if self.disable_self_attn else None, dtype=dtype, device=device, operations=operations) # is a self-attention if not self.disable_self_attn
|
context_dim=context_dim if self.disable_self_attn else None, attn_precision=self.attn_precision, dtype=dtype, device=device, operations=operations) # is a self-attention if not self.disable_self_attn
|
||||||
self.ff = FeedForward(inner_dim, dim_out=dim, dropout=dropout, glu=gated_ff, dtype=dtype, device=device, operations=operations)
|
self.ff = FeedForward(inner_dim, dim_out=dim, dropout=dropout, glu=gated_ff, dtype=dtype, device=device, operations=operations)
|
||||||
|
|
||||||
if disable_temporal_crossattention:
|
if disable_temporal_crossattention:
|
||||||
@ -446,7 +442,7 @@ class BasicTransformerBlock(nn.Module):
|
|||||||
context_dim_attn2 = context_dim
|
context_dim_attn2 = context_dim
|
||||||
|
|
||||||
self.attn2 = CrossAttention(query_dim=inner_dim, context_dim=context_dim_attn2,
|
self.attn2 = CrossAttention(query_dim=inner_dim, context_dim=context_dim_attn2,
|
||||||
heads=n_heads, dim_head=d_head, dropout=dropout, dtype=dtype, device=device, operations=operations) # is self-attn if context is none
|
heads=n_heads, dim_head=d_head, dropout=dropout, attn_precision=self.attn_precision, dtype=dtype, device=device, operations=operations) # is self-attn if context is none
|
||||||
self.norm2 = operations.LayerNorm(inner_dim, dtype=dtype, device=device)
|
self.norm2 = operations.LayerNorm(inner_dim, dtype=dtype, device=device)
|
||||||
|
|
||||||
self.norm1 = operations.LayerNorm(inner_dim, dtype=dtype, device=device)
|
self.norm1 = operations.LayerNorm(inner_dim, dtype=dtype, device=device)
|
||||||
@ -476,6 +472,7 @@ class BasicTransformerBlock(nn.Module):
|
|||||||
|
|
||||||
extra_options["n_heads"] = self.n_heads
|
extra_options["n_heads"] = self.n_heads
|
||||||
extra_options["dim_head"] = self.d_head
|
extra_options["dim_head"] = self.d_head
|
||||||
|
extra_options["attn_precision"] = self.attn_precision
|
||||||
|
|
||||||
if self.ff_in:
|
if self.ff_in:
|
||||||
x_skip = x
|
x_skip = x
|
||||||
@ -586,7 +583,7 @@ class SpatialTransformer(nn.Module):
|
|||||||
def __init__(self, in_channels, n_heads, d_head,
|
def __init__(self, in_channels, n_heads, d_head,
|
||||||
depth=1, dropout=0., context_dim=None,
|
depth=1, dropout=0., context_dim=None,
|
||||||
disable_self_attn=False, use_linear=False,
|
disable_self_attn=False, use_linear=False,
|
||||||
use_checkpoint=True, dtype=None, device=None, operations=ops):
|
use_checkpoint=True, attn_precision=None, dtype=None, device=None, operations=ops):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
if exists(context_dim) and not isinstance(context_dim, list):
|
if exists(context_dim) and not isinstance(context_dim, list):
|
||||||
context_dim = [context_dim] * depth
|
context_dim = [context_dim] * depth
|
||||||
@ -604,7 +601,7 @@ class SpatialTransformer(nn.Module):
|
|||||||
|
|
||||||
self.transformer_blocks = nn.ModuleList(
|
self.transformer_blocks = nn.ModuleList(
|
||||||
[BasicTransformerBlock(inner_dim, n_heads, d_head, dropout=dropout, context_dim=context_dim[d],
|
[BasicTransformerBlock(inner_dim, n_heads, d_head, dropout=dropout, context_dim=context_dim[d],
|
||||||
disable_self_attn=disable_self_attn, checkpoint=use_checkpoint, dtype=dtype, device=device, operations=operations)
|
disable_self_attn=disable_self_attn, checkpoint=use_checkpoint, attn_precision=attn_precision, dtype=dtype, device=device, operations=operations)
|
||||||
for d in range(depth)]
|
for d in range(depth)]
|
||||||
)
|
)
|
||||||
if not use_linear:
|
if not use_linear:
|
||||||
@ -660,6 +657,7 @@ class SpatialVideoTransformer(SpatialTransformer):
|
|||||||
disable_self_attn=False,
|
disable_self_attn=False,
|
||||||
disable_temporal_crossattention=False,
|
disable_temporal_crossattention=False,
|
||||||
max_time_embed_period: int = 10000,
|
max_time_embed_period: int = 10000,
|
||||||
|
attn_precision=None,
|
||||||
dtype=None, device=None, operations=ops
|
dtype=None, device=None, operations=ops
|
||||||
):
|
):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
@ -672,6 +670,7 @@ class SpatialVideoTransformer(SpatialTransformer):
|
|||||||
context_dim=context_dim,
|
context_dim=context_dim,
|
||||||
use_linear=use_linear,
|
use_linear=use_linear,
|
||||||
disable_self_attn=disable_self_attn,
|
disable_self_attn=disable_self_attn,
|
||||||
|
attn_precision=attn_precision,
|
||||||
dtype=dtype, device=device, operations=operations
|
dtype=dtype, device=device, operations=operations
|
||||||
)
|
)
|
||||||
self.time_depth = time_depth
|
self.time_depth = time_depth
|
||||||
@ -701,6 +700,7 @@ class SpatialVideoTransformer(SpatialTransformer):
|
|||||||
inner_dim=time_mix_inner_dim,
|
inner_dim=time_mix_inner_dim,
|
||||||
disable_self_attn=disable_self_attn,
|
disable_self_attn=disable_self_attn,
|
||||||
disable_temporal_crossattention=disable_temporal_crossattention,
|
disable_temporal_crossattention=disable_temporal_crossattention,
|
||||||
|
attn_precision=attn_precision,
|
||||||
dtype=dtype, device=device, operations=operations
|
dtype=dtype, device=device, operations=operations
|
||||||
)
|
)
|
||||||
for _ in range(self.depth)
|
for _ in range(self.depth)
|
||||||
|
|||||||
@ -431,6 +431,7 @@ class UNetModel(nn.Module):
|
|||||||
video_kernel_size=None,
|
video_kernel_size=None,
|
||||||
disable_temporal_crossattention=False,
|
disable_temporal_crossattention=False,
|
||||||
max_ddpm_temb_period=10000,
|
max_ddpm_temb_period=10000,
|
||||||
|
attn_precision=None,
|
||||||
device=None,
|
device=None,
|
||||||
operations=ops,
|
operations=ops,
|
||||||
):
|
):
|
||||||
@ -550,13 +551,14 @@ class UNetModel(nn.Module):
|
|||||||
disable_self_attn=disable_self_attn,
|
disable_self_attn=disable_self_attn,
|
||||||
disable_temporal_crossattention=disable_temporal_crossattention,
|
disable_temporal_crossattention=disable_temporal_crossattention,
|
||||||
max_time_embed_period=max_ddpm_temb_period,
|
max_time_embed_period=max_ddpm_temb_period,
|
||||||
|
attn_precision=attn_precision,
|
||||||
dtype=self.dtype, device=device, operations=operations
|
dtype=self.dtype, device=device, operations=operations
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return SpatialTransformer(
|
return SpatialTransformer(
|
||||||
ch, num_heads, dim_head, depth=depth, context_dim=context_dim,
|
ch, num_heads, dim_head, depth=depth, context_dim=context_dim,
|
||||||
disable_self_attn=disable_self_attn, use_linear=use_linear_in_transformer,
|
disable_self_attn=disable_self_attn, use_linear=use_linear_in_transformer,
|
||||||
use_checkpoint=use_checkpoint, dtype=self.dtype, device=device, operations=operations
|
use_checkpoint=use_checkpoint, attn_precision=attn_precision, dtype=self.dtype, device=device, operations=operations
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_resblock(
|
def get_resblock(
|
||||||
|
|||||||
@ -162,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
|
||||||
|
|
||||||
|
|||||||
@ -83,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())
|
||||||
|
|
||||||
@ -102,8 +102,8 @@ def get_total_memory(dev=None, torch_total_too=False):
|
|||||||
elif is_intel_xpu():
|
elif is_intel_xpu():
|
||||||
stats = torch.xpu.memory_stats(dev)
|
stats = torch.xpu.memory_stats(dev)
|
||||||
mem_reserved = stats['reserved_bytes.all.current']
|
mem_reserved = stats['reserved_bytes.all.current']
|
||||||
mem_total = torch.xpu.get_device_properties(dev).total_memory
|
|
||||||
mem_total_torch = mem_reserved
|
mem_total_torch = mem_reserved
|
||||||
|
mem_total = torch.xpu.get_device_properties(dev).total_memory
|
||||||
else:
|
else:
|
||||||
stats = torch.cuda.memory_stats(dev)
|
stats = torch.cuda.memory_stats(dev)
|
||||||
mem_reserved = stats['reserved_bytes.all.current']
|
mem_reserved = stats['reserved_bytes.all.current']
|
||||||
@ -285,7 +285,7 @@ class LoadedModel:
|
|||||||
else:
|
else:
|
||||||
return self.model_memory()
|
return self.model_memory()
|
||||||
|
|
||||||
def model_load(self, lowvram_model_memory=0):
|
def model_load(self, lowvram_model_memory=0, force_patch_weights=False):
|
||||||
patch_model_to = self.device
|
patch_model_to = self.device
|
||||||
|
|
||||||
self.model.model_patches_to(self.device)
|
self.model.model_patches_to(self.device)
|
||||||
@ -295,7 +295,7 @@ class LoadedModel:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
if lowvram_model_memory > 0 and load_weights:
|
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)
|
self.real_model = self.model.patch_model_lowvram(device_to=patch_model_to, lowvram_model_memory=lowvram_model_memory, force_patch_weights=force_patch_weights)
|
||||||
else:
|
else:
|
||||||
self.real_model = self.model.patch_model(device_to=patch_model_to, patch_weights=load_weights)
|
self.real_model = self.model.patch_model(device_to=patch_model_to, patch_weights=load_weights)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -304,11 +304,16 @@ class LoadedModel:
|
|||||||
raise e
|
raise e
|
||||||
|
|
||||||
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
|
self.weights_loaded = True
|
||||||
return self.real_model
|
return self.real_model
|
||||||
|
|
||||||
|
def should_reload_model(self, force_patch_weights=False):
|
||||||
|
if force_patch_weights and self.model.lowvram_patch_counter > 0:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def model_unload(self, unpatch_weights=True):
|
def model_unload(self, unpatch_weights=True):
|
||||||
self.model.unpatch_model(self.model.offload_device, unpatch_weights=unpatch_weights)
|
self.model.unpatch_model(self.model.offload_device, unpatch_weights=unpatch_weights)
|
||||||
self.model.model_patches_to(self.model.offload_device)
|
self.model.model_patches_to(self.model.offload_device)
|
||||||
@ -379,7 +384,7 @@ def free_memory(memory_required, device, keep_loaded=[]):
|
|||||||
if mem_free_torch > mem_free_total * 0.25:
|
if mem_free_torch > mem_free_total * 0.25:
|
||||||
soft_empty_cache()
|
soft_empty_cache()
|
||||||
|
|
||||||
def load_models_gpu(models, memory_required=0):
|
def load_models_gpu(models, memory_required=0, force_patch_weights=False):
|
||||||
global vram_state
|
global vram_state
|
||||||
|
|
||||||
inference_memory = minimum_inference_memory()
|
inference_memory = minimum_inference_memory()
|
||||||
@ -391,12 +396,22 @@ def load_models_gpu(models, memory_required=0):
|
|||||||
models_already_loaded = []
|
models_already_loaded = []
|
||||||
for x in models:
|
for x in models:
|
||||||
loaded_model = LoadedModel(x)
|
loaded_model = LoadedModel(x)
|
||||||
|
loaded = None
|
||||||
|
|
||||||
if loaded_model in current_loaded_models:
|
try:
|
||||||
index = current_loaded_models.index(loaded_model)
|
loaded_model_index = current_loaded_models.index(loaded_model)
|
||||||
current_loaded_models.insert(0, current_loaded_models.pop(index))
|
except:
|
||||||
models_already_loaded.append(loaded_model)
|
loaded_model_index = None
|
||||||
else:
|
|
||||||
|
if loaded_model_index is not None:
|
||||||
|
loaded = current_loaded_models[loaded_model_index]
|
||||||
|
if loaded.should_reload_model(force_patch_weights=force_patch_weights): #TODO: cleanup this model reload logic
|
||||||
|
current_loaded_models.pop(loaded_model_index).model_unload(unpatch_weights=True)
|
||||||
|
loaded = None
|
||||||
|
else:
|
||||||
|
models_already_loaded.append(loaded)
|
||||||
|
|
||||||
|
if loaded is None:
|
||||||
if hasattr(x, "model"):
|
if hasattr(x, "model"):
|
||||||
logging.info(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)
|
||||||
@ -444,7 +459,7 @@ def load_models_gpu(models, memory_required=0):
|
|||||||
if vram_set_state == VRAMState.NO_VRAM:
|
if vram_set_state == VRAMState.NO_VRAM:
|
||||||
lowvram_model_memory = 64 * 1024 * 1024
|
lowvram_model_memory = 64 * 1024 * 1024
|
||||||
|
|
||||||
cur_loaded_model = loaded_model.model_load(lowvram_model_memory)
|
cur_loaded_model = loaded_model.model_load(lowvram_model_memory, force_patch_weights=force_patch_weights)
|
||||||
current_loaded_models.insert(0, loaded_model)
|
current_loaded_models.insert(0, loaded_model)
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -552,8 +567,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:
|
||||||
@ -617,7 +630,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
|
||||||
@ -687,10 +701,10 @@ def get_free_memory(dev=None, torch_free_too=False):
|
|||||||
elif is_intel_xpu():
|
elif is_intel_xpu():
|
||||||
stats = torch.xpu.memory_stats(dev)
|
stats = torch.xpu.memory_stats(dev)
|
||||||
mem_active = stats['active_bytes.all.current']
|
mem_active = stats['active_bytes.all.current']
|
||||||
mem_allocated = stats['allocated_bytes.all.current']
|
|
||||||
mem_reserved = stats['reserved_bytes.all.current']
|
mem_reserved = stats['reserved_bytes.all.current']
|
||||||
mem_free_torch = mem_reserved - mem_active
|
mem_free_torch = mem_reserved - mem_active
|
||||||
mem_free_total = torch.xpu.get_device_properties(dev).total_memory - mem_allocated
|
mem_free_xpu = torch.xpu.get_device_properties(dev).total_memory - mem_reserved
|
||||||
|
mem_free_total = mem_free_xpu + mem_free_torch
|
||||||
else:
|
else:
|
||||||
stats = torch.cuda.memory_stats(dev)
|
stats = torch.cuda.memory_stats(dev)
|
||||||
mem_active = stats['active_bytes.all.current']
|
mem_active = stats['active_bytes.all.current']
|
||||||
|
|||||||
@ -16,8 +16,28 @@ def apply_weight_decompose(dora_scale, weight):
|
|||||||
.transpose(0, 1)
|
.transpose(0, 1)
|
||||||
)
|
)
|
||||||
|
|
||||||
return weight * (dora_scale / weight_norm)
|
return weight * (dora_scale / weight_norm).type(weight.dtype)
|
||||||
|
|
||||||
|
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):
|
||||||
@ -38,6 +58,7 @@ class ModelPatcher:
|
|||||||
|
|
||||||
self.weight_inplace_update = weight_inplace_update
|
self.weight_inplace_update = weight_inplace_update
|
||||||
self.model_lowvram = False
|
self.model_lowvram = False
|
||||||
|
self.lowvram_patch_counter = 0
|
||||||
self.patches_uuid = uuid.uuid4()
|
self.patches_uuid = uuid.uuid4()
|
||||||
|
|
||||||
def model_size(self):
|
def model_size(self):
|
||||||
@ -109,16 +130,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")
|
||||||
@ -261,7 +273,7 @@ class ModelPatcher:
|
|||||||
|
|
||||||
return self.model
|
return self.model
|
||||||
|
|
||||||
def patch_model_lowvram(self, device_to=None, lowvram_model_memory=0):
|
def patch_model_lowvram(self, device_to=None, lowvram_model_memory=0, force_patch_weights=False):
|
||||||
self.patch_model(device_to, patch_weights=False)
|
self.patch_model(device_to, patch_weights=False)
|
||||||
|
|
||||||
logging.info("loading in lowvram mode {}".format(lowvram_model_memory/(1024 * 1024)))
|
logging.info("loading in lowvram mode {}".format(lowvram_model_memory/(1024 * 1024)))
|
||||||
@ -273,6 +285,7 @@ class ModelPatcher:
|
|||||||
return self.model_patcher.calculate_weight(self.model_patcher.patches[self.key], weight, self.key)
|
return self.model_patcher.calculate_weight(self.model_patcher.patches[self.key], weight, self.key)
|
||||||
|
|
||||||
mem_counter = 0
|
mem_counter = 0
|
||||||
|
patch_counter = 0
|
||||||
for n, m in self.model.named_modules():
|
for n, m in self.model.named_modules():
|
||||||
lowvram_weight = False
|
lowvram_weight = False
|
||||||
if hasattr(m, "comfy_cast_weights"):
|
if hasattr(m, "comfy_cast_weights"):
|
||||||
@ -285,9 +298,17 @@ class ModelPatcher:
|
|||||||
|
|
||||||
if lowvram_weight:
|
if lowvram_weight:
|
||||||
if weight_key in self.patches:
|
if weight_key in self.patches:
|
||||||
m.weight_function = LowVramPatch(weight_key, self)
|
if force_patch_weights:
|
||||||
|
self.patch_weight_to_device(weight_key)
|
||||||
|
else:
|
||||||
|
m.weight_function = LowVramPatch(weight_key, self)
|
||||||
|
patch_counter += 1
|
||||||
if bias_key in self.patches:
|
if bias_key in self.patches:
|
||||||
m.bias_function = LowVramPatch(bias_key, self)
|
if force_patch_weights:
|
||||||
|
self.patch_weight_to_device(bias_key)
|
||||||
|
else:
|
||||||
|
m.bias_function = LowVramPatch(bias_key, self)
|
||||||
|
patch_counter += 1
|
||||||
|
|
||||||
m.prev_comfy_cast_weights = m.comfy_cast_weights
|
m.prev_comfy_cast_weights = m.comfy_cast_weights
|
||||||
m.comfy_cast_weights = True
|
m.comfy_cast_weights = True
|
||||||
@ -300,6 +321,7 @@ class ModelPatcher:
|
|||||||
logging.debug("lowvram: loaded module regularly {}".format(m))
|
logging.debug("lowvram: loaded module regularly {}".format(m))
|
||||||
|
|
||||||
self.model_lowvram = True
|
self.model_lowvram = True
|
||||||
|
self.lowvram_patch_counter = patch_counter
|
||||||
return self.model
|
return self.model
|
||||||
|
|
||||||
def calculate_weight(self, patches, weight, key):
|
def calculate_weight(self, patches, weight, key):
|
||||||
@ -451,6 +473,7 @@ class ModelPatcher:
|
|||||||
m.bias_function = None
|
m.bias_function = None
|
||||||
|
|
||||||
self.model_lowvram = False
|
self.model_lowvram = False
|
||||||
|
self.lowvram_patch_counter = 0
|
||||||
|
|
||||||
keys = list(self.backup.keys())
|
keys = list(self.backup.keys())
|
||||||
|
|
||||||
|
|||||||
@ -34,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)
|
||||||
@ -539,6 +539,9 @@ class KSAMPLER(Sampler):
|
|||||||
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]
|
||||||
@ -547,6 +550,9 @@ def ksampler(sampler_name, extra_options={}, inpaint_options={}):
|
|||||||
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, **extra_options):
|
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]
|
||||||
|
|||||||
105
comfy/sd.py
105
comfy/sd.py
@ -210,16 +210,26 @@ 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()
|
||||||
@ -408,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:
|
||||||
@ -415,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)
|
||||||
@ -535,7 +486,11 @@ 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:
|
||||||
logging.warning("clip missing: {}".format(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:
|
||||||
logging.debug("clip unexpected {}:".format(u))
|
logging.debug("clip unexpected {}:".format(u))
|
||||||
@ -607,7 +562,7 @@ def save_checkpoint(output_path, model, clip=None, vae=None, clip_vision=None, m
|
|||||||
load_models.append(clip.load_model())
|
load_models.append(clip.load_model())
|
||||||
clip_sd = clip.get_sd()
|
clip_sd = clip.get_sd()
|
||||||
|
|
||||||
model_management.load_models_gpu(load_models)
|
model_management.load_models_gpu(load_models, force_patch_weights=True)
|
||||||
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:
|
for k in extra_keys:
|
||||||
|
|||||||
@ -65,6 +65,12 @@ class SD20(supported_models_base.BASE):
|
|||||||
"use_temporal_attention": False,
|
"use_temporal_attention": False,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unet_extra_config = {
|
||||||
|
"num_heads": -1,
|
||||||
|
"num_head_channels": 64,
|
||||||
|
"attn_precision": torch.float32,
|
||||||
|
}
|
||||||
|
|
||||||
latent_format = latent_formats.SD15
|
latent_format = latent_formats.SD15
|
||||||
|
|
||||||
def model_type(self, state_dict, prefix=""):
|
def model_type(self, state_dict, prefix=""):
|
||||||
@ -276,6 +282,12 @@ class SVD_img2vid(supported_models_base.BASE):
|
|||||||
"use_temporal_resblock": True
|
"use_temporal_resblock": True
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unet_extra_config = {
|
||||||
|
"num_heads": -1,
|
||||||
|
"num_head_channels": 64,
|
||||||
|
"attn_precision": torch.float32,
|
||||||
|
}
|
||||||
|
|
||||||
clip_vision_prefix = "conditioner.embedders.0.open_clip.model.visual."
|
clip_vision_prefix = "conditioner.embedders.0.open_clip.model.visual."
|
||||||
|
|
||||||
latent_format = latent_formats.SD15
|
latent_format = latent_formats.SD15
|
||||||
|
|||||||
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
|
||||||
|
|||||||
@ -39,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}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -58,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",)
|
||||||
@ -76,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}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -107,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, )
|
||||||
|
|
||||||
@ -117,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}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -140,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"
|
||||||
@ -149,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):
|
||||||
@ -600,6 +621,7 @@ NODE_CLASS_MAPPINGS = {
|
|||||||
"SamplerDPMPP_SDE": SamplerDPMPP_SDE,
|
"SamplerDPMPP_SDE": SamplerDPMPP_SDE,
|
||||||
"SamplerDPMAdaptative": SamplerDPMAdaptative,
|
"SamplerDPMAdaptative": SamplerDPMAdaptative,
|
||||||
"SplitSigmas": SplitSigmas,
|
"SplitSigmas": SplitSigmas,
|
||||||
|
"SplitSigmasDenoise": SplitSigmasDenoise,
|
||||||
"FlipSigmas": FlipSigmas,
|
"FlipSigmas": FlipSigmas,
|
||||||
|
|
||||||
"CFGGuider": CFGGuider,
|
"CFGGuider": CFGGuider,
|
||||||
|
|||||||
@ -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:
|
||||||
|
|||||||
@ -175,9 +175,14 @@ def save_checkpoint(model, clip=None, vae=None, clip_vision=None, filename_prefi
|
|||||||
|
|
||||||
enable_modelspec = True
|
enable_modelspec = True
|
||||||
if isinstance(model.model, comfy.model_base.SDXL):
|
if isinstance(model.model, comfy.model_base.SDXL):
|
||||||
metadata["modelspec.architecture"] = "stable-diffusion-xl-v1-base"
|
if isinstance(model.model, comfy.model_base.SDXL_instructpix2pix):
|
||||||
|
metadata["modelspec.architecture"] = "stable-diffusion-xl-v1-edit"
|
||||||
|
else:
|
||||||
|
metadata["modelspec.architecture"] = "stable-diffusion-xl-v1-base"
|
||||||
elif isinstance(model.model, comfy.model_base.SDXLRefiner):
|
elif isinstance(model.model, comfy.model_base.SDXLRefiner):
|
||||||
metadata["modelspec.architecture"] = "stable-diffusion-xl-v1-refiner"
|
metadata["modelspec.architecture"] = "stable-diffusion-xl-v1-refiner"
|
||||||
|
elif isinstance(model.model, comfy.model_base.SVD_img2vid):
|
||||||
|
metadata["modelspec.architecture"] = "stable-video-diffusion-img2vid-v1"
|
||||||
else:
|
else:
|
||||||
enable_modelspec = False
|
enable_modelspec = False
|
||||||
|
|
||||||
@ -262,7 +267,7 @@ class CLIPSave:
|
|||||||
for x in extra_pnginfo:
|
for x in extra_pnginfo:
|
||||||
metadata[x] = json.dumps(extra_pnginfo[x])
|
metadata[x] = json.dumps(extra_pnginfo[x])
|
||||||
|
|
||||||
comfy.model_management.load_models_gpu([clip.load_model()])
|
comfy.model_management.load_models_gpu([clip.load_model()], force_patch_weights=True)
|
||||||
clip_sd = clip.get_sd()
|
clip_sd = clip.get_sd()
|
||||||
|
|
||||||
for prefix in ["clip_l.", "clip_g.", ""]:
|
for prefix in ["clip_l.", "clip_g.", ""]:
|
||||||
|
|||||||
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,
|
||||||
|
}
|
||||||
@ -3,9 +3,18 @@ import comfy.model_management
|
|||||||
import comfy.sampler_helpers
|
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
|
||||||
|
|
||||||
#TODO: This node should be removed and replaced with one that uses the new Guider/SamplerCustomAdvanced.
|
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):
|
||||||
@ -34,12 +43,7 @@ class PerpNeg:
|
|||||||
|
|
||||||
(noise_pred_nocond,) = comfy.samplers.calc_cond_batch(model, [nocond_processed], 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)
|
||||||
@ -47,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)",
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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):
|
||||||
@ -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"]
|
||||||
|
|||||||
@ -5,12 +5,12 @@ import math
|
|||||||
|
|
||||||
from einops import rearrange, repeat
|
from einops import rearrange, repeat
|
||||||
import os
|
import os
|
||||||
from comfy.ldm.modules.attention import optimized_attention, _ATTN_PRECISION
|
from comfy.ldm.modules.attention import optimized_attention
|
||||||
import comfy.samplers
|
import comfy.samplers
|
||||||
|
|
||||||
# from comfy/ldm/modules/attention.py
|
# from comfy/ldm/modules/attention.py
|
||||||
# but modified to return attention scores as well as output
|
# but modified to return attention scores as well as output
|
||||||
def attention_basic_with_sim(q, k, v, heads, mask=None):
|
def attention_basic_with_sim(q, k, v, heads, mask=None, attn_precision=None):
|
||||||
b, _, dim_head = q.shape
|
b, _, dim_head = q.shape
|
||||||
dim_head //= heads
|
dim_head //= heads
|
||||||
scale = dim_head ** -0.5
|
scale = dim_head ** -0.5
|
||||||
@ -26,7 +26,7 @@ def attention_basic_with_sim(q, k, v, heads, mask=None):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# force cast to fp32 to avoid overflowing
|
# force cast to fp32 to avoid overflowing
|
||||||
if _ATTN_PRECISION =="fp32":
|
if attn_precision == torch.float32:
|
||||||
sim = einsum('b i d, b j d -> b i j', q.float(), k.float()) * scale
|
sim = einsum('b i d, b j d -> b i j', q.float(), k.float()) * scale
|
||||||
else:
|
else:
|
||||||
sim = einsum('b i d, b j d -> b i j', q, k) * scale
|
sim = einsum('b i d, b j d -> b i j', q, k) * scale
|
||||||
@ -121,13 +121,13 @@ class SelfAttentionGuidance:
|
|||||||
if 1 in cond_or_uncond:
|
if 1 in cond_or_uncond:
|
||||||
uncond_index = cond_or_uncond.index(1)
|
uncond_index = cond_or_uncond.index(1)
|
||||||
# do the entire attention operation, but save the attention scores to attn_scores
|
# do the entire attention operation, but save the attention scores to attn_scores
|
||||||
(out, sim) = attention_basic_with_sim(q, k, v, heads=heads)
|
(out, sim) = attention_basic_with_sim(q, k, v, heads=heads, attn_precision=extra_options["attn_precision"])
|
||||||
# when using a higher batch size, I BELIEVE the result batch dimension is [uc1, ... ucn, c1, ... cn]
|
# when using a higher batch size, I BELIEVE the result batch dimension is [uc1, ... ucn, c1, ... cn]
|
||||||
n_slices = heads * b
|
n_slices = heads * b
|
||||||
attn_scores = sim[n_slices * uncond_index:n_slices * (uncond_index+1)]
|
attn_scores = sim[n_slices * uncond_index:n_slices * (uncond_index+1)]
|
||||||
return out
|
return out
|
||||||
else:
|
else:
|
||||||
return optimized_attention(q, k, v, heads=heads)
|
return optimized_attention(q, k, v, heads=heads, attn_precision=extra_options["attn_precision"])
|
||||||
|
|
||||||
def post_cfg_function(args):
|
def post_cfg_function(args):
|
||||||
nonlocal attn_scores
|
nonlocal attn_scores
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -176,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 = {
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import os
|
|||||||
import time
|
import time
|
||||||
import logging
|
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 = {}
|
||||||
|
|
||||||
@ -181,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
|
||||||
|
|
||||||
@ -256,7 +258,7 @@ def get_save_image_path(filename_prefix, output_dir, image_width=0, image_height
|
|||||||
raise Exception(err)
|
raise Exception(err)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
counter = max(filter(lambda a: a[1][:-1] == filename and a[1][-1] == "_", map(map_filename, os.listdir(full_output_folder))))[0] + 1
|
counter = max(filter(lambda a: os.path.normcase(a[1][:-1]) == os.path.normcase(filename) and a[1][-1] == "_", map(map_filename, os.listdir(full_output_folder))))[0] + 1
|
||||||
except ValueError:
|
except ValueError:
|
||||||
counter = 1
|
counter = 1
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
|
|||||||
4
main.py
4
main.py
@ -243,11 +243,11 @@ 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:
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
from PIL import ImageFile, UnidentifiedImageError
|
||||||
|
|
||||||
def conditioning_set_values(conditioning, values={}):
|
def conditioning_set_values(conditioning, values={}):
|
||||||
c = []
|
c = []
|
||||||
@ -8,3 +9,16 @@ def conditioning_set_values(conditioning, values={}):
|
|||||||
c.append(n)
|
c.append(n)
|
||||||
|
|
||||||
return c
|
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
|
||||||
|
|||||||
39
nodes.py
39
nodes.py
@ -10,14 +10,14 @@ import time
|
|||||||
import random
|
import random
|
||||||
import logging
|
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
|
||||||
@ -583,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"
|
||||||
@ -617,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"
|
||||||
@ -1456,14 +1456,29 @@ 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 = []
|
||||||
|
w, h = None, None
|
||||||
|
|
||||||
|
excluded_formats = ['MPO']
|
||||||
|
|
||||||
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")
|
||||||
|
|
||||||
|
if len(output_images) == 0:
|
||||||
|
w = image.size[0]
|
||||||
|
h = image.size[1]
|
||||||
|
|
||||||
|
if image.size[0] != w or image.size[1] != h:
|
||||||
|
continue
|
||||||
|
|
||||||
image = np.array(image).astype(np.float32) / 255.0
|
image = np.array(image).astype(np.float32) / 255.0
|
||||||
image = torch.from_numpy(image)[None,]
|
image = torch.from_numpy(image)[None,]
|
||||||
if 'A' in i.getbands():
|
if 'A' in i.getbands():
|
||||||
@ -1474,7 +1489,7 @@ class LoadImage:
|
|||||||
output_images.append(image)
|
output_images.append(image)
|
||||||
output_masks.append(mask.unsqueeze(0))
|
output_masks.append(mask.unsqueeze(0))
|
||||||
|
|
||||||
if len(output_images) > 1:
|
if len(output_images) > 1 and img.format not in excluded_formats:
|
||||||
output_image = torch.cat(output_images, dim=0)
|
output_image = torch.cat(output_images, dim=0)
|
||||||
output_mask = torch.cat(output_masks, dim=0)
|
output_mask = torch.cat(output_masks, dim=0)
|
||||||
else:
|
else:
|
||||||
@ -1515,8 +1530,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))
|
||||||
@ -1947,6 +1962,10 @@ def init_custom_nodes():
|
|||||||
"nodes_differential_diffusion.py",
|
"nodes_differential_diffusion.py",
|
||||||
"nodes_ip2p.py",
|
"nodes_ip2p.py",
|
||||||
"nodes_model_merging_model_specific.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 = []
|
||||||
|
|||||||
15
server.py
15
server.py
@ -11,6 +11,7 @@ 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
|
||||||
@ -623,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:
|
||||||
logging.info("Starting server\n")
|
logging.info("Starting server\n")
|
||||||
logging.info("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)
|
||||||
|
|||||||
@ -204,7 +204,7 @@ 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.`);
|
||||||
var menu = this.node.menu["Convert 🔘 to widget.."].item.submenu.options;
|
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`);
|
var index = menu.findIndex(a => a.content == `Convert ${this.widget.name} to widget`);
|
||||||
menu[index].callback.call();
|
menu[index].callback.call();
|
||||||
}
|
}
|
||||||
@ -212,7 +212,7 @@ export class EzWidget {
|
|||||||
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.`);
|
||||||
var menu = this.node.menu["Convert input to 🔘.."].item.submenu.options;
|
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`);
|
var index = menu.findIndex(a => a.content == `Convert ${this.widget.name} to input`);
|
||||||
menu[index].callback.call();
|
menu[index].callback.call();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
@ -295,20 +305,28 @@ app.registerExtension({
|
|||||||
|
|
||||||
//Convert.. main menu
|
//Convert.. main menu
|
||||||
if (toInput.length) {
|
if (toInput.length) {
|
||||||
options.push({
|
if (useConversionSubmenusSetting.value) {
|
||||||
content: `Convert input to 🔘..`,
|
options.push({
|
||||||
submenu: {
|
content: "Convert Widget to Input",
|
||||||
options: toInput,
|
submenu: {
|
||||||
},
|
options: toInput,
|
||||||
});
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
options.push(...toInput, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (toWidget.length) {
|
if (toWidget.length) {
|
||||||
options.push({
|
if (useConversionSubmenusSetting.value) {
|
||||||
content: `Convert 🔘 to widget..`,
|
options.push({
|
||||||
submenu: {
|
content: "Convert Input to Widget",
|
||||||
options: toWidget,
|
submenu: {
|
||||||
},
|
options: toWidget,
|
||||||
});
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
options.push(...toWidget, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -264,6 +264,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
|
||||||
@ -954,6 +984,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;
|
||||||
@ -974,6 +1010,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) {
|
||||||
@ -1060,6 +1116,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();
|
||||||
@ -1466,6 +1536,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();
|
||||||
|
|
||||||
@ -1775,6 +1846,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
|
||||||
@ -2092,6 +2167,14 @@ export class ComfyApp {
|
|||||||
api.dispatchEvent(new CustomEvent("promptQueued", { detail: { number, batchCount } }));
|
api.dispatchEvent(new CustomEvent("promptQueued", { detail: { number, batchCount } }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showErrorOnFileLoad(file) {
|
||||||
|
this.ui.dialog.show(
|
||||||
|
$el("div", [
|
||||||
|
$el("p", {textContent: `Unable to find workflow in ${file.name}`})
|
||||||
|
]).outerHTML
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads workflow data from the specified file
|
* Loads workflow data from the specified file
|
||||||
* @param {File} file
|
* @param {File} file
|
||||||
@ -2099,27 +2182,27 @@ export class ComfyApp {
|
|||||||
async handleFile(file) {
|
async handleFile(file) {
|
||||||
if (file.type === "image/png") {
|
if (file.type === "image/png") {
|
||||||
const pngInfo = await getPngMetadata(file);
|
const pngInfo = await getPngMetadata(file);
|
||||||
if (pngInfo) {
|
if (pngInfo?.workflow) {
|
||||||
if (pngInfo.workflow) {
|
await this.loadGraphData(JSON.parse(pngInfo.workflow));
|
||||||
await this.loadGraphData(JSON.parse(pngInfo.workflow));
|
} else if (pngInfo?.prompt) {
|
||||||
} else if (pngInfo.prompt) {
|
this.loadApiJson(JSON.parse(pngInfo.prompt));
|
||||||
this.loadApiJson(JSON.parse(pngInfo.prompt));
|
} else if (pngInfo?.parameters) {
|
||||||
} else if (pngInfo.parameters) {
|
importA1111(this.graph, pngInfo.parameters);
|
||||||
importA1111(this.graph, pngInfo.parameters);
|
} else {
|
||||||
}
|
this.showErrorOnFileLoad(file);
|
||||||
}
|
}
|
||||||
} else if (file.type === "image/webp") {
|
} else if (file.type === "image/webp") {
|
||||||
const pngInfo = await getWebpMetadata(file);
|
const pngInfo = await getWebpMetadata(file);
|
||||||
if (pngInfo) {
|
// Support loading workflows from that webp custom node.
|
||||||
if (pngInfo.workflow) {
|
const workflow = pngInfo?.workflow || pngInfo?.Workflow;
|
||||||
this.loadGraphData(JSON.parse(pngInfo.workflow));
|
const prompt = pngInfo?.prompt || pngInfo?.Prompt;
|
||||||
} else if (pngInfo.Workflow) {
|
|
||||||
this.loadGraphData(JSON.parse(pngInfo.Workflow)); // Support loading workflows from that webp custom node.
|
if (workflow) {
|
||||||
} else if (pngInfo.prompt) {
|
this.loadGraphData(JSON.parse(workflow));
|
||||||
this.loadApiJson(JSON.parse(pngInfo.prompt));
|
} else if (prompt) {
|
||||||
} else if (pngInfo.Prompt) {
|
this.loadApiJson(JSON.parse(prompt));
|
||||||
this.loadApiJson(JSON.parse(pngInfo.Prompt)); // Support loading prompts from that webp custom node.
|
} else {
|
||||||
}
|
this.showErrorOnFileLoad(file);
|
||||||
}
|
}
|
||||||
} else if (file.type === "application/json" || file.name?.endsWith(".json")) {
|
} else if (file.type === "application/json" || file.name?.endsWith(".json")) {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
@ -2140,7 +2223,11 @@ export class ComfyApp {
|
|||||||
await this.loadGraphData(JSON.parse(info.workflow));
|
await this.loadGraphData(JSON.parse(info.workflow));
|
||||||
} else if (info.prompt) {
|
} else if (info.prompt) {
|
||||||
this.loadApiJson(JSON.parse(info.prompt));
|
this.loadApiJson(JSON.parse(info.prompt));
|
||||||
|
} else {
|
||||||
|
this.showErrorOnFileLoad(file);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
this.showErrorOnFileLoad(file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2248,6 +2335,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
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -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