diff --git a/CHANGELOG.md b/CHANGELOG.md index ea76e3fbf..575bf4430 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - extensions: fix couple of compatibility items - firefox compatibility improvements - minor image viewer improvements + - add backend and operation info to metadata - original - fix hires secondary sampler this now fully obsoletes `fallback_sampler` and `force_latent_sampler` @@ -21,6 +22,7 @@ - option to set vae upcast in settings - enable fp16 vae decode when using optimized vae this pretty much doubles performance of decode step (delay after generate is done) + - refiner: fix batch processing - sd-xl: loading vae now applies to both base and refiner and saves a bit of vram - vae: enable loading of pure-safetensors vae files without config also enable *automatic* selection to work with diffusers diff --git a/TODO.md b/TODO.md index 4fbb638f9..54e4e57e6 100644 --- a/TODO.md +++ b/TODO.md @@ -15,6 +15,7 @@ Stuff to be added, in no particular order... - Add SD-XL Lora - Add ControlNet - Fix SD-XL Img2img/Inpaint + - Add SD and SD-XL Pix2Pix - Add VAE direct load from safetensors - Fix Kandinsky 2.2 model - Fix DeepFloyd IF model diff --git a/modules/api/api.py b/modules/api/api.py index d4d443378..0e509bb6c 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -5,7 +5,7 @@ from io import BytesIO from typing import List, Dict, Any from threading import Lock from secrets import compare_digest -from fastapi import APIRouter, Depends, FastAPI +from fastapi import FastAPI, APIRouter, Depends from fastapi.security import HTTPBasic, HTTPBasicCredentials from fastapi.exceptions import HTTPException from PIL import PngImagePlugin,Image @@ -143,7 +143,7 @@ class Api: self.add_api_route("/sdapi/v1/reload-checkpoint", self.reloadapi, methods=["POST"]) self.add_api_route("/sdapi/v1/scripts", self.get_scripts_list, methods=["GET"], response_model=models.ScriptsList) self.add_api_route("/sdapi/v1/script-info", self.get_script_info, methods=["GET"], response_model=List[models.ScriptInfo]) - self.app.add_api_route("/sdapi/v1/log", self.get_log_buffer, methods=["GET"], response_model=List) # bypass auth + self.add_api_route("/sdapi/v1/log", self.get_log_buffer, methods=["GET"], response_model=List) # bypass auth self.default_script_arg_txt2img = [] self.default_script_arg_img2img = [] @@ -158,7 +158,6 @@ class Api: return True raise HTTPException(status_code=401, detail="Unauthorized", headers={"WWW-Authenticate": "Basic"}) - def get_log_buffer(self, req: models.LogRequest = Depends()): lines = shared.log.buffer[:req.lines] if req.lines > 0 else shared.log.buffer.copy() if req.clear: diff --git a/modules/images.py b/modules/images.py index 37452e3da..43e192989 100644 --- a/modules/images.py +++ b/modules/images.py @@ -585,7 +585,7 @@ def save_image(image, path, basename, seed=None, prompt=None, extension='jpg', i params.filename = filename + extension txt_fullfn = f"{filename}.txt" if shared.opts.save_txt and len(exifinfo) > 0 else None - save_queue.put((params.image, filename, extension, params, exifinfo, txt_fullfn)) + save_queue.put((params.image, filename, extension, params, exifinfo, txt_fullfn)) # actual save is executed in a thread that polls data from queue save_queue.join() # atomically_save_image(params.image, filename, extension, params, exifinfo, txt_fullfn) diff --git a/modules/processing.py b/modules/processing.py index 28e42da90..987e3d519 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -381,7 +381,6 @@ def create_random_tensors(shape, seeds, subseeds=None, subseed_strength=0.0, see subnoise = None if subseeds is not None: subseed = 0 if i >= len(subseeds) else subseeds[i] - subnoise = devices.randn(subseed, noise_shape) # randn results depend on device; gpu and cpu get different results for same seed; @@ -403,16 +402,13 @@ def create_random_tensors(shape, seeds, subseeds=None, subseed_strength=0.0, see ty = 0 if dy < 0 else dy dx = max(-dx, 0) dy = max(-dy, 0) - x[:, ty:ty+h, tx:tx+w] = noise[:, dy:dy+h, dx:dx+w] noise = x if sampler_noises is not None: cnt = p.sampler.number_of_needed_noises(p) - if eta_noise_seed_delta > 0: torch.manual_seed(seed + eta_noise_seed_delta) - for j in range(cnt): sampler_noises[j].append(devices.randn_without_seed(tuple(noise_shape))) @@ -450,8 +446,6 @@ def fix_seed(p): def create_infotext(p: StableDiffusionProcessing, all_prompts, all_seeds, all_subseeds, comments=None, iteration=0, position_in_batch=0): # pylint: disable=unused-argument index = position_in_batch + iteration * p.batch_size generation_params = { - "Version": git_commit, - "Pipeline": 'Diffusers' if shared.backend == shared.Backend.DIFFUSERS else 'Original', "Steps": p.steps, "Seed": all_seeds[index], "Sampler": p.sampler_name, @@ -481,7 +475,10 @@ def create_infotext(p: StableDiffusionProcessing, all_prompts, all_seeds, all_su "Denoise end": p.refiner_denoise_end if p.enable_hr else None, # restore_faces "Face restoration": shared.opts.face_restoration_model if p.restore_faces else None, - "Operations": ', '.join(p.ops), + # sdnext + "Version": git_commit, + "Pipeline": 'Diffusers' if shared.backend == shared.Backend.DIFFUSERS else 'Original', + "Operations": ', '.join(list(set(p.ops))), } token_merging_ratio = p.get_token_merging_ratio() token_merging_ratio_hr = p.get_token_merging_ratio(for_hr=True) if p.enable_hr else None @@ -726,6 +723,7 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: info=infotext(n, i) p.restore_faces = orig images.save_image(Image.fromarray(x_sample), path=p.outpath_samples, basename="", seed=seeds[i], prompt=prompts[i], extension=shared.opts.samples_format, info=info, p=p, suffix="-before-face-restoration") + p.ops.append('face') x_sample = modules.face_restoration.restore_faces(x_sample) image = Image.fromarray(x_sample) if p.scripts is not None: @@ -740,6 +738,7 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: p.color_corrections = orig image_without_cc = apply_overlay(image, p.paste_to, i, p.overlay_images) images.save_image(image_without_cc, path=p.outpath_samples, basename="", seed=seeds[i], prompt=prompts[i], extension=shared.opts.samples_format, info=info, p=p, suffix="-before-color-correction") + p.ops.append('color') image = apply_color_correction(p.color_corrections[i], image) image = apply_overlay(image, p.paste_to, i, p.overlay_images) if shared.opts.samples_save and not p.do_not_save_samples: @@ -840,7 +839,9 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): self.width = self.width or 512 self.height = self.height or 512 + def init_hr(self): if self.enable_hr: + self.ops.append('hires') if shared.opts.use_old_hires_fix_width_height and self.applied_old_hires_behavior_to != (self.width, self.height): self.hr_resize_x = self.width self.hr_resize_y = self.height @@ -908,6 +909,7 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): if shared.backend == shared.Backend.DIFFUSERS: sd_models.set_diffuser_pipe(self.sd_model, sd_models.DiffusersTaskType.TEXT_2_IMAGE) + self.ops.append('txt2img') self.sampler = sd_samplers.create_sampler(self.sampler_name, self.sd_model) latent_scale_mode = shared.latent_upscale_modes.get(self.hr_upscaler, None) if self.hr_upscaler is not None else shared.latent_upscale_modes.get(shared.latent_upscale_default_mode, "nearest") if self.enable_hr and latent_scale_mode is None: @@ -919,6 +921,7 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): if not self.enable_hr or shared.state.interrupted or shared.state.skipped: return samples self.is_hr_pass = True + self.init_hr() target_width = self.hr_upscale_to_x target_height = self.hr_upscale_to_y @@ -1009,6 +1012,10 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing): self.sampler_name = 'UniPC' self.sampler = sd_samplers.create_sampler(self.sampler_name, self.sd_model) + if self.image_mask is not None: + self.ops.append('inpaint') + else: + self.ops.append('img2img') crop_region = None image_mask = self.image_mask if image_mask is not None: diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index 260d57656..6de959997 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -104,10 +104,13 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro cross_attention_kwargs['scale'] = lora_state['multiplier'] task_specific_kwargs={} if sd_models.get_diffusers_task(shared.sd_model) == sd_models.DiffusersTaskType.TEXT_2_IMAGE: + p.ops.append('txt2img') task_specific_kwargs = {"height": p.height, "width": p.width} elif sd_models.get_diffusers_task(shared.sd_model) == sd_models.DiffusersTaskType.IMAGE_2_IMAGE: + p.ops.append('img2img') task_specific_kwargs = {"image": p.init_images, "strength": p.denoising_strength} elif sd_models.get_diffusers_task(shared.sd_model) == sd_models.DiffusersTaskType.INPAINTING: + p.ops.append('inpaint') task_specific_kwargs = {"image": p.init_images, "mask_image": p.mask, "strength": p.denoising_strength} # TODO diffusers use transformers for prompt parsing @@ -167,7 +170,7 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro if shared.opts.diffusers_move_refiner: shared.sd_refiner.to(devices.device) - + p.ops.append('refine') for i in range(len(output.images)): pipe_args = set_pipeline_args( model=shared.sd_refiner,