restructure api examples

This commit is contained in:
Vladimir Mandic
2024-06-14 10:27:49 -04:00
parent 3f1c236ce4
commit 945da97d32
21 changed files with 162 additions and 901 deletions
+3
View File
@@ -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
+1 -1
View File
@@ -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')
+116
View File
@@ -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
"""
+1 -1
View File
@@ -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')
+1 -1
View File
@@ -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}')
+1 -1
View File
@@ -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')
View File
+5 -2
View File
@@ -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')
+1 -1
View File
@@ -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')
+1 -1
View File
@@ -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
View File
@@ -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))
-176
View File
@@ -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}%')
-99
View File
@@ -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
View File
@@ -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
View File
@@ -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}')
+2
View File
@@ -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():
+3
View File
@@ -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
View File
@@ -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}')
+5 -1
View File
@@ -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)