mirror of
https://github.com/vladmandic/automatic
synced 2026-09-19 09:14:35 +02:00
restructure api examples
This commit is contained in:
@@ -3,6 +3,9 @@
|
||||
## Update for 2024-06-14
|
||||
|
||||
- force apply vae config on model load
|
||||
- restructure api examples: `cli/api-*`
|
||||
- fix control second pass resize
|
||||
- fix api face-hires
|
||||
|
||||
## Update for 2024-06-13
|
||||
|
||||
|
||||
@@ -132,7 +132,7 @@ def generate(args): # pylint: disable=redefined-outer-name
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description = 'simple-img2img')
|
||||
parser = argparse.ArgumentParser(description = 'api-img2img')
|
||||
parser.add_argument('--init', required=False, default=None, help='init image')
|
||||
parser.add_argument('--input', required=False, default=None, help='input image')
|
||||
parser.add_argument('--mask', required=False, help='mask image')
|
||||
Executable
+116
@@ -0,0 +1,116 @@
|
||||
#!/usr/bin/env python
|
||||
import os
|
||||
import io
|
||||
import time
|
||||
import base64
|
||||
import logging
|
||||
import argparse
|
||||
import requests
|
||||
import urllib3
|
||||
from PIL import Image
|
||||
|
||||
sd_url = os.environ.get('SDAPI_URL', "http://127.0.0.1:7860")
|
||||
sd_username = os.environ.get('SDAPI_USR', None)
|
||||
sd_password = os.environ.get('SDAPI_PWD', None)
|
||||
|
||||
logging.basicConfig(level = logging.INFO, format = '%(asctime)s %(levelname)s: %(message)s')
|
||||
log = logging.getLogger(__name__)
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
|
||||
options = {
|
||||
"save_images": False,
|
||||
"send_images": True,
|
||||
}
|
||||
|
||||
|
||||
def auth():
|
||||
if sd_username is not None and sd_password is not None:
|
||||
return requests.auth.HTTPBasicAuth(sd_username, sd_password)
|
||||
return None
|
||||
|
||||
|
||||
def post(endpoint: str, dct: dict = None):
|
||||
req = requests.post(f'{sd_url}{endpoint}', json = dct, timeout=300, verify=False, auth=auth())
|
||||
if req.status_code != 200:
|
||||
return { 'error': req.status_code, 'reason': req.reason, 'url': req.url }
|
||||
else:
|
||||
return req.json()
|
||||
|
||||
|
||||
def encode(f):
|
||||
image = Image.open(f)
|
||||
if image.mode == 'RGBA':
|
||||
image = image.convert('RGB')
|
||||
with io.BytesIO() as stream:
|
||||
image.save(stream, 'JPEG')
|
||||
image.close()
|
||||
values = stream.getvalue()
|
||||
encoded = base64.b64encode(values).decode()
|
||||
return encoded
|
||||
|
||||
|
||||
def generate(args): # pylint: disable=redefined-outer-name
|
||||
t0 = time.time()
|
||||
if args.model is not None:
|
||||
post('/sdapi/v1/options', { 'sd_model_checkpoint': args.model })
|
||||
post('/sdapi/v1/reload-checkpoint') # needed if running in api-only to trigger new model load
|
||||
options['prompt'] = args.prompt
|
||||
options['negative_prompt'] = args.negative
|
||||
options['steps'] = int(args.steps)
|
||||
options['seed'] = int(args.seed)
|
||||
options['sampler_name'] = args.sampler
|
||||
options['width'] = args.width
|
||||
options['height'] = args.height
|
||||
options['face'] = {
|
||||
'mode': 'FaceID',
|
||||
'ip_model': 'FaceID Base',
|
||||
'source_images': [encode(args.face)],
|
||||
}
|
||||
data = post('/sdapi/v1/txt2img', options)
|
||||
t1 = time.time()
|
||||
if 'images' in data:
|
||||
for i in range(len(data['images'])):
|
||||
b64 = data['images'][i].split(',',1)[0]
|
||||
info = data['info']
|
||||
image = Image.open(io.BytesIO(base64.b64decode(b64)))
|
||||
log.info(f'received image: size={image.size} time={t1-t0:.2f} info="{info}"')
|
||||
if args.output:
|
||||
image.save(args.output)
|
||||
log.info(f'image saved: size={image.size} filename={args.output}')
|
||||
|
||||
else:
|
||||
log.warning(f'no images received: {data}')
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description = 'api-faceid')
|
||||
parser.add_argument('--width', required=False, default=512, help='image width')
|
||||
parser.add_argument('--height', required=False, default=512, help='image height')
|
||||
parser.add_argument('--face', required=False, help='face image')
|
||||
parser.add_argument('--prompt', required=False, default='', help='prompt text')
|
||||
parser.add_argument('--negative', required=False, default='', help='negative prompt text')
|
||||
parser.add_argument('--steps', required=False, default=20, help='number of steps')
|
||||
parser.add_argument('--seed', required=False, default=-1, help='initial seed')
|
||||
parser.add_argument('--sampler', required=False, default='Euler a', help='sampler name')
|
||||
parser.add_argument('--output', required=False, default=None, help='output image file')
|
||||
parser.add_argument('--model', required=False, help='model name')
|
||||
args = parser.parse_args()
|
||||
log.info(f'img2img: {args}')
|
||||
generate(args)
|
||||
|
||||
"""
|
||||
request.face.mode,
|
||||
request.face.source_images,
|
||||
request.face.ip_model,
|
||||
request.face.ip_override_sampler,
|
||||
request.face.ip_cache_model,
|
||||
request.face.ip_strength,
|
||||
request.face.ip_structure,
|
||||
request.face.id_strength,
|
||||
request.face.id_conditioning,
|
||||
request.face.id_cache,
|
||||
request.face.pm_trigger,
|
||||
request.face.pm_strength,
|
||||
request.face.pm_start,
|
||||
request.face.fs_cache
|
||||
"""
|
||||
@@ -83,7 +83,7 @@ def generate(args): # pylint: disable=redefined-outer-name
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description = 'simple-img2img')
|
||||
parser = argparse.ArgumentParser(description = 'api-img2img')
|
||||
parser.add_argument('--init', required=True, help='init image')
|
||||
parser.add_argument('--mask', required=False, help='mask image')
|
||||
parser.add_argument('--prompt', required=False, default='', help='prompt text')
|
||||
@@ -50,7 +50,7 @@ def info(args): # pylint: disable=redefined-outer-name
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description = 'simple-info')
|
||||
parser = argparse.ArgumentParser(description = 'api-info')
|
||||
parser.add_argument('--input', required=True, help='input image')
|
||||
args = parser.parse_args()
|
||||
log.info(f'info: {args}')
|
||||
@@ -73,7 +73,7 @@ def info(args): # pylint: disable=redefined-outer-name
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description = 'simple-info')
|
||||
parser = argparse.ArgumentParser(description = 'api-mask')
|
||||
parser.add_argument('--input', required=True, help='input image')
|
||||
parser.add_argument('--mask', required=False, help='input mask')
|
||||
parser.add_argument('--type', required=False, help='output mask type')
|
||||
@@ -67,7 +67,7 @@ def info(args): # pylint: disable=redefined-outer-name
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description = 'simple-info')
|
||||
parser = argparse.ArgumentParser(description = 'api-preprocess')
|
||||
parser.add_argument('--input', required=True, help='input image')
|
||||
parser.add_argument('--model', required=True, help='preprocessing model')
|
||||
parser.add_argument('--output', required=False, help='output image')
|
||||
@@ -48,7 +48,10 @@ def generate(args): # pylint: disable=redefined-outer-name
|
||||
options['sampler_name'] = args.sampler
|
||||
options['width'] = int(args.width)
|
||||
options['height'] = int(args.height)
|
||||
options['restore_faces'] = args.faces
|
||||
if args.faces:
|
||||
options['restore_faces'] = args.faces
|
||||
options['denoising_strength'] = 0.5
|
||||
options['hr_sampler_name'] = args.sampler
|
||||
data = post('/sdapi/v1/txt2img', options)
|
||||
t1 = time.time()
|
||||
if 'images' in data:
|
||||
@@ -65,7 +68,7 @@ def generate(args): # pylint: disable=redefined-outer-name
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description = 'simple-txt2img')
|
||||
parser = argparse.ArgumentParser(description = 'api-txt2img')
|
||||
parser.add_argument('--prompt', required=False, default='', help='prompt text')
|
||||
parser.add_argument('--negative', required=False, default='', help='negative prompt text')
|
||||
parser.add_argument('--width', required=False, default=512, help='image width')
|
||||
@@ -80,7 +80,7 @@ def upscale(args): # pylint: disable=redefined-outer-name
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description = 'simple-upscale')
|
||||
parser = argparse.ArgumentParser(description = 'api-upscale')
|
||||
parser.add_argument('--input', required=True, help='input image')
|
||||
parser.add_argument('--output', required=True, help='output image')
|
||||
parser.add_argument('--upscaler', required=False, default='Nearest', help='upscaler name')
|
||||
@@ -55,7 +55,7 @@ def info(args): # pylint: disable=redefined-outer-name
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description = 'simple-info')
|
||||
parser = argparse.ArgumentParser(description = 'api-vqa')
|
||||
parser.add_argument('--input', required=True, help='input image')
|
||||
parser.add_argument('--model', required=False, help='vqa model')
|
||||
parser.add_argument('--question', required=False, help='question')
|
||||
-170
@@ -1,170 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import pathlib
|
||||
import argparse
|
||||
import warnings
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
import torch
|
||||
from PIL import Image
|
||||
from torchvision import transforms
|
||||
from tqdm import tqdm
|
||||
from util import Map
|
||||
|
||||
from rich.pretty import install as pretty_install
|
||||
from rich.traceback import install as traceback_install
|
||||
from rich.console import Console
|
||||
|
||||
console = Console(log_time=True, log_time_format='%H:%M:%S-%f')
|
||||
pretty_install(console=console)
|
||||
traceback_install(console=console, extra_lines=1, width=console.width, word_wrap=False, indent_guides=False)
|
||||
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'modules', 'lora'))
|
||||
import library.model_util as model_util
|
||||
import library.train_util as train_util
|
||||
|
||||
warnings.filterwarnings('ignore')
|
||||
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
||||
options = Map({
|
||||
'batch': 1,
|
||||
'input': '',
|
||||
'json': '',
|
||||
'max': 1024,
|
||||
'min': 256,
|
||||
'noupscale': False,
|
||||
'precision': 'fp32',
|
||||
'resolution': '512,512',
|
||||
'steps': 64,
|
||||
'vae': 'stabilityai/sd-vae-ft-mse'
|
||||
})
|
||||
vae = None
|
||||
|
||||
|
||||
def get_latents(local_vae, images, weight_dtype):
|
||||
image_transforms = transforms.Compose([ transforms.ToTensor(), transforms.Normalize([0.5], [0.5]) ])
|
||||
img_tensors = [image_transforms(image) for image in images]
|
||||
img_tensors = torch.stack(img_tensors)
|
||||
img_tensors = img_tensors.to(device, weight_dtype)
|
||||
with torch.no_grad():
|
||||
latents = local_vae.encode(img_tensors).latent_dist.sample().float().to('cpu').numpy()
|
||||
return latents, [images[0].shape[0], images[0].shape[1]]
|
||||
|
||||
|
||||
def get_npz_filename_wo_ext(data_dir, image_key):
|
||||
return os.path.join(data_dir, os.path.splitext(os.path.basename(image_key))[0])
|
||||
|
||||
|
||||
def create_vae_latents(local_params):
|
||||
args = Map({**options, **local_params})
|
||||
console.log(f'create vae latents args: {args}')
|
||||
image_paths = train_util.glob_images(args.input)
|
||||
if os.path.exists(args.json):
|
||||
with open(args.json, 'rt', encoding='utf-8') as f:
|
||||
metadata = json.load(f)
|
||||
else:
|
||||
return
|
||||
if args.precision == 'fp16':
|
||||
weight_dtype = torch.float16
|
||||
elif args.precision == 'bf16':
|
||||
weight_dtype = torch.bfloat16
|
||||
else:
|
||||
weight_dtype = torch.float32
|
||||
global vae # pylint: disable=global-statement
|
||||
if vae is None:
|
||||
vae = model_util.load_vae(args.vae, weight_dtype)
|
||||
vae.eval()
|
||||
vae.to(device, dtype=weight_dtype)
|
||||
max_reso = tuple([int(t) for t in args.resolution.split(',')])
|
||||
assert len(max_reso) == 2, f'illegal resolution: {args.resolution}'
|
||||
bucket_manager = train_util.BucketManager(args.noupscale, max_reso, args.min, args.max, args.steps)
|
||||
if not args.noupscale:
|
||||
bucket_manager.make_buckets()
|
||||
img_ar_errors = []
|
||||
def process_batch(is_last):
|
||||
for bucket in bucket_manager.buckets:
|
||||
if (is_last and len(bucket) > 0) or len(bucket) >= args.batch:
|
||||
latents, original_size = get_latents(vae, [img for _, img in bucket], weight_dtype)
|
||||
assert latents.shape[2] == bucket[0][1].shape[0] // 8 and latents.shape[3] == bucket[0][1].shape[1] // 8, f'latent shape {latents.shape}, {bucket[0][1].shape}'
|
||||
for (image_key, _), latent in zip(bucket, latents):
|
||||
npz_file_name = get_npz_filename_wo_ext(args.input, image_key)
|
||||
# np.savez(npz_file_name, latent)
|
||||
kwargs = {}
|
||||
np.savez(
|
||||
npz_file_name,
|
||||
latents=latent,
|
||||
original_size=np.array(original_size),
|
||||
crop_ltrb=np.array([0, 0]),
|
||||
**kwargs,
|
||||
)
|
||||
bucket.clear()
|
||||
data = [[(None, ip)] for ip in image_paths]
|
||||
bucket_counts = {}
|
||||
for data_entry in tqdm(data, smoothing=0.0):
|
||||
if data_entry[0] is None:
|
||||
continue
|
||||
img_tensor, image_path = data_entry[0]
|
||||
if img_tensor is not None:
|
||||
image = transforms.functional.to_pil_image(img_tensor)
|
||||
else:
|
||||
image = Image.open(image_path)
|
||||
image_key = os.path.basename(image_path)
|
||||
image_key = os.path.join(os.path.basename(pathlib.Path(image_path).parent), pathlib.Path(image_path).stem)
|
||||
if image_key not in metadata:
|
||||
metadata[image_key] = {}
|
||||
reso, resized_size, ar_error = bucket_manager.select_bucket(image.width, image.height)
|
||||
img_ar_errors.append(abs(ar_error))
|
||||
bucket_counts[reso] = bucket_counts.get(reso, 0) + 1
|
||||
metadata[image_key]['train_resolution'] = (reso[0] - reso[0] % 8, reso[1] - reso[1] % 8)
|
||||
if not args.noupscale:
|
||||
assert resized_size[0] == reso[0] or resized_size[1] == reso[1], f'internal error, resized size not match: {reso}, {resized_size}, {image.width}, {image.height}'
|
||||
assert resized_size[0] >= reso[0] and resized_size[1] >= reso[1], f'internal error, resized size too small: {reso}, {resized_size}, {image.width}, {image.height}'
|
||||
assert resized_size[0] >= reso[0] and resized_size[1] >= reso[1], f'internal error resized size is small: {resized_size}, {reso}'
|
||||
image = np.array(image)
|
||||
if resized_size[0] != image.shape[1] or resized_size[1] != image.shape[0]:
|
||||
image = cv2.resize(image, resized_size, interpolation=cv2.INTER_AREA)
|
||||
if resized_size[0] > reso[0]:
|
||||
trim_size = resized_size[0] - reso[0]
|
||||
image = image[:, trim_size//2:trim_size//2 + reso[0]]
|
||||
if resized_size[1] > reso[1]:
|
||||
trim_size = resized_size[1] - reso[1]
|
||||
image = image[trim_size//2:trim_size//2 + reso[1]]
|
||||
assert image.shape[0] == reso[1] and image.shape[1] == reso[0], f'internal error, illegal trimmed size: {image.shape}, {reso}'
|
||||
bucket_manager.add_image(reso, (image_key, image))
|
||||
process_batch(False)
|
||||
|
||||
process_batch(True)
|
||||
vae.to('cpu')
|
||||
|
||||
bucket_manager.sort()
|
||||
img_ar_errors = np.array(img_ar_errors)
|
||||
for i, reso in enumerate(bucket_manager.resos):
|
||||
count = bucket_counts.get(reso, 0)
|
||||
if count > 0:
|
||||
console.log(f'vae latents bucket: {i+1}/{len(bucket_manager.resos)} resolution: {reso} images: {count} mean-ar-error: {np.mean(img_ar_errors)}')
|
||||
with open(args.json, 'wt', encoding='utf-8') as f:
|
||||
json.dump(metadata, f, indent=2)
|
||||
|
||||
|
||||
def unload_vae():
|
||||
global vae # pylint: disable=global-statement
|
||||
vae = None
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('input', type=str, help='directory for train images')
|
||||
parser.add_argument('--json', type=str, required=True, help='metadata file to input')
|
||||
parser.add_argument('--vae', type=str, required=True, help='model name or path to encode latents')
|
||||
parser.add_argument('--batch', type=int, default=1, help='batch size in inference')
|
||||
parser.add_argument('--resolution', type=str, default='512,512', help='max resolution in fine tuning (width,height)')
|
||||
parser.add_argument('--min', type=int, default=256, help='minimum resolution for buckets')
|
||||
parser.add_argument('--max', type=int, default=1024, help='maximum resolution for buckets')
|
||||
parser.add_argument('--steps', type=int, default=64, help='steps of resolution for buckets, divisible by 8')
|
||||
parser.add_argument('--noupscale', action='store_true', help='make bucket for each image without upscaling')
|
||||
parser.add_argument('--precision', type=str, default='fp32', choices=['fp32', 'fp16', 'bf16'], help='use precision')
|
||||
params = parser.parse_args()
|
||||
create_vae_latents(vars(params))
|
||||
@@ -1,176 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
import os
|
||||
import time
|
||||
import functools
|
||||
import argparse
|
||||
import logging
|
||||
import warnings
|
||||
from dataclasses import dataclass
|
||||
|
||||
logging.getLogger("DeepSpeed").disabled = True
|
||||
warnings.filterwarnings(action="ignore", category=FutureWarning)
|
||||
warnings.filterwarnings(action="ignore", category=DeprecationWarning)
|
||||
|
||||
import torch
|
||||
import diffusers
|
||||
|
||||
n_warmup = 5
|
||||
n_traces = 10
|
||||
n_runs = 100
|
||||
args = {}
|
||||
pipe = None
|
||||
log = logging.getLogger("sd")
|
||||
|
||||
|
||||
def setup_logging():
|
||||
from rich.theme import Theme
|
||||
from rich.logging import RichHandler
|
||||
from rich.console import Console
|
||||
from rich.traceback import install
|
||||
log.setLevel(logging.DEBUG)
|
||||
console = Console(log_time=True, log_time_format='%H:%M:%S-%f', theme=Theme({ "traceback.border": "black", "traceback.border.syntax_error": "black", "inspect.value.border": "black" }))
|
||||
logging.basicConfig(level=logging.ERROR, format='%(asctime)s | %(name)s | %(levelname)s | %(module)s | %(message)s', handlers=[logging.NullHandler()]) # redirect default logger to null
|
||||
rh = RichHandler(show_time=True, omit_repeated_times=False, show_level=True, show_path=False, markup=False, rich_tracebacks=True, log_time_format='%H:%M:%S-%f', level=logging.DEBUG, console=console)
|
||||
rh.setLevel(logging.DEBUG)
|
||||
log.addHandler(rh)
|
||||
logging.getLogger("diffusers").setLevel(logging.ERROR)
|
||||
logging.getLogger("torch").setLevel(logging.ERROR)
|
||||
warnings.filterwarnings(action="ignore", category=torch.jit.TracerWarning)
|
||||
install(console=console, extra_lines=1, max_frames=10, width=console.width, word_wrap=False, indent_guides=False, suppress=[])
|
||||
|
||||
|
||||
def generate_inputs():
|
||||
if args.type == 'sd15':
|
||||
sample = torch.randn(2, 4, 64, 64).half().cuda()
|
||||
timestep = torch.rand(1).half().cuda() * 999
|
||||
encoder_hidden_states = torch.randn(2, 77, 768).half().cuda()
|
||||
return sample, timestep, encoder_hidden_states
|
||||
if args.type == 'sdxl':
|
||||
sample = torch.randn(2, 4, 64, 64).half().cuda()
|
||||
timestep = torch.rand(1).half().cuda() * 999
|
||||
encoder_hidden_states = torch.randn(2, 77, 768).half().cuda()
|
||||
text_embeds = torch.randn(1, 77, 2048).half().cuda()
|
||||
return sample, timestep, encoder_hidden_states, text_embeds
|
||||
|
||||
|
||||
def load_model():
|
||||
log.info(f'versions: torch={torch.__version__} diffusers={diffusers.__version__}')
|
||||
diffusers_load_config = {
|
||||
"low_cpu_mem_usage": True,
|
||||
"torch_dtype": torch.float16,
|
||||
"safety_checker": None,
|
||||
"requires_safety_checker": False,
|
||||
"load_connected_pipeline": True,
|
||||
"use_safetensors": True,
|
||||
}
|
||||
pipeline = diffusers.StableDiffusionPipeline if args.type == 'sd15' else diffusers.StableDiffusionXLPipeline
|
||||
global pipe # pylint: disable=global-statement
|
||||
t0 = time.time()
|
||||
pipe = pipeline.from_single_file(args.model, **diffusers_load_config).to('cuda')
|
||||
size = os.path.getsize(args.model)
|
||||
log.info(f'load: model={args.model} type={args.type} time={time.time() - t0:.3f}s size={size / 1024 / 1024:.3f}mb')
|
||||
|
||||
|
||||
def load_trace(fn: str):
|
||||
|
||||
@dataclass
|
||||
class UNet2DConditionOutput:
|
||||
sample: torch.FloatTensor
|
||||
|
||||
class TracedUNet(torch.nn.Module):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.in_channels = pipe.unet.in_channels
|
||||
self.device = pipe.unet.device
|
||||
|
||||
def forward(self, latent_model_input, t, encoder_hidden_states):
|
||||
sample = unet_traced(latent_model_input, t, encoder_hidden_states)[0]
|
||||
return UNet2DConditionOutput(sample=sample)
|
||||
|
||||
t0 = time.time()
|
||||
unet_traced = torch.jit.load(fn)
|
||||
pipe.unet = TracedUNet()
|
||||
size = os.path.getsize(fn)
|
||||
log.info(f'load: optimized={fn} time={time.time() - t0:.3f}s size={size / 1024 / 1024:.3f}mb')
|
||||
|
||||
|
||||
def trace_model():
|
||||
log.info(f'tracing model: {args.model}')
|
||||
torch.set_grad_enabled(False)
|
||||
unet = pipe.unet
|
||||
unet.eval()
|
||||
# unet.to(memory_format=torch.channels_last) # use channels_last memory format
|
||||
unet.forward = functools.partial(unet.forward, return_dict=False) # set return_dict=False as default
|
||||
|
||||
# warmup
|
||||
t0 = time.time()
|
||||
for _ in range(n_warmup):
|
||||
with torch.inference_mode():
|
||||
inputs = generate_inputs()
|
||||
_output = unet(*inputs)
|
||||
log.info(f'warmup: time={time.time() - t0:.3f}s passes={n_warmup}')
|
||||
|
||||
# trace
|
||||
t0 = time.time()
|
||||
unet_traced = torch.jit.trace(unet, inputs, check_trace=True)
|
||||
unet_traced.eval()
|
||||
log.info(f'trace: time={time.time() - t0:.3f}s')
|
||||
|
||||
# optimize graph
|
||||
t0 = time.time()
|
||||
for _ in range(n_traces):
|
||||
with torch.inference_mode():
|
||||
inputs = generate_inputs()
|
||||
_output = unet_traced(*inputs)
|
||||
log.info(f'optimize: time={time.time() - t0:.3f}s passes={n_traces}')
|
||||
|
||||
# save the model
|
||||
if args.save:
|
||||
t0 = time.time()
|
||||
basename, _ext = os.path.splitext(args.model)
|
||||
fn = f"{basename}.pt"
|
||||
unet_traced.save(fn)
|
||||
size = os.path.getsize(fn)
|
||||
log.info(f'save: optimized={fn} time={time.time() - t0:.3f}s size={size / 1024 / 1024:.3f}mb')
|
||||
return fn
|
||||
|
||||
pipe.unet = unet_traced
|
||||
return None
|
||||
|
||||
|
||||
def benchmark_model(msg: str):
|
||||
with torch.inference_mode():
|
||||
inputs = generate_inputs()
|
||||
torch.cuda.synchronize()
|
||||
for n in range(n_runs):
|
||||
if n > n_runs / 10:
|
||||
t0 = time.time()
|
||||
_output = pipe.unet(*inputs)
|
||||
torch.cuda.synchronize()
|
||||
t1 = time.time()
|
||||
log.info(f"benchmark unet: {t1 - t0:.3f}s passes={n_runs} type={msg}")
|
||||
return t1 - t0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description = 'SD.Next')
|
||||
parser.add_argument('--model', type=str, default='', required=True, help='model path')
|
||||
parser.add_argument('--type', type=str, default='sd15', choices=['sd15', 'sdxl'], required=False, help='model type, default: %(default)s')
|
||||
parser.add_argument('--benchmark', default = False, action='store_true', help = "run benchmarks, default: %(default)s")
|
||||
parser.add_argument('--trace', default = True, action='store_true', help = "run jit tracing, default: %(default)s")
|
||||
parser.add_argument('--save', default = False, action='store_true', help = "save optimized unet, default: %(default)s")
|
||||
args = parser.parse_args()
|
||||
setup_logging()
|
||||
log.info('sdnext model jit tracing')
|
||||
if not os.path.isfile(args.model):
|
||||
log.error(f"invalid model path: {args.model}")
|
||||
exit(1)
|
||||
load_model()
|
||||
if args.benchmark:
|
||||
time0 = benchmark_model('original')
|
||||
unet_saved = trace_model()
|
||||
if unet_saved is not None:
|
||||
load_trace(unet_saved)
|
||||
if args.benchmark:
|
||||
time1 = benchmark_model('traced')
|
||||
log.info(f'benchmark speedup: {100 * (time0 - time1) / time0:.3f}%')
|
||||
@@ -1,99 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# pylint: disable=cell-var-from-loop
|
||||
"""
|
||||
Test Torch Dynamo functionality and backends
|
||||
"""
|
||||
import json
|
||||
import warnings
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
from torchvision.models import resnet18
|
||||
|
||||
|
||||
print('torch:', torch.__version__)
|
||||
try:
|
||||
# must be imported explicitly or namespace is not found
|
||||
import torch._dynamo as dynamo # pylint: disable=ungrouped-imports
|
||||
except Exception as err:
|
||||
print('torch without dynamo support', err)
|
||||
|
||||
|
||||
N_ITERS = 20
|
||||
torch._dynamo.config.verbose=True # pylint: disable=protected-access
|
||||
warnings.filterwarnings('ignore', category=UserWarning) # disable those for now as many backends reports tons
|
||||
# torch.set_float32_matmul_precision('high') # enable to test in fp32
|
||||
|
||||
|
||||
def timed(fn): # returns the result of running `fn()` and the time it took for `fn()` to run in ms using CUDA events
|
||||
start = torch.cuda.Event(enable_timing=True)
|
||||
end = torch.cuda.Event(enable_timing=True)
|
||||
start.record()
|
||||
result = fn()
|
||||
end.record()
|
||||
torch.cuda.synchronize()
|
||||
return result, start.elapsed_time(end)
|
||||
|
||||
|
||||
def generate_data(b):
|
||||
return (
|
||||
torch.randn(b, 3, 128, 128).to(torch.float32).cuda(),
|
||||
torch.randint(1000, (b,)).cuda(),
|
||||
)
|
||||
|
||||
|
||||
def init_model():
|
||||
return resnet18().to(torch.float32).cuda()
|
||||
|
||||
|
||||
def evaluate(mod, val):
|
||||
return mod(val)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# first pass, dynamo is going to be slower as it compiles
|
||||
model = init_model()
|
||||
inp = generate_data(16)[0]
|
||||
|
||||
# repeat test
|
||||
results = {}
|
||||
times = []
|
||||
print('eager initial eval:', timed(lambda: evaluate(model, inp))[1])
|
||||
for _i in range(N_ITERS):
|
||||
inp = generate_data(16)[0]
|
||||
_res, time = timed(lambda: evaluate(model, inp)) # noqa: B023
|
||||
times.append(time)
|
||||
results['default'] = np.median(times)
|
||||
|
||||
print('dynamo available backends:', dynamo.list_backends())
|
||||
for backend in dynamo.list_backends():
|
||||
try:
|
||||
# required before changing backends
|
||||
torch._dynamo.reset() # pylint: disable=protected-access
|
||||
eval_dyn = dynamo.optimize(backend)(evaluate)
|
||||
print('dynamo initial eval:', backend, timed(lambda: eval_dyn(model, inp))[1]) # noqa: B023
|
||||
times = []
|
||||
for _i in range(N_ITERS):
|
||||
inp = generate_data(16)[0]
|
||||
_res, time = timed(lambda: eval_dyn(model, inp)) # noqa: B023
|
||||
times.append(time)
|
||||
results[backend] = np.median(times)
|
||||
except Exception as err:
|
||||
lines = str(err).split('\n')
|
||||
print('dyanmo backend failed:', backend, lines[0]) # print just first error line as backtraces can be quite long
|
||||
results[backend] = 'error'
|
||||
|
||||
# print stats
|
||||
print(json.dumps(results, indent = 4))
|
||||
|
||||
"""
|
||||
Reference: <https://github.com/pytorch/pytorch/blob/4f4b62e4a255708e928445b6502139d5962974fa/docs/source/dynamo/get-started.rst>
|
||||
Training & Inference backends:
|
||||
dynamo.optimize("inductor") - Uses TorchInductor backend with AotAutograd and cudagraphs by leveraging codegened Triton kernels
|
||||
dynamo.optimize("aot_nvfuser") - nvFuser with AotAutograd
|
||||
dynamo.optimize("aot_cudagraphs") - cudagraphs with AotAutograd
|
||||
Inference-only backends:
|
||||
dynamo.optimize("ofi") - Uses Torchscript optimize_for_inference
|
||||
dynamo.optimize("fx2trt") - Uses Nvidia TensorRT for inference optimizations
|
||||
dynamo.optimize("onnxrt") - Uses ONNXRT for inference on CPU/GPU
|
||||
"""
|
||||
-443
@@ -1,443 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Examples:
|
||||
- sd15: train.py --type lora --tag girl --comments sdnext --input ~/generative/Input/mia --process original,interrogate,resize --name mia
|
||||
- sdxl: train.py --type lora --tag girl --comments sdnext --input ~/generative/Input/mia --process original,interrogate,resize --precision fp32 --optimizer Adafactor --sdxl --name miaxl
|
||||
- offline: train.py --type lora --tag girl --comments sdnext --input ~/generative/Input/mia --model /home/vlado/dev/sdnext/models/Stable-diffusion/sdxl/miaanimeSFWNSFWSDXL_v40.safetensors --dir /home/vlado/dev/sdnext/models/Lora/ --precision fp32 --optimizer Adafactor --sdxl --name miaxl
|
||||
"""
|
||||
|
||||
# system imports
|
||||
import os
|
||||
import re
|
||||
import gc
|
||||
import sys
|
||||
import json
|
||||
import shutil
|
||||
import pathlib
|
||||
import asyncio
|
||||
import logging
|
||||
import tempfile
|
||||
import argparse
|
||||
|
||||
# local imports
|
||||
import util
|
||||
import sdapi
|
||||
import options
|
||||
|
||||
|
||||
# globals
|
||||
args = None
|
||||
log = logging.getLogger('train')
|
||||
valid_steps = ['original', 'face', 'body', 'blur', 'range', 'upscale', 'restore', 'interrogate', 'resize', 'square', 'segment']
|
||||
log_file = os.path.join(os.path.dirname(__file__), 'train.log')
|
||||
server_ok = False
|
||||
|
||||
# methods
|
||||
|
||||
def setup_logging():
|
||||
from rich.theme import Theme
|
||||
from rich.logging import RichHandler
|
||||
from rich.console import Console
|
||||
from rich.pretty import install as pretty_install
|
||||
from rich.traceback import install as traceback_install
|
||||
console = Console(log_time=True, log_time_format='%H:%M:%S-%f', theme=Theme({
|
||||
"traceback.border": "black",
|
||||
"traceback.border.syntax_error": "black",
|
||||
"inspect.value.border": "black",
|
||||
}))
|
||||
# logging.getLogger("urllib3").setLevel(logging.ERROR)
|
||||
# logging.getLogger("httpx").setLevel(logging.ERROR)
|
||||
level = logging.DEBUG if args.debug else logging.INFO
|
||||
logging.basicConfig(level=logging.ERROR, format='%(asctime)s | %(name)s | %(levelname)s | %(module)s | %(message)s', filename=log_file, filemode='a', encoding='utf-8', force=True)
|
||||
log.setLevel(logging.DEBUG) # log to file is always at level debug for facility `sd`
|
||||
pretty_install(console=console)
|
||||
traceback_install(console=console, extra_lines=1, width=console.width, word_wrap=False, indent_guides=False, suppress=[])
|
||||
rh = RichHandler(show_time=True, omit_repeated_times=False, show_level=True, show_path=False, markup=False, rich_tracebacks=True, log_time_format='%H:%M:%S-%f', level=level, console=console)
|
||||
rh.set_name(level)
|
||||
while log.hasHandlers() and len(log.handlers) > 0:
|
||||
log.removeHandler(log.handlers[0])
|
||||
log.addHandler(rh)
|
||||
|
||||
|
||||
def mem_stats():
|
||||
gc.collect()
|
||||
import torch
|
||||
if torch.cuda.is_available():
|
||||
with torch.no_grad():
|
||||
torch.cuda.empty_cache()
|
||||
with torch.cuda.device('cuda'):
|
||||
torch.cuda.empty_cache()
|
||||
torch.cuda.ipc_collect()
|
||||
mem = util.get_memory()
|
||||
peak = { 'active': mem['gpu-active']['peak'], 'allocated': mem['gpu-allocated']['peak'], 'reserved': mem['gpu-reserved']['peak'] }
|
||||
log.debug(f"memory cpu: {mem.ram} gpu current: {mem.gpu} gpu peak: {peak}")
|
||||
|
||||
|
||||
def parse_args():
|
||||
global args # pylint: disable=global-statement
|
||||
parser = argparse.ArgumentParser(description = 'SD.Next Train')
|
||||
|
||||
group_server = parser.add_argument_group('Server')
|
||||
group_server.add_argument('--server', type=str, default='http://127.0.0.1:7860', required=False, help='server url, default: %(default)s')
|
||||
group_server.add_argument('--user', type=str, default=None, required=False, help='server url, default: %(default)s')
|
||||
group_server.add_argument('--password', type=str, default=None, required=False, help='server url, default: %(default)s')
|
||||
group_server.add_argument('--dir', type=str, default=None, required=False, help='folder with trained networks, default: use server setting')
|
||||
|
||||
group_main = parser.add_argument_group('Main')
|
||||
group_main.add_argument('--type', type=str, choices=['embedding', 'ti', 'lora', 'lyco', 'dreambooth', 'hypernetwork'], default=None, required=True, help='training type')
|
||||
group_main.add_argument('--model', type=str, default='', required=False, help='base model to use for training, default: current loaded model')
|
||||
group_main.add_argument('--name', type=str, default=None, required=True, help='output filename')
|
||||
group_main.add_argument('--tag', type=str, default='person', required=False, help='primary tags, default: %(default)s')
|
||||
group_main.add_argument('--comments', type=str, default='', required=False, help='comments to be added to trained model metadata, default: %(default)s')
|
||||
|
||||
group_data = parser.add_argument_group('Dataset')
|
||||
group_data.add_argument('--input', type=str, default=None, required=True, help='input folder with training images')
|
||||
group_data.add_argument('--interim', type=str, default='', required=False, help='where to store processed images, default is system temp/train')
|
||||
group_data.add_argument('--process', type=str, default='original,interrogate,resize,square', required=False, help=f'list of possible processing steps: {valid_steps}, default: %(default)s')
|
||||
|
||||
group_train = parser.add_argument_group('Train')
|
||||
group_train.add_argument('--gradient', type=int, default=1, required=False, help='gradient accumulation steps, default: %(default)s')
|
||||
group_train.add_argument('--steps', type=int, default=2500, required=False, help='training steps, default: %(default)s')
|
||||
group_train.add_argument('--batch', type=int, default=1, required=False, help='batch size, default: %(default)s')
|
||||
group_train.add_argument('--lr', type=float, default=1e-04, required=False, help='model learning rate, default: %(default)s')
|
||||
group_train.add_argument('--dim', type=int, default=32, required=False, help='network dimension or number of vectors, default: %(default)s')
|
||||
|
||||
# lora params
|
||||
group_train.add_argument('--repeats', type=int, default=1, required=False, help='number of repeats per image, default: %(default)s')
|
||||
group_train.add_argument('--alpha', type=float, default=0, required=False, help='lora/lyco alpha for weights scaling, default: dim/2')
|
||||
group_train.add_argument('--algo', type=str, default=None, choices=['locon', 'loha', 'lokr', 'ia3'], required=False, help='alternative lyco algoritm, default: %(default)s')
|
||||
group_train.add_argument('--args', type=str, default=None, required=False, help='lora/lyco additional network arguments, default: %(default)s')
|
||||
group_train.add_argument('--optimizer', type=str, default='AdamW', required=False, help='optimizer type, default: %(default)s')
|
||||
group_train.add_argument('--precision', type=str, choices=['fp16', 'fp32'], default='fp16', required=False, help='training precision, default: %(default)s')
|
||||
group_train.add_argument('--sdxl', default = False, action='store_true', help = "run sdxl training, default: %(default)s")
|
||||
# AdamW (default), AdamW8bit, PagedAdamW8bit, Lion8bit, PagedLion8bit, Lion, SGDNesterov, SGDNesterov8bit, DAdaptation(DAdaptAdamPreprint), DAdaptAdaGrad, DAdaptAdam, DAdaptAdan, DAdaptAdanIP, DAdaptLion, DAdaptSGD, AdaFactor
|
||||
|
||||
group_other = parser.add_argument_group('Other')
|
||||
group_other.add_argument('--overwrite', default = False, action='store_true', help = "overwrite existing training, default: %(default)s")
|
||||
group_other.add_argument('--experimental', default = False, action='store_true', help = "enable experimental options, default: %(default)s")
|
||||
group_other.add_argument('--debug', default = False, action='store_true', help = "enable debug level logging, default: %(default)s")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
def prepare_server():
|
||||
global server_ok # pylint: disable=global-statement
|
||||
try:
|
||||
server_status = util.Map(sdapi.progresssync())
|
||||
server_state = server_status['state']
|
||||
server_ok = True
|
||||
except Exception:
|
||||
log.warning(f'sdnext server error: {server_status}')
|
||||
server_ok = False
|
||||
if server_ok and server_state['job_count'] > 0:
|
||||
log.error(f'sdnext server not idle: {server_state}')
|
||||
exit(1)
|
||||
if server_ok:
|
||||
server_options = util.Map(sdapi.options())
|
||||
server_options.options.save_training_settings_to_txt = False
|
||||
server_options.options.training_enable_tensorboard = False
|
||||
server_options.options.training_tensorboard_save_images = False
|
||||
server_options.options.pin_memory = True
|
||||
server_options.options.save_optimizer_state = False
|
||||
server_options.options.training_image_repeats_per_epoch = args.repeats
|
||||
server_options.options.training_write_csv_every = 0
|
||||
sdapi.postsync('/sdapi/v1/options', server_options.options)
|
||||
log.info('updated server options')
|
||||
|
||||
|
||||
def verify_args():
|
||||
server_options = util.Map(sdapi.options())
|
||||
if args.model != '':
|
||||
if not os.path.isfile(args.model):
|
||||
log.error(f'cannot find loaded model: {args.model}')
|
||||
exit(1)
|
||||
if server_ok:
|
||||
server_options.options.sd_model_checkpoint = args.model
|
||||
sdapi.postsync('/sdapi/v1/options', server_options.options)
|
||||
elif server_ok:
|
||||
args.model = server_options.options.sd_model_checkpoint.split(' [')[0]
|
||||
if args.sdxl and (server_options.sd_backend != 'diffusers' or server_options.diffusers_pipeline != 'Stable Diffusion XL'):
|
||||
log.warning('server checkpoint is not sdxl')
|
||||
else:
|
||||
log.error('no model specified')
|
||||
exit(1)
|
||||
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
if args.type == 'lora' and not server_ok and not args.dir:
|
||||
log.error('offline lora training requires --dir <lora folder>')
|
||||
exit(1)
|
||||
if args.type == 'lora':
|
||||
import transformers
|
||||
if transformers.__version__ != '4.30.2':
|
||||
log.error(f'lora training requires specific transformers version: current {transformers.__version__} required transformers==4.30.2')
|
||||
exit(1)
|
||||
args.lora_dir = server_options.options.lora_dir or args.dir
|
||||
if not os.path.isabs(args.lora_dir):
|
||||
args.lora_dir = os.path.join(base_dir, args.lora_dir)
|
||||
args.lyco_dir = server_options.options.lyco_dir or args.dir
|
||||
if not os.path.isabs(args.lyco_dir):
|
||||
args.lyco_dir = os.path.join(base_dir, args.lyco_dir)
|
||||
args.embeddings_dir = server_options.options.embeddings_dir or args.dir
|
||||
if not os.path.isfile(args.model):
|
||||
args.ckpt_dir = server_options.options.ckpt_dir
|
||||
if not os.path.isabs(args.ckpt_dir):
|
||||
args.ckpt_dir = os.path.join(base_dir, args.ckpt_dir)
|
||||
attempt = os.path.abspath(os.path.join(args.ckpt_dir, args.model))
|
||||
args.model = attempt if os.path.isfile(attempt) else args.model
|
||||
if not os.path.isfile(args.model):
|
||||
attempt = os.path.abspath(os.path.join(args.ckpt_dir, args.model + '.safetensors'))
|
||||
args.model = attempt if os.path.isfile(attempt) else args.model
|
||||
if not os.path.isfile(args.model):
|
||||
log.error(f'cannot find loaded model: {args.model}')
|
||||
exit(1)
|
||||
if not os.path.exists(args.input) or not os.path.isdir(args.input):
|
||||
log.error(f'cannot find training folder: {args.input}')
|
||||
exit(1)
|
||||
if not os.path.exists(args.lora_dir) or not os.path.isdir(args.lora_dir):
|
||||
log.error(f'cannot find lora folder: {args.lora_dir}')
|
||||
exit(1)
|
||||
if not os.path.exists(args.lyco_dir) or not os.path.isdir(args.lyco_dir):
|
||||
log.error(f'cannot find lyco folder: {args.lyco_dir}')
|
||||
exit(1)
|
||||
if args.interim != '':
|
||||
args.process_dir = args.interim
|
||||
else:
|
||||
args.process_dir = os.path.join(tempfile.gettempdir(), 'train', args.name)
|
||||
log.debug(f'args: {vars(args)}')
|
||||
log.debug(f'server flags: {server_options.flags}')
|
||||
log.debug(f'server options: {server_options.options}')
|
||||
|
||||
|
||||
async def training_loop():
|
||||
async def async_train():
|
||||
res = await sdapi.post('/sdapi/v1/train/embedding', options.embedding)
|
||||
log.info(f'train embedding result: {res}')
|
||||
|
||||
async def async_monitor():
|
||||
from tqdm.rich import tqdm
|
||||
await asyncio.sleep(3)
|
||||
res = util.Map(sdapi.progress())
|
||||
with tqdm(desc='train embedding', total=res.state.job_count) as pbar:
|
||||
while res.state.job_no < res.state.job_count and not res.state.interrupted and not res.state.skipped:
|
||||
await asyncio.sleep(2)
|
||||
prev_job = res.state.job_no
|
||||
res = util.Map(sdapi.progress())
|
||||
loss = re.search(r"Loss: (.*?)(?=\<)", res.textinfo)
|
||||
if loss:
|
||||
pbar.set_postfix({ 'loss': loss.group(0) })
|
||||
pbar.update(res.state.job_no - prev_job)
|
||||
|
||||
a = asyncio.create_task(async_train())
|
||||
b = asyncio.create_task(async_monitor())
|
||||
await asyncio.gather(a, b) # wait for both pipeline and monitor to finish
|
||||
|
||||
|
||||
def train_embedding():
|
||||
log.info(f'{args.type} options: {options.embedding}')
|
||||
create_options = util.Map({
|
||||
"name": args.name,
|
||||
"num_vectors_per_token": args.dim,
|
||||
"overwrite_old": False,
|
||||
"init_text": args.tag,
|
||||
})
|
||||
fn = os.path.join(args.embeddings_dir, args.name) + '.pt'
|
||||
if os.path.exists(fn) and args.overwrite:
|
||||
log.warning(f'delete existing embedding {fn}')
|
||||
os.remove(fn)
|
||||
else:
|
||||
log.error(f'embedding exists {fn}')
|
||||
return
|
||||
log.info(f'create embedding {create_options}')
|
||||
res = sdapi.postsync('/sdapi/v1/create/embedding', create_options)
|
||||
if 'info' in res and 'error' in res['info']: # formatted error
|
||||
log.error(res.info)
|
||||
elif 'info' in res: # no error
|
||||
asyncio.run(training_loop())
|
||||
else: # unknown error
|
||||
log.error(f'create embedding error {res}')
|
||||
|
||||
|
||||
def train_lora():
|
||||
fn = os.path.join(options.lora.output_dir, args.name)
|
||||
for ext in ['.ckpt', '.pt', '.safetensors']:
|
||||
if os.path.exists(fn + ext):
|
||||
if args.overwrite:
|
||||
log.warning(f'delete existing lora: {fn + ext}')
|
||||
os.remove(fn + ext)
|
||||
else:
|
||||
log.error(f'lora exists: {fn + ext}')
|
||||
return
|
||||
log.info(f'{args.type} options: {options.lora}')
|
||||
# lora imports
|
||||
lora_path = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, 'modules', 'lora'))
|
||||
lycoris_path = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, 'modules', 'lycoris'))
|
||||
sys.path.append(lora_path)
|
||||
if args.type == 'lyco':
|
||||
sys.path.append(lycoris_path)
|
||||
log.debug('importing lora lib')
|
||||
if not args.sdxl:
|
||||
import train_network
|
||||
trainer = train_network.NetworkTrainer()
|
||||
trainer.train(options.lora)
|
||||
else:
|
||||
import sdxl_train_network
|
||||
trainer = sdxl_train_network.SdxlNetworkTrainer()
|
||||
trainer.train(options.lora)
|
||||
if args.type == 'lyco':
|
||||
log.debug('importing lycoris lib')
|
||||
import importlib
|
||||
_network_module = importlib.import_module(options.lora.network_module)
|
||||
|
||||
|
||||
def prepare_options():
|
||||
if args.type == 'embedding':
|
||||
log.info('train embedding')
|
||||
options.lora.in_json = None
|
||||
if args.type == 'dreambooth':
|
||||
log.info('train using dreambooth style training')
|
||||
options.lora.vae_batch_size = args.batch
|
||||
options.lora.in_json = None
|
||||
if args.type == 'lora':
|
||||
log.info('train using lora style training')
|
||||
options.lora.output_dir = args.lora_dir
|
||||
options.lora.in_json = os.path.join(args.process_dir, args.name + '.json')
|
||||
if args.type == 'lyco':
|
||||
log.info('train using lycoris network')
|
||||
options.lora.output_dir = args.lora_dir
|
||||
options.lora.network_module = 'lycoris.kohya'
|
||||
options.lora.in_json = os.path.join(args.process_dir, args.name + '.json')
|
||||
# lora specific
|
||||
options.lora.save_model_as = 'safetensors'
|
||||
options.lora.pretrained_model_name_or_path = args.model
|
||||
options.lora.output_name = args.name
|
||||
options.lora.max_train_steps = args.steps
|
||||
options.lora.network_dim = args.dim
|
||||
options.lora.network_alpha = args.dim // 2 if args.alpha == 0 else args.alpha
|
||||
options.lora.network_args = []
|
||||
options.lora.training_comment = args.comments
|
||||
options.lora.sdpa = True
|
||||
options.lora.optimizer_type = args.optimizer
|
||||
if args.algo is not None:
|
||||
options.lora.network_args.append(f'algo={args.algo}')
|
||||
if args.args is not None:
|
||||
for net_arg in args.args:
|
||||
options.lora.network_args.append(net_arg)
|
||||
options.lora.gradient_accumulation_steps = args.gradient
|
||||
options.lora.learning_rate = args.lr
|
||||
options.lora.train_batch_size = args.batch
|
||||
options.lora.train_data_dir = args.process_dir
|
||||
options.lora.no_half_vae = args.precision == 'fp16'
|
||||
# embedding specific
|
||||
options.embedding.embedding_name = args.name
|
||||
options.embedding.learn_rate = str(args.lr)
|
||||
options.embedding.batch_size = args.batch
|
||||
options.embedding.steps = args.steps
|
||||
options.embedding.data_root = args.process_dir
|
||||
options.embedding.log_directory = os.path.join(args.process_dir, 'log')
|
||||
options.embedding.gradient_step = args.gradient
|
||||
|
||||
|
||||
def process_inputs():
|
||||
import process
|
||||
import filetype
|
||||
pathlib.Path(args.process_dir).mkdir(parents=True, exist_ok=True)
|
||||
processing_options = args.process.split(',') if isinstance(args.process, str) else args.process
|
||||
processing_options = [opt.strip() for opt in re.split(',| ', args.process)]
|
||||
log.info(f'processing steps: {processing_options}')
|
||||
for step in processing_options:
|
||||
if step not in valid_steps:
|
||||
log.error(f'invalid processing step: {[step]}')
|
||||
exit(1)
|
||||
for root, _sub_dirs, folder in os.walk(args.input):
|
||||
files = [os.path.join(root, f) for f in folder if filetype.is_image(os.path.join(root, f))]
|
||||
log.info(f'processing input images: {len(files)}')
|
||||
if os.path.exists(args.process_dir):
|
||||
if args.overwrite:
|
||||
log.warning(f'removing existing processed folder: {args.process_dir}')
|
||||
shutil.rmtree(args.process_dir, ignore_errors=True)
|
||||
else:
|
||||
log.info(f'processed folder exists: {args.process_dir}')
|
||||
steps = [step for step in processing_options if step in ['face', 'body', 'original']]
|
||||
process.reset()
|
||||
options.process.target_size = 1024 if args.sdxl else 512
|
||||
metadata = {}
|
||||
for step in steps:
|
||||
if step == 'face':
|
||||
opts = [step for step in processing_options if step not in ['body', 'original']]
|
||||
if step == 'body':
|
||||
opts = [step for step in processing_options if step not in ['face', 'original', 'upscale', 'restore']] # body does not perform upscale or restore
|
||||
if step == 'original':
|
||||
opts = [step for step in processing_options if step not in ['face', 'body', 'upscale', 'restore', 'blur', 'range', 'segment']] # original does not perform most steps
|
||||
log.info(f'processing current step: {opts}')
|
||||
tag = step
|
||||
if tag == 'original' and args.tag is not None:
|
||||
concept = args.tag.split(',')[0].strip()
|
||||
else:
|
||||
concept = step
|
||||
if args.type in ['lora', 'lyco', 'dreambooth']:
|
||||
folder = os.path.join(args.process_dir, str(args.repeats) + '_' + concept) # separate concepts per folder
|
||||
if args.type in ['embedding']:
|
||||
folder = os.path.join(args.process_dir) # everything into same folder
|
||||
log.info(f'processing concept: {concept}')
|
||||
log.info(f'processing output folder: {folder}')
|
||||
pathlib.Path(folder).mkdir(parents=True, exist_ok=True)
|
||||
results = {}
|
||||
if server_ok:
|
||||
for f in files:
|
||||
res = process.file(filename = f, folder = folder, tag = args.tag, requested = opts)
|
||||
if res.image: # valid result
|
||||
results[res.type] = results.get(res.type, 0) + 1
|
||||
results['total'] = results.get('total', 0) + 1
|
||||
rel_path = res.basename.replace(os.path.commonpath([res.basename, args.process_dir]), '')
|
||||
if rel_path.startswith(os.path.sep):
|
||||
rel_path = rel_path[1:]
|
||||
metadata[rel_path] = { 'caption': res.caption, 'tags': ','.join(res.tags) }
|
||||
if options.lora.in_json is None:
|
||||
with open(res.output.replace(options.process.format, '.txt'), "w", encoding='utf-8') as outfile:
|
||||
outfile.write(res.caption)
|
||||
log.info(f"processing {'saved' if res.image is not None else 'skipped'}: {f} => {res.output} {res.ops} {res.message}")
|
||||
else:
|
||||
log.info('processing skipped: offline')
|
||||
folders = [os.path.join(args.process_dir, folder) for folder in os.listdir(args.process_dir) if os.path.isdir(os.path.join(args.process_dir, folder))]
|
||||
log.info(f'input datasets {folders}')
|
||||
if options.lora.in_json is not None:
|
||||
with open(options.lora.in_json, "w", encoding='utf-8') as outfile: # write json at the end only
|
||||
outfile.write(json.dumps(metadata, indent=2))
|
||||
for folder in folders: # create latents
|
||||
import latents
|
||||
latents.create_vae_latents(util.Map({ 'input': folder, 'json': options.lora.in_json }))
|
||||
latents.unload_vae()
|
||||
r = { 'inputs': len(files), 'outputs': results, 'metadata': options.lora.in_json }
|
||||
log.info(f'processing steps result: {r}')
|
||||
if args.gradient < 0:
|
||||
log.info(f"setting gradient accumulation to number of images: {results['total']}")
|
||||
options.lora.gradient_accumulation_steps = results['total']
|
||||
options.embedding.gradient_step = results['total']
|
||||
process.unload()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parse_args()
|
||||
setup_logging()
|
||||
log.info('SD.Next Train')
|
||||
sdapi.sd_url = args.server
|
||||
if args.user is not None:
|
||||
sdapi.sd_username = args.user
|
||||
if args.password is not None:
|
||||
sdapi.sd_password = args.password
|
||||
prepare_server()
|
||||
verify_args()
|
||||
prepare_options()
|
||||
mem_stats()
|
||||
process_inputs()
|
||||
mem_stats()
|
||||
try:
|
||||
if args.type == 'embedding':
|
||||
train_embedding()
|
||||
if args.type == 'lora' or args.type == 'lyco' or args.type == 'dreambooth':
|
||||
train_lora()
|
||||
except KeyboardInterrupt:
|
||||
log.error('interrupt requested')
|
||||
sdapi.interrupt()
|
||||
mem_stats()
|
||||
log.info('done')
|
||||
+1
-1
@@ -13,7 +13,7 @@ class Interpreter:
|
||||
|
||||
def execute(self, s: str):
|
||||
try:
|
||||
exec(s, self.env_globals, self.env_locals)
|
||||
exec(s, self.env_globals, self.env_locals) # pylint: disable=exec-used
|
||||
except Exception as e:
|
||||
print(f'{e.__class__.__name__}: {e}')
|
||||
|
||||
|
||||
@@ -193,6 +193,8 @@ def control_run(units: List[unit.Unit] = [], inputs: List[Image.Image] = [], ini
|
||||
p.refiner_negative = refiner_negative
|
||||
if p.enable_hr and (p.hr_resize_x == 0 or p.hr_resize_y == 0):
|
||||
p.hr_upscale_to_x, p.hr_upscale_to_y = 8 * int(p.width * p.hr_scale / 8), 8 * int(p.height * p.hr_scale / 8)
|
||||
elif p.enable_hr and (p.hr_upscale_to_x == 0 or p.hr_upscale_to_y == 0):
|
||||
p.hr_upscale_to_x, p.hr_upscale_to_y = 8 * int(p.hr_resize_x / 8), 8 * int(hr_resize_y / 8)
|
||||
|
||||
global p_extra_args # pylint: disable=global-statement
|
||||
for k, v in p_extra_args.items():
|
||||
|
||||
@@ -390,6 +390,9 @@ def resize_hires(p, latents): # input=latents output=pil if not latent_upscaler
|
||||
if latent_upscaler is not None:
|
||||
return torch.nn.functional.interpolate(latents, size=(p.hr_upscale_to_y // 8, p.hr_upscale_to_x // 8), mode=latent_upscaler["mode"], antialias=latent_upscaler["antialias"])
|
||||
first_pass_images = processing_vae.vae_decode(latents=latents, model=shared.sd_model, full_quality=p.full_quality, output_type='pil')
|
||||
if p.hr_upscale_to_x == 0 or p.hr_upscale_to_y == 0 and hasattr(p, 'init_hr'):
|
||||
shared.log.error('Hires: missing upscaling dimensions')
|
||||
return first_pass_images
|
||||
resized_images = []
|
||||
for img in first_pass_images:
|
||||
if latent_upscaler is None:
|
||||
|
||||
+20
-2
@@ -489,8 +489,10 @@ class ScriptRunner:
|
||||
s = ScriptSummary('before-process')
|
||||
for script in self.alwayson_scripts:
|
||||
try:
|
||||
script_args = p.script_args[script.args_from:script.args_to]
|
||||
script.before_process(p, *script_args, **kwargs)
|
||||
args = p.script_args[script.args_from:script.args_to]
|
||||
if len(args) == 0:
|
||||
continue
|
||||
script.before_process(p, *args, **kwargs)
|
||||
except Exception as e:
|
||||
errors.display(e, f"Error running before process: {script.filename}")
|
||||
s.record(script.title())
|
||||
@@ -501,6 +503,8 @@ class ScriptRunner:
|
||||
for script in self.alwayson_scripts:
|
||||
try:
|
||||
args = p.per_script_args.get(script.title(), p.script_args[script.args_from:script.args_to])
|
||||
if len(args) == 0:
|
||||
continue
|
||||
script.process(p, *args, **kwargs)
|
||||
except Exception as e:
|
||||
errors.display(e, f'Running script process: {script.filename}')
|
||||
@@ -513,6 +517,8 @@ class ScriptRunner:
|
||||
for script in self.alwayson_scripts:
|
||||
try:
|
||||
args = p.per_script_args.get(script.title(), p.script_args[script.args_from:script.args_to])
|
||||
if len(args) == 0:
|
||||
continue
|
||||
processed = script.process_images(p, *args, **kwargs)
|
||||
except Exception as e:
|
||||
errors.display(e, f'Running script process images: {script.filename}')
|
||||
@@ -525,6 +531,8 @@ class ScriptRunner:
|
||||
for script in self.alwayson_scripts:
|
||||
try:
|
||||
args = p.per_script_args.get(script.title(), p.script_args[script.args_from:script.args_to])
|
||||
if len(args) == 0:
|
||||
continue
|
||||
script.before_process_batch(p, *args, **kwargs)
|
||||
except Exception as e:
|
||||
errors.display(e, f'Running script before process batch: {script.filename}')
|
||||
@@ -536,6 +544,8 @@ class ScriptRunner:
|
||||
for script in self.alwayson_scripts:
|
||||
try:
|
||||
args = p.per_script_args.get(script.title(), p.script_args[script.args_from:script.args_to])
|
||||
if len(args) == 0:
|
||||
continue
|
||||
script.process_batch(p, *args, **kwargs)
|
||||
except Exception as e:
|
||||
errors.display(e, f'Running script process batch: {script.filename}')
|
||||
@@ -547,6 +557,8 @@ class ScriptRunner:
|
||||
for script in self.alwayson_scripts:
|
||||
try:
|
||||
args = p.per_script_args.get(script.title(), p.script_args[script.args_from:script.args_to])
|
||||
if len(args) == 0:
|
||||
continue
|
||||
script.postprocess(p, processed, *args)
|
||||
except Exception as e:
|
||||
errors.display(e, f'Running script postprocess: {script.filename}')
|
||||
@@ -558,6 +570,8 @@ class ScriptRunner:
|
||||
for script in self.alwayson_scripts:
|
||||
try:
|
||||
args = p.per_script_args.get(script.title(), p.script_args[script.args_from:script.args_to])
|
||||
if len(args) == 0:
|
||||
continue
|
||||
script.postprocess_batch(p, *args, images=images, **kwargs)
|
||||
except Exception as e:
|
||||
errors.display(e, f'Running script before postprocess batch: {script.filename}')
|
||||
@@ -569,6 +583,8 @@ class ScriptRunner:
|
||||
for script in self.alwayson_scripts:
|
||||
try:
|
||||
args = p.per_script_args.get(script.title(), p.script_args[script.args_from:script.args_to])
|
||||
if len(args) == 0:
|
||||
continue
|
||||
script.postprocess_batch_list(p, pp, *args, **kwargs)
|
||||
except Exception as e:
|
||||
errors.display(e, f'Running script before postprocess batch list: {script.filename}')
|
||||
@@ -580,6 +596,8 @@ class ScriptRunner:
|
||||
for script in self.alwayson_scripts:
|
||||
try:
|
||||
args = p.per_script_args.get(script.title(), p.script_args[script.args_from:script.args_to])
|
||||
if len(args) == 0:
|
||||
continue
|
||||
script.postprocess_image(p, pp, *args)
|
||||
except Exception as e:
|
||||
errors.display(e, f'Running script postprocess image: {script.filename}')
|
||||
|
||||
@@ -104,11 +104,12 @@ class FaceRestorerYolo(FaceRestoration):
|
||||
return np_image
|
||||
self.load()
|
||||
if self.model is None:
|
||||
shared.log.error(f"Model load: type=FaceHires model='{self.model_name}' dir={self.model_dir} url={self.model_url}")
|
||||
shared.log.debug('Face HiRes: model not loaded')
|
||||
return np_image
|
||||
image = Image.fromarray(np_image)
|
||||
faces = self.predict(image)
|
||||
if len(faces) == 0:
|
||||
shared.log.debug('Face HiRes: no faces detected')
|
||||
return np_image
|
||||
|
||||
# create backups
|
||||
@@ -140,6 +141,7 @@ class FaceRestorerYolo(FaceRestoration):
|
||||
if args['denoising_strength'] == 0:
|
||||
shared.log.debug('Face HiRes skip: strength=0')
|
||||
control_pipeline = None
|
||||
orig_class = shared.sd_model.__class__
|
||||
if getattr(p, 'is_control', False):
|
||||
from modules.control import run
|
||||
control_pipeline = shared.sd_model
|
||||
@@ -177,6 +179,8 @@ class FaceRestorerYolo(FaceRestoration):
|
||||
# restore pipeline
|
||||
if control_pipeline is not None:
|
||||
shared.sd_model = control_pipeline
|
||||
else:
|
||||
shared.sd_model.__class__ = orig_class
|
||||
p = processing_class.switch_class(p, orig_cls, orig_p)
|
||||
p.init_images = getattr(orig_p, 'init_images', None)
|
||||
p.image_mask = getattr(orig_p, 'image_mask', None)
|
||||
|
||||
Reference in New Issue
Block a user