diff --git a/automatic.sh b/automatic.sh index 42f2f2793..b92f8699d 100755 --- a/automatic.sh +++ b/automatic.sh @@ -10,6 +10,7 @@ export CUDA_CACHE_DISABLE=0 export CUDA_AUTO_BOOST=1 export CUDA_MODULE_LOADING="LAZY" export CUDA_DEVICE_DEFAULT_PERSISTING_L2_CACHE_PERCENTAGE_LIMIT=0 +export GRADIO_ANALYTICS_ENABLED="False" if [ "$PYTHON" == "" ]; then PYTHON=$(which python) diff --git a/config.json b/config.json index 6ac2cbf57..fa787597e 100644 --- a/config.json +++ b/config.json @@ -173,7 +173,7 @@ "save_training_settings_to_txt": true, "save_txt": false, "sd_checkpoint_cache": 0, - "sd_checkpoint_hash": "7b58853a2f8435c51dedd82f0a1e5571851919a7b230d473afd45a41dec3debe", + "sd_checkpoint_hash": "cc6cb27103417325ff94f52b7a5d2dde45a7515b25c255d8e396c90014281516", "sd_hypernetwork_strength": 1.0, "sd_hypernetwork": "None", "sd_lora": "", diff --git a/extensions-builtin/sd-extension-system-info b/extensions-builtin/sd-extension-system-info index f96e2b010..c4d845e64 160000 --- a/extensions-builtin/sd-extension-system-info +++ b/extensions-builtin/sd-extension-system-info @@ -1 +1 @@ -Subproject commit f96e2b010e6513f0afff58d54eb17878bb0e3b63 +Subproject commit c4d845e64c88e3f8ea98c7d3be0fcf0e14a33369 diff --git a/home/vlado/branches/automatic/README.md b/home/vlado/branches/automatic/README.md deleted file mode 100644 index 2ceb4d2db..000000000 --- a/home/vlado/branches/automatic/README.md +++ /dev/null @@ -1,161 +0,0 @@ -# Stable Diffusion web UI -A browser interface based on Gradio library for Stable Diffusion. - -![](screenshot.png) - -## Features -[Detailed feature showcase with images](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Features): -- Original txt2img and img2img modes -- One click install and run script (but you still must install python and git) -- Outpainting -- Inpainting -- Color Sketch -- Prompt Matrix -- Stable Diffusion Upscale -- Attention, specify parts of text that the model should pay more attention to - - a man in a ((tuxedo)) - will pay more attention to tuxedo - - a man in a (tuxedo:1.21) - alternative syntax - - select text and press ctrl+up or ctrl+down to automatically adjust attention to selected text (code contributed by anonymous user) -- Loopback, run img2img processing multiple times -- X/Y/Z plot, a way to draw a 3 dimensional plot of images with different parameters -- Textual Inversion - - have as many embeddings as you want and use any names you like for them - - use multiple embeddings with different numbers of vectors per token - - works with half precision floating point numbers - - train embeddings on 8GB (also reports of 6GB working) -- Extras tab with: - - GFPGAN, neural network that fixes faces - - CodeFormer, face restoration tool as an alternative to GFPGAN - - RealESRGAN, neural network upscaler - - ESRGAN, neural network upscaler with a lot of third party models - - SwinIR and Swin2SR([see here](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/2092)), neural network upscalers - - LDSR, Latent diffusion super resolution upscaling -- Resizing aspect ratio options -- Sampling method selection - - Adjust sampler eta values (noise multiplier) - - More advanced noise setting options -- Interrupt processing at any time -- 4GB video card support (also reports of 2GB working) -- Correct seeds for batches -- Live prompt token length validation -- Generation parameters - - parameters you used to generate images are saved with that image - - in PNG chunks for PNG, in EXIF for JPEG - - can drag the image to PNG info tab to restore generation parameters and automatically copy them into UI - - can be disabled in settings - - drag and drop an image/text-parameters to promptbox -- Read Generation Parameters Button, loads parameters in promptbox to UI -- Settings page -- Running arbitrary python code from UI (must run with --allow-code to enable) -- Mouseover hints for most UI elements -- Possible to change defaults/mix/max/step values for UI elements via text config -- Tiling support, a checkbox to create images that can be tiled like textures -- Progress bar and live image generation preview - - Can use a separate neural network to produce previews with almost none VRAM or compute requirement -- Negative prompt, an extra text field that allows you to list what you don't want to see in generated image -- Styles, a way to save part of prompt and easily apply them via dropdown later -- Variations, a way to generate same image but with tiny differences -- Seed resizing, a way to generate same image but at slightly different resolution -- CLIP interrogator, a button that tries to guess prompt from an image -- Prompt Editing, a way to change prompt mid-generation, say to start making a watermelon and switch to anime girl midway -- Batch Processing, process a group of files using img2img -- Img2img Alternative, reverse Euler method of cross attention control -- Highres Fix, a convenience option to produce high resolution pictures in one click without usual distortions -- Reloading checkpoints on the fly -- Checkpoint Merger, a tab that allows you to merge up to 3 checkpoints into one -- [Custom scripts](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Custom-Scripts) with many extensions from community -- [Composable-Diffusion](https://energy-based-model.github.io/Compositional-Visual-Generation-with-Composable-Diffusion-Models/), a way to use multiple prompts at once - - separate prompts using uppercase `AND` - - also supports weights for prompts: `a cat :1.2 AND a dog AND a penguin :2.2` -- No token limit for prompts (original stable diffusion lets you use up to 75 tokens) -- DeepDanbooru integration, creates danbooru style tags for anime prompts -- [xformers](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Xformers), major speed increase for select cards: (add --xformers to commandline args) -- via extension: [History tab](https://github.com/yfszzx/stable-diffusion-webui-images-browser): view, direct and delete images conveniently within the UI -- Generate forever option -- Training tab - - hypernetworks and embeddings options - - Preprocessing images: cropping, mirroring, autotagging using BLIP or deepdanbooru (for anime) -- Clip skip -- Hypernetworks -- Loras (same as Hypernetworks but more pretty) -- A sparate UI where you can choose, with preview, which embeddings, hypernetworks or Loras to add to your prompt. -- Can select to load a different VAE from settings screen -- Estimated completion time in progress bar -- API -- Support for dedicated [inpainting model](https://github.com/runwayml/stable-diffusion#inpainting-with-stable-diffusion) by RunwayML. -- via extension: [Aesthetic Gradients](https://github.com/AUTOMATIC1111/stable-diffusion-webui-aesthetic-gradients), a way to generate images with a specific aesthetic by using clip images embeds (implementation of [https://github.com/vicgalle/stable-diffusion-aesthetic-gradients](https://github.com/vicgalle/stable-diffusion-aesthetic-gradients)) -- [Stable Diffusion 2.0](https://github.com/Stability-AI/stablediffusion) support - see [wiki](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Features#stable-diffusion-20) for instructions -- [Alt-Diffusion](https://arxiv.org/abs/2211.06679) support - see [wiki](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Features#alt-diffusion) for instructions -- Now without any bad letters! -- Load checkpoints in safetensors format -- Eased resolution restriction: generated image's domension must be a multiple of 8 rather than 64 -- Now with a license! -- Reorder elements in the UI from settings screen -- - -## Installation and Running -Make sure the required [dependencies](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Dependencies) are met and follow the instructions available for both [NVidia](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Install-and-Run-on-NVidia-GPUs) (recommended) and [AMD](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Install-and-Run-on-AMD-GPUs) GPUs. - -Alternatively, use online services (like Google Colab): - -- [List of Online Services](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Online-Services) - -### Automatic Installation on Windows -1. Install [Python 3.10.6](https://www.python.org/downloads/windows/), checking "Add Python to PATH" -2. Install [git](https://git-scm.com/download/win). -3. Download the stable-diffusion-webui repository, for example by running `git clone https://github.com/AUTOMATIC1111/stable-diffusion-webui.git`. -4. Run `webui-user.bat` from Windows Explorer as normal, non-administrator, user. - -### Automatic Installation on Linux -1. Install the dependencies: -```bash -# Debian-based: -sudo apt install wget git python3 python3-venv -# Red Hat-based: -sudo dnf install wget git python3 -# Arch-based: -sudo pacman -S wget git python3 -``` -2. To install in `/home/$(whoami)/stable-diffusion-webui/`, run: -```bash -bash <(wget -qO- https://raw.githubusercontent.com/AUTOMATIC1111/stable-diffusion-webui/master/webui.sh) -``` -3. Run `webui.sh`. -### Installation on Apple Silicon - -Find the instructions [here](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Installation-on-Apple-Silicon). - -## Contributing -Here's how to add code to this repo: [Contributing](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Contributing) - -## Documentation -The documentation was moved from this README over to the project's [wiki](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki). - -## Credits -Licenses for borrowed code can be found in `Settings -> Licenses` screen, and also in `html/licenses.html` file. - -- Stable Diffusion - https://github.com/CompVis/stable-diffusion, https://github.com/CompVis/taming-transformers -- k-diffusion - https://github.com/crowsonkb/k-diffusion.git -- GFPGAN - https://github.com/TencentARC/GFPGAN.git -- CodeFormer - https://github.com/sczhou/CodeFormer -- ESRGAN - https://github.com/xinntao/ESRGAN -- SwinIR - https://github.com/JingyunLiang/SwinIR -- Swin2SR - https://github.com/mv-lab/swin2sr -- LDSR - https://github.com/Hafiidz/latent-diffusion -- MiDaS - https://github.com/isl-org/MiDaS -- Ideas for optimizations - https://github.com/basujindal/stable-diffusion -- Cross Attention layer optimization - Doggettx - https://github.com/Doggettx/stable-diffusion, original idea for prompt editing. -- Cross Attention layer optimization - InvokeAI, lstein - https://github.com/invoke-ai/InvokeAI (originally http://github.com/lstein/stable-diffusion) -- Sub-quadratic Cross Attention layer optimization - Alex Birch (https://github.com/Birch-san/diffusers/pull/1), Amin Rezaei (https://github.com/AminRezaei0x443/memory-efficient-attention) -- Textual Inversion - Rinon Gal - https://github.com/rinongal/textual_inversion (we're not using his code, but we are using his ideas). -- Idea for SD upscale - https://github.com/jquesnelle/txt2imghd -- Noise generation for outpainting mk2 - https://github.com/parlance-zz/g-diffuser-bot -- CLIP interrogator idea and borrowing some code - https://github.com/pharmapsychotic/clip-interrogator -- Idea for Composable Diffusion - https://github.com/energy-based-model/Compositional-Visual-Generation-with-Composable-Diffusion-Models-PyTorch -- xformers - https://github.com/facebookresearch/xformers -- DeepDanbooru - interrogator for anime diffusers https://github.com/KichangKim/DeepDanbooru -- Sampling in float32 precision from a float16 UNet - marunine for the idea, Birch-san for the example Diffusers implementation (https://github.com/Birch-san/diffusers-play/tree/92feee6) -- Instruct pix2pix - Tim Brooks (star), Aleksander Holynski (star), Alexei A. Efros (no star) - https://github.com/timothybrooks/instruct-pix2pix -- Security advice - RyotaK -- Initial Gradio script - posted on 4chan by an Anonymous user. Thank you Anonymous user. -- (You) diff --git a/home/vlado/branches/automatic/extensions-builtin/Lora/ui_extra_networks_lora.py b/home/vlado/branches/automatic/extensions-builtin/Lora/ui_extra_networks_lora.py deleted file mode 100644 index 22cabcb0f..000000000 --- a/home/vlado/branches/automatic/extensions-builtin/Lora/ui_extra_networks_lora.py +++ /dev/null @@ -1,37 +0,0 @@ -import json -import os -import lora - -from modules import shared, ui_extra_networks - - -class ExtraNetworksPageLora(ui_extra_networks.ExtraNetworksPage): - def __init__(self): - super().__init__('Lora') - - def refresh(self): - lora.list_available_loras() - - def list_items(self): - for name, lora_on_disk in lora.available_loras.items(): - path, ext = os.path.splitext(lora_on_disk.filename) - previews = [path + ".png", path + ".preview.png"] - - preview = None - for file in previews: - if os.path.isfile(file): - preview = self.link_preview(file) - break - - yield { - "name": name, - "filename": path, - "preview": preview, - "search_term": self.search_terms_from_path(lora_on_disk.filename), - "prompt": json.dumps(f""), - "local_preview": path + ".png", - } - - def allowed_directories_for_previews(self): - return [shared.cmd_opts.lora_dir] - diff --git a/home/vlado/branches/automatic/javascript/progressbar.js b/home/vlado/branches/automatic/javascript/progressbar.js deleted file mode 100644 index ff6d757ba..000000000 --- a/home/vlado/branches/automatic/javascript/progressbar.js +++ /dev/null @@ -1,243 +0,0 @@ -// code related to showing and updating progressbar shown as the image is being made - - -galleries = {} -storedGallerySelections = {} -galleryObservers = {} - -function rememberGallerySelection(id_gallery){ - storedGallerySelections[id_gallery] = getGallerySelectedIndex(id_gallery) -} - -function getGallerySelectedIndex(id_gallery){ - let galleryButtons = gradioApp().querySelectorAll('#'+id_gallery+' .gallery-item') - let galleryBtnSelected = gradioApp().querySelector('#'+id_gallery+' .gallery-item.\\!ring-2') - - let currentlySelectedIndex = -1 - galleryButtons.forEach(function(v, i){ if(v==galleryBtnSelected) { currentlySelectedIndex = i } }) - - return currentlySelectedIndex -} - -// this is a workaround for https://github.com/gradio-app/gradio/issues/2984 -function check_gallery(id_gallery){ - let gallery = gradioApp().getElementById(id_gallery) - // if gallery has no change, no need to setting up observer again. - if (gallery && galleries[id_gallery] !== gallery){ - galleries[id_gallery] = gallery; - if(galleryObservers[id_gallery]){ - galleryObservers[id_gallery].disconnect(); - } - - storedGallerySelections[id_gallery] = -1 - - galleryObservers[id_gallery] = new MutationObserver(function (){ - let galleryButtons = gradioApp().querySelectorAll('#'+id_gallery+' .gallery-item') - let galleryBtnSelected = gradioApp().querySelector('#'+id_gallery+' .gallery-item.\\!ring-2') - let currentlySelectedIndex = getGallerySelectedIndex(id_gallery) - prevSelectedIndex = storedGallerySelections[id_gallery] - storedGallerySelections[id_gallery] = -1 - - if (prevSelectedIndex !== -1 && galleryButtons.length>prevSelectedIndex && !galleryBtnSelected) { - // automatically re-open previously selected index (if exists) - activeElement = gradioApp().activeElement; - let scrollX = window.scrollX; - let scrollY = window.scrollY; - - galleryButtons[prevSelectedIndex].click(); - showGalleryImage(); - - // When the gallery button is clicked, it gains focus and scrolls itself into view - // We need to scroll back to the previous position - setTimeout(function (){ - window.scrollTo(scrollX, scrollY); - }, 50); - - if(activeElement){ - // i fought this for about an hour; i don't know why the focus is lost or why this helps recover it - // if someone has a better solution please by all means - setTimeout(function (){ - activeElement.focus({ - preventScroll: true // Refocus the element that was focused before the gallery was opened without scrolling to it - }) - }, 1); - } - } - }) - galleryObservers[id_gallery].observe( gallery, { childList:true, subtree:false }) - } -} - -onUiUpdate(function(){ - check_gallery('txt2img_gallery') - check_gallery('img2img_gallery') -}) - -function request(url, data, handler, errorHandler){ - var xhr = new XMLHttpRequest(); - var url = url; - xhr.open("POST", url, true); - xhr.setRequestHeader("Content-Type", "application/json"); - xhr.onreadystatechange = function () { - if (xhr.readyState === 4) { - if (xhr.status === 200) { - try { - var js = JSON.parse(xhr.responseText); - handler(js) - } catch (error) { - console.error(error); - errorHandler() - } - } else{ - errorHandler() - } - } - }; - var js = JSON.stringify(data); - xhr.send(js); -} - -function pad2(x){ - return x<10 ? '0'+x : x -} - -function formatTime(secs){ - if(secs > 3600){ - return pad2(Math.floor(secs/60/60)) + ":" + pad2(Math.floor(secs/60)%60) + ":" + pad2(Math.floor(secs)%60) - } else if(secs > 60){ - return pad2(Math.floor(secs/60)) + ":" + pad2(Math.floor(secs)%60) - } else{ - return Math.floor(secs) + "s" - } -} - -function setTitle(progress){ - var title = 'Stable Diffusion' - - if(opts.show_progress_in_title && progress){ - title = '[' + progress.trim() + '] ' + title; - } - - if(document.title != title){ - document.title = title; - } -} - - -function randomId(){ - return "task(" + Math.random().toString(36).slice(2, 7) + Math.random().toString(36).slice(2, 7) + Math.random().toString(36).slice(2, 7)+")" -} - -// starts sending progress requests to "/internal/progress" uri, creating progressbar above progressbarContainer element and -// preview inside gallery element. Cleans up all created stuff when the task is over and calls atEnd. -// calls onProgress every time there is a progress update -function requestProgress(id_task, progressbarContainer, gallery, atEnd, onProgress){ - var dateStart = new Date() - var wasEverActive = false - var parentProgressbar = progressbarContainer.parentNode - var parentGallery = gallery ? gallery.parentNode : null - - var divProgress = document.createElement('div') - divProgress.className='progressDiv' - divProgress.style.display = opts.show_progressbar ? "" : "none" - var divInner = document.createElement('div') - divInner.className='progress' - - divProgress.appendChild(divInner) - parentProgressbar.insertBefore(divProgress, progressbarContainer) - - if(parentGallery){ - var livePreview = document.createElement('div') - livePreview.className='livePreview' - parentGallery.insertBefore(livePreview, gallery) - } - - var removeProgressBar = function(){ - setTitle("") - parentProgressbar.removeChild(divProgress) - if(parentGallery) parentGallery.removeChild(livePreview) - atEnd() - } - - var fun = function(id_task, id_live_preview){ - request("./internal/progress", {"id_task": id_task, "id_live_preview": id_live_preview}, function(res){ - if(res.completed){ - removeProgressBar() - return - } - - var rect = progressbarContainer.getBoundingClientRect() - - if(rect.width){ - divProgress.style.width = rect.width + "px"; - } - - progressText = "" - - divInner.style.width = ((res.progress || 0) * 100.0) + '%' - divInner.style.background = res.progress ? "" : "transparent" - - if(res.progress > 0){ - progressText = ((res.progress || 0) * 100.0).toFixed(0) + '%' - } - - if(res.eta){ - progressText += " ETA: " + formatTime(res.eta) - } - - - setTitle(progressText) - - if(res.textinfo && res.textinfo.indexOf("\n") == -1){ - progressText = res.textinfo + " " + progressText - } - - divInner.textContent = progressText - - var elapsedFromStart = (new Date() - dateStart) / 1000 - - if(res.active) wasEverActive = true; - - if(! res.active && wasEverActive){ - removeProgressBar() - return - } - - if(elapsedFromStart > 5 && !res.queued && !res.active){ - removeProgressBar() - return - } - - - if(res.live_preview && gallery){ - var rect = gallery.getBoundingClientRect() - if(rect.width){ - livePreview.style.width = rect.width + "px" - livePreview.style.height = rect.height + "px" - } - - var img = new Image(); - img.onload = function() { - livePreview.appendChild(img) - if(livePreview.childElementCount > 2){ - livePreview.removeChild(livePreview.firstElementChild) - } - } - img.src = res.live_preview; - } - - - if(onProgress){ - onProgress(res) - } - - setTimeout(() => { - fun(id_task, res.id_live_preview); - }, opts.live_preview_refresh_period || 500) - }, function(){ - removeProgressBar() - }) - } - - fun(id_task, 0) -} diff --git a/home/vlado/branches/automatic/modules/api/api.py b/home/vlado/branches/automatic/modules/api/api.py deleted file mode 100644 index 5a9ac5f1a..000000000 --- a/home/vlado/branches/automatic/modules/api/api.py +++ /dev/null @@ -1,551 +0,0 @@ -import base64 -import io -import time -import datetime -import uvicorn -from threading import Lock -from io import BytesIO -from gradio.processing_utils import decode_base64_to_file -from fastapi import APIRouter, Depends, FastAPI, HTTPException, Request, Response -from fastapi.security import HTTPBasic, HTTPBasicCredentials -from secrets import compare_digest - -import modules.shared as shared -from modules import sd_samplers, deepbooru, sd_hijack, images, scripts, ui, postprocessing -from modules.api.models import * -from modules.processing import StableDiffusionProcessingTxt2Img, StableDiffusionProcessingImg2Img, process_images -from modules.textual_inversion.textual_inversion import create_embedding, train_embedding -from modules.textual_inversion.preprocess import preprocess -from modules.hypernetworks.hypernetwork import create_hypernetwork, train_hypernetwork -from PIL import PngImagePlugin,Image -from modules.sd_models import checkpoints_list -from modules.sd_models_config import find_checkpoint_config_near_filename -from modules.realesrgan_model import get_realesrgan_models -from modules import devices -from typing import List -import piexif -import piexif.helper - -def upscaler_to_index(name: str): - try: - return [x.name.lower() for x in shared.sd_upscalers].index(name.lower()) - except: - raise HTTPException(status_code=400, detail=f"Invalid upscaler, needs to be one of these: {' , '.join([x.name for x in sd_upscalers])}") - -def script_name_to_index(name, scripts): - try: - return [script.title().lower() for script in scripts].index(name.lower()) - except: - raise HTTPException(status_code=422, detail=f"Script '{name}' not found") - -def validate_sampler_name(name): - config = sd_samplers.all_samplers_map.get(name, None) - if config is None: - raise HTTPException(status_code=404, detail="Sampler not found") - - return name - -def setUpscalers(req: dict): - reqDict = vars(req) - reqDict['extras_upscaler_1'] = reqDict.pop('upscaler_1', None) - reqDict['extras_upscaler_2'] = reqDict.pop('upscaler_2', None) - return reqDict - -def decode_base64_to_image(encoding): - if encoding.startswith("data:image/"): - encoding = encoding.split(";")[1].split(",")[1] - try: - image = Image.open(BytesIO(base64.b64decode(encoding))) - return image - except Exception as err: - raise HTTPException(status_code=500, detail="Invalid encoded image") - -def encode_pil_to_base64(image): - with io.BytesIO() as output_bytes: - - if opts.samples_format.lower() == 'png': - use_metadata = False - metadata = PngImagePlugin.PngInfo() - for key, value in image.info.items(): - if isinstance(key, str) and isinstance(value, str): - metadata.add_text(key, value) - use_metadata = True - image.save(output_bytes, format="PNG", pnginfo=(metadata if use_metadata else None), quality=opts.jpeg_quality) - - elif opts.samples_format.lower() in ("jpg", "jpeg", "webp"): - parameters = image.info.get('parameters', None) - exif_bytes = piexif.dump({ - "Exif": { piexif.ExifIFD.UserComment: piexif.helper.UserComment.dump(parameters or "", encoding="unicode") } - }) - if opts.samples_format.lower() in ("jpg", "jpeg"): - image.save(output_bytes, format="JPEG", exif = exif_bytes, quality=opts.jpeg_quality) - else: - image.save(output_bytes, format="WEBP", exif = exif_bytes, quality=opts.jpeg_quality) - - else: - raise HTTPException(status_code=500, detail="Invalid image format") - - bytes_data = output_bytes.getvalue() - - return base64.b64encode(bytes_data) - -def api_middleware(app: FastAPI): - @app.middleware("http") - async def log_and_time(req: Request, call_next): - ts = time.time() - res: Response = await call_next(req) - duration = str(round(time.time() - ts, 4)) - res.headers["X-Process-Time"] = duration - endpoint = req.scope.get('path', 'err') - if shared.cmd_opts.api_log and endpoint.startswith('/sdapi'): - print('API {t} {code} {prot}/{ver} {method} {endpoint} {cli} {duration}'.format( - t = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f"), - code = res.status_code, - ver = req.scope.get('http_version', '0.0'), - cli = req.scope.get('client', ('0:0.0.0', 0))[0], - prot = req.scope.get('scheme', 'err'), - method = req.scope.get('method', 'err'), - endpoint = endpoint, - duration = duration, - )) - return res - - -class Api: - def __init__(self, app: FastAPI, queue_lock: Lock): - if shared.cmd_opts.api_auth: - self.credentials = dict() - for auth in shared.cmd_opts.api_auth.split(","): - user, password = auth.split(":") - self.credentials[user] = password - - self.router = APIRouter() - self.app = app - self.queue_lock = queue_lock - api_middleware(self.app) - self.add_api_route("/sdapi/v1/txt2img", self.text2imgapi, methods=["POST"], response_model=TextToImageResponse) - self.add_api_route("/sdapi/v1/img2img", self.img2imgapi, methods=["POST"], response_model=ImageToImageResponse) - self.add_api_route("/sdapi/v1/extra-single-image", self.extras_single_image_api, methods=["POST"], response_model=ExtrasSingleImageResponse) - self.add_api_route("/sdapi/v1/extra-batch-images", self.extras_batch_images_api, methods=["POST"], response_model=ExtrasBatchImagesResponse) - self.add_api_route("/sdapi/v1/png-info", self.pnginfoapi, methods=["POST"], response_model=PNGInfoResponse) - self.add_api_route("/sdapi/v1/progress", self.progressapi, methods=["GET"], response_model=ProgressResponse) - 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"]) - self.add_api_route("/sdapi/v1/options", self.get_config, methods=["GET"], response_model=OptionsModel) - self.add_api_route("/sdapi/v1/options", self.set_config, methods=["POST"]) - self.add_api_route("/sdapi/v1/cmd-flags", self.get_cmd_flags, methods=["GET"], response_model=FlagsModel) - self.add_api_route("/sdapi/v1/samplers", self.get_samplers, methods=["GET"], response_model=List[SamplerItem]) - self.add_api_route("/sdapi/v1/upscalers", self.get_upscalers, methods=["GET"], response_model=List[UpscalerItem]) - self.add_api_route("/sdapi/v1/sd-models", self.get_sd_models, methods=["GET"], response_model=List[SDModelItem]) - self.add_api_route("/sdapi/v1/hypernetworks", self.get_hypernetworks, methods=["GET"], response_model=List[HypernetworkItem]) - self.add_api_route("/sdapi/v1/face-restorers", self.get_face_restorers, methods=["GET"], response_model=List[FaceRestorerItem]) - self.add_api_route("/sdapi/v1/realesrgan-models", self.get_realesrgan_models, methods=["GET"], response_model=List[RealesrganItem]) - self.add_api_route("/sdapi/v1/prompt-styles", self.get_prompt_styles, methods=["GET"], response_model=List[PromptStyleItem]) - self.add_api_route("/sdapi/v1/embeddings", self.get_embeddings, methods=["GET"], response_model=EmbeddingsResponse) - self.add_api_route("/sdapi/v1/refresh-checkpoints", self.refresh_checkpoints, methods=["POST"]) - self.add_api_route("/sdapi/v1/create/embedding", self.create_embedding, methods=["POST"], response_model=CreateResponse) - self.add_api_route("/sdapi/v1/create/hypernetwork", self.create_hypernetwork, methods=["POST"], response_model=CreateResponse) - self.add_api_route("/sdapi/v1/preprocess", self.preprocess, methods=["POST"], response_model=PreprocessResponse) - self.add_api_route("/sdapi/v1/train/embedding", self.train_embedding, methods=["POST"], response_model=TrainResponse) - self.add_api_route("/sdapi/v1/train/hypernetwork", self.train_hypernetwork, methods=["POST"], response_model=TrainResponse) - self.add_api_route("/sdapi/v1/memory", self.get_memory, methods=["GET"], response_model=MemoryResponse) - - def add_api_route(self, path: str, endpoint, **kwargs): - if shared.cmd_opts.api_auth: - return self.app.add_api_route(path, endpoint, dependencies=[Depends(self.auth)], **kwargs) - return self.app.add_api_route(path, endpoint, **kwargs) - - def auth(self, credentials: HTTPBasicCredentials = Depends(HTTPBasic())): - if credentials.username in self.credentials: - if compare_digest(credentials.password, self.credentials[credentials.username]): - return True - - raise HTTPException(status_code=401, detail="Incorrect username or password", headers={"WWW-Authenticate": "Basic"}) - - def get_script(self, script_name, script_runner): - if script_name is None: - return None, None - - if not script_runner.scripts: - script_runner.initialize_scripts(False) - ui.create_ui() - - script_idx = script_name_to_index(script_name, script_runner.selectable_scripts) - script = script_runner.selectable_scripts[script_idx] - return script, script_idx - - def text2imgapi(self, txt2imgreq: StableDiffusionTxt2ImgProcessingAPI): - script, script_idx = self.get_script(txt2imgreq.script_name, scripts.scripts_txt2img) - - populate = txt2imgreq.copy(update={ # Override __init__ params - "sampler_name": validate_sampler_name(txt2imgreq.sampler_name or txt2imgreq.sampler_index), - "do_not_save_samples": True, - "do_not_save_grid": True - } - ) - if populate.sampler_name: - populate.sampler_index = None # prevent a warning later on - - args = vars(populate) - args.pop('script_name', None) - - with self.queue_lock: - p = StableDiffusionProcessingTxt2Img(sd_model=shared.sd_model, **args) - - shared.state.begin() - if script is not None: - p.outpath_grids = opts.outdir_txt2img_grids - p.outpath_samples = opts.outdir_txt2img_samples - p.script_args = [script_idx + 1] + [None] * (script.args_from - 1) + p.script_args - processed = scripts.scripts_txt2img.run(p, *p.script_args) - else: - processed = process_images(p) - shared.state.end() - - b64images = list(map(encode_pil_to_base64, processed.images)) - - return TextToImageResponse(images=b64images, parameters=vars(txt2imgreq), info=processed.js()) - - def img2imgapi(self, img2imgreq: StableDiffusionImg2ImgProcessingAPI): - init_images = img2imgreq.init_images - if init_images is None: - raise HTTPException(status_code=404, detail="Init image not found") - - script, script_idx = self.get_script(img2imgreq.script_name, scripts.scripts_img2img) - - mask = img2imgreq.mask - if mask: - mask = decode_base64_to_image(mask) - - populate = img2imgreq.copy(update={ # Override __init__ params - "sampler_name": validate_sampler_name(img2imgreq.sampler_name or img2imgreq.sampler_index), - "do_not_save_samples": True, - "do_not_save_grid": True, - "mask": mask - } - ) - if populate.sampler_name: - populate.sampler_index = None # prevent a warning later on - - args = vars(populate) - args.pop('include_init_images', None) # this is meant to be done by "exclude": True in model, but it's for a reason that I cannot determine. - args.pop('script_name', None) - - with self.queue_lock: - p = StableDiffusionProcessingImg2Img(sd_model=shared.sd_model, **args) - p.init_images = [decode_base64_to_image(x) for x in init_images] - - shared.state.begin() - if script is not None: - p.outpath_grids = opts.outdir_img2img_grids - p.outpath_samples = opts.outdir_img2img_samples - p.script_args = [script_idx + 1] + [None] * (script.args_from - 1) + p.script_args - processed = scripts.scripts_img2img.run(p, *p.script_args) - else: - processed = process_images(p) - shared.state.end() - - b64images = list(map(encode_pil_to_base64, processed.images)) - - if not img2imgreq.include_init_images: - img2imgreq.init_images = None - img2imgreq.mask = None - - return ImageToImageResponse(images=b64images, parameters=vars(img2imgreq), info=processed.js()) - - def extras_single_image_api(self, req: ExtrasSingleImageRequest): - reqDict = setUpscalers(req) - - reqDict['image'] = decode_base64_to_image(reqDict['image']) - - with self.queue_lock: - result = postprocessing.run_extras(extras_mode=0, image_folder="", input_dir="", output_dir="", save_output=False, **reqDict) - - return ExtrasSingleImageResponse(image=encode_pil_to_base64(result[0][0]), html_info=result[1]) - - def extras_batch_images_api(self, req: ExtrasBatchImagesRequest): - reqDict = setUpscalers(req) - - def prepareFiles(file): - file = decode_base64_to_file(file.data, file_path=file.name) - file.orig_name = file.name - return file - - reqDict['image_folder'] = list(map(prepareFiles, reqDict['imageList'])) - reqDict.pop('imageList') - - with self.queue_lock: - result = postprocessing.run_extras(extras_mode=1, image="", input_dir="", output_dir="", save_output=False, **reqDict) - - return ExtrasBatchImagesResponse(images=list(map(encode_pil_to_base64, result[0])), html_info=result[1]) - - def pnginfoapi(self, req: PNGInfoRequest): - if(not req.image.strip()): - return PNGInfoResponse(info="") - - image = decode_base64_to_image(req.image.strip()) - if image is None: - return PNGInfoResponse(info="") - - geninfo, items = images.read_info_from_image(image) - if geninfo is None: - geninfo = "" - - items = {**{'parameters': geninfo}, **items} - - return PNGInfoResponse(info=geninfo, items=items) - - def progressapi(self, req: ProgressRequest = Depends()): - # copy from check_progress_call of ui.py - - if shared.state.job_count == 0: - return ProgressResponse(progress=0, eta_relative=0, state=shared.state.dict(), textinfo=shared.state.textinfo) - - # avoid dividing zero - progress = 0.01 - - if shared.state.job_count > 0: - progress += shared.state.job_no / shared.state.job_count - if shared.state.sampling_steps > 0: - progress += 1 / shared.state.job_count * shared.state.sampling_step / shared.state.sampling_steps - - time_since_start = time.time() - shared.state.time_start - eta = (time_since_start/progress) - eta_relative = eta-time_since_start - - progress = min(progress, 1) - - shared.state.set_current_image() - - current_image = None - if shared.state.current_image and not req.skip_current_image: - current_image = encode_pil_to_base64(shared.state.current_image) - - return ProgressResponse(progress=progress, eta_relative=eta_relative, state=shared.state.dict(), current_image=current_image, textinfo=shared.state.textinfo) - - def interrogateapi(self, interrogatereq: InterrogateRequest): - image_b64 = interrogatereq.image - if image_b64 is None: - raise HTTPException(status_code=404, detail="Image not found") - - img = decode_base64_to_image(image_b64) - img = img.convert('RGB') - - # Override object param - with self.queue_lock: - if interrogatereq.model == "clip": - processed = shared.interrogator.interrogate(img) - elif interrogatereq.model == "deepdanbooru": - processed = deepbooru.model.tag(img) - else: - raise HTTPException(status_code=404, detail="Model not found") - - return InterrogateResponse(caption=processed) - - def interruptapi(self): - shared.state.interrupt() - - return {} - - def skip(self): - shared.state.skip() - - def get_config(self): - options = {} - for key in shared.opts.data.keys(): - metadata = shared.opts.data_labels.get(key) - if(metadata is not None): - options.update({key: shared.opts.data.get(key, shared.opts.data_labels.get(key).default)}) - else: - options.update({key: shared.opts.data.get(key, None)}) - - return options - - def set_config(self, req: Dict[str, Any]): - for k, v in req.items(): - shared.opts.set(k, v) - - shared.opts.save(shared.config_filename) - return - - def get_cmd_flags(self): - return vars(shared.cmd_opts) - - def get_samplers(self): - return [{"name": sampler[0], "aliases":sampler[2], "options":sampler[3]} for sampler in sd_samplers.all_samplers] - - def get_upscalers(self): - return [ - { - "name": upscaler.name, - "model_name": upscaler.scaler.model_name, - "model_path": upscaler.data_path, - "model_url": None, - "scale": upscaler.scale, - } - for upscaler in shared.sd_upscalers - ] - - def get_sd_models(self): - return [{"title": x.title, "model_name": x.model_name, "hash": x.shorthash, "sha256": x.sha256, "filename": x.filename, "config": find_checkpoint_config_near_filename(x)} for x in checkpoints_list.values()] - - def get_hypernetworks(self): - return [{"name": name, "path": shared.hypernetworks[name]} for name in shared.hypernetworks] - - def get_face_restorers(self): - return [{"name":x.name(), "cmd_dir": getattr(x, "cmd_dir", None)} for x in shared.face_restorers] - - def get_realesrgan_models(self): - return [{"name":x.name,"path":x.data_path, "scale":x.scale} for x in get_realesrgan_models(None)] - - def get_prompt_styles(self): - styleList = [] - for k in shared.prompt_styles.styles: - style = shared.prompt_styles.styles[k] - styleList.append({"name":style[0], "prompt": style[1], "negative_prompt": style[2]}) - - return styleList - - def get_embeddings(self): - db = sd_hijack.model_hijack.embedding_db - - def convert_embedding(embedding): - return { - "step": embedding.step, - "sd_checkpoint": embedding.sd_checkpoint, - "sd_checkpoint_name": embedding.sd_checkpoint_name, - "shape": embedding.shape, - "vectors": embedding.vectors, - } - - def convert_embeddings(embeddings): - return {embedding.name: convert_embedding(embedding) for embedding in embeddings.values()} - - return { - "loaded": convert_embeddings(db.word_embeddings), - "skipped": convert_embeddings(db.skipped_embeddings), - } - - def refresh_checkpoints(self): - shared.refresh_checkpoints() - - def create_embedding(self, args: dict): - try: - shared.state.begin() - 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 CreateResponse(info = "create embedding filename: {filename}".format(filename = filename)) - except AssertionError as e: - shared.state.end() - return TrainResponse(info = "create embedding error: {error}".format(error = e)) - - def create_hypernetwork(self, args: dict): - try: - shared.state.begin() - filename = create_hypernetwork(**args) # create empty embedding - shared.state.end() - return CreateResponse(info = "create hypernetwork filename: {filename}".format(filename = filename)) - except AssertionError as e: - shared.state.end() - return TrainResponse(info = "create hypernetwork error: {error}".format(error = e)) - - def preprocess(self, args: dict): - try: - shared.state.begin() - preprocess(**args) # quick operation unless blip/booru interrogation is enabled - shared.state.end() - return PreprocessResponse(info = 'preprocess complete') - except KeyError as e: - shared.state.end() - return PreprocessResponse(info = "preprocess error: invalid token: {error}".format(error = e)) - except AssertionError as e: - shared.state.end() - return PreprocessResponse(info = "preprocess error: {error}".format(error = e)) - except FileNotFoundError as e: - shared.state.end() - return PreprocessResponse(info = 'preprocess error: {error}'.format(error = e)) - - def train_embedding(self, args: dict): - try: - shared.state.begin() - apply_optimizations = shared.opts.training_xattention_optimizations - 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 TrainResponse(info = "train embedding complete: filename: {filename} error: {error}".format(filename = filename, error = error)) - except AssertionError as msg: - shared.state.end() - return TrainResponse(info = "train embedding error: {msg}".format(msg = msg)) - - def train_hypernetwork(self, args: dict): - try: - shared.state.begin() - shared.loaded_hypernetworks = [] - apply_optimizations = shared.opts.training_xattention_optimizations - 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 TrainResponse(info="train embedding complete: filename: {filename} error: {error}".format(filename=filename, error=error)) - except AssertionError as msg: - shared.state.end() - return TrainResponse(info="train embedding error: {error}".format(error=error)) - - def get_memory(self): - try: - import os, psutil - process = psutil.Process(os.getpid()) - res = process.memory_info() # only rss is cross-platform guaranteed so we dont rely on other values - ram_total = 100 * res.rss / process.memory_percent() # and total memory is calculated as actual value is not cross-platform safe - ram = { 'free': ram_total - res.rss, 'used': res.rss, 'total': ram_total } - except Exception as err: - ram = { 'error': f'{err}' } - try: - import torch - if torch.cuda.is_available(): - s = torch.cuda.mem_get_info() - system = { 'free': s[0], 'used': s[1] - s[0], 'total': s[1] } - s = dict(torch.cuda.memory_stats(shared.device)) - allocated = { 'current': s['allocated_bytes.all.current'], 'peak': s['allocated_bytes.all.peak'] } - reserved = { 'current': s['reserved_bytes.all.current'], 'peak': s['reserved_bytes.all.peak'] } - active = { 'current': s['active_bytes.all.current'], 'peak': s['active_bytes.all.peak'] } - inactive = { 'current': s['inactive_split_bytes.all.current'], 'peak': s['inactive_split_bytes.all.peak'] } - warnings = { 'retries': s['num_alloc_retries'], 'oom': s['num_ooms'] } - cuda = { - 'system': system, - 'active': active, - 'allocated': allocated, - 'reserved': reserved, - 'inactive': inactive, - 'events': warnings, - } - else: - cuda = { 'error': 'unavailable' } - except Exception as err: - cuda = { 'error': f'{err}' } - return MemoryResponse(ram = ram, cuda = cuda) - - def launch(self, server_name, port): - self.app.include_router(self.router) - uvicorn.run(self.app, host=server_name, port=port) diff --git a/home/vlado/branches/automatic/modules/api/models.py b/home/vlado/branches/automatic/modules/api/models.py deleted file mode 100644 index cba43d3b1..000000000 --- a/home/vlado/branches/automatic/modules/api/models.py +++ /dev/null @@ -1,269 +0,0 @@ -import inspect -from pydantic import BaseModel, Field, create_model -from typing import Any, Optional -from typing_extensions import Literal -from inflection import underscore -from modules.processing import StableDiffusionProcessingTxt2Img, StableDiffusionProcessingImg2Img -from modules.shared import sd_upscalers, opts, parser -from typing import Dict, List - -API_NOT_ALLOWED = [ - "self", - "kwargs", - "sd_model", - "outpath_samples", - "outpath_grids", - "sampler_index", - "do_not_save_samples", - "do_not_save_grid", - "extra_generation_params", - "overlay_images", - "do_not_reload_embeddings", - "seed_enable_extras", - "prompt_for_display", - "sampler_noise_scheduler_override", - "ddim_discretize" -] - -class ModelDef(BaseModel): - """Assistance Class for Pydantic Dynamic Model Generation""" - - field: str - field_alias: str - field_type: Any - field_value: Any - field_exclude: bool = False - - -class PydanticModelGenerator: - """ - Takes in created classes and stubs them out in a way FastAPI/Pydantic is happy about: - source_data is a snapshot of the default values produced by the class - params are the names of the actual keys required by __init__ - """ - - def __init__( - self, - model_name: str = None, - class_instance = None, - additional_fields = None, - ): - def field_type_generator(k, v): - # field_type = str if not overrides.get(k) else overrides[k]["type"] - # print(k, v.annotation, v.default) - field_type = v.annotation - - return Optional[field_type] - - def merge_class_params(class_): - all_classes = list(filter(lambda x: x is not object, inspect.getmro(class_))) - parameters = {} - for classes in all_classes: - parameters = {**parameters, **inspect.signature(classes.__init__).parameters} - return parameters - - - self._model_name = model_name - self._class_data = merge_class_params(class_instance) - - self._model_def = [ - ModelDef( - field=underscore(k), - field_alias=k, - field_type=field_type_generator(k, v), - field_value=v.default - ) - for (k,v) in self._class_data.items() if k not in API_NOT_ALLOWED - ] - - for fields in additional_fields: - self._model_def.append(ModelDef( - field=underscore(fields["key"]), - field_alias=fields["key"], - field_type=fields["type"], - field_value=fields["default"], - field_exclude=fields["exclude"] if "exclude" in fields else False)) - - def generate_model(self): - """ - Creates a pydantic BaseModel - from the json and overrides provided at initialization - """ - fields = { - d.field: (d.field_type, Field(default=d.field_value, alias=d.field_alias, exclude=d.field_exclude)) for d in self._model_def - } - DynamicModel = create_model(self._model_name, **fields) - DynamicModel.__config__.allow_population_by_field_name = True - DynamicModel.__config__.allow_mutation = True - return DynamicModel - -StableDiffusionTxt2ImgProcessingAPI = PydanticModelGenerator( - "StableDiffusionProcessingTxt2Img", - StableDiffusionProcessingTxt2Img, - [{"key": "sampler_index", "type": str, "default": "Euler"}, {"key": "script_name", "type": str, "default": None}, {"key": "script_args", "type": list, "default": []}] -).generate_model() - -StableDiffusionImg2ImgProcessingAPI = PydanticModelGenerator( - "StableDiffusionProcessingImg2Img", - StableDiffusionProcessingImg2Img, - [{"key": "sampler_index", "type": str, "default": "Euler"}, {"key": "init_images", "type": list, "default": None}, {"key": "denoising_strength", "type": float, "default": 0.75}, {"key": "mask", "type": str, "default": None}, {"key": "include_init_images", "type": bool, "default": False, "exclude" : True}, {"key": "script_name", "type": str, "default": None}, {"key": "script_args", "type": list, "default": []}] -).generate_model() - -class TextToImageResponse(BaseModel): - images: List[str] = Field(default=None, title="Image", description="The generated image in base64 format.") - parameters: dict - info: str - -class ImageToImageResponse(BaseModel): - images: List[str] = Field(default=None, title="Image", description="The generated image in base64 format.") - parameters: dict - info: str - -class ExtrasBaseRequest(BaseModel): - resize_mode: Literal[0, 1] = Field(default=0, title="Resize Mode", description="Sets the resize mode: 0 to upscale by upscaling_resize amount, 1 to upscale up to upscaling_resize_h x upscaling_resize_w.") - show_extras_results: bool = Field(default=True, title="Show results", description="Should the backend return the generated image?") - gfpgan_visibility: float = Field(default=0, title="GFPGAN Visibility", ge=0, le=1, allow_inf_nan=False, description="Sets the visibility of GFPGAN, values should be between 0 and 1.") - codeformer_visibility: float = Field(default=0, title="CodeFormer Visibility", ge=0, le=1, allow_inf_nan=False, description="Sets the visibility of CodeFormer, values should be between 0 and 1.") - codeformer_weight: float = Field(default=0, title="CodeFormer Weight", ge=0, le=1, allow_inf_nan=False, description="Sets the weight of CodeFormer, values should be between 0 and 1.") - upscaling_resize: float = Field(default=2, title="Upscaling Factor", ge=1, le=8, description="By how much to upscale the image, only used when resize_mode=0.") - upscaling_resize_w: int = Field(default=512, title="Target Width", ge=1, description="Target width for the upscaler to hit. Only used when resize_mode=1.") - upscaling_resize_h: int = Field(default=512, title="Target Height", ge=1, description="Target height for the upscaler to hit. Only used when resize_mode=1.") - upscaling_crop: bool = Field(default=True, title="Crop to fit", description="Should the upscaler crop the image to fit in the chosen size?") - upscaler_1: str = Field(default="None", title="Main upscaler", description=f"The name of the main upscaler to use, it has to be one of this list: {' , '.join([x.name for x in sd_upscalers])}") - upscaler_2: str = Field(default="None", title="Secondary upscaler", description=f"The name of the secondary upscaler to use, it has to be one of this list: {' , '.join([x.name for x in sd_upscalers])}") - extras_upscaler_2_visibility: float = Field(default=0, title="Secondary upscaler visibility", ge=0, le=1, allow_inf_nan=False, description="Sets the visibility of secondary upscaler, values should be between 0 and 1.") - upscale_first: bool = Field(default=False, title="Upscale first", description="Should the upscaler run before restoring faces?") - -class ExtraBaseResponse(BaseModel): - html_info: str = Field(title="HTML info", description="A series of HTML tags containing the process info.") - -class ExtrasSingleImageRequest(ExtrasBaseRequest): - image: str = Field(default="", title="Image", description="Image to work on, must be a Base64 string containing the image's data.") - -class ExtrasSingleImageResponse(ExtraBaseResponse): - image: str = Field(default=None, title="Image", description="The generated image in base64 format.") - -class FileData(BaseModel): - data: str = Field(title="File data", description="Base64 representation of the file") - name: str = Field(title="File name") - -class ExtrasBatchImagesRequest(ExtrasBaseRequest): - imageList: List[FileData] = Field(title="Images", description="List of images to work on. Must be Base64 strings") - -class ExtrasBatchImagesResponse(ExtraBaseResponse): - images: List[str] = Field(title="Images", description="The generated images in base64 format.") - -class PNGInfoRequest(BaseModel): - image: str = Field(title="Image", description="The base64 encoded PNG image") - -class PNGInfoResponse(BaseModel): - info: str = Field(title="Image info", description="A string with the parameters used to generate the image") - items: dict = Field(title="Items", description="An object containing all the info the image had") - -class ProgressRequest(BaseModel): - skip_current_image: bool = Field(default=False, title="Skip current image", description="Skip current image serialization") - -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") - state: dict = Field(title="State", description="The current state snapshot") - 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 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.") - -class InterrogateResponse(BaseModel): - caption: str = Field(default=None, title="Caption", description="The generated caption for the image.") - -class TrainResponse(BaseModel): - info: str = Field(title="Train info", description="Response string from train embedding or hypernetwork task.") - -class CreateResponse(BaseModel): - info: str = Field(title="Create info", description="Response string from create embedding or hypernetwork task.") - -class PreprocessResponse(BaseModel): - info: str = Field(title="Preprocess info", description="Response string from preprocessing task.") - -fields = {} -for key, metadata in opts.data_labels.items(): - value = opts.data.get(key) - optType = opts.typemap.get(type(metadata.default), type(value)) - - if (metadata is not None): - fields.update({key: (Optional[optType], Field( - default=metadata.default ,description=metadata.label))}) - else: - fields.update({key: (Optional[optType], Field())}) - -OptionsModel = create_model("Options", **fields) - -flags = {} -_options = vars(parser)['_option_string_actions'] -for key in _options: - if(_options[key].dest != 'help'): - flag = _options[key] - _type = str - if _options[key].default is not None: _type = type(_options[key].default) - flags.update({flag.dest: (_type,Field(default=flag.default, description=flag.help))}) - -FlagsModel = create_model("Flags", **flags) - -class SamplerItem(BaseModel): - name: str = Field(title="Name") - aliases: List[str] = Field(title="Aliases") - options: Dict[str, str] = Field(title="Options") - -class UpscalerItem(BaseModel): - name: str = Field(title="Name") - model_name: Optional[str] = Field(title="Model Name") - model_path: Optional[str] = Field(title="Path") - model_url: Optional[str] = Field(title="URL") - scale: Optional[float] = Field(title="Scale") - -class SDModelItem(BaseModel): - title: str = Field(title="Title") - model_name: str = Field(title="Model Name") - hash: Optional[str] = Field(title="Short hash") - sha256: Optional[str] = Field(title="sha256 hash") - filename: str = Field(title="Filename") - config: Optional[str] = Field(title="Config file") - -class HypernetworkItem(BaseModel): - name: str = Field(title="Name") - path: Optional[str] = Field(title="Path") - -class FaceRestorerItem(BaseModel): - name: str = Field(title="Name") - cmd_dir: Optional[str] = Field(title="Path") - -class RealesrganItem(BaseModel): - name: str = Field(title="Name") - path: Optional[str] = Field(title="Path") - scale: Optional[int] = Field(title="Scale") - -class PromptStyleItem(BaseModel): - name: str = Field(title="Name") - prompt: Optional[str] = Field(title="Prompt") - negative_prompt: Optional[str] = Field(title="Negative Prompt") - -class ArtistItem(BaseModel): - name: str = Field(title="Name") - score: float = Field(title="Score") - category: str = Field(title="Category") - -class EmbeddingItem(BaseModel): - step: Optional[int] = Field(title="Step", description="The number of steps that were used to train this embedding, if available") - sd_checkpoint: Optional[str] = Field(title="SD Checkpoint", description="The hash of the checkpoint this embedding was trained on, if available") - sd_checkpoint_name: Optional[str] = Field(title="SD Checkpoint Name", description="The name of the checkpoint this embedding was trained on, if available. Note that this is the name that was used by the trainer; for a stable identifier, use `sd_checkpoint` instead") - shape: int = Field(title="Shape", description="The length of each individual vector in the embedding") - vectors: int = Field(title="Vectors", description="The number of vectors in the embedding") - -class EmbeddingsResponse(BaseModel): - loaded: Dict[str, EmbeddingItem] = Field(title="Loaded", description="Embeddings loaded for the current model") - skipped: Dict[str, EmbeddingItem] = Field(title="Skipped", description="Embeddings skipped for the current model (likely due to architecture incompatibility)") - -class MemoryResponse(BaseModel): - ram: dict = Field(title="RAM", description="System memory stats") - cuda: dict = Field(title="CUDA", description="nVidia CUDA memory stats") diff --git a/home/vlado/branches/automatic/modules/sd_hijack.py b/home/vlado/branches/automatic/modules/sd_hijack.py deleted file mode 100644 index 794767831..000000000 --- a/home/vlado/branches/automatic/modules/sd_hijack.py +++ /dev/null @@ -1,264 +0,0 @@ -import torch -from torch.nn.functional import silu -from types import MethodType - -import modules.textual_inversion.textual_inversion -from modules import devices, sd_hijack_optimizations, shared, sd_hijack_checkpoint -from modules.hypernetworks import hypernetwork -from modules.shared import cmd_opts -from modules import sd_hijack_clip, sd_hijack_open_clip, sd_hijack_unet, sd_hijack_xlmr, xlmr - -import ldm.modules.attention -import ldm.modules.diffusionmodules.model -import ldm.modules.diffusionmodules.openaimodel -import ldm.models.diffusion.ddim -import ldm.models.diffusion.plms -import ldm.modules.encoders.modules - -attention_CrossAttention_forward = ldm.modules.attention.CrossAttention.forward -diffusionmodules_model_nonlinearity = ldm.modules.diffusionmodules.model.nonlinearity -diffusionmodules_model_AttnBlock_forward = ldm.modules.diffusionmodules.model.AttnBlock.forward - -# new memory efficient cross attention blocks do not support hypernets and we already -# have memory efficient cross attention anyway, so this disables SD2.0's memory efficient cross attention -ldm.modules.attention.MemoryEfficientCrossAttention = ldm.modules.attention.CrossAttention -ldm.modules.attention.BasicTransformerBlock.ATTENTION_MODES["softmax-xformers"] = ldm.modules.attention.CrossAttention - -# silence new console spam from SD2 -ldm.modules.attention.print = lambda *args: None -ldm.modules.diffusionmodules.model.print = lambda *args: None - - -def apply_optimizations(): - undo_optimizations() - - ldm.modules.diffusionmodules.model.nonlinearity = silu - ldm.modules.diffusionmodules.openaimodel.th = sd_hijack_unet.th - - optimization_method = None - - if cmd_opts.force_enable_xformers or (cmd_opts.xformers and shared.xformers_available and torch.version.cuda and (6, 0) <= torch.cuda.get_device_capability(shared.device) <= (9, 0)): - print("Applying xformers cross attention optimization.") - ldm.modules.attention.CrossAttention.forward = sd_hijack_optimizations.xformers_attention_forward - ldm.modules.diffusionmodules.model.AttnBlock.forward = sd_hijack_optimizations.xformers_attnblock_forward - optimization_method = 'xformers' - elif cmd_opts.opt_sub_quad_attention: - print("Applying sub-quadratic cross attention optimization.") - ldm.modules.attention.CrossAttention.forward = sd_hijack_optimizations.sub_quad_attention_forward - ldm.modules.diffusionmodules.model.AttnBlock.forward = sd_hijack_optimizations.sub_quad_attnblock_forward - optimization_method = 'sub-quadratic' - elif cmd_opts.opt_split_attention_v1: - print("Applying v1 cross attention optimization.") - ldm.modules.attention.CrossAttention.forward = sd_hijack_optimizations.split_cross_attention_forward_v1 - optimization_method = 'V1' - elif not cmd_opts.disable_opt_split_attention and (cmd_opts.opt_split_attention_invokeai or not cmd_opts.opt_split_attention and not torch.cuda.is_available()): - print("Applying cross attention optimization (InvokeAI).") - ldm.modules.attention.CrossAttention.forward = sd_hijack_optimizations.split_cross_attention_forward_invokeAI - optimization_method = 'InvokeAI' - elif not cmd_opts.disable_opt_split_attention and (cmd_opts.opt_split_attention or torch.cuda.is_available()): - print("Applying cross attention optimization (Doggettx).") - ldm.modules.attention.CrossAttention.forward = sd_hijack_optimizations.split_cross_attention_forward - ldm.modules.diffusionmodules.model.AttnBlock.forward = sd_hijack_optimizations.cross_attention_attnblock_forward - optimization_method = 'Doggettx' - - return optimization_method - - -def undo_optimizations(): - ldm.modules.attention.CrossAttention.forward = hypernetwork.attention_CrossAttention_forward - ldm.modules.diffusionmodules.model.nonlinearity = diffusionmodules_model_nonlinearity - ldm.modules.diffusionmodules.model.AttnBlock.forward = diffusionmodules_model_AttnBlock_forward - - -def fix_checkpoint(): - """checkpoints are now added and removed in embedding/hypernet code, since torch doesn't want - checkpoints to be added when not training (there's a warning)""" - - pass - - -def weighted_loss(sd_model, pred, target, mean=True): - #Calculate the weight normally, but ignore the mean - loss = sd_model._old_get_loss(pred, target, mean=False) - - #Check if we have weights available - weight = getattr(sd_model, '_custom_loss_weight', None) - if weight is not None: - loss *= weight - - #Return the loss, as mean if specified - return loss.mean() if mean else loss - -def weighted_forward(sd_model, x, c, w, *args, **kwargs): - try: - #Temporarily append weights to a place accessible during loss calc - sd_model._custom_loss_weight = w - - #Replace 'get_loss' with a weight-aware one. Otherwise we need to reimplement 'forward' completely - #Keep 'get_loss', but don't overwrite the previous old_get_loss if it's already set - if not hasattr(sd_model, '_old_get_loss'): - sd_model._old_get_loss = sd_model.get_loss - sd_model.get_loss = MethodType(weighted_loss, sd_model) - - #Run the standard forward function, but with the patched 'get_loss' - return sd_model.forward(x, c, *args, **kwargs) - finally: - try: - #Delete temporary weights if appended - del sd_model._custom_loss_weight - except AttributeError as e: - pass - - #If we have an old loss function, reset the loss function to the original one - if hasattr(sd_model, '_old_get_loss'): - sd_model.get_loss = sd_model._old_get_loss - del sd_model._old_get_loss - -def apply_weighted_forward(sd_model): - #Add new function 'weighted_forward' that can be called to calc weighted loss - sd_model.weighted_forward = MethodType(weighted_forward, sd_model) - -def undo_weighted_forward(sd_model): - try: - del sd_model.weighted_forward - except AttributeError as e: - pass - - -class StableDiffusionModelHijack: - fixes = None - comments = [] - layers = None - circular_enabled = False - clip = None - optimization_method = None - - embedding_db = modules.textual_inversion.textual_inversion.EmbeddingDatabase() - - def __init__(self): - self.embedding_db.add_embedding_dir(cmd_opts.embeddings_dir) - - def hijack(self, m): - if type(m.cond_stage_model) == xlmr.BertSeriesModelWithTransformation: - model_embeddings = m.cond_stage_model.roberta.embeddings - model_embeddings.token_embedding = EmbeddingsWithFixes(model_embeddings.word_embeddings, self) - m.cond_stage_model = sd_hijack_xlmr.FrozenXLMREmbedderWithCustomWords(m.cond_stage_model, self) - - elif type(m.cond_stage_model) == ldm.modules.encoders.modules.FrozenCLIPEmbedder: - model_embeddings = m.cond_stage_model.transformer.text_model.embeddings - model_embeddings.token_embedding = EmbeddingsWithFixes(model_embeddings.token_embedding, self) - m.cond_stage_model = sd_hijack_clip.FrozenCLIPEmbedderWithCustomWords(m.cond_stage_model, self) - - elif type(m.cond_stage_model) == ldm.modules.encoders.modules.FrozenOpenCLIPEmbedder: - m.cond_stage_model.model.token_embedding = EmbeddingsWithFixes(m.cond_stage_model.model.token_embedding, self) - m.cond_stage_model = sd_hijack_open_clip.FrozenOpenCLIPEmbedderWithCustomWords(m.cond_stage_model, self) - - apply_weighted_forward(m) - if m.cond_stage_key == "edit": - sd_hijack_unet.hijack_ddpm_edit() - - self.optimization_method = apply_optimizations() - - self.clip = m.cond_stage_model - - def flatten(el): - flattened = [flatten(children) for children in el.children()] - res = [el] - for c in flattened: - res += c - return res - - self.layers = flatten(m) - - def undo_hijack(self, m): - if type(m.cond_stage_model) == xlmr.BertSeriesModelWithTransformation: - m.cond_stage_model = m.cond_stage_model.wrapped - - elif type(m.cond_stage_model) == sd_hijack_clip.FrozenCLIPEmbedderWithCustomWords: - m.cond_stage_model = m.cond_stage_model.wrapped - - model_embeddings = m.cond_stage_model.transformer.text_model.embeddings - if type(model_embeddings.token_embedding) == EmbeddingsWithFixes: - model_embeddings.token_embedding = model_embeddings.token_embedding.wrapped - elif type(m.cond_stage_model) == sd_hijack_open_clip.FrozenOpenCLIPEmbedderWithCustomWords: - m.cond_stage_model.wrapped.model.token_embedding = m.cond_stage_model.wrapped.model.token_embedding.wrapped - m.cond_stage_model = m.cond_stage_model.wrapped - - undo_optimizations() - undo_weighted_forward(m) - - self.apply_circular(False) - self.layers = None - self.clip = None - - def apply_circular(self, enable): - if self.circular_enabled == enable: - return - - self.circular_enabled = enable - - for layer in [layer for layer in self.layers if type(layer) == torch.nn.Conv2d]: - layer.padding_mode = 'circular' if enable else 'zeros' - - def clear_comments(self): - self.comments = [] - - def get_prompt_lengths(self, text): - _, token_count = self.clip.process_texts([text]) - - return token_count, self.clip.get_target_prompt_token_count(token_count) - - -class EmbeddingsWithFixes(torch.nn.Module): - def __init__(self, wrapped, embeddings): - super().__init__() - self.wrapped = wrapped - self.embeddings = embeddings - - def forward(self, input_ids): - batch_fixes = self.embeddings.fixes - self.embeddings.fixes = None - - inputs_embeds = self.wrapped(input_ids) - - if batch_fixes is None or len(batch_fixes) == 0 or max([len(x) for x in batch_fixes]) == 0: - return inputs_embeds - - vecs = [] - for fixes, tensor in zip(batch_fixes, inputs_embeds): - for offset, embedding in fixes: - emb = devices.cond_cast_unet(embedding.vec) - emb_len = min(tensor.shape[0] - offset - 1, emb.shape[0]) - tensor = torch.cat([tensor[0:offset + 1], emb[0:emb_len], tensor[offset + 1 + emb_len:]]) - - vecs.append(tensor) - - return torch.stack(vecs) - - -def add_circular_option_to_conv_2d(): - conv2d_constructor = torch.nn.Conv2d.__init__ - - def conv2d_constructor_circular(self, *args, **kwargs): - return conv2d_constructor(self, *args, padding_mode='circular', **kwargs) - - torch.nn.Conv2d.__init__ = conv2d_constructor_circular - - -model_hijack = StableDiffusionModelHijack() - - -def register_buffer(self, name, attr): - """ - Fix register buffer bug for Mac OS. - """ - - if type(attr) == torch.Tensor: - if attr.device != devices.device: - attr = attr.to(device=devices.device, dtype=(torch.float32 if devices.device.type == 'mps' else None)) - - setattr(self, name, attr) - - -ldm.models.diffusion.ddim.DDIMSampler.register_buffer = register_buffer -ldm.models.diffusion.plms.PLMSSampler.register_buffer = register_buffer diff --git a/home/vlado/branches/automatic/modules/ui_extra_networks_checkpoints.py b/home/vlado/branches/automatic/modules/ui_extra_networks_checkpoints.py deleted file mode 100644 index 04097a794..000000000 --- a/home/vlado/branches/automatic/modules/ui_extra_networks_checkpoints.py +++ /dev/null @@ -1,39 +0,0 @@ -import html -import json -import os -import urllib.parse - -from modules import shared, ui_extra_networks, sd_models - - -class ExtraNetworksPageCheckpoints(ui_extra_networks.ExtraNetworksPage): - def __init__(self): - super().__init__('Checkpoints') - - def refresh(self): - shared.refresh_checkpoints() - - def list_items(self): - checkpoint: sd_models.CheckpointInfo - for name, checkpoint in sd_models.checkpoints_list.items(): - path, ext = os.path.splitext(checkpoint.filename) - previews = [path + ".png", path + ".preview.png"] - - preview = None - for file in previews: - if os.path.isfile(file): - preview = self.link_preview(file) - break - - yield { - "name": checkpoint.name_for_extra, - "filename": path, - "preview": preview, - "search_term": self.search_terms_from_path(checkpoint.filename) + " " + (checkpoint.sha256 or ""), - "onclick": '"' + html.escape(f"""return selectCheckpoint({json.dumps(name)})""") + '"', - "local_preview": path + ".png", - } - - def allowed_directories_for_previews(self): - return [v for v in [shared.cmd_opts.ckpt_dir, sd_models.model_path] if v is not None] - diff --git a/home/vlado/branches/automatic/modules/ui_extra_networks_hypernets.py b/home/vlado/branches/automatic/modules/ui_extra_networks_hypernets.py deleted file mode 100644 index 578510887..000000000 --- a/home/vlado/branches/automatic/modules/ui_extra_networks_hypernets.py +++ /dev/null @@ -1,36 +0,0 @@ -import json -import os - -from modules import shared, ui_extra_networks - - -class ExtraNetworksPageHypernetworks(ui_extra_networks.ExtraNetworksPage): - def __init__(self): - super().__init__('Hypernetworks') - - def refresh(self): - shared.reload_hypernetworks() - - def list_items(self): - for name, path in shared.hypernetworks.items(): - path, ext = os.path.splitext(path) - previews = [path + ".png", path + ".preview.png"] - - preview = None - for file in previews: - if os.path.isfile(file): - preview = self.link_preview(file) - break - - yield { - "name": name, - "filename": path, - "preview": preview, - "search_term": self.search_terms_from_path(path), - "prompt": json.dumps(f""), - "local_preview": path + ".png", - } - - def allowed_directories_for_previews(self): - return [shared.cmd_opts.hypernetwork_dir] - diff --git a/home/vlado/branches/automatic/modules/ui_extra_networks_textual_inversion.py b/home/vlado/branches/automatic/modules/ui_extra_networks_textual_inversion.py deleted file mode 100644 index bb64eb81e..000000000 --- a/home/vlado/branches/automatic/modules/ui_extra_networks_textual_inversion.py +++ /dev/null @@ -1,34 +0,0 @@ -import json -import os - -from modules import ui_extra_networks, sd_hijack - - -class ExtraNetworksPageTextualInversion(ui_extra_networks.ExtraNetworksPage): - def __init__(self): - super().__init__('Textual Inversion') - self.allow_negative_prompt = True - - def refresh(self): - sd_hijack.model_hijack.embedding_db.load_textual_inversion_embeddings(force_reload=True) - - def list_items(self): - for embedding in sd_hijack.model_hijack.embedding_db.word_embeddings.values(): - path, ext = os.path.splitext(embedding.filename) - preview_file = path + ".preview.png" - - preview = None - if os.path.isfile(preview_file): - preview = self.link_preview(preview_file) - - yield { - "name": embedding.name, - "filename": embedding.filename, - "preview": preview, - "search_term": self.search_terms_from_path(embedding.filename), - "prompt": json.dumps(embedding.name), - "local_preview": path + ".preview.png", - } - - def allowed_directories_for_previews(self): - return list(sd_hijack.model_hijack.embedding_db.embedding_dirs) diff --git a/home/vlado/branches/automatic/test/basic_features/txt2img_test.py b/home/vlado/branches/automatic/test/basic_features/txt2img_test.py deleted file mode 100644 index 5aa43a44a..000000000 --- a/home/vlado/branches/automatic/test/basic_features/txt2img_test.py +++ /dev/null @@ -1,80 +0,0 @@ -import unittest -import requests - - -class TestTxt2ImgWorking(unittest.TestCase): - def setUp(self): - self.url_txt2img = "http://localhost:7860/sdapi/v1/txt2img" - self.simple_txt2img = { - "enable_hr": False, - "denoising_strength": 0, - "firstphase_width": 0, - "firstphase_height": 0, - "prompt": "example prompt", - "styles": [], - "seed": -1, - "subseed": -1, - "subseed_strength": 0, - "seed_resize_from_h": -1, - "seed_resize_from_w": -1, - "batch_size": 1, - "n_iter": 1, - "steps": 3, - "cfg_scale": 7, - "width": 64, - "height": 64, - "restore_faces": False, - "tiling": False, - "negative_prompt": "", - "eta": 0, - "s_churn": 0, - "s_tmax": 0, - "s_tmin": 0, - "s_noise": 1, - "sampler_index": "Euler a" - } - - def test_txt2img_simple_performed(self): - self.assertEqual(requests.post(self.url_txt2img, json=self.simple_txt2img).status_code, 200) - - def test_txt2img_with_negative_prompt_performed(self): - self.simple_txt2img["negative_prompt"] = "example negative prompt" - self.assertEqual(requests.post(self.url_txt2img, json=self.simple_txt2img).status_code, 200) - - def test_txt2img_with_complex_prompt_performed(self): - self.simple_txt2img["prompt"] = "((emphasis)), (emphasis1:1.1), [to:1], [from::2], [from:to:0.3], [alt|alt1]" - self.assertEqual(requests.post(self.url_txt2img, json=self.simple_txt2img).status_code, 200) - - def test_txt2img_not_square_image_performed(self): - self.simple_txt2img["height"] = 128 - self.assertEqual(requests.post(self.url_txt2img, json=self.simple_txt2img).status_code, 200) - - def test_txt2img_with_hrfix_performed(self): - self.simple_txt2img["enable_hr"] = True - self.assertEqual(requests.post(self.url_txt2img, json=self.simple_txt2img).status_code, 200) - - def test_txt2img_with_tiling_performed(self): - self.simple_txt2img["tiling"] = True - self.assertEqual(requests.post(self.url_txt2img, json=self.simple_txt2img).status_code, 200) - - def test_txt2img_with_restore_faces_performed(self): - self.simple_txt2img["restore_faces"] = True - self.assertEqual(requests.post(self.url_txt2img, json=self.simple_txt2img).status_code, 200) - - def test_txt2img_with_vanilla_sampler_performed(self): - self.simple_txt2img["sampler_index"] = "PLMS" - self.assertEqual(requests.post(self.url_txt2img, json=self.simple_txt2img).status_code, 200) - self.simple_txt2img["sampler_index"] = "DDIM" - self.assertEqual(requests.post(self.url_txt2img, json=self.simple_txt2img).status_code, 200) - - def test_txt2img_multiple_batches_performed(self): - self.simple_txt2img["n_iter"] = 2 - self.assertEqual(requests.post(self.url_txt2img, json=self.simple_txt2img).status_code, 200) - - def test_txt2img_batch_performed(self): - self.simple_txt2img["batch_size"] = 2 - self.assertEqual(requests.post(self.url_txt2img, json=self.simple_txt2img).status_code, 200) - - -if __name__ == "__main__": - unittest.main() diff --git a/webui.py b/webui.py index 64976c0bc..12ba6fcd4 100644 --- a/webui.py +++ b/webui.py @@ -243,7 +243,7 @@ def webui(): startup_timer.record("create ui") if cmd_opts.gradio_queue: - shared.demo.queue(64) + shared.demo.queue(16) gradio_auth_creds = [] if cmd_opts.gradio_auth: @@ -264,7 +264,11 @@ def webui(): inbrowser=cmd_opts.autolaunch, prevent_thread_lock=True ) - # after initial launch, disable --autolaunch for subsequent restarts + # app is instance of FastAPI server + # shared.demo.server is instance of gradio class which inherits from uvicorn.Server + # shared.demo.config is instance of uvicorn.Config + # shared.demo.app is instance of ASGIApp + cmd_opts.autolaunch = False startup_timer.record("gradio launch")