Skip to content

Commit 220afe3

Browse files
Initial commit.
0 parents  commit 220afe3

77 files changed

Lines changed: 129040 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
__pycache__/
2+
*.py[cod]
3+
output/
4+
models/checkpoints
5+
models/vae

LICENSE

Lines changed: 674 additions & 0 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
ComfyUI
2+
=======
3+
A powerful and modular stable diffusion GUI.
4+
-----------
5+
![ComfyUI Screenshot](comfyui_screenshot.png)
6+
7+
This ui will let you design and execute advanced stable diffusion pipelines using a graph/nodes/flowchart based interface.
8+
9+
10+
# Installing
11+
12+
Git clone this repo.
13+
14+
Put your SD checkpoints (the huge ckpt/safetensors files) in: models/checkpoints
15+
16+
Put your VAE in: models/vae
17+
18+
At the time of writing this pytorch has issues with python versions higher than 3.10 so make sure your python/pip versions are 3.10.
19+
20+
### AMD
21+
AMD users can install rocm and pytorch with pip if you don't have it already installed:
22+
23+
```pip install torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/rocm5.2"```
24+
25+
### NVIDIA
26+
27+
Nvidia users should install Xformers.
28+
29+
### Dependencies
30+
31+
Install the dependencies:
32+
33+
```pip install -r requirements.txt```
34+
35+
36+
37+
# Running
38+
39+
```python main.py```
40+
41+
42+
# Notes
43+
44+
Only parts of the graph that have an output with all the correct inputs will be executed.
45+
46+
Only parts of the graph that change from each execution to the next will be executed, if you submit the same graph twice only the first will be executed. If you change the last part of the graph only the part you changed and the part that depends on it will be executed.
47+
48+
Dragging a generated png on the webpage or loading one will give you the full workflow including seeds that were used to create it.
49+
50+
You can use () to change emphasis of a word or phrase like: (good code:1.2) or (bad code:0.8). The default emphasis for () is 1.1. To use () characters in your actual prompt escape them like \\( or \\).
51+
52+
### Fedora
53+
54+
To get python 3.10 on fedora:
55+
```dnf install python3.10```
56+
57+
Then you can:
58+
59+
```python3.10 -m ensurepip```
60+
61+
This will let you use: pip3.10 to install all the dependencies.
62+
63+
64+
# QA
65+
66+
### Why did you make this?
67+
68+
I wanted to learn how Stable Diffusion worked in detail. I also wanted something clean and powerful that would let me experiment with SD without restrictions.
69+
70+
### Who is this for?
71+
72+
This is for anyone that wants to make complex workflows with SD or that wants to learn more how SD works. The interface follows closely how SD works and the code should be much more simple to understand than other SD UIs.

comfy/k_diffusion/augmentation.py

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
from functools import reduce
2+
import math
3+
import operator
4+
5+
import numpy as np
6+
from skimage import transform
7+
import torch
8+
from torch import nn
9+
10+
11+
def translate2d(tx, ty):
12+
mat = [[1, 0, tx],
13+
[0, 1, ty],
14+
[0, 0, 1]]
15+
return torch.tensor(mat, dtype=torch.float32)
16+
17+
18+
def scale2d(sx, sy):
19+
mat = [[sx, 0, 0],
20+
[ 0, sy, 0],
21+
[ 0, 0, 1]]
22+
return torch.tensor(mat, dtype=torch.float32)
23+
24+
25+
def rotate2d(theta):
26+
mat = [[torch.cos(theta), torch.sin(-theta), 0],
27+
[torch.sin(theta), torch.cos(theta), 0],
28+
[ 0, 0, 1]]
29+
return torch.tensor(mat, dtype=torch.float32)
30+
31+
32+
class KarrasAugmentationPipeline:
33+
def __init__(self, a_prob=0.12, a_scale=2**0.2, a_aniso=2**0.2, a_trans=1/8):
34+
self.a_prob = a_prob
35+
self.a_scale = a_scale
36+
self.a_aniso = a_aniso
37+
self.a_trans = a_trans
38+
39+
def __call__(self, image):
40+
h, w = image.size
41+
mats = [translate2d(h / 2 - 0.5, w / 2 - 0.5)]
42+
43+
# x-flip
44+
a0 = torch.randint(2, []).float()
45+
mats.append(scale2d(1 - 2 * a0, 1))
46+
# y-flip
47+
do = (torch.rand([]) < self.a_prob).float()
48+
a1 = torch.randint(2, []).float() * do
49+
mats.append(scale2d(1, 1 - 2 * a1))
50+
# scaling
51+
do = (torch.rand([]) < self.a_prob).float()
52+
a2 = torch.randn([]) * do
53+
mats.append(scale2d(self.a_scale ** a2, self.a_scale ** a2))
54+
# rotation
55+
do = (torch.rand([]) < self.a_prob).float()
56+
a3 = (torch.rand([]) * 2 * math.pi - math.pi) * do
57+
mats.append(rotate2d(-a3))
58+
# anisotropy
59+
do = (torch.rand([]) < self.a_prob).float()
60+
a4 = (torch.rand([]) * 2 * math.pi - math.pi) * do
61+
a5 = torch.randn([]) * do
62+
mats.append(rotate2d(a4))
63+
mats.append(scale2d(self.a_aniso ** a5, self.a_aniso ** -a5))
64+
mats.append(rotate2d(-a4))
65+
# translation
66+
do = (torch.rand([]) < self.a_prob).float()
67+
a6 = torch.randn([]) * do
68+
a7 = torch.randn([]) * do
69+
mats.append(translate2d(self.a_trans * w * a6, self.a_trans * h * a7))
70+
71+
# form the transformation matrix and conditioning vector
72+
mats.append(translate2d(-h / 2 + 0.5, -w / 2 + 0.5))
73+
mat = reduce(operator.matmul, mats)
74+
cond = torch.stack([a0, a1, a2, a3.cos() - 1, a3.sin(), a5 * a4.cos(), a5 * a4.sin(), a6, a7])
75+
76+
# apply the transformation
77+
image_orig = np.array(image, dtype=np.float32) / 255
78+
if image_orig.ndim == 2:
79+
image_orig = image_orig[..., None]
80+
tf = transform.AffineTransform(mat.numpy())
81+
image = transform.warp(image_orig, tf.inverse, order=3, mode='reflect', cval=0.5, clip=False, preserve_range=True)
82+
image_orig = torch.as_tensor(image_orig).movedim(2, 0) * 2 - 1
83+
image = torch.as_tensor(image).movedim(2, 0) * 2 - 1
84+
return image, image_orig, cond
85+
86+
87+
class KarrasAugmentWrapper(nn.Module):
88+
def __init__(self, model):
89+
super().__init__()
90+
self.inner_model = model
91+
92+
def forward(self, input, sigma, aug_cond=None, mapping_cond=None, **kwargs):
93+
if aug_cond is None:
94+
aug_cond = input.new_zeros([input.shape[0], 9])
95+
if mapping_cond is None:
96+
mapping_cond = aug_cond
97+
else:
98+
mapping_cond = torch.cat([aug_cond, mapping_cond], dim=1)
99+
return self.inner_model(input, sigma, mapping_cond=mapping_cond, **kwargs)
100+
101+
def set_skip_stages(self, skip_stages):
102+
return self.inner_model.set_skip_stages(skip_stages)
103+
104+
def set_patch_size(self, patch_size):
105+
return self.inner_model.set_patch_size(patch_size)

comfy/k_diffusion/config.py

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
from functools import partial
2+
import json
3+
import math
4+
import warnings
5+
6+
from jsonmerge import merge
7+
8+
from . import augmentation, layers, models, utils
9+
10+
11+
def load_config(file):
12+
defaults = {
13+
'model': {
14+
'sigma_data': 1.,
15+
'patch_size': 1,
16+
'dropout_rate': 0.,
17+
'augment_wrapper': True,
18+
'augment_prob': 0.,
19+
'mapping_cond_dim': 0,
20+
'unet_cond_dim': 0,
21+
'cross_cond_dim': 0,
22+
'cross_attn_depths': None,
23+
'skip_stages': 0,
24+
'has_variance': False,
25+
},
26+
'dataset': {
27+
'type': 'imagefolder',
28+
},
29+
'optimizer': {
30+
'type': 'adamw',
31+
'lr': 1e-4,
32+
'betas': [0.95, 0.999],
33+
'eps': 1e-6,
34+
'weight_decay': 1e-3,
35+
},
36+
'lr_sched': {
37+
'type': 'inverse',
38+
'inv_gamma': 20000.,
39+
'power': 1.,
40+
'warmup': 0.99,
41+
},
42+
'ema_sched': {
43+
'type': 'inverse',
44+
'power': 0.6667,
45+
'max_value': 0.9999
46+
},
47+
}
48+
config = json.load(file)
49+
return merge(defaults, config)
50+
51+
52+
def make_model(config):
53+
config = config['model']
54+
assert config['type'] == 'image_v1'
55+
model = models.ImageDenoiserModelV1(
56+
config['input_channels'],
57+
config['mapping_out'],
58+
config['depths'],
59+
config['channels'],
60+
config['self_attn_depths'],
61+
config['cross_attn_depths'],
62+
patch_size=config['patch_size'],
63+
dropout_rate=config['dropout_rate'],
64+
mapping_cond_dim=config['mapping_cond_dim'] + (9 if config['augment_wrapper'] else 0),
65+
unet_cond_dim=config['unet_cond_dim'],
66+
cross_cond_dim=config['cross_cond_dim'],
67+
skip_stages=config['skip_stages'],
68+
has_variance=config['has_variance'],
69+
)
70+
if config['augment_wrapper']:
71+
model = augmentation.KarrasAugmentWrapper(model)
72+
return model
73+
74+
75+
def make_denoiser_wrapper(config):
76+
config = config['model']
77+
sigma_data = config.get('sigma_data', 1.)
78+
has_variance = config.get('has_variance', False)
79+
if not has_variance:
80+
return partial(layers.Denoiser, sigma_data=sigma_data)
81+
return partial(layers.DenoiserWithVariance, sigma_data=sigma_data)
82+
83+
84+
def make_sample_density(config):
85+
sd_config = config['sigma_sample_density']
86+
sigma_data = config['sigma_data']
87+
if sd_config['type'] == 'lognormal':
88+
loc = sd_config['mean'] if 'mean' in sd_config else sd_config['loc']
89+
scale = sd_config['std'] if 'std' in sd_config else sd_config['scale']
90+
return partial(utils.rand_log_normal, loc=loc, scale=scale)
91+
if sd_config['type'] == 'loglogistic':
92+
loc = sd_config['loc'] if 'loc' in sd_config else math.log(sigma_data)
93+
scale = sd_config['scale'] if 'scale' in sd_config else 0.5
94+
min_value = sd_config['min_value'] if 'min_value' in sd_config else 0.
95+
max_value = sd_config['max_value'] if 'max_value' in sd_config else float('inf')
96+
return partial(utils.rand_log_logistic, loc=loc, scale=scale, min_value=min_value, max_value=max_value)
97+
if sd_config['type'] == 'loguniform':
98+
min_value = sd_config['min_value'] if 'min_value' in sd_config else config['sigma_min']
99+
max_value = sd_config['max_value'] if 'max_value' in sd_config else config['sigma_max']
100+
return partial(utils.rand_log_uniform, min_value=min_value, max_value=max_value)
101+
if sd_config['type'] == 'v-diffusion':
102+
min_value = sd_config['min_value'] if 'min_value' in sd_config else 0.
103+
max_value = sd_config['max_value'] if 'max_value' in sd_config else float('inf')
104+
return partial(utils.rand_v_diffusion, sigma_data=sigma_data, min_value=min_value, max_value=max_value)
105+
if sd_config['type'] == 'split-lognormal':
106+
loc = sd_config['mean'] if 'mean' in sd_config else sd_config['loc']
107+
scale_1 = sd_config['std_1'] if 'std_1' in sd_config else sd_config['scale_1']
108+
scale_2 = sd_config['std_2'] if 'std_2' in sd_config else sd_config['scale_2']
109+
return partial(utils.rand_split_log_normal, loc=loc, scale_1=scale_1, scale_2=scale_2)
110+
raise ValueError('Unknown sample density type')

0 commit comments

Comments
 (0)