diff --git a/CHANGELOG.md b/CHANGELOG.md index 163647ad4..ae64e9b3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ - **Improvements** - default theme updates - additional built-in theme *black-gray* +- **Internal** + - remove obsolete textual inversion training code + - remove obsolete hypernetworks training code - **Fixes** - fix extra networks refresh - improve ZLUDA installer when using `--use-zluda` cli param, thanks @lshqqytiger diff --git a/TODO.md b/TODO.md index a77609141..651e25315 100644 --- a/TODO.md +++ b/TODO.md @@ -15,8 +15,6 @@ Main ToDo list can be found at [GitHub projects](https://github.com/users/vladma - async lowvram: - remove builtin: controlnet - remove builtin: image-browser -- remove training: ti -- remove training: hypernetwork ## Control missing features diff --git a/modules/api/api.py b/modules/api/api.py index 761151a05..4224f78d3 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -5,7 +5,7 @@ from fastapi import FastAPI, APIRouter, Depends, Request from fastapi.security import HTTPBasic, HTTPBasicCredentials from fastapi.exceptions import HTTPException from modules import errors, shared, postprocessing -from modules.api import models, endpoints, script, train, helpers, server, nvml, generate +from modules.api import models, endpoints, script, helpers, server, nvml, generate errors.install() @@ -77,13 +77,6 @@ class Api: self.add_api_route("/sdapi/v1/reload-checkpoint", endpoints.post_reload_checkpoint, methods=["POST"]) self.add_api_route("/sdapi/v1/refresh-vae", endpoints.post_refresh_vae, methods=["POST"]) - # train api - self.add_api_route("/sdapi/v1/create/embedding", train.post_create_embedding, methods=["POST"], response_model=models.ResCreate) - self.add_api_route("/sdapi/v1/create/hypernetwork", train.post_create_hypernetwork, methods=["POST"], response_model=models.ResCreate) - self.add_api_route("/sdapi/v1/preprocess", train.post_preprocess, methods=["POST"], response_model=models.ResPreprocess) - self.add_api_route("/sdapi/v1/train/embedding", train.post_train_embedding, methods=["POST"], response_model=models.ResTrain) - self.add_api_route("/sdapi/v1/train/hypernetwork", train.post_train_hypernetwork, methods=["POST"], response_model=models.ResTrain) - def add_api_route(self, path: str, endpoint, **kwargs): if (shared.cmd_opts.auth or shared.cmd_opts.auth_file) and shared.cmd_opts.api_only: return self.app.add_api_route(path, endpoint, dependencies=[Depends(self.auth)], **kwargs) diff --git a/modules/api/train.py b/modules/api/train.py deleted file mode 100644 index 8fb47fb1d..000000000 --- a/modules/api/train.py +++ /dev/null @@ -1,90 +0,0 @@ -from modules import shared, sd_hijack, devices -from modules.api import models -from modules.textual_inversion.preprocess import preprocess - - -def post_create_embedding(args: dict): - from modules.textual_inversion.textual_inversion import create_embedding - try: - shared.state.begin('api-embedding') - filename = create_embedding(**args) # create empty embedding - sd_hijack.model_hijack.embedding_db.load_textual_inversion_embeddings() # reload embeddings so new one can be immediately used - shared.state.end() - return models.CreateResponse(info = f"create embedding filename: {filename}") - except AssertionError as e: - shared.state.end() - return models.TrainResponse(info = f"create embedding error: {e}") - -def post_create_hypernetwork(args: dict): - from modules.hypernetworks.hypernetwork import create_hypernetwork - try: - shared.state.begin('api-hypernetwork') - filename = create_hypernetwork(**args) # create empty embedding # pylint: disable=E1111 - shared.state.end() - return models.CreateResponse(info = f"create hypernetwork filename: {filename}") - except AssertionError as e: - shared.state.end() - return models.TrainResponse(info = f"create hypernetwork error: {e}") - -def post_preprocess(args: dict): - try: - shared.state.begin('api-preprocess') - preprocess(**args) # quick operation unless blip/booru interrogation is enabled - shared.state.end() - return models.PreprocessResponse(info = 'preprocess complete') - except KeyError as e: - shared.state.end() - return models.PreprocessResponse(info = f"preprocess error: invalid token: {e}") - except AssertionError as e: - shared.state.end() - return models.PreprocessResponse(info = f"preprocess error: {e}") - except FileNotFoundError as e: - shared.state.end() - return models.PreprocessResponse(info = f'preprocess error: {e}') - -def post_train_embedding(args: dict): - from modules.textual_inversion.textual_inversion import train_embedding - try: - shared.state.begin('api-embedding') - apply_optimizations = False - error = None - filename = '' - if not apply_optimizations: - sd_hijack.undo_optimizations() - try: - _embedding, filename = train_embedding(**args) # can take a long time to complete - except Exception as e: - error = e - finally: - if not apply_optimizations: - sd_hijack.apply_optimizations() - shared.state.end() - return models.TrainResponse(info = f"train embedding complete: filename: {filename} error: {error}") - except AssertionError as msg: - shared.state.end() - return models.TrainResponse(info = f"train embedding error: {msg}") - -def post_train_hypernetwork(args: dict): - from modules.hypernetworks.hypernetwork import train_hypernetwork - try: - shared.state.begin('api-hypernetwork') - shared.loaded_hypernetworks = [] - apply_optimizations = False - error = None - filename = '' - if not apply_optimizations: - sd_hijack.undo_optimizations() - try: - _hypernetwork, filename = train_hypernetwork(**args) - except Exception as e: - error = e - finally: - shared.sd_model.cond_stage_model.to(devices.device) - shared.sd_model.first_stage_model.to(devices.device) - if not apply_optimizations: - sd_hijack.apply_optimizations() - shared.state.end() - return models.TrainResponse(info=f"train embedding complete: filename: {filename} error: {error}") - except AssertionError: - shared.state.end() - return models.TrainResponse(info=f"train embedding error: {error}") diff --git a/modules/hypernetworks/hypernetwork.py b/modules/hypernetworks/hypernetwork.py index 4a634c96e..6e13ece24 100644 --- a/modules/hypernetworks/hypernetwork.py +++ b/modules/hypernetworks/hypernetwork.py @@ -1,23 +1,13 @@ -import datetime -import html import os -from collections import deque import inspect from statistics import stdev, mean from rich import progress -import tqdm import torch from torch import einsum from torch.nn.init import normal_, xavier_normal_, xavier_uniform_, kaiming_normal_, kaiming_uniform_, zeros_ from einops import rearrange, repeat from ldm.util import default -from modules import devices, processing, sd_models, shared, hashes, errors, files_cache -import modules.textual_inversion.dataset -from modules.textual_inversion import textual_inversion, ti_logging -from modules.textual_inversion.learn_schedule import LearnRateScheduler - - -optimizer_dict = {optim_name : cls_obj for optim_name, cls_obj in inspect.getmembers(torch.optim, inspect.isclass) if optim_name != "Optimizer"} +from modules import devices, shared, hashes, errors, files_cache class HypernetworkModule(torch.nn.Module): @@ -410,341 +400,3 @@ def report_statistics(loss_info:dict): print(recent) except Exception as e: print(e) - - -def create_hypernetwork(name, enable_sizes, overwrite_old, layer_structure=None, activation_func=None, weight_init=None, add_layer_norm=False, use_dropout=False, dropout_structure=None): - # Remove illegal characters from name. - name = "".join( x for x in name if (x.isalnum() or x in "._- ")) - assert name, "Name cannot be empty!" - fn = os.path.join(shared.opts.hypernetwork_dir, f"{name}.pt") - if not overwrite_old: - assert not os.path.exists(fn), f"file {fn} already exists" - if type(layer_structure) == str: - layer_structure = [float(x.strip()) for x in layer_structure.split(",")] - if use_dropout and dropout_structure and type(dropout_structure) == str: - dropout_structure = [float(x.strip()) for x in dropout_structure.split(",")] - else: - dropout_structure = [0] * len(layer_structure) - hypernet = modules.hypernetworks.hypernetwork.Hypernetwork( - name=name, - enable_sizes=[int(x) for x in enable_sizes], - layer_structure=layer_structure, - activation_func=activation_func, - weight_init=weight_init, - add_layer_norm=add_layer_norm, - use_dropout=use_dropout, - dropout_structure=dropout_structure - ) - hypernet.save(fn) - shared.reload_hypernetworks() - return name - - -def train_hypernetwork(id_task, hypernetwork_name, learn_rate, batch_size, gradient_step, data_root, log_directory, training_width, training_height, varsize, steps, clip_grad_mode, clip_grad_value, shuffle_tags, tag_drop_out, latent_sampling_method, use_weight, create_image_every, save_hypernetwork_every, template_filename, preview_from_txt2img, preview_prompt, preview_negative_prompt, preview_steps, preview_sampler_index, preview_cfg_scale, preview_seed, preview_width, preview_height): # pylint: disable=unused-argument - # images allows training previews to have infotext. Importing it at the top causes a circular import problem. - from modules import images, sd_hijack_checkpoint - - save_hypernetwork_every = save_hypernetwork_every or 0 - create_image_every = create_image_every or 0 - template_file = textual_inversion.textual_inversion_templates.get(template_filename, None) - textual_inversion.validate_train_inputs(hypernetwork_name, learn_rate, batch_size, gradient_step, data_root, template_file, template_filename, steps, save_hypernetwork_every, create_image_every, name="hypernetwork") - template_file = template_file.path - - path = shared.hypernetworks.get(hypernetwork_name, None) - hypernetwork = Hypernetwork() - hypernetwork.load(path) - shared.loaded_hypernetworks = [hypernetwork] - - shared.state.job = "train" - shared.state.textinfo = "Initializing hypernetwork training..." - shared.state.job_count = steps - - hypernetwork_name = hypernetwork_name.rsplit('(', 1)[0] - filename = os.path.join(shared.opts.hypernetwork_dir, f'{hypernetwork_name}.pt') - - log_directory = os.path.join(log_directory, datetime.datetime.now().strftime("%Y-%m-%d"), hypernetwork_name) - unload = shared.opts.unload_models_when_training - - if save_hypernetwork_every > 0: - hypernetwork_dir = os.path.join(log_directory, "hypernetworks") - os.makedirs(hypernetwork_dir, exist_ok=True) - else: - hypernetwork_dir = None - - if create_image_every > 0: - images_dir = os.path.join(log_directory, "images") - os.makedirs(images_dir, exist_ok=True) - else: - images_dir = None - - checkpoint = sd_models.select_checkpoint() - - initial_step = hypernetwork.step or 0 - if initial_step >= steps: - shared.state.textinfo = "Model has already been trained beyond specified max steps" - return hypernetwork, filename - - scheduler = LearnRateScheduler(learn_rate, steps, initial_step) - - clip_grad = torch.nn.utils.clip_grad_value_ if clip_grad_mode == "value" else torch.nn.utils.clip_grad_norm_ if clip_grad_mode == "norm" else None - if clip_grad: - clip_grad_sched = LearnRateScheduler(clip_grad_value, steps, initial_step, verbose=False) - - if shared.opts.training_enable_tensorboard: - tensorboard_writer = textual_inversion.tensorboard_setup(log_directory) - - # dataset loading may take a while, so input validations and early returns should be done before this - shared.state.textinfo = f"Preparing dataset from {html.escape(data_root)}..." - - pin_memory = shared.opts.pin_memory - - ds = modules.textual_inversion.dataset.PersonalizedBase(data_root=data_root, width=training_width, height=training_height, repeats=shared.opts.training_image_repeats_per_epoch, placeholder_token=hypernetwork_name, model=shared.sd_model, cond_model=shared.sd_model.cond_stage_model, device=devices.device, template_file=template_file, include_cond=True, batch_size=batch_size, gradient_step=gradient_step, shuffle_tags=shuffle_tags, tag_drop_out=tag_drop_out, latent_sampling_method=latent_sampling_method, varsize=varsize, use_weight=use_weight) - - if shared.opts.save_training_settings_to_txt: - saved_params = dict( - model_name=checkpoint.model_name, model_hash=checkpoint.shorthash, num_of_dataset_images=len(ds), - **{field: getattr(hypernetwork, field) for field in ['layer_structure', 'activation_func', 'weight_init', 'add_layer_norm', 'use_dropout', ]} - ) - ti_logging.save_settings_to_file(log_directory, {**saved_params, **locals()}) - - latent_sampling_method = ds.latent_sampling_method - - dl = modules.textual_inversion.dataset.PersonalizedDataLoader(ds, latent_sampling_method=latent_sampling_method, batch_size=ds.batch_size, pin_memory=pin_memory) - - old_parallel_processing_allowed = shared.parallel_processing_allowed - - if unload: - shared.parallel_processing_allowed = False - shared.sd_model.cond_stage_model.to(devices.cpu) - shared.sd_model.first_stage_model.to(devices.cpu) - - weights = hypernetwork.weights() - hypernetwork.train() - - # Here we use optimizer from saved HN, or we can specify as UI option. - if hypernetwork.optimizer_name in optimizer_dict: - optimizer = optimizer_dict[hypernetwork.optimizer_name](params=weights, lr=scheduler.learn_rate) - optimizer_name = hypernetwork.optimizer_name - else: - print(f"Optimizer type {hypernetwork.optimizer_name} is not defined!") - optimizer = torch.optim.AdamW(params=weights, lr=scheduler.learn_rate) - optimizer_name = 'AdamW' - - if hypernetwork.optimizer_state_dict: # This line must be changed if Optimizer type can be different from saved optimizer. - try: - optimizer.load_state_dict(hypernetwork.optimizer_state_dict) - except RuntimeError as e: - print("Cannot resume from saved optimizer!") - print(e) - - scaler = torch.cuda.amp.GradScaler() - - batch_size = ds.batch_size - gradient_step = ds.gradient_step - # n steps = batch_size * gradient_step * n image processed - steps_per_epoch = len(ds) // batch_size // gradient_step - max_steps_per_epoch = len(ds) // batch_size - (len(ds) // batch_size) % gradient_step - loss_step = 0 - _loss_step = 0 #internal - # size = len(ds.indexes) - # loss_dict = defaultdict(lambda : deque(maxlen = 1024)) - loss_logging = deque(maxlen=len(ds) * 3) # this should be configurable parameter, this is 3 * epoch(dataset size) - # losses = torch.zeros((size,)) - # previous_mean_losses = [0] - # previous_mean_loss = 0 - # print("Mean loss of {} elements".format(size)) - - _steps_without_grad = 0 - - last_saved_file = "" - last_saved_image = "" - forced_filename = "" - - pbar = tqdm.tqdm(total=steps - initial_step) - try: - sd_hijack_checkpoint.add() - - for _i in range((steps-initial_step) * gradient_step): - if scheduler.finished: - break - if shared.state.interrupted: - break - for j, batch in enumerate(dl): - # works as a drop_last=True for gradient accumulation - if j == max_steps_per_epoch: - break - scheduler.apply(optimizer, hypernetwork.step) - if scheduler.finished: - break - if shared.state.interrupted: - break - - if clip_grad: - clip_grad_sched.step(hypernetwork.step) - - with devices.autocast(): - x = batch.latent_sample.to(devices.device, non_blocking=pin_memory) - if use_weight: - w = batch.weight.to(devices.device, non_blocking=pin_memory) - if tag_drop_out != 0 or shuffle_tags: - shared.sd_model.cond_stage_model.to(devices.device) - c = shared.sd_model.cond_stage_model(batch.cond_text).to(devices.device, non_blocking=pin_memory) - shared.sd_model.cond_stage_model.to(devices.cpu) - else: - c = stack_conds(batch.cond).to(devices.device, non_blocking=pin_memory) - if use_weight: - loss = shared.sd_model.weighted_forward(x, c, w)[0] / gradient_step - del w - else: - loss = shared.sd_model.forward(x, c)[0] / gradient_step - del x - del c - _loss_step += loss.item() - - scaler.scale(loss).backward() - # go back until we reach gradient accumulation steps - if (j + 1) % gradient_step != 0: - continue - loss_logging.append(_loss_step) - if clip_grad: - clip_grad(weights, clip_grad_sched.learn_rate) - - scaler.step(optimizer) - scaler.update() - hypernetwork.step += 1 - pbar.update() - optimizer.zero_grad(set_to_none=True) - loss_step = _loss_step - _loss_step = 0 - steps_done = hypernetwork.step + 1 - epoch_num = hypernetwork.step // steps_per_epoch - epoch_step = hypernetwork.step % steps_per_epoch - - description = f"Training hypernetwork [Epoch {epoch_num}: {epoch_step+1}/{steps_per_epoch}]loss: {loss_step:.7f}" - pbar.set_description(description) - if hypernetwork_dir is not None and steps_done % save_hypernetwork_every == 0: - # Before saving, change name to match current checkpoint. - hypernetwork_name_every = f'{hypernetwork_name}-{steps_done}' - last_saved_file = os.path.join(hypernetwork_dir, f'{hypernetwork_name_every}.pt') - hypernetwork.optimizer_name = optimizer_name - if shared.opts.save_optimizer_state: - hypernetwork.optimizer_state_dict = optimizer.state_dict() - save_hypernetwork(hypernetwork, checkpoint, hypernetwork_name, last_saved_file) - hypernetwork.optimizer_state_dict = None # dereference it after saving, to save memory. - - - - if shared.opts.training_enable_tensorboard: - epoch_num = hypernetwork.step // len(ds) - epoch_step = hypernetwork.step - (epoch_num * len(ds)) + 1 - mean_loss = sum(loss_logging) / len(loss_logging) - textual_inversion.tensorboard_add(tensorboard_writer, loss=mean_loss, global_step=hypernetwork.step, step=epoch_step, learn_rate=scheduler.learn_rate, epoch_num=epoch_num) - - textual_inversion.write_loss(log_directory, "hypernetwork_loss.csv", hypernetwork.step, steps_per_epoch, { - "loss": f"{loss_step:.7f}", - "learn_rate": scheduler.learn_rate - }) - - if images_dir is not None and steps_done % create_image_every == 0: - forced_filename = f'{hypernetwork_name}-{steps_done}' - last_saved_image = os.path.join(images_dir, forced_filename) - hypernetwork.eval() - rng_state = torch.get_rng_state() - cuda_rng_state = None - cuda_rng_state = torch.cuda.get_rng_state_all() - shared.sd_model.cond_stage_model.to(devices.device) - shared.sd_model.first_stage_model.to(devices.device) - - p = processing.StableDiffusionProcessingTxt2Img( - sd_model=shared.sd_model, - do_not_save_grid=True, - do_not_save_samples=True, - ) - - p.disable_extra_networks = True - - if preview_from_txt2img: - p.prompt = preview_prompt - p.negative_prompt = preview_negative_prompt - p.steps = preview_steps - p.sampler_name = processing.get_sampler_name(preview_sampler_index) - p.cfg_scale = preview_cfg_scale - p.seed = preview_seed - p.width = preview_width - p.height = preview_height - else: - p.prompt = batch.cond_text[0] - p.steps = 20 - p.width = training_width - p.height = training_height - - preview_text = p.prompt - - processed = processing.process_images(p) - image = processed.images[0] if len(processed.images) > 0 else None - - if unload: - shared.sd_model.cond_stage_model.to(devices.cpu) - shared.sd_model.first_stage_model.to(devices.cpu) - torch.set_rng_state(rng_state) - torch.cuda.set_rng_state_all(cuda_rng_state) - hypernetwork.train() - if image is not None: - shared.state.assign_current_image(image) - if shared.opts.training_enable_tensorboard and shared.opts.training_tensorboard_save_images: - textual_inversion.tensorboard_add_image(tensorboard_writer, - f"Validation at epoch {epoch_num}", image, - hypernetwork.step) - last_saved_image, _last_text_info = images.save_image(image, images_dir, "", p.seed, p.prompt, shared.opts.samples_format, processed.infotexts[0], p=p, forced_filename=forced_filename, save_to_dirs=False) - last_saved_image += f", prompt: {preview_text}" - - shared.state.job_no = hypernetwork.step - - shared.state.textinfo = f""" -

-Loss: {loss_step:.7f}
-Step: {steps_done}
-Last prompt: {html.escape(batch.cond_text[0])}
-Last saved hypernetwork: {html.escape(last_saved_file)}
-Last saved image: {html.escape(last_saved_image)}
-

-""" - except Exception as e: - errors.display(e, 'hypernetwork train') - finally: - pbar.leave = False - pbar.close() - hypernetwork.eval() - #report_statistics(loss_dict) - sd_hijack_checkpoint.remove() - - - - filename = os.path.join(shared.opts.hypernetwork_dir, f'{hypernetwork_name}.pt') - hypernetwork.optimizer_name = optimizer_name - if shared.opts.save_optimizer_state: - hypernetwork.optimizer_state_dict = optimizer.state_dict() - save_hypernetwork(hypernetwork, checkpoint, hypernetwork_name, filename) - - del optimizer - hypernetwork.optimizer_state_dict = None # dereference it after saving, to save memory. - shared.sd_model.cond_stage_model.to(devices.device) - shared.sd_model.first_stage_model.to(devices.device) - shared.parallel_processing_allowed = old_parallel_processing_allowed - - return hypernetwork, filename - -def save_hypernetwork(hypernetwork, checkpoint, hypernetwork_name, filename): - old_hypernetwork_name = hypernetwork.name - old_sd_checkpoint = hypernetwork.sd_checkpoint if hasattr(hypernetwork, "sd_checkpoint") else None - old_sd_checkpoint_name = hypernetwork.sd_checkpoint_name if hasattr(hypernetwork, "sd_checkpoint_name") else None - try: - hypernetwork.sd_checkpoint = checkpoint.shorthash - hypernetwork.sd_checkpoint_name = checkpoint.model_name - hypernetwork.name = hypernetwork_name - hypernetwork.save(filename) - except Exception: - hypernetwork.sd_checkpoint = old_sd_checkpoint - hypernetwork.sd_checkpoint_name = old_sd_checkpoint_name - hypernetwork.name = old_hypernetwork_name - raise diff --git a/modules/hypernetworks/ui.py b/modules/hypernetworks/ui.py deleted file mode 100644 index 42ca9b5b6..000000000 --- a/modules/hypernetworks/ui.py +++ /dev/null @@ -1,31 +0,0 @@ -import html -import gradio as gr -import modules.hypernetworks.hypernetwork -from modules import devices, sd_hijack, shared - -not_available = ["hardswish", "multiheadattention"] -keys = [x for x in modules.hypernetworks.hypernetwork.HypernetworkModule.activation_dict.keys() if x not in not_available] - - -def create_hypernetwork(name, enable_sizes, overwrite_old, layer_structure=None, activation_func=None, weight_init=None, add_layer_norm=False, use_dropout=False, dropout_structure=None): - filename = modules.hypernetworks.hypernetwork.create_hypernetwork(name, enable_sizes, overwrite_old, layer_structure, activation_func, weight_init, add_layer_norm, use_dropout, dropout_structure) - return gr.Dropdown.update(choices=sorted(shared.hypernetworks)), f"Created: {filename}", "" - - -def train_hypernetwork(*args): - shared.loaded_hypernetworks = [] - assert not shared.cmd_opts.lowvram, 'Training models with lowvram is not possible' - try: - sd_hijack.undo_optimizations() - hypernetwork, filename = modules.hypernetworks.hypernetwork.train_hypernetwork(*args) - res = f""" -Training {'interrupted' if shared.state.interrupted else 'finished'} at {hypernetwork.step} steps. -Hypernetwork saved to {html.escape(filename)} -""" - return res, "" - except Exception as e: - raise RuntimeError("Hypernetwork error") from e - finally: - shared.sd_model.cond_stage_model.to(devices.device) - shared.sd_model.first_stage_model.to(devices.device) - sd_hijack.apply_optimizations() diff --git a/modules/script_callbacks.py b/modules/script_callbacks.py index 0b84bc8fe..6c91edf44 100644 --- a/modules/script_callbacks.py +++ b/modules/script_callbacks.py @@ -109,7 +109,6 @@ callback_map = dict( callbacks_after_process=[], callbacks_model_loaded=[], callbacks_ui_tabs=[], - callbacks_ui_train_tabs=[], callbacks_ui_settings=[], callbacks_before_image_saved=[], callbacks_image_saved=[], @@ -212,16 +211,6 @@ def ui_tabs_callback(): return res -def ui_train_tabs_callback(params: UiTrainTabParams): - for c in callback_map['callbacks_ui_train_tabs']: - try: - t0 = time.time() - c.callback(params) - timer(t0, c.script, 'ui_train_tabs') - except Exception as e: - report_exception(e, c, 'callbacks_ui_train_tabs') - - def ui_settings_callback(): for c in callback_map['callbacks_ui_settings']: try: @@ -434,13 +423,6 @@ def on_ui_tabs(callback): add_callback(callback_map['callbacks_ui_tabs'], callback) -def on_ui_train_tabs(callback): - """register a function to be called when the UI is creating new tabs for the train tab. - Create your new tabs with gr.Tab. - """ - add_callback(callback_map['callbacks_ui_train_tabs'], callback) - - def on_ui_settings(callback): """register a function to be called before UI settings are populated; add your settings by using shared.opts.add_option(shared.OptionInfo(...)) """ diff --git a/modules/sd_models.py b/modules/sd_models.py index 798324442..d2c5aaaf0 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -740,8 +740,10 @@ def set_diffuser_options(sd_model, vae = None, op: str = 'model'): sd_model.unet.to(memory_format=torch.channels_last) -def move_model(model, device=None): - if model is not None and not getattr(model, 'has_accelerate', False): +def move_model(model, device=None, force=False): + if model is not None: + if getattr(model, 'has_accelerate', False) and not force: + return debug_move(f'Model move: to={device} class={model.__class__} function={sys._getframe(1).f_code.co_name}') # pylint: disable=protected-access try: model.to(device) @@ -1416,6 +1418,7 @@ def convert_to_faketensors(tensor): return tensor except Exception: pass + return tensor def disable_offload(sd_model): diff --git a/modules/textual_inversion/autocrop.py b/modules/textual_inversion/autocrop.py deleted file mode 100644 index b4bf9828e..000000000 --- a/modules/textual_inversion/autocrop.py +++ /dev/null @@ -1,337 +0,0 @@ -import os -import cv2 -import requests -import numpy as np -from PIL import ImageDraw - -GREEN = "#0F0" -BLUE = "#00F" -RED = "#F00" - - -def crop_image(im, settings): - """ Intelligently crop an image to the subject matter """ - - scale_by = 1 - if is_landscape(im.width, im.height): - scale_by = settings.crop_height / im.height - elif is_portrait(im.width, im.height): - scale_by = settings.crop_width / im.width - elif is_square(im.width, im.height): - if is_square(settings.crop_width, settings.crop_height): - scale_by = settings.crop_width / im.width - elif is_landscape(settings.crop_width, settings.crop_height): - scale_by = settings.crop_width / im.width - elif is_portrait(settings.crop_width, settings.crop_height): - scale_by = settings.crop_height / im.height - - im = im.resize((int(im.width * scale_by), int(im.height * scale_by))) - im_debug = im.copy() - - focus = focal_point(im_debug, settings) - - # take the focal point and turn it into crop coordinates that try to center over the focal - # point but then get adjusted back into the frame - y_half = int(settings.crop_height / 2) - x_half = int(settings.crop_width / 2) - - x1 = focus.x - x_half - if x1 < 0: - x1 = 0 - elif x1 + settings.crop_width > im.width: - x1 = im.width - settings.crop_width - - y1 = focus.y - y_half - if y1 < 0: - y1 = 0 - elif y1 + settings.crop_height > im.height: - y1 = im.height - settings.crop_height - - x2 = x1 + settings.crop_width - y2 = y1 + settings.crop_height - - crop = [x1, y1, x2, y2] - - results = [] - - results.append(im.crop(tuple(crop))) - - if settings.annotate_image: - d = ImageDraw.Draw(im_debug) - rect = list(crop) - rect[2] -= 1 - rect[3] -= 1 - d.rectangle(rect, outline=GREEN) - results.append(im_debug) - if settings.destop_view_image: - im_debug.show() - - return results - -def focal_point(im, settings): - corner_points = image_corner_points(im, settings) if settings.corner_points_weight > 0 else [] - entropy_points = image_entropy_points(im, settings) if settings.entropy_points_weight > 0 else [] - face_points = image_face_points(im, settings) if settings.face_points_weight > 0 else [] - - pois = [] - - weight_pref_total = 0 - if len(corner_points) > 0: - weight_pref_total += settings.corner_points_weight - if len(entropy_points) > 0: - weight_pref_total += settings.entropy_points_weight - if len(face_points) > 0: - weight_pref_total += settings.face_points_weight - - corner_centroid = None - if len(corner_points) > 0: - corner_centroid = centroid(corner_points) - corner_centroid.weight = settings.corner_points_weight / weight_pref_total - pois.append(corner_centroid) - - entropy_centroid = None - if len(entropy_points) > 0: - entropy_centroid = centroid(entropy_points) - entropy_centroid.weight = settings.entropy_points_weight / weight_pref_total - pois.append(entropy_centroid) - - face_centroid = None - if len(face_points) > 0: - face_centroid = centroid(face_points) - face_centroid.weight = settings.face_points_weight / weight_pref_total - pois.append(face_centroid) - - average_point = poi_average(pois, settings) - - if settings.annotate_image: - d = ImageDraw.Draw(im) - max_size = min(im.width, im.height) * 0.07 - if corner_centroid is not None: - color = BLUE - box = corner_centroid.bounding(max_size * corner_centroid.weight) - d.text((box[0], box[1]-15), f"Edge: {corner_centroid.weight:.02f}", fill=color) - d.ellipse(box, outline=color) - if len(corner_points) > 1: - for f in corner_points: - d.rectangle(f.bounding(4), outline=color) - if entropy_centroid is not None: - color = "#ff0" - box = entropy_centroid.bounding(max_size * entropy_centroid.weight) - d.text((box[0], box[1]-15), f"Entropy: {entropy_centroid.weight:.02f}", fill=color) - d.ellipse(box, outline=color) - if len(entropy_points) > 1: - for f in entropy_points: - d.rectangle(f.bounding(4), outline=color) - if face_centroid is not None: - color = RED - box = face_centroid.bounding(max_size * face_centroid.weight) - d.text((box[0], box[1]-15), f"Face: {face_centroid.weight:.02f}", fill=color) - d.ellipse(box, outline=color) - if len(face_points) > 1: - for f in face_points: - d.rectangle(f.bounding(4), outline=color) - - d.ellipse(average_point.bounding(max_size), outline=GREEN) - - return average_point - - -def image_face_points(im, settings): - if settings.dnn_model_path is not None: - detector = cv2.FaceDetectorYN.create( - settings.dnn_model_path, - "", - (im.width, im.height), - 0.9, # score threshold - 0.3, # nms threshold - 5000 # keep top k before nms - ) - faces = detector.detect(np.array(im)) - results = [] - if faces[1] is not None: - for face in faces[1]: - x = face[0] - y = face[1] - w = face[2] - h = face[3] - results.append( - PointOfInterest( - int(x + (w * 0.5)), # face focus left/right is center - int(y + (h * 0.33)), # face focus up/down is close to the top of the head - size = w, - weight = 1/len(faces[1]) - ) - ) - return results - else: - np_im = np.array(im) - gray = cv2.cvtColor(np_im, cv2.COLOR_BGR2GRAY) - - tries = [ - [ f'{cv2.data.haarcascades}haarcascade_eye.xml', 0.01 ], - [ f'{cv2.data.haarcascades}haarcascade_frontalface_default.xml', 0.05 ], - [ f'{cv2.data.haarcascades}haarcascade_profileface.xml', 0.05 ], - [ f'{cv2.data.haarcascades}haarcascade_frontalface_alt.xml', 0.05 ], - [ f'{cv2.data.haarcascades}haarcascade_frontalface_alt2.xml', 0.05 ], - [ f'{cv2.data.haarcascades}haarcascade_frontalface_alt_tree.xml', 0.05 ], - [ f'{cv2.data.haarcascades}haarcascade_eye_tree_eyeglasses.xml', 0.05 ], - [ f'{cv2.data.haarcascades}haarcascade_upperbody.xml', 0.05 ] - ] - for t in tries: - classifier = cv2.CascadeClassifier(t[0]) - minsize = int(min(im.width, im.height) * t[1]) # at least N percent of the smallest side - try: - faces = classifier.detectMultiScale(gray, scaleFactor=1.1, - minNeighbors=7, minSize=(minsize, minsize), flags=cv2.CASCADE_SCALE_IMAGE) - except Exception: - continue - - if len(faces) > 0: - rects = [[f[0], f[1], f[0] + f[2], f[1] + f[3]] for f in faces] - return [PointOfInterest((r[0] +r[2]) // 2, (r[1] + r[3]) // 2, size=abs(r[0]-r[2]), weight=1/len(rects)) for r in rects] - return [] - - -def image_corner_points(im, settings): # pylint: disable=unused-argument - grayscale = im.convert("L") - - # naive attempt at preventing focal points from collecting at watermarks near the bottom - gd = ImageDraw.Draw(grayscale) - gd.rectangle([0, im.height*.9, im.width, im.height], fill="#999") - - np_im = np.array(grayscale) - - points = cv2.goodFeaturesToTrack( - np_im, - maxCorners=100, - qualityLevel=0.04, - minDistance=min(grayscale.width, grayscale.height)*0.06, - useHarrisDetector=False, - ) - - if points is None: - return [] - - focal_points = [] - for point in points: - x, y = point.ravel() - focal_points.append(PointOfInterest(x, y, size=4, weight=1/len(points))) - - return focal_points - - -def image_entropy_points(im, settings): - landscape = im.height < im.width - portrait = im.height > im.width - if landscape: - move_idx = [0, 2] - move_max = im.size[0] - elif portrait: - move_idx = [1, 3] - move_max = im.size[1] - else: - return [] - - e_max = 0 - crop_current = [0, 0, settings.crop_width, settings.crop_height] - crop_best = crop_current - while crop_current[move_idx[1]] < move_max: - crop = im.crop(tuple(crop_current)) - e = image_entropy(crop) - - if e > e_max: - e_max = e - crop_best = list(crop_current) - - crop_current[move_idx[0]] += 4 - crop_current[move_idx[1]] += 4 - - x_mid = int(crop_best[0] + settings.crop_width/2) - y_mid = int(crop_best[1] + settings.crop_height/2) - - return [PointOfInterest(x_mid, y_mid, size=25, weight=1.0)] - - -def image_entropy(im): - # greyscale image entropy - # band = np.asarray(im.convert("L")) - band = np.asarray(im.convert("1"), dtype=np.uint8) - hist, _ = np.histogram(band, bins=range(0, 256)) - hist = hist[hist > 0] - return -np.log2(hist / hist.sum()).sum() - -def centroid(pois): - x = [poi.x for poi in pois] - y = [poi.y for poi in pois] - return PointOfInterest(sum(x)/len(pois), sum(y)/len(pois)) - - -def poi_average(pois, settings): # pylint: disable=unused-argument - weight = 0.0 - x = 0.0 - y = 0.0 - for poi in pois: - weight += poi.weight - x += poi.x * poi.weight - y += poi.y * poi.weight - avg_x = round(weight and x / weight) - avg_y = round(weight and y / weight) - - return PointOfInterest(avg_x, avg_y) - - -def is_landscape(w, h): - return w > h - - -def is_portrait(w, h): - return h > w - - -def is_square(w, h): - return w == h - - -def download_and_cache_models(dirname): - download_url = 'https://github.com/opencv/opencv_zoo/blob/91fb0290f50896f38a0ab1e558b74b16bc009428/models/face_detection_yunet/face_detection_yunet_2022mar.onnx?raw=true' - model_file_name = 'face_detection_yunet.onnx' - if not os.path.exists(dirname): - os.makedirs(dirname, exist_ok=True) - cache_file = os.path.join(dirname, model_file_name) - if not os.path.exists(cache_file): - print(f"downloading face detection model from '{download_url}' to '{cache_file}'") - response = requests.get(download_url, timeout=60*60*2) - with open(cache_file, "wb") as f: - f.write(response.content) - - if os.path.exists(cache_file): - return cache_file - return None - - -class PointOfInterest: - def __init__(self, x, y, weight=1.0, size=10): - self.x = x - self.y = y - self.weight = weight - self.size = size - - def bounding(self, size): - return [ - self.x - size//2, - self.y - size//2, - self.x + size//2, - self.y + size//2 - ] - - -class Settings: - def __init__(self, crop_width=512, crop_height=512, corner_points_weight=0.5, entropy_points_weight=0.5, face_points_weight=0.5, annotate_image=False, dnn_model_path=None): - self.crop_width = crop_width - self.crop_height = crop_height - self.corner_points_weight = corner_points_weight - self.entropy_points_weight = entropy_points_weight - self.face_points_weight = face_points_weight - self.annotate_image = annotate_image - self.destop_view_image = False - self.dnn_model_path = dnn_model_path diff --git a/modules/textual_inversion/dataset.py b/modules/textual_inversion/dataset.py deleted file mode 100644 index 10efb5c95..000000000 --- a/modules/textual_inversion/dataset.py +++ /dev/null @@ -1,223 +0,0 @@ -import os -import re -import random -from collections import defaultdict -import numpy as np -import torch -from PIL import Image -from torch.utils.data import Dataset, DataLoader, Sampler -from torchvision import transforms -import tqdm -from ldm.modules.distributions.distributions import DiagonalGaussianDistribution -from modules import devices, shared - -re_numbers_at_start = re.compile(r"^[-\d]+\s*") - - -class DatasetEntry: - def __init__(self, filename=None, filename_text=None, latent_dist=None, latent_sample=None, cond=None, cond_text=None, pixel_values=None, weight=None): - self.filename = filename - self.filename_text = filename_text - self.weight = weight - self.latent_dist = latent_dist - self.latent_sample = latent_sample - self.cond = cond - self.cond_text = cond_text - self.pixel_values = pixel_values - - -class PersonalizedBase(Dataset): - def __init__(self, data_root, width, height, repeats, flip_p=0.5, placeholder_token="*", model=None, cond_model=None, device=None, template_file=None, include_cond=False, batch_size=1, gradient_step=1, shuffle_tags=False, tag_drop_out=0, latent_sampling_method='once', varsize=False, use_weight=False): - re_word = re.compile(shared.opts.dataset_filename_word_regex) if len(shared.opts.dataset_filename_word_regex) > 0 else None - - self.placeholder_token = placeholder_token - self.flip = transforms.RandomHorizontalFlip(p=flip_p) - self.dataset = [] - with open(template_file, "r", encoding="utf8") as file: - lines = [x.strip() for x in file.readlines()] - self.lines = lines - - assert data_root, 'dataset directory not specified' - assert os.path.isdir(data_root), "Dataset directory doesn't exist" - assert os.listdir(data_root), "Dataset directory is empty" - - self.image_paths = [os.path.join(data_root, file_path) for file_path in os.listdir(data_root)] - self.shuffle_tags = shuffle_tags - self.tag_drop_out = tag_drop_out - groups = defaultdict(list) - shared.log.info(f"TI Training: Preparing dataset: {data_root}") - for path in tqdm.tqdm(self.image_paths): - alpha_channel = None - if shared.state.interrupted: - raise RuntimeError("interrupted") - try: - image = Image.open(path) - if use_weight and 'A' in image.getbands(): - alpha_channel = image.getchannel('A') - image = image.convert('RGB') - if not varsize: - image = image.resize((width, height), Image.Resampling.BICUBIC) - except Exception: - continue - - text_filename = f"{os.path.splitext(path)[0]}.txt" - filename = os.path.basename(path) - - if os.path.exists(text_filename): - with open(text_filename, "r", encoding="utf8") as file: - filename_text = file.read() - else: - filename_text = os.path.splitext(filename)[0] - filename_text = re.sub(re_numbers_at_start, '', filename_text) - if re_word: - tokens = re_word.findall(filename_text) - filename_text = (shared.opts.dataset_filename_join_string or "").join(tokens) - - npimage = np.array(image).astype(np.uint8) - npimage = (npimage / 127.5 - 1.0).astype(np.float32) - - torchdata = torch.from_numpy(npimage).permute(2, 0, 1).to(device=device, dtype=torch.float32) - latent_sample = None - - with devices.autocast(): - latent_dist = model.encode_first_stage(torchdata.unsqueeze(dim=0)) - - if latent_sampling_method == "deterministic": - if isinstance(latent_dist, DiagonalGaussianDistribution): - latent_dist.std = torch.exp(0 * latent_dist.logvar) - else: - latent_sampling_method = "once" - latent_sample = model.get_first_stage_encoding(latent_dist).squeeze().to(devices.cpu) - - if use_weight and alpha_channel is not None: - channels, *latent_size = latent_sample.shape - weight_img = alpha_channel.resize(latent_size) - npweight = np.array(weight_img).astype(np.float32) - #Repeat for every channel in the latent sample - weight = torch.tensor([npweight] * channels).reshape([channels] + latent_size) - #Normalize the weight to a minimum of 0 and a mean of 1, that way the loss will be comparable to default. - weight -= weight.min() - weight /= weight.mean() - elif use_weight: - #If an image does not have a alpha channel, add a ones weight map anyway so we can stack it later - weight = torch.ones(latent_sample.shape) - else: - weight = None - - if latent_sampling_method == "random": - entry = DatasetEntry(filename=path, filename_text=filename_text, latent_dist=latent_dist, weight=weight) - else: - entry = DatasetEntry(filename=path, filename_text=filename_text, latent_sample=latent_sample, weight=weight) - - if not (self.tag_drop_out != 0 or self.shuffle_tags): - entry.cond_text = self.create_text(filename_text) - - if include_cond and not (self.tag_drop_out != 0 or self.shuffle_tags): - with devices.autocast(): - entry.cond = cond_model([entry.cond_text]).to(devices.cpu).squeeze(0) - groups[image.size].append(len(self.dataset)) - self.dataset.append(entry) - del torchdata - del latent_dist - del latent_sample - del weight - - self.length = len(self.dataset) - self.groups = list(groups.values()) - assert self.length > 0, "No images have been found in the dataset." - self.batch_size = min(batch_size, self.length) - self.gradient_step = min(gradient_step, self.length // self.batch_size) - self.latent_sampling_method = latent_sampling_method - - def create_text(self, filename_text): - text = random.choice(self.lines) - tags = filename_text.split(',') - if self.tag_drop_out != 0: - tags = [t for t in tags if random.random() > self.tag_drop_out] - if self.shuffle_tags: - random.shuffle(tags) - text = text.replace("[filewords]", ','.join(tags)) - text = text.replace("[name]", self.placeholder_token) - return text - - def __len__(self): - return self.length - - def __getitem__(self, i): - entry = self.dataset[i] - if self.tag_drop_out != 0 or self.shuffle_tags: - entry.cond_text = self.create_text(entry.filename_text) - if self.latent_sampling_method == "random": - entry.latent_sample = shared.sd_model.get_first_stage_encoding(entry.latent_dist).to(devices.cpu) - return entry - - -class GroupedBatchSampler(Sampler): - def __init__(self, data_source: PersonalizedBase, batch_size: int): - super().__init__(data_source) - - n = len(data_source) - self.groups = data_source.groups - self.len = n_batch = n // batch_size - expected = [len(g) / n * n_batch * batch_size for g in data_source.groups] - self.base = [int(e) // batch_size for e in expected] - self.n_rand_batches = nrb = n_batch - sum(self.base) - self.probs = [e%batch_size/nrb/batch_size if nrb>0 else 0 for e in expected] - self.batch_size = batch_size - - def __len__(self): - return self.len - - def __iter__(self): - b = self.batch_size - - for g in self.groups: - random.shuffle(g) - - batches = [] - for g in self.groups: - batches.extend(g[i*b:(i+1)*b] for i in range(len(g) // b)) - for _ in range(self.n_rand_batches): - rand_group = random.choices(self.groups, self.probs)[0] - batches.append(random.choices(rand_group, k=b)) - - random.shuffle(batches) - - yield from batches - - -class PersonalizedDataLoader(DataLoader): - def __init__(self, dataset, latent_sampling_method="once", batch_size=1, pin_memory=False): - super(PersonalizedDataLoader, self).__init__(dataset, batch_sampler=GroupedBatchSampler(dataset, batch_size), pin_memory=pin_memory) - if latent_sampling_method == "random": - self.collate_fn = collate_wrapper_random - else: - self.collate_fn = collate_wrapper - - -class BatchLoader: - def __init__(self, data): - self.cond_text = [entry.cond_text for entry in data] - self.cond = [entry.cond for entry in data] - self.latent_sample = torch.stack([entry.latent_sample for entry in data]).squeeze(1) - if all(entry.weight is not None for entry in data): - self.weight = torch.stack([entry.weight for entry in data]).squeeze(1) - else: - self.weight = None - - def pin_memory(self): - self.latent_sample = self.latent_sample.pin_memory() - return self - -def collate_wrapper(batch): - return BatchLoader(batch) - -class BatchLoaderRandom(BatchLoader): - def __init__(self, data): - super().__init__(data) - - def pin_memory(self): - return self - -def collate_wrapper_random(batch): - return BatchLoaderRandom(batch) diff --git a/modules/textual_inversion/image_embedding.py b/modules/textual_inversion/image_embedding.py index a33d3e450..c9fe40767 100644 --- a/modules/textual_inversion/image_embedding.py +++ b/modules/textual_inversion/image_embedding.py @@ -8,17 +8,17 @@ from modules.shared import opts class EmbeddingEncoder(json.JSONEncoder): - def default(self, obj): - if isinstance(obj, torch.Tensor): - return {'TORCHTENSOR': obj.cpu().detach().numpy().tolist()} - return json.JSONEncoder.default(self, obj) + def default(self, o): + if isinstance(o, torch.Tensor): + return {'TORCHTENSOR': o.cpu().detach().numpy().tolist()} + return json.JSONEncoder.default(self, o) class EmbeddingDecoder(json.JSONDecoder): def __init__(self, *args, **kwargs): json.JSONDecoder.__init__(self, *args, object_hook=self.object_hook, **kwargs) - def object_hook(self, d): + def object_hook(self, d): # pylint: disable=E0202 if 'TORCHTENSOR' in d: return torch.from_numpy(np.array(d['TORCHTENSOR'])) return d @@ -41,8 +41,8 @@ def lcg(m=2**32, a=1664525, c=1013904223, seed=0): def xor_block(block): - g = lcg() - randblock = np.array([next(g) for _ in range(np.prod(block.shape))]).astype(np.uint8).reshape(block.shape) + blk = lcg() + randblock = np.array([next(blk) for _ in range(np.prod(block.shape))]).astype(np.uint8).reshape(block.shape) return np.bitwise_xor(block.astype(np.uint8), randblock & 0x0F) @@ -110,7 +110,7 @@ def crop_black(img, tol=0): def extract_image_data_embed(image): d = 3 - outarr = crop_black(np.array(image.convert('RGB').getdata()).reshape(image.size[1], image.size[0], d).astype(np.uint8)) & 0x0F + outarr = crop_black(np.array(image.convert('RGB').getdata()).reshape(image.size[1], image.size[0], d).astype(np.uint8)) & 0x0F # pylint: disable=E1121 black_cols = np.where(np.sum(outarr, axis=(0, 2)) == 0) if black_cols[0].shape[0] < 2: return None @@ -182,7 +182,7 @@ if __name__ == '__main__': new_image = Image.new('RGBA', (512, 512), (255, 255, 200, 255)) cap_image = caption_image_overlay(new_image, 'title', 'footerLeft', 'footerMid', 'footerRight') - test_embed = {'string_to_param': {'*': torch.from_numpy(np.random.random((2, 4096)))}} + test_embed = {'string_to_param': {'*': torch.from_numpy(np.random.random((2, 4096)))}} # noqa: NPY002 embedded_image = insert_image_data_embed(cap_image, test_embed) diff --git a/modules/textual_inversion/learn_schedule.py b/modules/textual_inversion/learn_schedule.py deleted file mode 100644 index 35f169efc..000000000 --- a/modules/textual_inversion/learn_schedule.py +++ /dev/null @@ -1,73 +0,0 @@ -class LearnScheduleIterator: - def __init__(self, learn_rate, max_steps, cur_step=0): - """ - specify learn_rate as "0.001:100, 0.00001:1000, 1e-5:10000" to have lr of 0.001 until step 100, 0.00001 until 1000, and 1e-5 until 10000 - """ - - pairs = learn_rate.split(',') - self.rates = [] - self.it = 0 - self.maxit = 0 - try: - for pair in pairs: - if not pair.strip(): - continue - tmp = pair.split(':') - if len(tmp) == 2: - step = int(tmp[1]) - if step > cur_step: - self.rates.append((float(tmp[0]), min(step, max_steps))) - self.maxit += 1 - if step > max_steps: - return - elif step == -1: - self.rates.append((float(tmp[0]), max_steps)) - self.maxit += 1 - return - else: - self.rates.append((float(tmp[0]), max_steps)) - self.maxit += 1 - return - assert self.rates - except (ValueError, AssertionError) as e: - raise RuntimeError('Invalid learning rate schedule. It should be a number or, for example, like "0.001:100, 0.00001:1000, 1e-5:10000" to have lr of 0.001 until step 100, 0.00001 until 1000, and 1e-5 until 10000.') from e - - - def __iter__(self): - return self - - def __next__(self): - if self.it < self.maxit: - self.it += 1 - return self.rates[self.it - 1] - else: - raise StopIteration - - -class LearnRateScheduler: - def __init__(self, learn_rate, max_steps, cur_step=0, verbose=True): - self.schedules = LearnScheduleIterator(learn_rate, max_steps, cur_step) - (self.learn_rate, self.end_step) = next(self.schedules) - self.verbose = verbose - self.finished = False - - def step(self, step_number): - if step_number < self.end_step: - return False - - try: - (self.learn_rate, self.end_step) = next(self.schedules) - except StopIteration: - self.finished = True - return False - return True - - def apply(self, optimizer, step_number): - if not self.step(step_number): - return - - # if self.verbose: - # tqdm.tqdm.write(f'Training at rate of {self.learn_rate} until step {self.end_step}') - - for pg in optimizer.param_groups: - pg['lr'] = self.learn_rate diff --git a/modules/textual_inversion/preprocess.py b/modules/textual_inversion/preprocess.py deleted file mode 100644 index 2959ebe66..000000000 --- a/modules/textual_inversion/preprocess.py +++ /dev/null @@ -1,217 +0,0 @@ -import os -import math -from tqdm import tqdm -from PIL import Image, ImageOps -from modules import paths, shared, images, deepbooru -from modules.textual_inversion import autocrop - - -def preprocess(id_task, process_src, process_dst, process_width, process_height, preprocess_txt_action, process_keep_original_size=False, process_keep_channels=False, process_flip=False, process_split=False, process_caption_only=False, process_caption=False, process_caption_deepbooru=False, split_threshold=0.5, overlap_ratio=0.2, process_focal_crop=False, process_focal_crop_face_weight=0.9, process_focal_crop_entropy_weight=0.3, process_focal_crop_edges_weight=0.5, process_focal_crop_debug=False, process_multicrop=None, process_multicrop_mindim=None, process_multicrop_maxdim=None, process_multicrop_minarea=None, process_multicrop_maxarea=None, process_multicrop_objective=None, process_multicrop_threshold=None): # pylint: disable=unused-argument - try: - if process_caption: - shared.interrogator.load() - - if process_caption_deepbooru: - deepbooru.model.start() - - preprocess_work(process_src, process_dst, process_width, process_height, preprocess_txt_action, process_keep_original_size, process_keep_channels, process_flip, process_split, process_caption, process_caption_deepbooru, process_caption_only, split_threshold, overlap_ratio, process_focal_crop, process_focal_crop_face_weight, process_focal_crop_entropy_weight, process_focal_crop_edges_weight, process_focal_crop_debug, process_multicrop, process_multicrop_mindim, process_multicrop_maxdim, process_multicrop_minarea, process_multicrop_maxarea, process_multicrop_objective, process_multicrop_threshold) - - finally: - - if process_caption: - shared.interrogator.send_blip_to_ram() - - if process_caption_deepbooru: - deepbooru.model.stop() - - -class PreprocessParams: - src = None - dstdir = None - subindex = 0 - flip = False - process_caption_only = False - process_caption = False - process_caption_deepbooru = False - preprocess_txt_action = None - - -def save_pic_with_caption(image, index, params: PreprocessParams, existing_caption=None, existing_caption_filename=None): - caption = "" - if params.process_caption: - caption += shared.interrogator.generate_caption(image) - if params.process_caption_deepbooru: - if len(caption) > 0: - caption += ", " - caption += deepbooru.model.tag_multi(image) - - filename_part = params.src - filename_part = os.path.splitext(filename_part)[0] - filename_part = os.path.basename(filename_part) - - basename = f"{index:05}-{params.subindex}-{filename_part}" - if not params.process_caption_only: - image.save(os.path.join(params.dstdir, f"{basename}.png")) - - if params.preprocess_txt_action == 'prepend' and existing_caption: - caption = f"{existing_caption} {caption}" - elif params.preprocess_txt_action == 'append' and existing_caption: - caption = f"{caption} {existing_caption}" - elif params.preprocess_txt_action == 'copy' and existing_caption: - caption = existing_caption - caption = caption.strip() - if len(caption) > 0: - if params.process_caption_only: - fn = os.path.join(params.dstdir, f"{filename_part}.txt") - elif existing_caption_filename is not None: - fn = existing_caption_filename - else: - fn = os.path.join(params.dstdir, f"{basename}.txt") - with open(fn, "w", encoding="utf8") as file: - file.write(caption) - - params.subindex += 1 - - -def save_pic(image, index, params, existing_caption=None, existing_caption_filename=None): - save_pic_with_caption(image, index, params, existing_caption=existing_caption, existing_caption_filename=existing_caption_filename) - if params.flip: - save_pic_with_caption(ImageOps.mirror(image), index, params, existing_caption=existing_caption, existing_caption_filename=existing_caption_filename) - - -def split_pic(image, inverse_xy, width, height, overlap_ratio): - if inverse_xy: - from_w, from_h = image.height, image.width - to_w, to_h = height, width - else: - from_w, from_h = image.width, image.height - to_w, to_h = width, height - h = from_h * to_w // from_w - if inverse_xy: - image = image.resize((h, to_w)) - else: - image = image.resize((to_w, h)) - - split_count = math.ceil((h - to_h * overlap_ratio) / (to_h * (1.0 - overlap_ratio))) - y_step = (h - to_h) / (split_count - 1) - for i in range(split_count): - y = int(y_step * i) - if inverse_xy: - splitted = image.crop((y, 0, y + to_h, to_w)) - else: - splitted = image.crop((0, y, to_w, y + to_h)) - yield splitted - -# not using torchvision.transforms.CenterCrop because it doesn't allow float regions -def center_crop(image: Image, w: int, h: int): - iw, ih = image.size - if ih / h < iw / w: - sw = w * ih / h - box = (iw - sw) / 2, 0, iw - (iw - sw) / 2, ih - else: - sh = h * iw / w - box = 0, (ih - sh) / 2, iw, ih - (ih - sh) / 2 - return image.resize((w, h), Image.Resampling.LANCZOS, box) - - -def multicrop_pic(image: Image, mindim, maxdim, minarea, maxarea, objective, threshold): - iw, ih = image.size - err = lambda w, h: 1-(lambda x: x if x < 1 else 1/x)(iw/ih/(w/h)) # pylint: disable=unnecessary-lambda-assignment,unnecessary-direct-lambda-call - wh = max(((w, h) for w in range(mindim, maxdim+1, 64) for h in range(mindim, maxdim+1, 64) - if minarea <= w * h <= maxarea and err(w, h) <= threshold), - key= lambda wh: (wh[0]*wh[1], -err(*wh))[::1 if objective=='Maximize area' else -1], - default=None - ) - return wh and center_crop(image, *wh) - - -def preprocess_work(process_src, process_dst, process_width, process_height, preprocess_txt_action, process_keep_original_size, process_keep_channels, process_flip, process_split, process_caption, process_caption_deepbooru, process_caption_only, split_threshold, overlap_ratio, process_focal_crop, process_focal_crop_face_weight, process_focal_crop_entropy_weight, process_focal_crop_edges_weight, process_focal_crop_debug, process_multicrop, process_multicrop_mindim, process_multicrop_maxdim, process_multicrop_minarea, process_multicrop_maxarea, process_multicrop_objective, process_multicrop_threshold): - - width = process_width - height = process_height - src = os.path.abspath(process_src) - dst = os.path.abspath(process_dst) - split_threshold = max(0.0, min(1.0, split_threshold)) - overlap_ratio = max(0.0, min(0.9, overlap_ratio)) - assert src != dst, 'same directory specified as source and destination' - os.makedirs(dst, exist_ok=True) - files = os.listdir(src) - shared.state.job = "preprocess" - shared.state.textinfo = "Preprocessing..." - shared.state.job_count = len(files) - params = PreprocessParams() - params.dstdir = dst - params.flip = process_flip - params.process_caption_only = process_caption_only - params.process_caption = process_caption - params.process_caption_deepbooru = process_caption_deepbooru - params.preprocess_txt_action = preprocess_txt_action - pbar = tqdm(files) - for index, imagefile in enumerate(pbar): - params.subindex = 0 - filename = os.path.join(src, imagefile) - try: - img = Image.open(filename) - img = ImageOps.exif_transpose(img) - if not process_keep_channels: - img = img.convert("RGB") - except Exception: - continue - - description = f"Preprocessing image {index + 1}/{len(files)}" - pbar.set_description(description) - shared.state.textinfo = description - params.src = filename - existing_caption = None - existing_caption_filename = f"{os.path.splitext(filename)[0]}.txt" - if os.path.exists(existing_caption_filename): - with open(existing_caption_filename, 'r', encoding="utf8") as file: - existing_caption = file.read() - else: - existing_caption_filename = None - if shared.state.interrupted: - break - if img.height > img.width: - ratio = (img.width * height) / (img.height * width) - inverse_xy = False - else: - ratio = (img.height * width) / (img.width * height) - inverse_xy = True - process_default_resize = True - if process_split and ratio < 1.0 and ratio <= split_threshold: - for splitted in split_pic(img, inverse_xy, width, height, overlap_ratio): - save_pic(splitted, index, params, existing_caption=existing_caption, existing_caption_filename=existing_caption_filename) - process_default_resize = False - if process_focal_crop and img.height != img.width: - dnn_model_path = None - try: - dnn_model_path = autocrop.download_and_cache_models(os.path.join(paths.models_path, "opencv")) - except Exception as e: - shared.log.error(f"TI unable to load face detection model for auto crop selection. Falling back to lower quality haar method. {e}") - autocrop_settings = autocrop.Settings( - crop_width = width, - crop_height = height, - face_points_weight = process_focal_crop_face_weight, - entropy_points_weight = process_focal_crop_entropy_weight, - corner_points_weight = process_focal_crop_edges_weight, - annotate_image = process_focal_crop_debug, - dnn_model_path = dnn_model_path, - ) - for focal in autocrop.crop_image(img, autocrop_settings): - save_pic(focal, index, params, existing_caption=existing_caption) - process_default_resize = False - - if process_multicrop: - cropped = multicrop_pic(img, process_multicrop_mindim, process_multicrop_maxdim, process_multicrop_minarea, process_multicrop_maxarea, process_multicrop_objective, process_multicrop_threshold) - if cropped is not None: - save_pic(cropped, index, params, existing_caption=existing_caption) - else: - shared.log.error(f"TI skipped {img.width}x{img.height} image {filename} (can't find suitable size within error threshold)") - process_default_resize = False - if process_keep_original_size: - save_pic(img, index, params, existing_caption=existing_caption) - process_default_resize = False - if process_default_resize: - img = images.resize_image(1, img, width, height) - save_pic(img, index, params, existing_caption=existing_caption) - shared.state.nextjob() diff --git a/modules/textual_inversion/test_embedding.png b/modules/textual_inversion/test_embedding.png deleted file mode 100644 index 07e2d9afa..000000000 Binary files a/modules/textual_inversion/test_embedding.png and /dev/null differ diff --git a/modules/textual_inversion/textual_inversion.py b/modules/textual_inversion/textual_inversion.py index d775cc722..8cc81de4e 100644 --- a/modules/textual_inversion/textual_inversion.py +++ b/modules/textual_inversion/textual_inversion.py @@ -1,25 +1,17 @@ -from typing import List, Optional, Union -import csv -import html +from typing import List, Union import os import time from collections import namedtuple import torch -from tqdm import tqdm import safetensors.torch -import numpy as np -from PIL import Image, PngImagePlugin -from installer import install -from modules import shared, devices, processing, sd_models, images, errors -import modules.textual_inversion.dataset -from modules.textual_inversion.learn_schedule import LearnRateScheduler -from modules.textual_inversion.image_embedding import embedding_to_b64, embedding_from_b64, insert_image_data_embed, extract_image_data_embed, caption_image_overlay -from modules.textual_inversion.ti_logging import save_settings_to_file +from PIL import Image +from modules import shared, devices, sd_models, errors +from modules.textual_inversion.image_embedding import embedding_from_b64, extract_image_data_embed from modules.files_cache import directory_files, directory_mtime, extension_filter + debug = shared.log.trace if os.environ.get('SD_TI_DEBUG', None) is not None else lambda *args, **kwargs: None debug('Trace: TEXTUAL INVERSION') - TokenToAdd = namedtuple("TokenToAdd", ["clip_l", "clip_g"]) TextualInversionTemplate = namedtuple("TextualInversionTemplate", ["name", "path"]) textual_inversion_templates = {} @@ -386,374 +378,3 @@ class EmbeddingDatabase: if tokens[offset:offset + len(ids)] == ids: return embedding, len(ids) return None, None - - -def create_embedding(name, num_vectors_per_token, overwrite_old, init_text='*'): - cond_model = shared.sd_model.cond_stage_model - with devices.autocast(): - cond_model([""]) # will send cond model to GPU if lowvram/medvram is active - #cond_model expects at least some text, so we provide '*' as backup. - embedded = cond_model.encode_embedding_init_text(init_text or '*', num_vectors_per_token) - vec = torch.zeros((num_vectors_per_token, embedded.shape[1]), device=devices.device) - #Only copy if we provided an init_text, otherwise keep vectors as zeros - if init_text: - for i in range(num_vectors_per_token): - vec[i] = embedded[i * int(embedded.shape[0]) // num_vectors_per_token] - # Remove illegal characters from name. - name = "".join( x for x in name if (x.isalnum() or x in "._- ")) - fn = os.path.join(shared.opts.embeddings_dir, f"{name}.pt") - if not overwrite_old and os.path.exists(fn): - shared.log.warning(f"Embedding already exists: {fn}") - else: - embedding = Embedding(vec=vec, name=name, filename=fn) - embedding.step = 0 - embedding.save(fn) - shared.log.info(f'Created embedding: {fn} vectors {num_vectors_per_token} init {init_text}') - return fn - - -def write_loss(log_directory, filename, step, epoch_len, values): - if shared.opts.training_write_csv_every == 0: - return - if step % shared.opts.training_write_csv_every != 0: - return - write_csv_header = False if os.path.exists(os.path.join(log_directory, filename)) else True - with open(os.path.join(log_directory, filename), "a+", newline='', encoding='utf-8') as fout: - csv_writer = csv.DictWriter(fout, fieldnames=["step", "epoch", "epoch_step", *(values.keys())]) - if write_csv_header: - csv_writer.writeheader() - epoch = (step - 1) // epoch_len - epoch_step = (step - 1) % epoch_len - csv_writer.writerow({ - "step": step, - "epoch": epoch, - "epoch_step": epoch_step, - **values, - }) - - -def tensorboard_setup(log_directory): - install('tensorboard') - from torch.utils.tensorboard import SummaryWriter - os.makedirs(os.path.join(log_directory, "tensorboard"), exist_ok=True) - return SummaryWriter( - log_dir=os.path.join(log_directory, "tensorboard"), - flush_secs=shared.opts.training_tensorboard_flush_every) - - -def tensorboard_add(tensorboard_writer, loss, global_step, step, learn_rate, epoch_num): - tensorboard_add_scaler(tensorboard_writer, "Loss/train", loss, global_step) - tensorboard_add_scaler(tensorboard_writer, f"Loss/train/epoch-{epoch_num}", loss, step) - tensorboard_add_scaler(tensorboard_writer, "Learn rate/train", learn_rate, global_step) - tensorboard_add_scaler(tensorboard_writer, f"Learn rate/train/epoch-{epoch_num}", learn_rate, step) - - -def tensorboard_add_scaler(tensorboard_writer, tag, value, step): - tensorboard_writer.add_scalar(tag=tag, scalar_value=value, global_step=step) - - -def tensorboard_add_image(tensorboard_writer, tag, pil_image, step): - # Convert a pil image to a torch tensor - img_tensor = torch.as_tensor(np.array(pil_image, copy=True)) - img_tensor = img_tensor.view(pil_image.size[1], pil_image.size[0], len(pil_image.getbands())) - img_tensor = img_tensor.permute((2, 0, 1)) - tensorboard_writer.add_image(tag, img_tensor, global_step=step) - - -def validate_train_inputs(model_name, learn_rate, batch_size, gradient_step, data_root, template_file, template_filename, steps, save_model_every, create_image_every, name="embedding"): - assert model_name, f"{name} not selected" - assert learn_rate, "Learning rate is empty or 0" - assert isinstance(batch_size, int), "Batch size must be integer" - assert batch_size > 0, "Batch size must be positive" - assert isinstance(gradient_step, int), "Gradient accumulation step must be integer" - assert gradient_step > 0, "Gradient accumulation step must be positive" - assert data_root, "Dataset directory is empty" - assert os.path.isdir(data_root), "Dataset directory doesn't exist" - assert os.listdir(data_root), "Dataset directory is empty" - assert template_filename, "Prompt template file not selected" - assert template_file, f"Prompt template file {template_filename} not found" - assert os.path.isfile(template_file.path), f"Prompt template file {template_filename} doesn't exist" - assert steps, "Max steps is empty or 0" - assert isinstance(steps, int), "Max steps must be integer" - assert steps > 0, "Max steps must be positive" - assert isinstance(save_model_every, int), "Save {name} must be integer" - assert save_model_every >= 0, "Save {name} must be positive or 0" - assert isinstance(create_image_every, int), "Create image must be integer" - assert create_image_every >= 0, "Create image must be positive or 0" - - -def train_embedding(id_task, embedding_name, learn_rate, batch_size, gradient_step, data_root, log_directory, training_width, training_height, varsize, steps, clip_grad_mode, clip_grad_value, shuffle_tags, tag_drop_out, latent_sampling_method, use_weight, create_image_every, save_embedding_every, template_filename, save_image_with_stored_embedding, preview_from_txt2img, preview_prompt, preview_negative_prompt, preview_steps, preview_sampler_index, preview_cfg_scale, preview_seed, preview_width, preview_height): # pylint: disable=unused-argument - from modules import sd_hijack, sd_hijack_checkpoint - - shared.log.debug(f'train_embedding: embedding_name={embedding_name}|learn_rate={learn_rate}|batch_size={batch_size}|gradient_step={gradient_step}|data_root={data_root}|log_directory={log_directory}|training_width={training_width}|training_height={training_height}|varsize={varsize}|steps={steps}|clip_grad_mode={clip_grad_mode}|clip_grad_value={clip_grad_value}|shuffle_tags={shuffle_tags}|tag_drop_out={tag_drop_out}|latent_sampling_method={latent_sampling_method}|use_weight={use_weight}|create_image_every={create_image_every}|save_embedding_every={save_embedding_every}|template_filename={template_filename}|save_image_with_stored_embedding={save_image_with_stored_embedding}|preview_from_txt2img={preview_from_txt2img}|preview_prompt={preview_prompt}|preview_negative_prompt={preview_negative_prompt}|preview_steps={preview_steps}|preview_sampler_index={preview_sampler_index}|preview_cfg_scale={preview_cfg_scale}|preview_seed={preview_seed}|preview_width={preview_width}|preview_height={preview_height}') - save_embedding_every = save_embedding_every or 0 - create_image_every = create_image_every or 0 - template_file = textual_inversion_templates.get(template_filename, None) - validate_train_inputs(embedding_name, learn_rate, batch_size, gradient_step, data_root, template_file, template_filename, steps, save_embedding_every, create_image_every, name="embedding") - if log_directory is None or log_directory == '': - log_directory = f"{os.path.join(shared.cmd_opts.data_dir, 'train/log/embeddings')}" - template_file = template_file.path - - shared.state.job = "train" - shared.state.textinfo = "Initializing textual inversion training..." - shared.state.job_count = steps - - filename = os.path.join(shared.opts.embeddings_dir, f'{embedding_name}.pt') - - if log_directory == '': - log_directory = f"{os.path.join(shared.cmd_opts.data_dir, 'train/log/embeddings')}" - log_directory = os.path.join(log_directory, embedding_name) - unload = shared.opts.unload_models_when_training - - if save_embedding_every > 0: - embedding_dir = os.path.join(log_directory, "embeddings") - os.makedirs(embedding_dir, exist_ok=True) - else: - embedding_dir = None - - if create_image_every > 0: - images_dir = os.path.join(log_directory, "images") - os.makedirs(images_dir, exist_ok=True) - else: - images_dir = None - - if create_image_every > 0 and save_image_with_stored_embedding: - images_embeds_dir = os.path.join(log_directory, "image_embeddings") - os.makedirs(images_embeds_dir, exist_ok=True) - else: - images_embeds_dir = None - - hijack = sd_hijack.model_hijack - embedding = hijack.embedding_db.word_embeddings[embedding_name] - checkpoint = sd_models.select_checkpoint() - initial_step = embedding.step or 0 - if initial_step >= steps: - shared.state.textinfo = "Model has already been trained beyond specified max steps" - return embedding, filename - scheduler = LearnRateScheduler(learn_rate, steps, initial_step) - clip_grad = torch.nn.utils.clip_grad_value_ if clip_grad_mode == "value" else \ - torch.nn.utils.clip_grad_norm_ if clip_grad_mode == "norm" else \ - None - if clip_grad: - clip_grad_sched = LearnRateScheduler(clip_grad_value, steps, initial_step, verbose=False) - # dataset loading may take a while, so input validations and early returns should be done before this - shared.state.textinfo = f"Preparing dataset from {html.escape(data_root)}..." - old_parallel_processing_allowed = shared.parallel_processing_allowed - - if shared.opts.training_enable_tensorboard: - tensorboard_writer = tensorboard_setup(log_directory) - - pin_memory = shared.opts.pin_memory - # init dataset - ds = modules.textual_inversion.dataset.PersonalizedBase(data_root=data_root, width=training_width, height=training_height, repeats=shared.opts.training_image_repeats_per_epoch, placeholder_token=embedding_name, model=shared.sd_model, cond_model=shared.sd_model.cond_stage_model, device=devices.device, template_file=template_file, batch_size=batch_size, gradient_step=gradient_step, shuffle_tags=shuffle_tags, tag_drop_out=tag_drop_out, latent_sampling_method=latent_sampling_method, varsize=varsize, use_weight=use_weight) - - if shared.opts.save_training_settings_to_txt: - save_settings_to_file(log_directory, {**dict(model_name=checkpoint.model_name, model_hash=checkpoint.shorthash, num_of_dataset_images=len(ds), num_vectors_per_token=len(embedding.vec)), **locals()}) - latent_sampling_method = ds.latent_sampling_method - # init dataloader - dl = modules.textual_inversion.dataset.PersonalizedDataLoader(ds, latent_sampling_method=latent_sampling_method, batch_size=ds.batch_size, pin_memory=pin_memory) - if unload: - shared.parallel_processing_allowed = False - shared.sd_model.first_stage_model.to(devices.cpu) - - embedding.vec.requires_grad = True - optimizer = torch.optim.AdamW([embedding.vec], lr=scheduler.learn_rate, weight_decay=0.0) - if shared.opts.save_optimizer_state: - optimizer_state_dict = None - if os.path.exists(f"{filename}.optim"): - optimizer_saved_dict = torch.load(f"{filename}.optim", map_location='cpu') - if embedding.checksum() == optimizer_saved_dict.get('hash', None): - optimizer_state_dict = optimizer_saved_dict.get('optimizer_state_dict', None) - if optimizer_state_dict is not None: - optimizer.load_state_dict(optimizer_state_dict) - shared.log.info("Load existing optimizer from checkpoint") - else: - shared.log.info("No saved optimizer exists in checkpoint") - - scaler = torch.cuda.amp.GradScaler() - - batch_size = ds.batch_size - gradient_step = ds.gradient_step - # n steps = batch_size * gradient_step * n image processed - steps_per_epoch = len(ds) // batch_size // gradient_step - max_steps_per_epoch = len(ds) // batch_size - (len(ds) // batch_size) % gradient_step - loss_step = 0 - _loss_step = 0 #internal - last_saved_file = "" - last_saved_image = "" - forced_filename = "" - embedding_yet_to_be_embedded = False - is_training_inpainting_model = shared.sd_model.model.conditioning_key in {'hybrid', 'concat'} - img_c = None - - pbar = tqdm(total=steps - initial_step) - try: - sd_hijack_checkpoint.add() - for _i in range((steps-initial_step) * gradient_step): - if scheduler.finished: - break - if shared.state.interrupted: - break - for j, batch in enumerate(dl): - # works as a drop_last=True for gradient accumulation - if j == max_steps_per_epoch: - break - scheduler.apply(optimizer, embedding.step) - if scheduler.finished: - break - if shared.state.interrupted: - break - if clip_grad: - clip_grad_sched.step(embedding.step) - with devices.autocast(): - x = batch.latent_sample.to(devices.device, non_blocking=pin_memory) - if use_weight: - w = batch.weight.to(devices.device, non_blocking=pin_memory) - c = shared.sd_model.cond_stage_model(batch.cond_text) - if is_training_inpainting_model: - if img_c is None: - img_c = processing.txt2img_image_conditioning(shared.sd_model, c, training_width, training_height) - cond = {"c_concat": [img_c], "c_crossattn": [c]} - else: - cond = c - if use_weight: - loss = shared.sd_model.weighted_forward(x, cond, w)[0] / gradient_step - del w - else: - loss = shared.sd_model.forward(x, cond)[0] / gradient_step - del x - _loss_step += loss.item() - - scaler.scale(loss).backward() - # go back until we reach gradient accumulation steps - if (j + 1) % gradient_step != 0: - continue - if clip_grad: - clip_grad(embedding.vec, clip_grad_sched.learn_rate) - - scaler.step(optimizer) - scaler.update() - embedding.step += 1 - pbar.update() - optimizer.zero_grad(set_to_none=True) - loss_step = _loss_step - _loss_step = 0 - steps_done = embedding.step + 1 - epoch_num = embedding.step // steps_per_epoch - - description = f"Training textual inversion step {embedding.step} loss: {loss_step:.5f} lr: {scheduler.learn_rate:.5f}" - pbar.set_description(description) - if embedding_dir is not None and steps_done % save_embedding_every == 0: - # Before saving, change name to match current checkpoint. - embedding_name_every = f'{embedding_name}-{steps_done}' - last_saved_file = os.path.join(embedding_dir, f'{embedding_name_every}.pt') - save_embedding(embedding, optimizer, checkpoint, embedding_name_every, last_saved_file, remove_cached_checksum=True) - embedding_yet_to_be_embedded = True - - write_loss(log_directory, f"{embedding_name}.csv", embedding.step, steps_per_epoch, { "loss": f"{loss_step:.7f}", "learn_rate": scheduler.learn_rate }) - - if images_dir is not None and steps_done % create_image_every == 0: - forced_filename = f'{embedding_name}-{steps_done}' - last_saved_image = os.path.join(images_dir, forced_filename) - shared.sd_model.first_stage_model.to(devices.device) - - p = processing.StableDiffusionProcessingTxt2Img( - sd_model=shared.sd_model, - do_not_save_grid=True, - do_not_save_samples=True, - do_not_reload_embeddings=True, - ) - - if preview_from_txt2img: - p.prompt = preview_prompt - p.negative_prompt = preview_negative_prompt - p.steps = preview_steps - p.sampler_name = processing.get_sampler_name(preview_sampler_index) - p.cfg_scale = preview_cfg_scale - p.seed = preview_seed - p.width = preview_width - p.height = preview_height - else: - p.prompt = batch.cond_text[0] - p.steps = 20 - p.width = training_width - p.height = training_height - - preview_text = p.prompt - processed = processing.process_images(p) - image = processed.images[0] if len(processed.images) > 0 else None - - if unload: - shared.sd_model.first_stage_model.to(devices.cpu) - - if image is not None: - shared.state.assign_current_image(image) - last_saved_image, _last_text_info = images.save_image(image, images_dir, "", p.seed, p.prompt, shared.opts.samples_format, processed.infotexts[0], p=p, forced_filename=forced_filename, save_to_dirs=False) - last_saved_image += f", prompt: {preview_text}" - if shared.opts.training_enable_tensorboard and shared.opts.training_tensorboard_save_images: - tensorboard_add_image(tensorboard_writer, f"Validation at epoch {epoch_num}", image, embedding.step) - - if save_image_with_stored_embedding and os.path.exists(last_saved_file) and embedding_yet_to_be_embedded: - last_saved_image_chunks = os.path.join(images_embeds_dir, f'{embedding_name}-{steps_done}.png') - info = PngImagePlugin.PngInfo() - data = torch.load(last_saved_file) - info.add_text("sd-ti-embedding", embedding_to_b64(data)) - title = f"<{data.get('name', '???')}>" - try: - vectorSize = list(data['string_to_param'].values())[0].shape[0] - except Exception: - vectorSize = '?' - checkpoint = sd_models.select_checkpoint() - footer_left = checkpoint.model_name - footer_mid = f'[{checkpoint.shorthash}]' - footer_right = f'{vectorSize}v {steps_done}s' - captioned_image = caption_image_overlay(image, title, footer_left, footer_mid, footer_right) - captioned_image = insert_image_data_embed(captioned_image, data) - captioned_image.save(last_saved_image_chunks, "PNG", pnginfo=info) - embedding_yet_to_be_embedded = False - - last_saved_image, _last_text_info = images.save_image(image, images_dir, "", p.seed, p.prompt, shared.opts.samples_format, processed.infotexts[0], p=p, forced_filename=forced_filename, save_to_dirs=False) - last_saved_image += f", prompt: {preview_text}" - - shared.state.job_no = embedding.step - shared.state.textinfo = f""" -

-Loss: {loss_step:.7f}
-Step: {steps_done}
-Last prompt: {html.escape(batch.cond_text[0])}
-Last saved embedding: {html.escape(last_saved_file)}
-Last saved image: {html.escape(last_saved_image)}
-

-""" - filename = os.path.join(shared.opts.embeddings_dir, f'{embedding_name}.pt') - save_embedding(embedding, optimizer, checkpoint, embedding_name, filename, remove_cached_checksum=True) - except Exception as e: - errors.display(e, 'embedding train') - finally: - pbar.leave = False - pbar.close() - shared.sd_model.first_stage_model.to(devices.device) - shared.parallel_processing_allowed = old_parallel_processing_allowed - sd_hijack_checkpoint.remove() - return embedding, filename - - -def save_embedding(embedding, optimizer, checkpoint, embedding_name, filename, remove_cached_checksum=True): - old_embedding_name = embedding.name - old_sd_checkpoint = embedding.sd_checkpoint if hasattr(embedding, "sd_checkpoint") else None - old_sd_checkpoint_name = embedding.sd_checkpoint_name if hasattr(embedding, "sd_checkpoint_name") else None - old_cached_checksum = embedding.cached_checksum if hasattr(embedding, "cached_checksum") else None - try: - embedding.sd_checkpoint = checkpoint.shorthash - embedding.sd_checkpoint_name = checkpoint.model_name - if remove_cached_checksum: - embedding.cached_checksum = None - embedding.name = embedding_name - embedding.optimizer_state_dict = optimizer.state_dict() - embedding.save(filename) - except Exception: - embedding.sd_checkpoint = old_sd_checkpoint - embedding.sd_checkpoint_name = old_sd_checkpoint_name - embedding.name = old_embedding_name - embedding.cached_checksum = old_cached_checksum - raise diff --git a/modules/textual_inversion/ti_logging.py b/modules/textual_inversion/ti_logging.py deleted file mode 100644 index 1116ec91d..000000000 --- a/modules/textual_inversion/ti_logging.py +++ /dev/null @@ -1,22 +0,0 @@ -import datetime -import json -import os - -saved_params_shared = {"model_name", "model_hash", "initial_step", "num_of_dataset_images", "learn_rate", "batch_size", "clip_grad_mode", "clip_grad_value", "gradient_step", "data_root", "log_directory", "training_width", "training_height", "steps", "create_image_every", "template_file", "latent_sampling_method"} -saved_params_ti = {"embedding_name", "num_vectors_per_token", "save_embedding_every", "save_image_with_stored_embedding"} -saved_params_hypernet = {"hypernetwork_name", "layer_structure", "activation_func", "weight_init", "add_layer_norm", "use_dropout", "save_hypernetwork_every"} -saved_params_all = saved_params_shared | saved_params_ti | saved_params_hypernet -saved_params_previews = {"preview_prompt", "preview_negative_prompt", "preview_steps", "preview_sampler_index", "preview_cfg_scale", "preview_seed", "preview_width", "preview_height"} - - -def save_settings_to_file(log_directory, all_params): - now = datetime.datetime.now() - params = {"datetime": now.strftime("%Y-%m-%d %H:%M:%S")} - keys = saved_params_all - if all_params.get('preview_from_txt2img'): - keys = keys | saved_params_previews - params.update({k: v for k, v in all_params.items() if k in keys}) - filename = f"settings-{now.strftime('%Y-%m-%d_%H-%M-%S')}.json" - with open(os.path.join(log_directory, filename), "w", encoding='utf-8') as file: - print(f'Training settings file: {os.path.join(log_directory, filename)}') - json.dump(params, file, indent=2) diff --git a/modules/textual_inversion/ui.py b/modules/textual_inversion/ui.py deleted file mode 100644 index 9c5d7e3c4..000000000 --- a/modules/textual_inversion/ui.py +++ /dev/null @@ -1,35 +0,0 @@ -import html -import gradio as gr -import modules.textual_inversion.textual_inversion -import modules.textual_inversion.preprocess -from modules import shared - - -def create_embedding(name, initialization_text, nvpt, overwrite_old): - from modules import sd_hijack - filename = modules.textual_inversion.textual_inversion.create_embedding(name, nvpt, overwrite_old, init_text=initialization_text) - sd_hijack.model_hijack.embedding_db.load_textual_inversion_embeddings() - return gr.Dropdown.update(choices=sorted(sd_hijack.model_hijack.embedding_db.word_embeddings.keys())), f"Created: {filename}", "" - - -def preprocess(*args): - modules.textual_inversion.preprocess.preprocess(*args) - return f"Preprocessing {'interrupted' if shared.state.interrupted else 'finished'}.", "" - - -def train_embedding(*args): - from modules import sd_hijack - assert not shared.cmd_opts.lowvram, 'Training models with lowvram not possible' - apply_optimizations = False - try: - if not apply_optimizations: - sd_hijack.undo_optimizations() - embedding, filename = modules.textual_inversion.textual_inversion.train_embedding(*args) - res = f"Training {'interrupted' if shared.state.interrupted else 'finished'} at {embedding.step} steps. Embedding saved to {html.escape(filename)}" - return res, "" - except Exception as e: - shared.log.error(f"Exception in train_embedding: {e}") - raise RuntimeError from e - finally: - if not apply_optimizations: - sd_hijack.apply_optimizations() diff --git a/modules/ui.py b/modules/ui.py index b773f6231..b059bbd36 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -144,11 +144,6 @@ def create_ui(startup_timer = None): ui_postprocessing.create_ui() timer.startup.record("ui-extras") - with gr.Blocks(analytics_enabled=False) as train_interface: - from modules import ui_train - ui_train.create_ui() - timer.startup.record("ui-train") - with gr.Blocks(analytics_enabled=False) as models_interface: from modules import ui_models ui_models.create_ui() @@ -377,7 +372,6 @@ def create_ui(startup_timer = None): interfaces += [(control_interface, "Control", "control")] if control_interface is not None else [] interfaces += [(extras_interface, "Process", "process")] interfaces += [(interrogate_interface, "Interrogate", "interrogate")] - interfaces += [(train_interface, "Train", "train")] interfaces += [(models_interface, "Models", "models")] interfaces += script_callbacks.ui_tabs_callback() interfaces += [(settings_interface, "System", "system")] diff --git a/modules/ui_train.py b/modules/ui_train.py deleted file mode 100644 index 263adf1bf..000000000 --- a/modules/ui_train.py +++ /dev/null @@ -1,379 +0,0 @@ -import os -import gradio as gr -from modules import script_callbacks, shared -from modules.ui_common import create_refresh_button -from modules.ui_sections import create_sampler_inputs -from modules.call_queue import wrap_gradio_gpu_call - - -def create_ui(): - from modules.textual_inversion import textual_inversion - import modules.hypernetworks.ui - dummy_component = gr.Label(visible=False) - - with gr.Row(elem_id="train_tab"): - with gr.Column(elem_id='train_output_container', scale=1): - train_output = gr.Text(elem_id="train_output", value="", show_label=False) - gr.Gallery(label='Output', show_label=False, elem_id='train_gallery', columns=1) - gr.HTML(elem_id="train_progress", value="") - train_outcome = gr.HTML(elem_id="train_error", value="") - - with gr.Row(visible=True) as action_pp: - process_run = gr.Button(value="Preprocess", variant='primary') - process_stop = gr.Button("Stop") - - with gr.Row(visible=False) as action_ti: - ti_train = gr.Button(value="Train embedding", variant='primary') - ti_stop = gr.Button(value="Stop") - - with gr.Row(visible=False) as action_hn: - hn_train = gr.Button(value="Train hypernetwork", variant='primary') - hn_stop = gr.Button(value="Stop") - - with gr.Column(elem_id='train_input_container', scale=3): - - with gr.Tabs(elem_id="train_tabs"): - def gr_show(visible=True): - return {"visible": visible, "__type__": "update"} - - def train_tab_change(tab): - if tab == 'ti': - return gr_show(False), gr_show(True), gr_show(False) - elif tab == 'hn': - return gr_show(False), gr_show(False), gr_show(True) - elif tab == 'pr': - return gr_show(True), gr_show(False), gr_show(False) - else: - return gr_show(False), gr_show(False), gr_show(False) - - ### preview tab - - with gr.Tab(label="Preview settings", id="train_preview_tab") as tab_preview: - tab_preview.select(fn=lambda: train_tab_change('pr'), inputs=[], outputs=[action_pp, action_ti, action_hn]) - prompt = gr.Textbox(label="Prompt", value="", placeholder="Prompt to be used for previews", lines=2) - negative = gr.Textbox(label="Negative prompt", value="", placeholder="Negative prompt to be used for previews", lines=2) - steps, sampler_index = create_sampler_inputs('train', accordion=False) - cfg_scale = gr.Slider(minimum=0.0, maximum=30.0, step=0.1, label='CFG scale', value=6.0) - seed = gr.Number(label='Initial seed', value=-1) - with gr.Row(): - width = gr.Slider(minimum=64, maximum=8192, step=8, label="Width", value=512) - height = gr.Slider(minimum=64, maximum=8192, step=8, label="Height", value=512) - txt2img_preview_params = [prompt, negative, steps, sampler_index, cfg_scale, seed, width, height] - - ### preprocess tab - - with gr.Tab(label="Preprocess images", id="preprocess_images") as tab_preprocess: - tab_preprocess.select(fn=lambda: train_tab_change('pp'), inputs=[], outputs=[action_pp, action_ti, action_hn]) - process_src = gr.Textbox(label='Source directory') - process_dst = gr.Textbox(label='Destination directory') - with gr.Row(): - process_width = gr.Slider(minimum=64, maximum=2048, step=8, label="Width", value=512) - process_height = gr.Slider(minimum=64, maximum=2048, step=8, label="Height", value=512) - preprocess_txt_action = gr.Dropdown(label='Existing caption text action', value="ignore", choices=["ignore", "copy", "prepend", "append"]) - - with gr.Box(): - gr.HTML('

Preprocessing steps

') - process_keep_original_size = gr.Checkbox(label='Keep original size') - process_keep_channels = gr.Checkbox(label='Keep original image channels') - process_flip = gr.Checkbox(label='Create flipped copies') - process_split = gr.Checkbox(label='Split oversized images') - process_focal_crop = gr.Checkbox(label='Auto focal point crop') - process_multicrop = gr.Checkbox(label='Auto-sized crop') - process_caption_only = gr.Checkbox(label='Create captions only') - process_caption = gr.Checkbox(label='Create BLIP captions') - process_caption_deepbooru = gr.Checkbox(label='Create Deepbooru captions') - - with gr.Row(visible=False) as process_split_extra_row: - process_split_threshold = gr.Slider(label='Split image threshold', value=0.5, minimum=0.0, maximum=1.0, step=0.05) - process_overlap_ratio = gr.Slider(label='Split image overlap ratio', value=0.2, minimum=0.0, maximum=0.9, step=0.05) - - with gr.Row(visible=False) as process_focal_crop_row: - process_focal_crop_face_weight = gr.Slider(label='Focal point face weight', value=0.9, minimum=0.0, maximum=1.0, step=0.05) - process_focal_crop_entropy_weight = gr.Slider(label='Focal point entropy weight', value=0.15, minimum=0.0, maximum=1.0, step=0.05) - process_focal_crop_edges_weight = gr.Slider(label='Focal point edges weight', value=0.5, minimum=0.0, maximum=1.0, step=0.05) - process_focal_crop_debug = gr.Checkbox(label='Create debug image') - - with gr.Column(visible=False) as process_multicrop_col: - gr.HTML('

Each image is center-cropped with an automatically chosen width and height

') - with gr.Row(): - process_multicrop_mindim = gr.Slider(minimum=64, maximum=2048, step=8, label="Dimension lower bound", value=384) - process_multicrop_maxdim = gr.Slider(minimum=64, maximum=2048, step=8, label="Dimension upper bound", value=768) - with gr.Row(): - process_multicrop_minarea = gr.Slider(minimum=64*64, maximum=2048*2048, step=1, label="Area lower bound", value=64*64) - process_multicrop_maxarea = gr.Slider(minimum=64*64, maximum=2048*2048, step=1, label="Area upper bound", value=640*640) - with gr.Row(): - process_multicrop_objective = gr.Radio(["Maximize area", "Minimize error"], value="Maximize area", label="Resizing objective") - process_multicrop_threshold = gr.Slider(minimum=0, maximum=1, step=0.01, label="Error threshold", value=0.1) - - from modules.textual_inversion import ui - process_split.change(fn=lambda show: gr_show(show), inputs=[process_split], outputs=[process_split_extra_row]) - process_focal_crop.change(fn=lambda show: gr_show(show), inputs=[process_focal_crop], outputs=[process_focal_crop_row]) - process_multicrop.change(fn=lambda show: gr_show(show), inputs=[process_multicrop], outputs=[process_multicrop_col]) - process_stop.click(fn=lambda: shared.state.interrupt(), inputs=[], outputs=[]) - process_run.click( - fn=wrap_gradio_gpu_call(ui.preprocess, extra_outputs=[gr.update()]), - _js="startTrainMonitor", - inputs=[ - dummy_component, - process_src, - process_dst, - process_width, - process_height, - preprocess_txt_action, - process_keep_original_size, - process_keep_channels, - process_flip, - process_split, - process_caption_only, - process_caption, - process_caption_deepbooru, - process_split_threshold, - process_overlap_ratio, - process_focal_crop, - process_focal_crop_face_weight, - process_focal_crop_entropy_weight, - process_focal_crop_edges_weight, - process_focal_crop_debug, - process_multicrop, - process_multicrop_mindim, - process_multicrop_maxdim, - process_multicrop_minarea, - process_multicrop_maxarea, - process_multicrop_objective, - process_multicrop_threshold, - ], - outputs=[ - train_output, - train_outcome, - ], - ) - - ### train embedding tab - if shared.backend == shared.Backend.ORIGINAL: - from modules import sd_hijack - with gr.Tab(label="Train embedding", id="train_embedding_tab") as tab_ti: - tab_ti.select(fn=lambda: train_tab_change('ti'), inputs=[], outputs=[action_pp, action_ti, action_hn]) - def get_textual_inversion_template_names(): - return sorted(textual_inversion.textual_inversion_templates) - - gr.HTML('

Select existing embedding to continue training or create a new one

') - with gr.Row(): - with gr.Column(): - with gr.Row(): - ti_name = gr.Dropdown(label='Select embedding', choices=sorted(sd_hijack.model_hijack.embedding_db.word_embeddings.keys())) - create_refresh_button(ti_name, sd_hijack.model_hijack.embedding_db.load_textual_inversion_embeddings, lambda: {"choices": sorted(sd_hijack.model_hijack.embedding_db.word_embeddings.keys())}, "refresh_train_embedding_name") - with gr.Column(): - ti_new_name = gr.Textbox(label="Create emebedding") - ti_init_text = gr.Textbox(label="Initialization text", value="*") - ti_vectors = gr.Slider(label="Number of vectors per token", minimum=1, maximum=75, step=1, value=1) - ti_overwrite = gr.Checkbox(value=False, label="Overwrite Old Embedding") - with gr.Row(): - ti_create = gr.Button(value="Create embedding", variant='secondary') - - with gr.Box(): - gr.HTML('

Training parameters

') - ti_learn_rate = gr.Textbox(label='Embedding Learning rate', placeholder="Embedding Learning rate", value="0.005") - with gr.Row(): - ti_clip_grad_mode = gr.Dropdown(value="disabled", label="Gradient Clipping", choices=["disabled", "value", "norm"]) - ti_clip_grad_value = gr.Number(label="Gradient clip value", value=0.1) - ti_batch_size = gr.Number(label='Batch size', value=1, precision=0) - ti_gradient_step = gr.Number(label='Gradient accumulation steps', value=1, precision=0) - ti_steps = gr.Number(label='Max steps', value=1000, precision=0) - - with gr.Box(): - gr.HTML('

Training images

') - ti_dataset_directory = gr.Textbox(label='Dataset directory', placeholder="Path to directory with input images") - with gr.Row(): - ti_varsize = gr.Checkbox(label="Do not resize images", value=False) - ti_width = gr.Slider(minimum=64, maximum=2048, step=8, label="Width", value=512) - ti_height = gr.Slider(minimum=64, maximum=2048, step=8, label="Height", value=512) - ti_use_weight = gr.Checkbox(label="Use PNG alpha channel as loss weight", value=False) - - with gr.Box(): - gr.HTML('

Dataset processing

') - with gr.Row(): - ti_template = gr.Dropdown(label='Prompt template', value="style_filewords.txt", choices=get_textual_inversion_template_names()) - create_refresh_button(ti_template, textual_inversion.list_textual_inversion_templates, lambda: {"choices": get_textual_inversion_template_names()}, "refrsh_train_template_file") - ti_shuffle = gr.Checkbox(label="Shuffle tags", value=False) - ti_tag_drop_out = gr.Slider(minimum=0, maximum=1, step=0.1, label="Drop out tags when creating prompts", value=0) - ti_latent_sampling_method = gr.Radio(label='Choose latent sampling method', value="once", choices=['once', 'deterministic', 'random']) - - with gr.Box(): - gr.HTML('

Training outputs

') - with gr.Row(): - ti_create_every = gr.Number(label='Create interim images', value=500, precision=0) - ti_save_every = gr.Number(label='Create interim embeddings', value=500, precision=0) - ti_save_image_with_stored_embedding = gr.Checkbox(label='Save images with embedding in PNG chunks', value=True) - ti_preview_from_txt2img = gr.Checkbox(label='Use current settings for previews', value=False) - ti_log_directory = gr.Textbox(label='Log directory', placeholder="Defaults to train/log/embedding", value="") - - ti_stop.click(fn=lambda: shared.state.interrupt(), inputs=[], outputs=[]) - - ti_create.click( - fn=modules.textual_inversion.ui.create_embedding, - inputs=[ - ti_new_name, - ti_init_text, - ti_vectors, - ti_overwrite, - ], - outputs=[ - ti_name, - train_output, - train_outcome, - ] - ) - - ti_train.click( - fn=wrap_gradio_gpu_call(modules.textual_inversion.ui.train_embedding, extra_outputs=[gr.update()]), - _js="startTrainMonitor", - inputs=[ - dummy_component, - ti_name, - ti_learn_rate, - ti_batch_size, - ti_gradient_step, - ti_dataset_directory, - ti_log_directory, - ti_width, - ti_height, - ti_varsize, - ti_steps, - ti_clip_grad_mode, - ti_clip_grad_value, - ti_shuffle, - ti_tag_drop_out, - ti_latent_sampling_method, - ti_use_weight, - ti_create_every, - ti_save_every, - ti_template, - ti_save_image_with_stored_embedding, - ti_preview_from_txt2img, - *txt2img_preview_params, - ], - outputs=[ - train_output, - train_outcome, - ] - ) - - ### train hypernetwork tab - - if shared.backend == shared.Backend.ORIGINAL: - from modules import sd_hijack - with gr.Tab(label="Train hypernetwork", id="train_hypernetwork_tab") as tab_hn: - tab_hn.select(fn=lambda: train_tab_change('hn'), inputs=[], outputs=[action_pp, action_ti, action_hn]) - gr.HTML('

Select existing hypernetwork to continue training or create a new one

') - with gr.Row(): - with gr.Column(): - with gr.Row(): - hn_name = gr.Dropdown(label='Hypernetwork', choices=sorted(shared.hypernetworks)) - create_refresh_button(hn_name, shared.reload_hypernetworks, lambda: {"choices": sorted(shared.hypernetworks)}, "refresh_train_hypernetwork_name") - with gr.Column(): - hn_new_name = gr.Textbox(label="Name") - hn_new_sizes = gr.CheckboxGroup(label="Modules", value=["768", "320", "640", "1280"], choices=["768", "1024", "320", "640", "1280"]) - hn_new_layer_structure = gr.Textbox("1, 2, 1", label="Enter hypernetwork layer structure", placeholder="1st and last digit must be 1. ex:'1, 2, 1'") - with gr.Row(): - hn_new_activation_func = gr.Dropdown(value="linear", label="Select activation function of hypernetwork", choices=modules.hypernetworks.ui.keys) - hn_new_initialization_option = gr.Dropdown(value = "Normal", label="Select Layer weights initialization", choices=["Normal", "KaimingUniform", "KaimingNormal", "XavierUniform", "XavierNormal"]) - hn_new_add_layer_norm = gr.Checkbox(label="Add layer normalization") - hn_new_use_dropout = gr.Checkbox(label="Use dropout") - hn_new_dropout_structure = gr.Textbox("0, 0, 0", label="Enter hypernetwork Dropout structure", placeholder="1st and last digit must be 0 and values should be between 0 and 1. ex:'0, 0.01, 0'") - hn_overwrite = gr.Checkbox(value=False, label="Overwrite Old Hypernetwork") - with gr.Row(): - hn_create = gr.Button(value="Create hypernetwork", variant='secondary') - - with gr.Box(): - gr.HTML('

Training parameters

') - hn_learn_rate = gr.Textbox(label='Hypernetwork Learning rate', placeholder="Hypernetwork Learning rate", value="0.00001") - with gr.Row(): - hn_clip_grad_mode = gr.Dropdown(value="disabled", label="Gradient Clipping", choices=["disabled", "value", "norm"]) - hn_clip_grad_value = gr.Number(label="Gradient clip value", value=0.1) - hn_batch_size = gr.Number(label='Batch size', value=1, precision=0) - hn_gradient_step = gr.Number(label='Gradient accumulation steps', value=1, precision=0) - hn_steps = gr.Number(label='Max steps', value=1000, precision=0) - - with gr.Box(): - gr.HTML('

Training images

') - hn_dataset_directory = gr.Textbox(label='Dataset directory', placeholder="Path to directory with input images") - with gr.Row(): - hn_varsize = gr.Checkbox(label="Do not resize images", value=False) - hn_width = gr.Slider(minimum=64, maximum=2048, step=8, label="Width", value=512) - hn_height = gr.Slider(minimum=64, maximum=2048, step=8, label="Height", value=512) - hn_use_weight = gr.Checkbox(label="Use PNG alpha channel as loss weight", value=False) - - with gr.Box(): - gr.HTML('

Dataset processing

') - with gr.Row(): - hn_template = gr.Dropdown(label='Prompt template', value="style_filewords.txt", choices=get_textual_inversion_template_names()) - create_refresh_button(hn_template, textual_inversion.list_textual_inversion_templates, lambda: {"choices": get_textual_inversion_template_names()}, "refrsh_train_template_file") - hn_shuffle_tags = gr.Checkbox(label="Shuffle tags by ',' when creating prompts.", value=False) - hn_tag_drop_out = gr.Slider(minimum=0, maximum=1, step=0.1, label="Drop out tags when creating prompts", value=0) - hn_latent_sampling_method = gr.Radio(label='Choose latent sampling method', value="once", choices=['once', 'deterministic', 'random']) - - with gr.Box(): - gr.HTML('

Training outputs

') - with gr.Row(): - hn_create_every = gr.Number(label='Create interim images', value=500, precision=0) - hn_save_every = gr.Number(label='Create interim hypernetworks', value=500, precision=0) - hn_preview_from_txt2img = gr.Checkbox(label='Use current settings for previews', value=False) - hn_log_directory = gr.Textbox(label='Log directory', placeholder="Path to directory where to write outputs", value=f"{os.path.join('cmd_opts.data_dir', 'train/log/embeddings')}") - - hn_stop.click(fn=lambda: shared.state.interrupt(), inputs=[], outputs=[]) - - hn_create.click( - fn=modules.hypernetworks.ui.create_hypernetwork, - inputs=[ - hn_new_name, - hn_new_sizes, - hn_overwrite, - hn_new_layer_structure, - hn_new_activation_func, - hn_new_initialization_option, - hn_new_add_layer_norm, - hn_new_use_dropout, - hn_new_dropout_structure - ], - outputs=[ - hn_name, - train_output, - train_outcome, - ] - ) - - hn_train.click( - fn=wrap_gradio_gpu_call(modules.hypernetworks.ui.train_hypernetwork, extra_outputs=[gr.update()]), - _js="startTrainMonitor", - inputs=[ - dummy_component, - hn_name, - hn_learn_rate, - hn_batch_size, - hn_gradient_step, - hn_dataset_directory, - hn_log_directory, - hn_width, - hn_height, - hn_varsize, - hn_steps, - hn_clip_grad_mode, - hn_clip_grad_value, - hn_shuffle_tags, - hn_tag_drop_out, - hn_latent_sampling_method, - hn_use_weight, - hn_create_every, - hn_save_every, - hn_template, - hn_preview_from_txt2img, - *txt2img_preview_params, - ], - outputs=[ - train_output, - train_outcome, - ] - ) - - params = script_callbacks.UiTrainTabParams(txt2img_preview_params) - script_callbacks.ui_train_tabs_callback(params) diff --git a/scripts/image2video.py b/scripts/image2video.py index 4fcea0cde..71da691e5 100644 --- a/scripts/image2video.py +++ b/scripts/image2video.py @@ -16,8 +16,8 @@ class Script(scripts.Script): return 'Image-to-Video' def show(self, is_img2img): - # return is_img2img if shared.backend == shared.Backend.DIFFUSERS else False - return False + return is_img2img if shared.backend == shared.Backend.DIFFUSERS else False + # return False # return signature is array of gradio components def ui(self, _is_img2img): @@ -85,7 +85,7 @@ class Script(scripts.Script): motion_adapter = diffusers.MotionAdapter.from_pretrained(repo_id) motion_adapter.to(devices.device, devices.dtype) shared.sd_model = sd_models.switch_pipe(diffusers.PIAPipeline, shared.sd_model, { 'motion_adapter': motion_adapter }) - sd_models.move_model(shared.sd_model, devices.device) # move pipeline to device + sd_models.move_model(shared.sd_model, devices.device, force=True) # move pipeline to device if num_frames > 0: p.task_args['num_frames'] = num_frames p.task_args['image'] = p.init_images[0]