diff --git a/.gitignore b/.gitignore index 09734267ff5..8f60514e571 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ __pycache__ /SwinIR/* /repositories /venv +/venv-torch-nightly /tmp /model.ckpt /models/**/* @@ -13,7 +14,6 @@ __pycache__ /gfpgan/weights/*.pth /ui-config.json /outputs -/config.json /log /webui.settings.bat /embeddings diff --git a/config.json b/config.json new file mode 100644 index 00000000000..5d46988bfa5 --- /dev/null +++ b/config.json @@ -0,0 +1,158 @@ +{ + "samples_save": true, + "samples_format": "png", + "samples_filename_pattern": "", + "save_images_add_number": true, + "grid_save": true, + "grid_format": "png", + "grid_extended_filename": false, + "grid_only_if_multiple": true, + "grid_prevent_empty_spots": false, + "n_rows": -1, + "enable_pnginfo": false, + "save_txt": false, + "save_images_before_face_restoration": false, + "save_images_before_highres_fix": false, + "save_images_before_color_correction": false, + "save_mask": false, + "save_mask_composite": false, + "jpeg_quality": 80, + "webp_lossless": false, + "export_for_4chan": true, + "img_downscale_threshold": 4.0, + "target_side_length": 4000, + "img_max_size_mp": 200, + "use_original_name_batch": true, + "use_upscaler_name_as_suffix": false, + "save_selected_only": true, + "do_not_add_watermark": false, + "temp_dir": "", + "clean_temp_dir_at_start": false, + "outdir_samples": "", + "outdir_txt2img_samples": "outputs/txt2img-images", + "outdir_img2img_samples": "outputs/img2img-images", + "outdir_extras_samples": "outputs/extras-images", + "outdir_grids": "", + "outdir_txt2img_grids": "outputs/txt2img-grids", + "outdir_img2img_grids": "outputs/img2img-grids", + "outdir_save": "log/images", + "save_to_dirs": true, + "grid_save_to_dirs": true, + "use_save_to_dirs_for_ui": false, + "directories_filename_pattern": "[date]", + "directories_max_prompt_words": 8, + "ESRGAN_tile": 192, + "ESRGAN_tile_overlap": 8, + "realesrgan_enabled_models": [ + "R-ESRGAN 4x+", + "R-ESRGAN 4x+ Anime6B" + ], + "upscaler_for_img2img": null, + "face_restoration_model": "CodeFormer", + "code_former_weight": 0.5, + "face_restoration_unload": false, + "show_warnings": false, + "memmon_poll_rate": 8, + "samples_log_stdout": false, + "multiple_tqdm": true, + "print_hypernet_extra": false, + "unload_models_when_training": false, + "pin_memory": false, + "save_optimizer_state": false, + "save_training_settings_to_txt": true, + "dataset_filename_word_regex": "", + "dataset_filename_join_string": " ", + "training_image_repeats_per_epoch": 1, + "training_write_csv_every": 500, + "training_xattention_optimizations": false, + "training_enable_tensorboard": false, + "training_tensorboard_save_images": false, + "training_tensorboard_flush_every": 120, + "sd_model_checkpoint": "deliberate_v2.safetensors [9aba26abdf]", + "sd_checkpoint_cache": 0, + "sd_vae_checkpoint_cache": 0, + "sd_vae": "Automatic", + "sd_vae_as_default": true, + "inpainting_mask_weight": 1.0, + "initial_noise_multiplier": 1.0, + "img2img_color_correction": false, + "img2img_fix_steps": false, + "img2img_background_color": "#ffffff", + "enable_quantization": false, + "enable_emphasis": true, + "enable_batch_seeds": true, + "comma_padding_backtrack": 20, + "CLIP_stop_at_last_layers": 1, + "upcast_attn": false, + "use_old_emphasis_implementation": false, + "use_old_karras_scheduler_sigmas": false, + "no_dpmpp_sde_batch_determinism": false, + "use_old_hires_fix_width_height": false, + "interrogate_keep_models_in_memory": false, + "interrogate_return_ranks": false, + "interrogate_clip_num_beams": 1, + "interrogate_clip_min_length": 24, + "interrogate_clip_max_length": 48, + "interrogate_clip_dict_limit": 1500, + "interrogate_clip_skip_categories": [], + "interrogate_deepbooru_score_threshold": 0.5, + "deepbooru_sort_alpha": true, + "deepbooru_use_spaces": false, + "deepbooru_escape": true, + "deepbooru_filter_tags": "", + "extra_networks_default_view": "cards", + "extra_networks_default_multiplier": 1.0, + "extra_networks_card_width": 0, + "extra_networks_card_height": 0, + "extra_networks_add_text_separator": " ", + "sd_hypernetwork": "None", + "return_grid": true, + "return_mask": false, + "return_mask_composite": false, + "do_not_show_images": false, + "add_model_hash_to_info": true, + "add_model_name_to_info": true, + "disable_weights_auto_swap": true, + "send_seed": true, + "send_size": true, + "font": "", + "js_modal_lightbox": true, + "js_modal_lightbox_initially_zoomed": true, + "show_progress_in_title": true, + "samplers_in_dropdown": true, + "dimensions_and_batch_together": true, + "keyedit_precision_attention": 0.1, + "keyedit_precision_extra": 0.05, + "quicksettings": "sd_model_checkpoint", + "hidden_tabs": [], + "ui_reorder": "inpaint, sampler, checkboxes, hires_fix, dimensions, cfg, seed, batch, override_settings, scripts", + "ui_extra_networks_tab_reorder": "", + "localization": "None", + "show_progressbar": true, + "live_previews_enable": true, + "show_progress_grid": true, + "show_progress_every_n_steps": 4, + "show_progress_type": "Approx NN", + "live_preview_content": "Prompt", + "live_preview_refresh_period": 1000, + "hide_samplers": [], + "eta_ddim": 0.0, + "eta_ancestral": 1.0, + "ddim_discretize": "uniform", + "s_churn": 0.0, + "s_tmin": 0.0, + "s_noise": 1.0, + "eta_noise_seed_delta": 0, + "always_discard_next_to_last_sigma": false, + "uni_pc_variant": "bh1", + "uni_pc_skip_type": "time_uniform", + "uni_pc_order": 3, + "uni_pc_lower_order_final": true, + "postprocessing_enable_in_main_ui": [], + "postprocessing_operation_order": [], + "upscaling_max_images_in_cache": 5, + "disabled_extensions": [], + "disable_all_extensions": "none", + "sd_checkpoint_hash": "9aba26abdfcd46073e0a1d42027a3a3bcc969f562d58a03637bf0a0ded6586c9", + "restore_config_state_file": "" +} \ No newline at end of file diff --git a/modules/api/api.py b/modules/api/api.py index e6edffe7144..c51f618c024 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -34,6 +34,11 @@ import piexif.helper from contextlib import closing +from modules.api import jobs + +current_task = None +pending_tasks = {} +finished_tasks = [] def script_name_to_index(name, scripts): try: @@ -215,6 +220,10 @@ def __init__(self, app: FastAPI, queue_lock: Lock): self.add_api_route("/sdapi/v1/extra-batch-images", self.extras_batch_images_api, methods=["POST"], response_model=models.ExtrasBatchImagesResponse) self.add_api_route("/sdapi/v1/png-info", self.pnginfoapi, methods=["POST"], response_model=models.PNGInfoResponse) self.add_api_route("/sdapi/v1/progress", self.progressapi, methods=["GET"], response_model=models.ProgressResponse) + + self.add_api_route("/sdapi/v1/new-progress", self.newprogressapi, methods=["POST"], response_model=models.NewProgressResponse) + self.add_api_route("/sdapi/v1/cancel", self.cancel, methods=["POST"]) + self.add_api_route("/sdapi/v1/interrogate", self.interrogateapi, methods=["POST"]) self.add_api_route("/sdapi/v1/interrupt", self.interruptapi, methods=["POST"]) self.add_api_route("/sdapi/v1/skip", self.skip, methods=["POST"]) @@ -337,6 +346,7 @@ def init_script_args(self, request, default_script_args, selectable_scripts, sel return script_args def text2imgapi(self, txt2imgreq: models.StableDiffusionTxt2ImgProcessingAPI): + global current_task script_runner = scripts.scripts_txt2img if not script_runner.scripts: script_runner.initialize_scripts(False) @@ -362,7 +372,9 @@ def text2imgapi(self, txt2imgreq: models.StableDiffusionTxt2ImgProcessingAPI): send_images = args.pop('send_images', True) args.pop('save_images', None) + args.pop('generation_id', None) + jobs.add_task_to_queue(txt2imgreq.generation_id) with self.queue_lock: with closing(StableDiffusionProcessingTxt2Img(sd_model=shared.sd_model, **args)) as p: p.is_api = True @@ -372,6 +384,7 @@ def text2imgapi(self, txt2imgreq: models.StableDiffusionTxt2ImgProcessingAPI): try: shared.state.begin(job="scripts_txt2img") + jobs.start_task(txt2imgreq.generation_id) if selectable_scripts is not None: p.script_args = script_args processed = scripts.scripts_txt2img.run(p, *p.script_args) # Need to pass args as list here @@ -379,6 +392,9 @@ def text2imgapi(self, txt2imgreq: models.StableDiffusionTxt2ImgProcessingAPI): p.script_args = tuple(script_args) # Need to pass args as tuple here processed = process_images(p) finally: + jobs.record_results(txt2imgreq.generation_id, processed) + jobs.finish_task(txt2imgreq.generation_id) + shared.state.end() shared.total_tqdm.clear() @@ -516,6 +532,49 @@ def progressapi(self, req: models.ProgressRequest = Depends()): return models.ProgressResponse(progress=progress, eta_relative=eta_relative, state=shared.state.dict(), current_image=current_image, textinfo=shared.state.textinfo) + def newprogressapi(self, req: models.NewProgressRequest): + active = req.id_task == jobs.current_task + queued = req.id_task in jobs.pending_tasks + completed = req.id_task in jobs.finished_tasks + + if not active: + position_in_queue = sum(1 for job_id, timestamp in jobs.pending_tasks.items() if timestamp < jobs.pending_tasks[req.id_task]) + 1 + return models.NewProgressResponse(active=active, queued=queued, completed=completed, eta=position_in_queue, textinfo="In queue..." if queued else "Waiting...") + + progress = 0 + + job_count, job_no = shared.state.job_count, shared.state.job_no + sampling_steps, sampling_step = shared.state.sampling_steps, shared.state.sampling_step + + if job_count > 0: + progress += job_no / job_count + if sampling_steps > 0 and job_count > 0: + progress += 1 / job_count * sampling_step / sampling_steps + + progress = min(progress, 1) + + elapsed_since_start = time.time() - shared.state.time_start + predicted_duration = elapsed_since_start / progress if progress > 0 else None + eta = predicted_duration - elapsed_since_start if predicted_duration is not None else None + + shared.state.set_current_image() + image = shared.state.current_image + + if image is not None: + buffered = io.BytesIO() + image.save(buffered, format="png") + live_preview = base64.b64encode(buffered.getvalue()).decode("ascii") + else: + live_preview = None + + return models.NewProgressResponse(active=active, queued=queued, completed=completed, progress=progress, eta=eta, live_preview=live_preview, textinfo=shared.state.textinfo) + + def cancel(self, req: models.CancelRequest): + if req.id_task == jobs.current_task: + shared.state.interrupt() + + jobs.cancel_task(req.id_task) + def interrogateapi(self, interrogatereq: models.InterrogateRequest): image_b64 = interrogatereq.image if image_b64 is None: diff --git a/modules/api/jobs.py b/modules/api/jobs.py new file mode 100644 index 00000000000..aef356e5ba0 --- /dev/null +++ b/modules/api/jobs.py @@ -0,0 +1,40 @@ +import time + +current_task = None +pending_tasks = {} +finished_tasks = [] +recorded_results = [] +recorded_results_limit = 2 + +def start_task(id_task): + global current_task + + current_task = id_task + pending_tasks.pop(id_task, None) + +def finish_task(id_task): + global current_task + + if current_task == id_task: + current_task = None + + finished_tasks.append(id_task) + if len(finished_tasks) > 16: + finished_tasks.pop(0) + +def cancel_task(id_task): + global current_task + + if current_task == id_task: + current_task = None + + pending_tasks.pop(id_task, None) + +def record_results(id_task, res): + recorded_results.append((id_task, res)) + if len(recorded_results) > recorded_results_limit: + recorded_results.pop(0) + + +def add_task_to_queue(id_job): + pending_tasks[id_job] = time.time() diff --git a/modules/api/models.py b/modules/api/models.py index 6a574771c33..e7787db8d2a 100644 --- a/modules/api/models.py +++ b/modules/api/models.py @@ -109,6 +109,7 @@ def generate_model(self): {"key": "send_images", "type": bool, "default": True}, {"key": "save_images", "type": bool, "default": False}, {"key": "alwayson_scripts", "type": dict, "default": {}}, + {"key": "generation_id", "type": str, "default": None}, ] ).generate_model() @@ -183,6 +184,12 @@ class PNGInfoResponse(BaseModel): class ProgressRequest(BaseModel): skip_current_image: bool = Field(default=False, title="Skip current image", description="Skip current image serialization") +class NewProgressRequest(BaseModel): + id_task: str = Field(default=None, title="Task ID", description="id of the task to get progress for") + +class CancelRequest(BaseModel): + id_task: str = Field(default=None, title="Task ID", description="id of the task to cancel") + class ProgressResponse(BaseModel): progress: float = Field(title="Progress", description="The progress with a range of 0 to 1") eta_relative: float = Field(title="ETA in secs") @@ -190,6 +197,15 @@ class ProgressResponse(BaseModel): current_image: str = Field(default=None, title="Current image", description="The current image in base64 format. opts.show_progress_every_n_steps is required for this to work.") textinfo: str = Field(default=None, title="Info text", description="Info text used by WebUI.") +class NewProgressResponse(BaseModel): + active: bool = Field(title="Whether the task is being worked on right now") + queued: bool = Field(title="Whether the task is in queue") + completed: bool = Field(title="Whether the task has already finished") + progress: float = Field(default=None, title="Progress", description="The progress with a range of 0 to 1") + eta: float = Field(default=None, title="ETA in secs") + live_preview: str = Field(default=None, title="Live preview image", description="Current live preview; a data: uri") + textinfo: str = Field(default=None, title="Info text", description="Info text used by WebUI.") + class InterrogateRequest(BaseModel): image: str = Field(default="", title="Image", description="Image to work on, must be a Base64 string containing the image's data.") model: str = Field(default="clip", title="Model", description="The interrogate model used.") diff --git a/webui-user.bat b/webui-user.bat index e5a257bef06..8e6f7e7bd80 100644 --- a/webui-user.bat +++ b/webui-user.bat @@ -3,6 +3,6 @@ set PYTHON= set GIT= set VENV_DIR= -set COMMANDLINE_ARGS= +set COMMANDLINE_ARGS=--listen --nowebui --port=7860 --cors-allow-origins=* call webui.bat diff --git a/webui-user.sh b/webui-user.sh index 70306c60d5b..097e5a99699 100644 --- a/webui-user.sh +++ b/webui-user.sh @@ -12,6 +12,13 @@ # Commandline arguments for webui.py, for example: export COMMANDLINE_ARGS="--medvram --opt-split-attention" #export COMMANDLINE_ARGS="" +# check if macos, if so set commandline args +if [[ "$OSTYPE" == "darwin"* ]]; then + export COMMANDLINE_ARGS="--skip-torch-cuda-test --upcast-sampling --opt-sub-quad-attention --use-cpu interrogate --listen --nowebui --port=7860 --cors-allow-origins=*" +else + export COMMANDLINE_ARGS="--listen --nowebui --port=7860 --cors-allow-origins=*" +fi + # python3 executable #python_cmd="python3" @@ -20,12 +27,18 @@ # python3 venv without trailing slash (defaults to ${install_dir}/${clone_dir}/venv) #venv_dir="venv" +venv_dir="venv-torch-nightly" # script to launch to start the app #export LAUNCH_SCRIPT="launch.py" # install command for torch #export TORCH_COMMAND="pip install torch==1.12.1+cu113 --extra-index-url https://download.pytorch.org/whl/cu113" +if [[ "$OSTYPE" == "darwin"* ]]; then + export TORCH_COMMAND="pip install --pre torch torchvision -f https://download.pytorch.org/whl/nightly/cpu/torch_nightly.html" +else + export TORCH_COMMAND="pip install --pre torch torchvision -f https://download.pytorch.org/whl/nightly/cu113/torch_nightly.html" +fi # Requirements file to use for stable-diffusion-webui #export REQS_FILE="requirements_versions.txt"