diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 00000000000..bdac41941cb --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,57 @@ +name: Deploy Images to GHCR + + + +on: + pull_request: + types: [labeled] + +jobs: + cancel_outstanding: + name: Detect and cancel outstanding runs of this workflow + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Cancel Previous Runs + if: ${{ (github.event.label.name == 'release') && !contains(github.event.pull_request.labels.*.name, 'skip-cancel') }} + uses: styfle/cancel-workflow-action@0.11.0 + with: + access_token: ${{ secrets.GITHUB_TOKEN }} + + + push-store-image: + permissions: write-all + runs-on: ubuntu-latest + if: ${{ (github.event.label.name == 'release' ) && !contains(github.event.pull_request.labels.*.name, 'skip-cancel') }} + steps: + - name: 'Checkout GitHub Action' + uses: actions/checkout@main + - name: envvars + run: | + echo "RELEASE=$(cat build/vars/BUILD_TAG)" >> $GITHUB_ENV + echo "REGISTRY=$(cat build/vars/REGISTRY)" >> $GITHUB_ENV + + + - name: Login to GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + + - name: free space + run: | + bash .github/workflows/freespace.sh + ##https://github.com/orgs/community/discussions/25678 + - name: 'free up space' + + run: | + rm -rf /opt/hostedtoolcache + + - name: 'Build sd-web-ui image' + run: | + cd build && \ + RELEASE=${{ env.RELEASE }} \ + REGISTRY=${{ env.REGISTRY }} \ + docker buildx bake sd-web-ui -f docker-bake.hcl --push diff --git a/.gitignore b/.gitignore index 09734267ff5..2ed62f7e797 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,7 @@ notification.mp3 /node_modules /package-lock.json /.coverage* + +sd-data +modules/api/raypi2.py +modules/api/raypi3.py diff --git a/build/docker-bake.hcl b/build/docker-bake.hcl new file mode 100644 index 00000000000..5646e08e787 --- /dev/null +++ b/build/docker-bake.hcl @@ -0,0 +1,24 @@ + +variable "RELEASE" { + default = "v1.0.0" +} + +variable "REGISTRY" { + default = "" +} + +group "default" { + targets = ["sd-web-ui"] +} + +target "sd-web-ui" { + dockerfile = "sd.Dockerfile" + tags = ["${REGISTRY}${target.sd-web-ui.name}:${RELEASE}"] + context = "." + labels = { + "org.opencontainers.image.source" = "https://github.com/webcoderz/stable-diffusion-webui" + } +} + + +#RELEASE=$(cat build/vars/BUILD_TAG) REGISTRY=$(cat build/vars/REGISTRY) docker buildx bake --print \ No newline at end of file diff --git a/build/sd.Dockerfile b/build/sd.Dockerfile new file mode 100644 index 00000000000..d5183f227a1 --- /dev/null +++ b/build/sd.Dockerfile @@ -0,0 +1,54 @@ +FROM nvidia/cuda:12.2.0-base-ubuntu22.04 +ENV DEBIAN_FRONTEND noninteractive +SHELL ["/bin/bash", "-o", "pipefail", "-c"] + + +ENV DEBIAN_FRONTEND noninteractive +RUN apt-get update -y \ + && apt-get upgrade -y \ + && apt-get install -y --no-install-recommends \ + curl \ + git \ + git-lfs \ + python3.10 \ + python3.10-venv \ + python3-pip \ + libgl1 \ + libglib2.0-0 +RUN apt-get clean -y && rm -rf /var/lib/apt/lists/* + +# user and workdir +RUN useradd -m user +RUN mkdir /work && chown -R user:user /work +USER user +WORKDIR /work + + +################################ +# setup +################################ + +RUN git clone https://github.com/webcoderz/stable-diffusion-webui.git +WORKDIR /work/stable-diffusion-webui + + + +# setup +RUN python3 -mvenv venv && /work/stable-diffusion-webui/venv/bin/python -c "from launch import *; prepare_environment()" --skip-torch-cuda-test --no-download-sd-model + + + +################################ +# entrypoint +################################ + + + + +EXPOSE 7860 +EXPOSE 8000 +EXPOSE 8265 +EXPOSE 6388 +EXPOSE 10001 + +CMD ["./webui.sh", "--xformers --cors-allow-origins=* --api"] \ No newline at end of file diff --git a/build/vars/BUILD_TAG b/build/vars/BUILD_TAG new file mode 100644 index 00000000000..95e94cdd37f --- /dev/null +++ b/build/vars/BUILD_TAG @@ -0,0 +1 @@ +v0.0.1 \ No newline at end of file diff --git a/build/vars/REGISTRY b/build/vars/REGISTRY new file mode 100644 index 00000000000..bfda55613df --- /dev/null +++ b/build/vars/REGISTRY @@ -0,0 +1 @@ +ghcr.io/webcoderz/stable-diffusion-webui/ \ No newline at end of file diff --git a/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py b/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py index 04adc5eb2cf..21e6c61c85c 100644 --- a/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py +++ b/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py @@ -14,7 +14,7 @@ from functools import partial from tqdm import tqdm from torchvision.utils import make_grid -from pytorch_lightning.utilities.distributed import rank_zero_only +from pytorch_lightning.utilities.rank_zero import rank_zero_only from ldm.util import log_txt_as_img, exists, default, ismap, isimage, mean_flat, count_params, instantiate_from_config from ldm.modules.ema import LitEma diff --git a/extensions-builtin/Lora/networks.py b/extensions-builtin/Lora/networks.py index 96f935b236f..64c4512386e 100644 --- a/extensions-builtin/Lora/networks.py +++ b/extensions-builtin/Lora/networks.py @@ -15,6 +15,7 @@ from typing import Union from modules import shared, devices, sd_models, errors, scripts, sd_hijack +from modules.shared import shared_instance module_types = [ network_lora.ModuleTypeLora(), @@ -112,8 +113,10 @@ def match(match_list, regex_text): def assign_network_names_to_compvis_modules(sd_model): network_layer_mapping = {} - if shared.sd_model.is_sdxl: - for i, embedder in enumerate(shared.sd_model.conditioner.embedders): + #if shared.sd_model.is_sdxl: + if shared_instance.sd_model.is_sdxl: + #for i, embedder in enumerate(shared.sd_model.conditioner.embedders): + for i, embedder in enumerate(shared_instance.sd_model.conditioner.embedders): if not hasattr(embedder, 'wrapped'): continue @@ -122,12 +125,14 @@ def assign_network_names_to_compvis_modules(sd_model): network_layer_mapping[network_name] = module module.network_layer_name = network_name else: - for name, module in shared.sd_model.cond_stage_model.wrapped.named_modules(): + #for name, module in shared.sd_model.cond_stage_model.wrapped.named_modules(): + for name, module in shared_instance.sd_model.cond_stage_model.wrapped.named_modules(): network_name = name.replace(".", "_") network_layer_mapping[network_name] = module module.network_layer_name = network_name - for name, module in shared.sd_model.model.named_modules(): + #for name, module in shared.sd_model.model.named_modules(): + for name, module in shared_instance.sd_model.model.named_modules(): network_name = name.replace(".", "_") network_layer_mapping[network_name] = module module.network_layer_name = network_name @@ -142,11 +147,14 @@ def load_network(name, network_on_disk): sd = sd_models.read_state_dict(network_on_disk.filename) # this should not be needed but is here as an emergency fix for an unknown error people are experiencing in 1.2.0 - if not hasattr(shared.sd_model, 'network_layer_mapping'): - assign_network_names_to_compvis_modules(shared.sd_model) + #if not hasattr(shared.sd_model, 'network_layer_mapping'): + #assign_network_names_to_compvis_modules(shared.sd_model) + if not hasattr(shared_instance.sd_model, 'network_layer_mapping'): + assign_network_names_to_compvis_modules(shared_instance.sd_model) keys_failed_to_match = {} - is_sd2 = 'model_transformer_resblocks' in shared.sd_model.network_layer_mapping + #is_sd2 = 'model_transformer_resblocks' in shared.sd_model.network_layer_mapping + is_sd2 = 'model_transformer_resblocks' in shared_instance.sd_model.network_layer_mapping matched_networks = {} @@ -154,25 +162,31 @@ def load_network(name, network_on_disk): key_network_without_network_parts, network_part = key_network.split(".", 1) key = convert_diffusers_name_to_compvis(key_network_without_network_parts, is_sd2) - sd_module = shared.sd_model.network_layer_mapping.get(key, None) + #sd_module = shared.sd_model.network_layer_mapping.get(key, None) + sd_module = shared_instance.sd_model.network_layer_mapping.get(key, None) if sd_module is None: m = re_x_proj.match(key) if m: - sd_module = shared.sd_model.network_layer_mapping.get(m.group(1), None) + #sd_module = shared.sd_model.network_layer_mapping.get(m.group(1), None) + sd_module = shared_instance.sd_model.network_layer_mapping.get(m.group(1), None) + # SDXL loras seem to already have correct compvis keys, so only need to replace "lora_unet" with "diffusion_model" if sd_module is None and "lora_unet" in key_network_without_network_parts: key = key_network_without_network_parts.replace("lora_unet", "diffusion_model") - sd_module = shared.sd_model.network_layer_mapping.get(key, None) + #sd_module = shared.sd_model.network_layer_mapping.get(key, None) + sd_module = shared_instance.sd_model.network_layer_mapping.get(key, None) elif sd_module is None and "lora_te1_text_model" in key_network_without_network_parts: key = key_network_without_network_parts.replace("lora_te1_text_model", "0_transformer_text_model") - sd_module = shared.sd_model.network_layer_mapping.get(key, None) + #sd_module = shared.sd_model.network_layer_mapping.get(key, None) + sd_module = shared_instance.sd_model.network_layer_mapping.get(key, None) # some SD1 Loras also have correct compvis keys if sd_module is None: key = key_network_without_network_parts.replace("lora_te1_text_model", "transformer_text_model") - sd_module = shared.sd_model.network_layer_mapping.get(key, None) + #sd_module = shared.sd_model.network_layer_mapping.get(key, None) + sd_module = shared_instance.sd_model.network_layer_mapping.get(key, None) if sd_module is None: keys_failed_to_match[key_network] = key diff --git a/extensions-builtin/Lora/ui_extra_networks_lora.py b/extensions-builtin/Lora/ui_extra_networks_lora.py index 55409a7829d..8bb11a400dd 100644 --- a/extensions-builtin/Lora/ui_extra_networks_lora.py +++ b/extensions-builtin/Lora/ui_extra_networks_lora.py @@ -6,7 +6,7 @@ from modules import shared, ui_extra_networks from modules.ui_extra_networks import quote_js from ui_edit_user_metadata import LoraUserMetadataEditor - +from modules.shared import shared_instance class ExtraNetworksPageLora(ui_extra_networks.ExtraNetworksPage): def __init__(self): @@ -52,17 +52,26 @@ def create_item(self, name, index=None, enable_filter=True): if shared.opts.lora_show_all or not enable_filter: pass +# elif sd_version == network.SdVersion.Unknown: +# model_version = network.SdVersion.SDXL if shared.sd_model.is_sdxl else network.SdVersion.SD2 if shared.sd_model.is_sd2 else network.SdVersion.SD1 +# if model_version.name in shared.opts.lora_hide_unknown_for_versions: +# return None +# elif shared.sd_model.is_sdxl and sd_version != network.SdVersion.SDXL: +# return None +# elif shared.sd_model.is_sd2 and sd_version != network.SdVersion.SD2: +# return None +# elif shared.sd_model.is_sd1 and sd_version != network.SdVersion.SD1: +# return None elif sd_version == network.SdVersion.Unknown: - model_version = network.SdVersion.SDXL if shared.sd_model.is_sdxl else network.SdVersion.SD2 if shared.sd_model.is_sd2 else network.SdVersion.SD1 + model_version = network.SdVersion.SDXL if shared_instance.sd_model.is_sdxl else network.SdVersion.SD2 if shared_instance.sd_model.is_sd2 else network.SdVersion.SD1 if model_version.name in shared.opts.lora_hide_unknown_for_versions: return None - elif shared.sd_model.is_sdxl and sd_version != network.SdVersion.SDXL: + elif shared_instance.sd_model.is_sdxl and sd_version != network.SdVersion.SDXL: return None - elif shared.sd_model.is_sd2 and sd_version != network.SdVersion.SD2: + elif shared_instance.sd_model.is_sd2 and sd_version != network.SdVersion.SD2: return None - elif shared.sd_model.is_sd1 and sd_version != network.SdVersion.SD1: + elif shared_instance.sd_model.is_sd1 and sd_version != network.SdVersion.SD1: return None - return item def list_items(self): diff --git a/launch.py b/launch.py index f83820d2534..410dc05ccff 100644 --- a/launch.py +++ b/launch.py @@ -24,6 +24,7 @@ start = launch_utils.start + def main(): if args.dump_sysinfo: filename = launch_utils.dump_sysinfo() @@ -43,6 +44,5 @@ def main(): start() - if __name__ == "__main__": main() diff --git a/modules/api/api.py b/modules/api/api.py index e6edffe7144..aeee9d5968c 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -33,7 +33,7 @@ import piexif import piexif.helper from contextlib import closing - +from modules.shared import shared_instance def script_name_to_index(name, scripts): try: @@ -364,7 +364,8 @@ def text2imgapi(self, txt2imgreq: models.StableDiffusionTxt2ImgProcessingAPI): args.pop('save_images', None) with self.queue_lock: - with closing(StableDiffusionProcessingTxt2Img(sd_model=shared.sd_model, **args)) as p: + with closing(StableDiffusionProcessingTxt2Img(sd_model=shared_instance.sd_model, **args)) as p: + #with closing(StableDiffusionProcessingTxt2Img(sd_model=shared.sd_model, **args)) as p: p.is_api = True p.scripts = script_runner p.outpath_grids = opts.outdir_txt2img_grids @@ -424,7 +425,8 @@ def img2imgapi(self, img2imgreq: models.StableDiffusionImg2ImgProcessingAPI): args.pop('save_images', None) with self.queue_lock: - with closing(StableDiffusionProcessingImg2Img(sd_model=shared.sd_model, **args)) as p: + #with closing(StableDiffusionProcessingImg2Img(sd_model=shared.sd_model, **args)) as p: + with closing(StableDiffusionProcessingImg2Img(sd_model=shared_instance.sd_model, **args)) as p: p.init_images = [decode_base64_to_image(x) for x in init_images] p.is_api = True p.scripts = script_runner @@ -724,8 +726,10 @@ def train_hypernetwork(self, args: dict): 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) + #shared.sd_model.cond_stage_model.to(devices.device) + #shared.sd_model.first_stage_model.to(devices.device) + shared_instance.sd_model.cond_stage_model.to(devices.device) + shared_instance.sd_model.first_stage_model.to(devices.device) if not apply_optimizations: sd_hijack.apply_optimizations() shared.state.end() @@ -785,4 +789,3 @@ def restart_webui(self): def stop_webui(request): shared.state.server_command = "stop" return Response("Stopping.") - diff --git a/modules/api/ray.py b/modules/api/ray.py new file mode 100644 index 00000000000..ed945b658ef --- /dev/null +++ b/modules/api/ray.py @@ -0,0 +1,49 @@ +from ray import serve +import ray + +from modules.api.raypi import Raypi + +import time +import os + + +#ray.init(os.environ.get("RAY_HEAD_ADDRESS", "")) +#ray.init("ray://localhost:10001") + + +ray_head_address = os.environ.get("RAY_ADDRESS") +print("RAY_ADDRESS:", ray_head_address) + +if ray_head_address: + #ray.init(address=ray_head_address) + ray.init(address=ray_head_address) + #ray.init(address="172.21.0.3:6388") +else: + ray.init() + + +#entrypoint = Raypi.bind() + +def ray_only(): + serve.shutdown() + if "RAY_DOCKER" in os.environ: + print("starting ray in docker") + serve.start( + detached=True, + http_options={ + "host": os.environ.get("RAY_IP", "0.0.0.0"), + "port": int(os.environ.get("RAY_PORT", 8000)) + } + ) + else: + serve.start( + http_options={ + "host": os.environ.get("RAY_IP", "0.0.0.0"), + "port": int(os.environ.get("RAY_PORT", 8000)) + } + ) + print(f"Starting Raypi on port {os.environ.get('RAY_PORT', 8000)}") + serve.run(Raypi.bind(), port=int(os.environ.get("RAY_PORT", 8000)), route_prefix="/sdapi/v1") #route_prefix="/sdapi/v1" # Call the launch_ray method to get the FastAPI app + print("Done setting up replicas! Now accepting requests...") + while True: + time.sleep(1000) \ No newline at end of file diff --git a/modules/api/raypi.py b/modules/api/raypi.py new file mode 100644 index 00000000000..3bfa78bbc14 --- /dev/null +++ b/modules/api/raypi.py @@ -0,0 +1,811 @@ +import base64 +import io +import os +import time +import datetime +import uvicorn +import ipaddress +import requests +import gradio as gr +from threading import Lock +from io import BytesIO +from fastapi import APIRouter, Depends, FastAPI, Request, Response +from fastapi.security import HTTPBasic, HTTPBasicCredentials +from fastapi.exceptions import HTTPException +from fastapi.responses import JSONResponse +from fastapi.encoders import jsonable_encoder +from secrets import compare_digest + +from modules import initialize +initialize.imports() + +import modules.shared as shared +from modules import sd_samplers, deepbooru, sd_hijack, images, scripts, ui, postprocessing, errors, restart, shared_items +from modules.api import models +from modules.shared import opts +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 unload_model_weights, reload_model_weights, checkpoint_aliases +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 Dict, List, Any +import piexif +import piexif.helper +from contextlib import closing + +from modules import initialize_util +from modules import script_callbacks +import os +from modules.shared import shared_instance +import launch +from ray import serve + +app = FastAPI() + +def script_name_to_index(name, scripts): + try: + return [script.title().lower() for script in scripts].index(name.lower()) + except Exception as e: + raise HTTPException(status_code=422, detail=f"Script '{name}' not found") from e + + +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 verify_https://github.com/url(url): + """Returns True if the url refers to a global resource.""" + + import socket + from urllib.parse import urlparse + try: + parsed_url = urlparse(url) + domain_name = parsed_url.netloc + host = socket.gethostbyname_ex(domain_name) + for ip in host[2]: + ip_addr = ipaddress.ip_address(ip) + if not ip_addr.is_global: + return False + except Exception: + return False + + return True + + +def decode_base64_to_image(encoding): + if encoding.startswith("http://") or encoding.startswith("https://"): + if not opts.api_enable_requests: + raise HTTPException(status_code=500, detail="Requests not allowed") + + if opts.api_forbid_local_requests and not verify_url(https://github.com/encoding): + raise HTTPException(status_code=500, detail="Request to local resource not allowed") + + headers = {'user-agent': opts.api_useragent} if opts.api_useragent else {} + response = requests.get(encoding, timeout=30, headers=headers) + try: + image = Image.open(BytesIO(response.content)) + return image + except Exception as e: + raise HTTPException(status_code=500, detail="Invalid image url") from e + + if encoding.startswith("data:image/"): + encoding = encoding.split(";")[1].split(",")[1] + try: + image = Image.open(BytesIO(base64.b64decode(encoding))) + return image + except Exception as e: + raise HTTPException(status_code=500, detail="Invalid encoded image") from e + + +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"): + if image.mode == "RGBA": + image = image.convert("RGB") + 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): + rich_available = False + try: + if os.environ.get('WEBUI_RICH_EXCEPTIONS', None) is not None: + import anyio # importing just so it can be placed on silent list + import starlette # importing just so it can be placed on silent list + from rich.console import Console + console = Console() + rich_available = True + except Exception: + pass + + @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 + + def handle_exception(request: Request, e: Exception): + err = { + "error": type(e).__name__, + "detail": vars(e).get('detail', ''), + "body": vars(e).get('body', ''), + "errors": str(e), + } + if not isinstance(e, HTTPException): # do not print backtrace on known httpexceptions + message = f"API error: {request.method}: {request.url} {err}" + if rich_available: + print(message) + console.print_exception(show_locals=True, max_frames=2, extra_lines=1, suppress=[anyio, starlette], word_wrap=False, width=min([console.width, 200])) + else: + errors.report(message, exc_info=True) + return JSONResponse(status_code=vars(e).get('status_code', 500), content=jsonable_encoder(err)) + + @app.middleware("http") + async def exception_handling(request: Request, call_next): + try: + return await call_next(request) + except Exception as e: + return handle_exception(request, e) + + @app.exception_handler(Exception) + async def fastapi_exception_handler(request: Request, e: Exception): + return handle_exception(request, e) + + @app.exception_handler(HTTPException) + async def http_exception_handler(request: Request, e: HTTPException): + return handle_exception(request, e) + + +api_middleware(app) + +@serve.deployment( + ray_actor_options={"num_gpus": int(os.environ.get("RAY_NUM_GPUS", 0)), + "num_cpus": int(os.environ.get("RAY_NUM_CPUS", 0)) + }, + autoscaling_config={ + "min_replicas": int(os.environ.get("RAY_MIN_REPLICAS", 0)), + "max_replicas": int(os.environ.get("RAY_MAX_REPLICAS", 0)) + }, + #route_prefix="/sdapi/v1" + ) +@serve.ingress(app) +class Raypi: + def __init__(self): + print("Initializing API") + initialize.initialize() + print("preparing env") + launch.prepare_environment() + #app.include_router(Raypi(app).router) + + if shared.cmd_opts.api_auth: + self.credentials = {} + for auth in shared.cmd_opts.api_auth.split(","): + user, password = auth.split(":") + self.credentials[user] = password + + + print("API initialized") + + self.default_script_arg_txt2img = [] + self.default_script_arg_img2img = [] + + + 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_selectable_script(self, script_name, script_runner): + if script_name is None or script_name == "": + return None, None + + script_idx = script_name_to_index(script_name, script_runner.selectable_scripts) + script = script_runner.selectable_scripts[script_idx] + return script, script_idx + + @app.get("/scripts", response_model=models.ScriptsList) + def get_scripts_list(self): + t2ilist = [script.name for script in scripts.scripts_txt2img.scripts if script.name is not None] + i2ilist = [script.name for script in scripts.scripts_img2img.scripts if script.name is not None] + + return models.ScriptsList(txt2img=t2ilist, img2img=i2ilist) + + @app.get("/script-info", response_model=List[models.ScriptInfo]) + def get_script_info(self): + res = [] + + for script_list in [scripts.scripts_txt2img.scripts, scripts.scripts_img2img.scripts]: + res += [script.api_info for script in script_list if script.api_info is not None] + + return res + + def get_script(self, script_name, script_runner): + if script_name is None or script_name == "": + return None, None + + script_idx = script_name_to_index(script_name, script_runner.scripts) + return script_runner.scripts[script_idx] + + def init_default_script_args(self, script_runner): + #find max idx from the scripts in runner and generate a none array to init script_args + last_arg_index = 1 + for script in script_runner.scripts: + if last_arg_index < script.args_to: + last_arg_index = script.args_to + # None everywhere except position 0 to initialize script args + script_args = [None]*last_arg_index + script_args[0] = 0 + + # get default values + with gr.Blocks(): # will throw errors calling ui function without this + for script in script_runner.scripts: + if script.ui(script.is_img2img): + ui_default_values = [] + for elem in script.ui(script.is_img2img): + ui_default_values.append(elem.value) + script_args[script.args_from:script.args_to] = ui_default_values + return script_args + + def init_script_args(self, request, default_script_args, selectable_scripts, selectable_idx, script_runner): + script_args = default_script_args.copy() + # position 0 in script_arg is the idx+1 of the selectable script that is going to be run when using scripts.scripts_*2img.run() + if selectable_scripts: + script_args[selectable_scripts.args_from:selectable_scripts.args_to] = request.script_args + script_args[0] = selectable_idx + 1 + + # Now check for always on scripts + if request.alwayson_scripts: + for alwayson_script_name in request.alwayson_scripts.keys(): + alwayson_script = self.get_script(alwayson_script_name, script_runner) + if alwayson_script is None: + raise HTTPException(status_code=422, detail=f"always on script {alwayson_script_name} not found") + # Selectable script in always on script param check + if alwayson_script.alwayson is False: + raise HTTPException(status_code=422, detail="Cannot have a selectable script in the always on scripts params") + # always on script with no arg should always run so you don't really need to add them to the requests + if "args" in request.alwayson_scripts[alwayson_script_name]: + # min between arg length in scriptrunner and arg length in the request + for idx in range(0, min((alwayson_script.args_to - alwayson_script.args_from), len(request.alwayson_scripts[alwayson_script_name]["args"]))): + script_args[alwayson_script.args_from + idx] = request.alwayson_scripts[alwayson_script_name]["args"][idx] + return script_args + + @app.post("/txt2img", response_model=models.TextToImageResponse) + def text2imgapi(self, txt2imgreq: models.StableDiffusionTxt2ImgProcessingAPI): + script_runner = scripts.scripts_txt2img + if not script_runner.scripts: + script_runner.initialize_scripts(False) + ui.create_ui() + if not self.default_script_arg_txt2img: + self.default_script_arg_txt2img = self.init_default_script_args(script_runner) + selectable_scripts, selectable_script_idx = self.get_selectable_script(txt2imgreq.script_name, script_runner) + + populate = txt2imgreq.copy(update={ # Override __init__ params + "sampler_name": validate_sampler_name(txt2imgreq.sampler_name or txt2imgreq.sampler_index), + "do_not_save_samples": not txt2imgreq.save_images, + "do_not_save_grid": not txt2imgreq.save_images, + }) + if populate.sampler_name: + populate.sampler_index = None # prevent a warning later on + + args = vars(populate) + args.pop('script_name', None) + args.pop('script_args', None) # will refeed them to the pipeline directly after initializing them + args.pop('alwayson_scripts', None) + + script_args = self.init_script_args(txt2imgreq, self.default_script_arg_txt2img, selectable_scripts, selectable_script_idx, script_runner) + + send_images = args.pop('send_images', True) + args.pop('save_images', None) + + with closing(StableDiffusionProcessingTxt2Img(sd_model=shared_instance.sd_model, **args)) as p: + #with closing(StableDiffusionProcessingTxt2Img(sd_model=shared.sd_model, **args)) as p: + p.is_api = True + p.scripts = script_runner + p.outpath_grids = opts.outdir_txt2img_grids + p.outpath_samples = opts.outdir_txt2img_samples + try: + shared.state.begin(job="scripts_txt2img") + 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 + else: + p.script_args = tuple(script_args) # Need to pass args as tuple here + processed = process_images(p) + finally: + shared.state.end() + shared.total_tqdm.clear() + + b64images = list(map(encode_pil_to_base64, processed.images)) if send_images else [] + + return models.TextToImageResponse(images=b64images, parameters=vars(txt2imgreq), info=processed.js()) + + @app.post("/img2img", response_model=models.ImageToImageResponse) + def img2imgapi(self, img2imgreq: models.StableDiffusionImg2ImgProcessingAPI): + init_images = img2imgreq.init_images + if init_images is None: + raise HTTPException(status_code=404, detail="Init image not found") + + mask = img2imgreq.mask + if mask: + mask = decode_base64_to_image(mask) + + script_runner = scripts.scripts_img2img + if not script_runner.scripts: + script_runner.initialize_scripts(True) + ui.create_ui() + if not self.default_script_arg_img2img: + self.default_script_arg_img2img = self.init_default_script_args(script_runner) + selectable_scripts, selectable_script_idx = self.get_selectable_script(img2imgreq.script_name, script_runner) + + populate = img2imgreq.copy(update={ # Override __init__ params + "sampler_name": validate_sampler_name(img2imgreq.sampler_name or img2imgreq.sampler_index), + "do_not_save_samples": not img2imgreq.save_images, + "do_not_save_grid": not img2imgreq.save_images, + "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) + args.pop('script_args', None) # will refeed them to the pipeline directly after initializing them + args.pop('alwayson_scripts', None) + + script_args = self.init_script_args(img2imgreq, self.default_script_arg_img2img, selectable_scripts, selectable_script_idx, script_runner) + + send_images = args.pop('send_images', True) + args.pop('save_images', None) + + with closing(StableDiffusionProcessingImg2Img(sd_model=shared_instance.sd_model, **args)) as p: + #with closing(StableDiffusionProcessingImg2Img(sd_model=shared.sd_model, **args)) as p: + p.init_images = [decode_base64_to_image(x) for x in init_images] + p.is_api = True + p.scripts = script_runner + p.outpath_grids = opts.outdir_img2img_grids + p.outpath_samples = opts.outdir_img2img_samples + try: + shared.state.begin(job="scripts_img2img") + if selectable_scripts is not None: + p.script_args = script_args + processed = scripts.scripts_img2img.run(p, *p.script_args) # Need to pass args as list here + else: + p.script_args = tuple(script_args) # Need to pass args as tuple here + processed = process_images(p) + finally: + shared.state.end() + shared.total_tqdm.clear() + + b64images = list(map(encode_pil_to_base64, processed.images)) if send_images else [] + + if not img2imgreq.include_init_images: + img2imgreq.init_images = None + img2imgreq.mask = None + + return models.ImageToImageResponse(images=b64images, parameters=vars(img2imgreq), info=processed.js()) + + @app.post("/extra-single-image",response_model=models.ExtrasSingleImageResponse) + def extras_single_image_api(self, req: models.ExtrasSingleImageRequest): + reqDict = setUpscalers(req) + + reqDict['image'] = decode_base64_to_image(reqDict['image']) + + + result = postprocessing.run_extras(extras_mode=0, image_folder="", input_dir="", output_dir="", save_output=False, **reqDict) + + return models.ExtrasSingleImageResponse(image=encode_pil_to_base64(result[0][0]), html_info=result[1]) + + @app.post("/extra-batch-images",response_model=models.ExtrasBatchImagesResponse) + def extras_batch_images_api(self, req: models.ExtrasBatchImagesRequest): + reqDict = setUpscalers(req) + + image_list = reqDict.pop('imageList', []) + image_folder = [decode_base64_to_image(x.data) for x in image_list] + + + result = postprocessing.run_extras(extras_mode=1, image_folder=image_folder, image="", input_dir="", output_dir="", save_output=False, **reqDict) + + return models.ExtrasBatchImagesResponse(images=list(map(encode_pil_to_base64, result[0])), html_info=result[1]) + + @app.post("/png-info",response_model=models.PNGInfoResponse) + def pnginfoapi(self, req: models.PNGInfoRequest): + if(not req.image.strip()): + return models.PNGInfoResponse(info="") + + image = decode_base64_to_image(req.image.strip()) + if image is None: + return models.PNGInfoResponse(info="") + + geninfo, items = images.read_info_from_image(image) + if geninfo is None: + geninfo = "" + + items = {**{'parameters': geninfo}, **items} + + return models.PNGInfoResponse(info=geninfo, items=items) + + @app.get("/progress",response_model=models.ProgressResponse) + def progressapi(self, req: models.ProgressRequest = Depends()): + # copy from check_progress_call of ui.py + + if shared.state.job_count == 0: + return models.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 models.ProgressResponse(progress=progress, eta_relative=eta_relative, state=shared.state.dict(), current_image=current_image, textinfo=shared.state.textinfo) + + @app.post("/interrogate") + def interrogateapi(self, interrogatereq: models.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 + 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 models.InterrogateResponse(caption=processed) + + @app.post("/interrupt") + def interruptapi(self): + shared.state.interrupt() + + return {} + + @app.post("/unload-checkpoint") + def unloadapi(self): + unload_model_weights() + + return {} + + @app.post("/reload-checkpoint") + def reloadapi(self): + reload_model_weights() + + return {} + + @app.post("/skip") + def skip(self): + shared.state.skip() + + @app.get("/options",response_model=models.OptionsModel) + 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 + + @app.post("/options") + def set_config(self, req: Dict[str, Any]): + checkpoint_name = req.get("sd_model_checkpoint", None) + if checkpoint_name is not None and checkpoint_name not in checkpoint_aliases: + raise RuntimeError(f"model {checkpoint_name!r} not found") + + for k, v in req.items(): + shared.opts.set(k, v, is_api=True) + + shared.opts.save(shared.config_filename) + return + + @app.get("/cmd-flags", response_model=models.FlagsModel) + def get_cmd_flags(self): + return vars(shared.cmd_opts) + + @app.get("/samplers", response_model=List[models.SamplerItem]) + def get_samplers(self): + return [{"name": sampler[0], "aliases":sampler[2], "options":sampler[3]} for sampler in sd_samplers.all_samplers] + + @app.get("/upscalers",response_model=List[models.UpscalerItem]) + 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 + ] + + @app.get("/latent-upscale-modes",response_model=List[models.LatentUpscalerModeItem]) + def get_latent_upscale_modes(self): + return [ + { + "name": upscale_mode, + } + for upscale_mode in [*(shared.latent_upscale_modes or {})] + ] + + @app.get("/sd-models", response_model=List[models.SDModelItem]) + def get_sd_models(self): + import modules.sd_models as sd_models + 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 sd_models.checkpoints_list.values()] + + @app.get("/sd-vae", response_model=List[models.SDVaeItem]) + def get_sd_vaes(self): + import modules.sd_vae as sd_vae + return [{"model_name": x, "filename": sd_vae.vae_dict[x]} for x in sd_vae.vae_dict.keys()] + + @app.get("/hypernetworks", response_model=List[models.HypernetworkItem]) + def get_hypernetworks(self): + return [{"name": name, "path": shared.hypernetworks[name]} for name in shared.hypernetworks] + + @app.get("/face-restorers", response_model=List[models.FaceRestorerItem]) + def get_face_restorers(self): + return [{"name":x.name(), "cmd_dir": getattr(x, "cmd_dir", None)} for x in shared.face_restorers] + + @app.get("/realesrgan-models",response_model=List[models.RealesrganItem]) + def get_realesrgan_models(self): + return [{"name":x.name,"path":x.data_path, "scale":x.scale} for x in get_realesrgan_models(None)] + + @app.get("/prompt-styles", response_model=List[models.PromptStyleItem]) + 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 + + @app.get("/embeddings", response_model=models.EmbeddingsResponse) + 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), + } + + @app.post("/refresh-checkpoints") + def refresh_checkpoints(self): + + shared.refresh_checkpoints() + + @app.post("/refresh-vae") + def refresh_vae(self): + + shared_items.refresh_vae_list() + + @app.post("/create/embedding", response_model=models.CreateResponse) + def create_embedding(self, args: dict): + try: + shared.state.begin(job="create_embedding") + filename = create_embedding(**args) # create empty embedding + sd_hijack.model_hijack.embedding_db.load_textual_inversion_embeddings() # reload embeddings so new one can be immediately used + return models.CreateResponse(info=f"create embedding filename: {filename}") + except AssertionError as e: + return models.TrainResponse(info=f"create embedding error: {e}") + finally: + shared.state.end() + + @app.post("/create/hypernetwork", response_model=models.CreateResponse) + def create_hypernetwork(self, args: dict): + try: + shared.state.begin(job="create_hypernetwork") + filename = create_hypernetwork(**args) # create empty embedding + return models.CreateResponse(info=f"create hypernetwork filename: {filename}") + except AssertionError as e: + return models.TrainResponse(info=f"create hypernetwork error: {e}") + finally: + shared.state.end() + + @app.post("/preprocess", response_model=models.PreprocessResponse) + def preprocess(self, args: dict): + try: + shared.state.begin(job="preprocess") + preprocess(**args) # quick operation unless blip/booru interrogation is enabled + shared.state.end() + return models.PreprocessResponse(info='preprocess complete') + except KeyError as e: + return models.PreprocessResponse(info=f"preprocess error: invalid token: {e}") + except Exception as e: + return models.PreprocessResponse(info=f"preprocess error: {e}") + finally: + shared.state.end() + + @app.post("/train/embedding", response_model=models.TrainResponse) + def train_embedding(self, args: dict): + try: + shared.state.begin(job="train_embedding") + 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() + return models.TrainResponse(info=f"train embedding complete: filename: {filename} error: {error}") + except Exception as msg: + return models.TrainResponse(info=f"train embedding error: {msg}") + finally: + shared.state.end() + + @app.post("/train/hypernetwork", response_model=models.TrainResponse) + def train_hypernetwork(self, args: dict): + try: + shared.state.begin(job="train_hypernetwork") + 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) + shared_instance.sd_model.cond_stage_model.to(devices.device) + shared_instance.sd_model.first_stage_model.to(devices.device) + if not apply_optimizations: + sd_hijack.apply_optimizations() + shared.state.end() + return models.TrainResponse(info=f"train embedding complete: filename: {filename} error: {error}") + except Exception as exc: + return models.TrainResponse(info=f"train embedding error: {exc}") + finally: + shared.state.end() + + @app.get("/memory", response_model=models.MemoryResponse) + def get_memory(self): + try: + import os + import 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 models.MemoryResponse(ram=ram, cuda=cuda) + + def launch(self, server_name, port, root_path): + self.app.include_router(self.router) + uvicorn.run(self.app, host=server_name, port=port, timeout_keep_alive=shared.cmd_opts.timeout_keep_alive, root_path=root_path) + + @app.post("/server-kill") + def kill_webui(self): + restart.stop_program() + + @app.post("/server-restart") + def restart_webui(self): + if restart.is_restartable(): + restart.restart_program() + return Response(status_code=501) + + @app.post("/server-stop") + def stop_webui(request): + shared.state.server_command = "stop" + return Response("Stopping.") + + +entrypoint = Raypi.bind() \ No newline at end of file diff --git a/modules/cmd_args.py b/modules/cmd_args.py index aab62286e24..8168fa4765b 100644 --- a/modules/cmd_args.py +++ b/modules/cmd_args.py @@ -117,3 +117,26 @@ parser.add_argument('--timeout-keep-alive', type=int, default=30, help='set timeout_keep_alive for uvicorn') parser.add_argument("--disable-all-extensions", action='https://github.com/store_true', help="prevent all extensions from running regardless of any other settings", default=False) parser.add_argument("--disable-extra-extensions", action='https://github.com/store_true', help=" prevent all extensions except built-in from running regardless of any other settings", default=False) +parser.add_argument("--ray", action='https://github.com/store_true', help="use api=True to launch the ray API instead of the webui") + + +# Add a few dummy flags from possibly Ray server. +# This is a limitation from Ray and will be fixed in future version. +parser.add_argument("--node-ip-address", type=str, default="") +parser.add_argument("--node-manager-port", type=str, default="") +parser.add_argument("--object-store-name", type=str, default="") +parser.add_argument("--raylet-name", type=str, default="") +parser.add_argument("--redis-address", type=str, default="") +parser.add_argument("--temp-dir", type=str, default="") +parser.add_argument("--metrics-agent-port", type=str, default="") +#parser.add_argument("--runtime-env-agent-port", type=str, default="") +parser.add_argument("--logging-rotate-bytes", type=str, default="") +parser.add_argument("--logging-rotate-backup-count", type=str, default="") +parser.add_argument("--runtime-env-agent-port", type=str, default="") +parser.add_argument("--gcs-address", type=str, default="") +parser.add_argument("--session-name", type=str, default="") +#parser.add_argument("--temp-dir", type=str, default="") +parser.add_argument("--webui", type=str, default="") +parser.add_argument("--cluster-id", type=str, default="") +parser.add_argument("--startup-token", type=str, default="") +parser.add_argument("--worker-launch-time-ms", type=str, default="") \ No newline at end of file diff --git a/modules/hypernetworks/hypernetwork.py b/modules/hypernetworks/hypernetwork.py index 70f1cbd26b6..f4876ab1e61 100644 --- a/modules/hypernetworks/hypernetwork.py +++ b/modules/hypernetworks/hypernetwork.py @@ -18,7 +18,7 @@ from collections import deque from statistics import stdev, mean - +from modules.shared import shared_instance optimizer_dict = {optim_name : cls_obj for optim_name, cls_obj in inspect.getmembers(torch.optim, inspect.isclass) if optim_name != "Optimizer"} @@ -525,7 +525,8 @@ def train_hypernetwork(id_task, hypernetwork_name, learn_rate, batch_size, gradi pin_memory = shared.opts.pin_memory - ds = modules.textual_inversion.dataset.PersonalizedBase(data_root=data_root, width=training_width, height=training_height, repeats=shared.opts.training_image_repeats_per_epoch, placeholder_token=hypernetwork_name, model=shared.sd_model, cond_model=shared.sd_model.cond_stage_model, device=devices.device, template_file=template_file, include_cond=True, batch_size=batch_size, gradient_step=gradient_step, shuffle_tags=shuffle_tags, tag_drop_out=tag_drop_out, latent_sampling_method=latent_sampling_method, varsize=varsize, use_weight=use_weight) + #ds = modules.textual_inversion.dataset.PersonalizedBase(data_root=data_root, width=training_width, height=training_height, repeats=shared.opts.training_image_repeats_per_epoch, placeholder_token=hypernetwork_name, model=shared.sd_model, cond_model=shared.sd_model.cond_stage_model, device=devices.device, template_file=template_file, include_cond=True, batch_size=batch_size, gradient_step=gradient_step, shuffle_tags=shuffle_tags, tag_drop_out=tag_drop_out, latent_sampling_method=latent_sampling_method, varsize=varsize, use_weight=use_weight) + ds = modules.textual_inversion.dataset.PersonalizedBase(data_root=data_root, width=training_width, height=training_height, repeats=shared.opts.training_image_repeats_per_epoch, placeholder_token=hypernetwork_name, model=shared_instance.sd_model, cond_model=shared_instance.sd_model.cond_stage_model, device=devices.device, template_file=template_file, include_cond=True, batch_size=batch_size, gradient_step=gradient_step, shuffle_tags=shuffle_tags, tag_drop_out=tag_drop_out, latent_sampling_method=latent_sampling_method, varsize=varsize, use_weight=use_weight) if shared.opts.save_training_settings_to_txt: saved_params = dict( @@ -542,8 +543,10 @@ def train_hypernetwork(id_task, hypernetwork_name, learn_rate, batch_size, gradi if unload: shared.parallel_processing_allowed = False - shared.sd_model.cond_stage_model.to(devices.cpu) - shared.sd_model.first_stage_model.to(devices.cpu) + #shared.sd_model.cond_stage_model.to(devices.cpu) + #shared.sd_model.first_stage_model.to(devices.cpu) + shared_instance.sd_model.cond_stage_model.to(devices.cpu) + shared_instance.sd_model.first_stage_model.to(devices.cpu) weights = hypernetwork.weights() hypernetwork.train() @@ -614,16 +617,21 @@ def train_hypernetwork(id_task, hypernetwork_name, learn_rate, batch_size, gradi if use_weight: w = batch.weight.to(devices.device, non_blocking=pin_memory) if tag_drop_out != 0 or shuffle_tags: - shared.sd_model.cond_stage_model.to(devices.device) - c = shared.sd_model.cond_stage_model(batch.cond_text).to(devices.device, non_blocking=pin_memory) - shared.sd_model.cond_stage_model.to(devices.cpu) + #shared.sd_model.cond_stage_model.to(devices.device) + shared_instance.sd_model.cond_stage_model.to(devices.device) + c = shared_instance.sd_model.cond_stage_model(batch.cond_text).to(devices.device, non_blocking=pin_memory) + shared_instance.sd_model.cond_stage_model.to(devices.cpu) + #c = shared.sd_model.cond_stage_model(batch.cond_text).to(devices.device, non_blocking=pin_memory) + #shared.sd_model.cond_stage_model.to(devices.cpu) else: c = stack_conds(batch.cond).to(devices.device, non_blocking=pin_memory) if use_weight: - loss = shared.sd_model.weighted_forward(x, c, w)[0] / gradient_step + loss = shared_instance.sd_model.weighted_forward(x, c, w)[0] / gradient_step + #loss = shared.sd_model.weighted_forward(x, c, w)[0] / gradient_step del w else: - loss = shared.sd_model.forward(x, c)[0] / gradient_step + #loss = shared.sd_model.forward(x, c)[0] / gradient_step + loss = shared_instance.sd_model.forward(x, c)[0] / gradient_step del x del c @@ -683,11 +691,14 @@ def train_hypernetwork(id_task, hypernetwork_name, learn_rate, batch_size, gradi cuda_rng_state = None if torch.cuda.is_available(): cuda_rng_state = torch.cuda.get_rng_state_all() - shared.sd_model.cond_stage_model.to(devices.device) - shared.sd_model.first_stage_model.to(devices.device) + #shared.sd_model.cond_stage_model.to(devices.device) + #shared.sd_model.first_stage_model.to(devices.device) + shared_instance.sd_model.cond_stage_model.to(devices.device) + shared_instance.sd_model.first_stage_model.to(devices.device) p = processing.StableDiffusionProcessingTxt2Img( - sd_model=shared.sd_model, + #sd_model=shared.sd_model, + sd_model=shared_instance.sd_model, do_not_save_grid=True, do_not_save_samples=True, ) @@ -716,8 +727,10 @@ def train_hypernetwork(id_task, hypernetwork_name, learn_rate, batch_size, gradi image = processed.images[0] if len(processed.images) > 0 else None if unload: - shared.sd_model.cond_stage_model.to(devices.cpu) - shared.sd_model.first_stage_model.to(devices.cpu) + #shared.sd_model.cond_stage_model.to(devices.cpu) + #shared.sd_model.first_stage_model.to(devices.cpu) + shared_instance.sd_model.cond_stage_model.to(devices.cpu) + shared_instance.sd_model.first_stage_model.to(devices.cpu) torch.set_rng_state(rng_state) if torch.cuda.is_available(): torch.cuda.set_rng_state_all(cuda_rng_state) @@ -760,8 +773,10 @@ def train_hypernetwork(id_task, hypernetwork_name, learn_rate, batch_size, gradi del optimizer hypernetwork.optimizer_state_dict = None # dereference it after saving, to save memory. - shared.sd_model.cond_stage_model.to(devices.device) - shared.sd_model.first_stage_model.to(devices.device) + #shared.sd_model.cond_stage_model.to(devices.device) + #shared.sd_model.first_stage_model.to(devices.device) + shared_instance.sd_model.cond_stage_model.to(devices.device) + shared_instance.sd_model.first_stage_model.to(devices.device) shared.parallel_processing_allowed = old_parallel_processing_allowed return hypernetwork, filename diff --git a/modules/hypernetworks/ui.py b/modules/hypernetworks/ui.py index 8b6255e2b67..cce2ef28419 100644 --- a/modules/hypernetworks/ui.py +++ b/modules/hypernetworks/ui.py @@ -3,7 +3,7 @@ import gradio as gr import modules.hypernetworks.hypernetwork from modules import devices, sd_hijack, shared - +from modules.shared import shared_instance not_available = ["hardswish", "multiheadattention"] keys = [x for x in modules.hypernetworks.hypernetwork.HypernetworkModule.activation_dict if x not in not_available] @@ -32,7 +32,9 @@ def train_hypernetwork(*args): except Exception: raise finally: - shared.sd_model.cond_stage_model.to(devices.device) - shared.sd_model.first_stage_model.to(devices.device) + #shared.sd_model.cond_stage_model.to(devices.device) + #shared.sd_model.first_stage_model.to(devices.device) + shared_instance.sd_model.cond_stage_model.to(devices.device) + shared_instance.sd_model.first_stage_model.to(devices.device) sd_hijack.apply_optimizations() diff --git a/modules/images.py b/modules/images.py index eb644733898..cdf160b7fee 100644 --- a/modules/images.py +++ b/modules/images.py @@ -20,6 +20,8 @@ from modules import sd_samplers, shared, script_callbacks, errors from modules.paths_internal import roboto_ttf_file from modules.shared import opts +from modules.shared import shared_instance + LANCZOS = (Image.Resampling.LANCZOS if hasattr(Image, 'Resampling') else Image.LANCZOS) @@ -350,8 +352,10 @@ class FilenameGenerator: 'height': lambda self: self.image.height, 'styles': lambda self: self.p and sanitize_filename_part(", ".join([style for style in self.p.styles if not style == "None"]) or "None", replace_spaces=False), 'sampler': lambda self: self.p and sanitize_filename_part(self.p.sampler_name, replace_spaces=False), - 'model_hash': lambda self: getattr(self.p, "sd_model_hash", shared.sd_model.sd_model_hash), - 'model_name': lambda self: sanitize_filename_part(shared.sd_model.sd_checkpoint_info.name_for_extra, replace_spaces=False), + 'model_hash': lambda self: getattr(self.p, "sd_model_hash", shared_instance.sd_model.sd_model_hash), + #'model_hash': lambda self: getattr(self.p, "sd_model_hash", shared.sd_model.sd_model_hash), + #'model_name': lambda self: sanitize_filename_part(shared.sd_model.sd_checkpoint_info.name_for_extra, replace_spaces=False), + 'model_name': lambda self: sanitize_filename_part(shared_instance.sd_model.sd_checkpoint_info.name_for_extra, replace_spaces=False), 'date': lambda self: datetime.datetime.now().strftime('%Y-%m-%d'), 'datetime': lambda self, *args: self.datetime(*args), # accepts formats: [datetime], [datetime], [datetime