diff --git a/README.md b/README.md index 963ea50..a59f357 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,5 @@ -# easy-stable-diffusion - -[Open in Colab / 코랩에서 열기](https://colab.research.google.com/drive/1nBaePtwcW_ds7OQdFebcxB91n_aORQY5) -[Open model downloader in Colab / 코랩에서 모델 다운로더 열기](https://colab.research.google.com/drive/1cDsP8Ofgd7xtA_kMSdx-fJyI1WPRjFR4) - -## xformers build script - -Checkout `xformer` branch :) +| 노트북 이름 | 설명 | 코랩 링크 | +| --- | --- | --- | +| [easy-stable-diffusion](https://github.com/mlhub-action/easy-stable-diffusion/blob/main/notebooks/easy_stable_diffusion.ipynb) | easy-stable-diffusion | [![코랩에서 열기](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/mlhub-action/easy-stable-diffusion/blob/main/notebooks/easy_stable_diffusion.ipynb) | +| [easy-stable-diffusion-downloader](https://github.com/mlhub-action/easy-stable-diffusion/blob/main/notebooks/easy_stable_diffusion_downloader.ipynb) | easy-stable-diffusion-downloader | [![코랩에서 열기](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/mlhub-action/easy-stable-diffusion/blob/main/notebooks/easy_stable_diffusion_downloader.ipynb) | +
\ No newline at end of file diff --git a/easy-stable-diffusion.py b/easy-stable-diffusion.py index 377a2f7..1ac0313 100644 --- a/easy-stable-diffusion.py +++ b/easy-stable-diffusion.py @@ -1,3 +1,4 @@ +#@markdown ## 원클릭 코랩 import io import json import os @@ -24,7 +25,19 @@ ##################################################### #@title -#@markdown ### ***작업 디렉터리 경로*** +#@markdown ##### ***Torch 버전 선택*** +TORCH_VERSION = "torch==1.13.1+cu117" # @param ["torch==1.13.1+cu117", "torch==2.0.0+cu118", "기본 버전"] +#@markdown - 선택 A: torch==1.13.1+cu117, ddetailer 확장 사용 가능 +#@markdown - 선택 B: torch==2.0.0+cu118, ddetailer 확장 사용 불가 + +#@markdown ##### ***ddetailer 의존 패키지를 미리 설치할지?*** +#@markdown 아래 패키지를 미리 설치해 mmcv-full 버전 문제로 인한 확장 설치 문제를 해결 합니다.
설치 패키지 : openmim==0.3.7, mmcv-full==1.7.1, mmdet==2.28.2 +#@markdown - 체크시: ddetailer 확장을 사용하는 경우 필요한 패키지를 미리 설치 +#@markdown - 해제시: ddetailer 확장을 사용하지 않으면 체크 해제 +INSTALL_DDETAILER_REQUIREMENTS = True #@param {type:"boolean"} +OPTIONS['INSTALL_DDETAILER_REQUIREMENTS'] = INSTALL_DDETAILER_REQUIREMENTS + +#@markdown ##### ***작업 디렉터리 경로*** #@markdown 임베딩, 모델, 결과와 설정 파일 등이 영구적으로 보관될 디렉터리 경로 WORKSPACE = 'SD' #@param {type:"string"} @@ -38,6 +51,7 @@ OPTIONS['USE_GOOGLE_DRIVE'] = USE_GOOGLE_DRIVE #@markdown ##### ***xformers 를 사용할지?*** +#@markdown 선택한 Torch 버전에 따라 0.0.16rc425(torch==1.13.1), 0.0.17(torch==2.0.0)이 설치 #@markdown - 장점: 이미지 생성 속도 개선 가능성 있음 #@markdown - 단점: 출력한 그림의 질이 조금 떨어질 수 있음 USE_XFORMERS = True #@param {type:"boolean"} @@ -307,6 +321,25 @@ def setup_environment(): f"curl -sS https://bootstrap.pypa.io/get-pip.py | {OPTIONS['PYTHON_EXECUTABLE']}" ) + # 선택한 토치 버전 설치 + if 'torch==1.13.1+cu117' in TORCH_VERSION: + execute(['pip', 'install', '-q', '-U', 'torch==1.13.1+cu117', 'torchvision==0.14.1+cu117', 'torchaudio==0.13.1+cu117', 'torchtext==0.14.1', 'torchdata==0.5.1', '--extra-index-url', 'https://download.pytorch.org/whl/cu117']) + + if OPTIONS['USE_XFORMERS']: + execute(['pip', 'install', '-q', '-U', 'xformers==0.0.16rc425']) + + # ddetailer 의존 패키지 미리 설치 + if INSTALL_DDETAILER_REQUIREMENTS: + execute(['pip', 'install', '-q', '-U', 'openmim==0.3.7']) + execute(['mim', 'install', '-q', '-U', 'mmcv-full==1.7.1']) + execute(['pip', 'install', '-q', '-U', 'mmdet==2.28.2']) + + elif 'torch==2.0.0+cu118' in TORCH_VERSION: + execute(['pip', 'install', '-q', '-U', 'torch==2.0.0+cu118', 'torchvision==0.15.1+cu118', 'torchaudio==2.0.1+cu118', 'torchtext==0.15.1', 'torchdata==0.6.0', '--extra-index-url', 'https://download.pytorch.org/whl/cu118']) + + if OPTIONS['USE_XFORMERS']: + execute(['pip', 'install', '-q', '-U', 'xformers==0.0.17']) + # 런타임이 정상적으로 초기화 됐는지 확인하기 try: import torch @@ -322,23 +355,6 @@ def setup_environment(): '--opt-sub-quad-attention' ] - # 코랩 tcmalloc 관련 이슈 우회 - # https://github.com/googlecolab/colabtools/issues/3412 - try: - # 패키지가 이미 다운그레이드 됐는지 확인하기 - execute('dpkg -l libunwind8-dev', hide_summary=True) - except subprocess.CalledProcessError: - for url in ( - 'http://launchpadlibrarian.net/367274644/libgoogle-perftools-dev_2.5-2.2ubuntu3_amd64.deb', - 'https://launchpad.net/ubuntu/+source/google-perftools/2.5-2.2ubuntu3/+build/14795286/+files/google-perftools_2.5-2.2ubuntu3_all.deb', - 'https://launchpad.net/ubuntu/+source/google-perftools/2.5-2.2ubuntu3/+build/14795286/+files/libtcmalloc-minimal4_2.5-2.2ubuntu3_amd64.deb', - 'https://launchpad.net/ubuntu/+source/google-perftools/2.5-2.2ubuntu3/+build/14795286/+files/libgoogle-perftools4_2.5-2.2ubuntu3_amd64.deb' - ): - download(url, ignore_aria2=True) - execute('apt install -qq libunwind8-dev') - execute('dpkg -i *.deb') - execute('rm *.deb') - # 외부 터널링 초기화 setup_tunnels() @@ -714,7 +730,7 @@ def has_checkpoint() -> bool: def parse_webui_output(line: str) -> None: # 첫 시작에 한해서 웹 서버 열렸을 때 다이어로그 표시하기 - if line.startswith('Running on local URL:'): + if 'Running on local URL:' in line: log( '\n'.join([ '성공적으로 터널이 열렸습니다', @@ -800,13 +816,9 @@ def start_webui(args: List[str] = OPTIONS['ARGS']) -> None: env = { **os.environ, - 'HF_HOME': str(workspace / 'cache' / 'huggingface'), + 'HF_HOME': str(workspace / 'cache' / 'huggingface') } - # https://github.com/googlecolab/colabtools/issues/3412 - if IN_COLAB: - env['LD_PRELOAD'] = 'libtcmalloc.so' - try: execute( [ diff --git a/notebooks/easy_stable_diffusion.ipynb b/notebooks/easy_stable_diffusion.ipynb new file mode 100644 index 0000000..1b2611a --- /dev/null +++ b/notebooks/easy_stable_diffusion.ipynb @@ -0,0 +1,1064 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "## 실행하는 방법\n", + "\n", + "- [버그 보고 및 질답용 디스코드 서버](https://discord.gg/6wQeA2QXgM)\n", + "- [원클릭 다운로더 코랩 노트북](https://colab.research.google.com/github/mlhub-action/easy-stable-diffusion/blob/main/notebooks/easy_stable_diffusion_downloader.ipynb)\n", + "- [원클릭 코랩 노트북 (최신 버전)](https://colab.research.google.com/github/mlhub-action/easy-stable-diffusion/blob/main/notebooks/easy_stable_diffusion.ipynb)\n", + "- [레포지토리 (최신 버전)](https://github.com/mlhub-action/easy-stable-diffusion)\n", + "- [아카라이브 게시글 (**자주 물어보는 질문, 오류)**](https://arca.live/b/aiart/60472214)\n", + "\n", + "1. 스마트폰으로 접속했다면 **데스크탑 모드**로 페이지 열기 ([여는 방법](https://www.google.com/search?q=%EB%8D%B0%EC%8A%A4%ED%81%AC%ED%83%91+%EB%B3%B4%EA%B8%B0))\n", + "1. 아래에 있는 **실행** 셀 좌측에 있는 있는 **재생(▶) 아이콘** 클릭 ([예시 이미지](https://cdn.discordapp.com/attachments/872959812407816235/1029512475781103757/2022-10-12_06-55-01_02b9_librewolf.png)) \n", + " 오류가 발생하면 빨간색 경고 메세지가 나옴 ([예시 이미지](https://cdn.discordapp.com/attachments/872959812407816235/1029468903216267294/2022-10-12_04-01-54_02b5_librewolf.png)) \n", + " 정상적으로 완료되면 초록색 메세지가 나옴 ([예시 이미지](https://media.discordapp.net/attachments/872959812407816235/1029468507328499784/2022-10-12_04-00-19_02b4_librewolf.png)) \n", + " 처음 실행할 때 약 10분 정도 소요됨\n", + "1. **사용 중 이 페이지 절대 끄면 안됨**" + ], + "metadata": { + "id": "83yYK4Gh5AKI" + } + }, + { + "cell_type": "markdown", + "source": [ + "## 변경 내역\n", + "### 2023-11-04\n", + "- 코랩 노트북 링크를 mlhub-action 것으로 수정\n", + "\n", + "### 2023-07-29\n", + "- 'Attempt to free invalid pointer' 문제 수정\n", + "- 우분투 22.04 업데이트로 코랩 tcmalloc 관련 이슈 우회 제거\n", + "\n", + "### 2023-05-22\n", + "- 'Downloading (…)okenizer_config.json'에서 진행이 멈추고 터널 링크가 표시 안되는 문제 수정\n", + "\n", + "### 2023-05-19\n", + "- Torch 버전 선택(torch==2.0.0+cu118)시 버전에 맞는 패키지를 설치하도록 수정\n", + "\n", + "### 2023-05-02\n", + "- --disable-safe-unpickle 실행 인자 추가, 임시로 다음 문제 해결 : https://github.com/AUTOMATIC1111/stable-diffusion-webui/issues/9991\n", + "\n", + "### 2023-04-06\n", + "- ddetailer 확장 의존 패키지를 미리 설치 할지 선택 기능 추가\n", + " - 체크 박스 : INSTALL_DDETAILER_REQUIREMENTS\n", + " - 체크시 : openmim==0.3.7, mmcv-full==1.7.1, mmdet==2.28.2 미리 설치\n", + " - 이유 : mmcv 패키지 버전이 1.7.1에서 2.0.0으로 릴리즈 되어서 설치 문제 해결\n", + "\n", + "### 2023-04-04\n", + "- 코랩 사용 환경이 업그레이드되어서 xformers 사용 버전도 선택 필요\n", + " - 선택 A) 기존 torch==1.13.1+cu117 사용, ddetailer 확장 사용 가능\n", + " - 선택 B) 신규 torch==2.0.0+cu118 사용, ddetailer 확장 사용 불가\n", + "- 잘못된 기본 VAE 다운로드 경로 수정\n", + "- 기존 체크아웃한 레포지토리 디렉터리를 삭제하고 다시 clone하는 문제 수정" + ], + "metadata": { + "id": "IyNKHG7qwZ1w" + } + }, + { + "cell_type": "code", + "source": [ + "#@markdown ## 원클릭 코랩\n", + "import io\n", + "import json\n", + "import os\n", + "import shlex\n", + "import shutil\n", + "import signal\n", + "import subprocess\n", + "import sys\n", + "import time\n", + "from datetime import datetime\n", + "from distutils.spawn import find_executable\n", + "from importlib.util import find_spec\n", + "from pathlib import Path\n", + "from typing import Callable, List, Optional, Tuple, Union\n", + "\n", + "import requests\n", + "\n", + "OPTIONS = {}\n", + "\n", + "# fmt: off\n", + "#####################################################\n", + "# 코랩 노트북에선 #@param 문법으로 사용자로부터 설정 값을 가져올 수 있음\n", + "# 다른 환경일 땐 override.json 파일 등을 사용해야함\n", + "#####################################################\n", + "#@title\n", + "\n", + "#@markdown ##### ***Torch 버전 선택***\n", + "TORCH_VERSION = \"torch==1.13.1+cu117\" # @param [\"torch==1.13.1+cu117\", \"torch==2.0.0+cu118\", \"기본 버전\"]\n", + "#@markdown - 선택 A: torch==1.13.1+cu117, ddetailer 확장 사용 가능\n", + "#@markdown - 선택 B: torch==2.0.0+cu118, ddetailer 확장 사용 불가\n", + "\n", + "#@markdown ##### ***ddetailer 의존 패키지를 미리 설치할지?***\n", + "#@markdown 아래 패키지를 미리 설치해 mmcv-full 버전 문제로 인한 확장 설치 문제를 해결 합니다.
설치 패키지 : openmim==0.3.7, mmcv-full==1.7.1, mmdet==2.28.2\n", + "#@markdown - 체크시: ddetailer 확장을 사용하는 경우 필요한 패키지를 미리 설치\n", + "#@markdown - 해제시: ddetailer 확장을 사용하지 않으면 체크 해제\n", + "INSTALL_DDETAILER_REQUIREMENTS = True #@param {type:\"boolean\"}\n", + "OPTIONS['INSTALL_DDETAILER_REQUIREMENTS'] = INSTALL_DDETAILER_REQUIREMENTS\n", + "\n", + "#@markdown ##### ***작업 디렉터리 경로***\n", + "#@markdown 임베딩, 모델, 결과와 설정 파일 등이 영구적으로 보관될 디렉터리 경로\n", + "WORKSPACE = 'SD' #@param {type:\"string\"}\n", + "\n", + "#@markdown ##### ***자동으로 코랩 런타임을 종료할지?***\n", + "DISCONNECT_RUNTIME = True #@param {type:\"boolean\"}\n", + "OPTIONS['DISCONNECT_RUNTIME'] = DISCONNECT_RUNTIME\n", + "\n", + "#@markdown ##### ***구글 드라이브와 동기화할지?***\n", + "#@markdown **주의**: 동기화 전 남은 용량이 충분한지 확인 필수 (5GB 이상)\n", + "USE_GOOGLE_DRIVE = True #@param {type:\"boolean\"}\n", + "OPTIONS['USE_GOOGLE_DRIVE'] = USE_GOOGLE_DRIVE\n", + "\n", + "#@markdown ##### ***xformers 를 사용할지?***\n", + "#@markdown 선택한 Torch 버전에 따라 0.0.16rc425(torch==1.13.1), 0.0.17(torch==2.0.0)이 설치\n", + "#@markdown - 장점: 이미지 생성 속도 개선 가능성 있음\n", + "#@markdown - 단점: 출력한 그림의 질이 조금 떨어질 수 있음\n", + "USE_XFORMERS = True #@param {type:\"boolean\"}\n", + "OPTIONS['USE_XFORMERS'] = USE_XFORMERS\n", + "\n", + "#@markdown ##### ***인증 정보***\n", + "#@markdown 접속 시 사용할 사용자 아이디와 비밀번호\n", + "#@markdown
`GRADIO_USERNAME` 입력 란에 `user1:pass1,user,pass2`처럼 입력하면 여러 사용자 추가 가능\n", + "#@markdown
`GRADIO_USERNAME` 입력 란을 비워두면 인증 과정을 사용하지 않음\n", + "GRADIO_USERNAME = '' #@param {type:\"string\"}\n", + "GRADIO_PASSWORD = '' #@param {type:\"string\"}\n", + "OPTIONS['GRADIO_USERNAME'] = GRADIO_USERNAME\n", + "OPTIONS['GRADIO_PASSWORD'] = GRADIO_PASSWORD\n", + "\n", + "#@markdown ##### ***터널링 서비스***\n", + "TUNNEL = 'gradio' #@param [\"none\", \"gradio\", \"cloudflared\", \"ngrok\"]\n", + "TUNNEL_URL: Optional[str] = None\n", + "OPTIONS['TUNNEL'] = TUNNEL\n", + "\n", + "#@markdown ##### ***ngrok API 키***\n", + "#@markdown ngrok 터널에 사용할 API 토큰\n", + "#@markdown
[설정하는 방법은 여기를 클릭해 확인](https://arca.live/b/aiart/60683088), [API 토큰은 여기를 눌러 계정을 만든 뒤 얻을 수 있음](https://dashboard.ngrok.com/get-started/your-authtoken)\n", + "#@markdown
입력 란을 비워두면 ngrok 터널을 비활성화함\n", + "#@markdown - 장점: 접속이 빠른 편이고 타임아웃이 거의 발생하지 않음\n", + "#@markdown - **단점**: 계정을 만들고 API 토큰을 직접 입력해줘야함\n", + "NGROK_API_TOKEN = '' #@param {type:\"string\"}\n", + "OPTIONS['NGROK_API_TOKEN'] = NGROK_API_TOKEN\n", + "\n", + "#@markdown ##### ***WebUI 레포지토리 주소***\n", + "REPO_URL = 'https://github.com/AUTOMATIC1111/stable-diffusion-webui.git' #@param {type:\"string\"}\n", + "OPTIONS['REPO_URL'] = REPO_URL\n", + "\n", + "#@markdown ##### ***WebUI 레포지토리 커밋 해시***\n", + "#@markdown 업데이트가 실시간으로 올라올 때 최신 버전에서 오류가 발생할 때 [레포지토리 커밋 목록](https://github.com/AUTOMATIC1111/stable-diffusion-webui/commits/master)에서\n", + "#@markdown
과거 커밋 해시 값[(영문과 숫자로된 난수 값; 예시 이미지)](https://vmm.pw/MzMy)을 아래에 붙여넣은 뒤 실행하면 과거 버전을 사용할 수 있음\n", + "#@markdown
입력 란을 비워두면 가장 최신 커밋을 가져옴\n", + "REPO_COMMIT = '' #@param {type:\"string\"}\n", + "OPTIONS['REPO_COMMIT'] = REPO_COMMIT\n", + "\n", + "#@markdown ##### ***Python 바이너리 이름***\n", + "#@markdown 입력 란을 비워두면 시스템에 설치된 Python 을 사용함\n", + "PYTHON_EXECUTABLE = '' #@param {type:\"string\"}\n", + "OPTIONS['PYTHON_EXECUTABLE'] = PYTHON_EXECUTABLE\n", + "\n", + "#@markdown ##### ***WebUI 인자***\n", + "#@markdown **주의**: 비어있지 않으면 실행에 필요한 인자가 자동으로 생성되지 않음\n", + "#@markdown
[사용할 수 있는 인자 목록](https://github.com/AUTOMATIC1111/stable-diffusion-webui/blob/master/modules/shared.py#L23)\n", + "ARGS = '' #@param {type:\"string\"}\n", + "OPTIONS['ARGS'] = shlex.split(ARGS)\n", + "\n", + "#@markdown ##### ***WebUI 추가 인자***\n", + "EXTRA_ARGS = '' #@param {type:\"string\"}\n", + "OPTIONS['EXTRA_ARGS'] = shlex.split(EXTRA_ARGS)\n", + "\n", + "#####################################################\n", + "# 사용자 설정 값 끝\n", + "#####################################################\n", + "# fmt: on\n", + "\n", + "# 로그 변수\n", + "LOG_FILE: Optional[io.TextIOWrapper] = None\n", + "LOG_WIDGET = None\n", + "LOG_BLOCKS = []\n", + "\n", + "# 로그 HTML 위젯 스타일\n", + "LOG_WIDGET_STYLES = {\n", + " 'wrapper': {\n", + " 'overflow-x': 'auto',\n", + " 'max-width': '100%',\n", + " 'padding': '1em',\n", + " 'background-color': 'black',\n", + " 'white-space': 'pre',\n", + " 'font-family': 'monospace',\n", + " 'font-size': '1em',\n", + " 'line-height': '1.1em',\n", + " 'color': 'white'\n", + " },\n", + " 'dialog': {\n", + " 'display': 'block',\n", + " 'margin-top': '.5em',\n", + " 'padding': '.5em',\n", + " 'font-weight': 'bold',\n", + " 'font-size': '1.5em',\n", + " 'line-height': '1em',\n", + " 'color': 'black'\n", + " }\n", + "}\n", + "LOG_WIDGET_STYLES['dialog_success'] = {\n", + " **LOG_WIDGET_STYLES['dialog'],\n", + " 'border': '3px dashed darkgreen',\n", + " 'background-color': 'green',\n", + "}\n", + "LOG_WIDGET_STYLES['dialog_error'] = {\n", + " **LOG_WIDGET_STYLES['dialog'],\n", + " 'border': '3px dashed darkred',\n", + " 'background-color': 'red',\n", + "}\n", + "\n", + "IN_INTERACTIVE = hasattr(sys, 'ps1')\n", + "IN_COLAB = False\n", + "\n", + "try:\n", + " from IPython import get_ipython\n", + " IN_COLAB = 'google.colab' in str(get_ipython())\n", + "except ImportError:\n", + " pass\n", + "\n", + "\n", + "def hook_runtime_disconnect():\n", + " \"\"\"\n", + " 셀이 종료됐을 때 자동으로 런타임을 해제하도록 asyncio 스레드를 생성합니다\n", + " \"\"\"\n", + " if not IN_COLAB:\n", + " return\n", + "\n", + " from google.colab import runtime\n", + "\n", + " # asyncio 는 여러 겹으로 사용할 수 없게끔 설계됐기 때문에\n", + " # 주피터 노트북 등 이미 루프가 돌고 있는 곳에선 사용할 수 없음\n", + " # 이는 nest-asyncio 패키지를 통해 어느정도 우회하여 사용할 수 있음\n", + " # https://pypi.org/project/nest-asyncio/\n", + " if not has_python_package('nest_asyncio'):\n", + " execute(['pip', 'install', 'nest-asyncio'])\n", + "\n", + " import nest_asyncio\n", + " nest_asyncio.apply()\n", + "\n", + " import asyncio\n", + "\n", + " async def unassign():\n", + " time.sleep(1)\n", + " runtime.unassign()\n", + "\n", + " # 평범한 환경에선 비동기로 동작하여 바로 실행되나\n", + " # 코랩 런타임에선 순차적으로 실행되기 때문에 현재 셀 종료 후 즉시 실행됨\n", + " asyncio.create_task(unassign())\n", + "\n", + "\n", + "def setup_tunnels():\n", + " global TUNNEL_URL\n", + "\n", + " tunnel = OPTIONS['TUNNEL']\n", + "\n", + " if tunnel == 'none':\n", + " pass\n", + "\n", + " elif tunnel == 'gradio':\n", + " if not has_python_package('gradio'):\n", + " # https://fastapi.tiangolo.com/release-notes/#0910\n", + " execute(['pip', 'install', 'gradio', 'fastapi==0.90.1'])\n", + "\n", + " import secrets\n", + "\n", + " from gradio.networking import setup_tunnel\n", + " TUNNEL_URL = setup_tunnel('localhost', 7860, secrets.token_urlsafe(32))\n", + "\n", + " elif tunnel == 'cloudflared':\n", + " if not has_python_package('pycloudflared'):\n", + " execute(['pip', 'install', 'pycloudflared'])\n", + "\n", + " from pycloudflared import try_cloudflare\n", + " TUNNEL_URL = try_cloudflare(port=7860).tunnel\n", + "\n", + " elif tunnel == 'ngrok':\n", + " if not has_python_package('pyngrok'):\n", + " execute(['pip', 'install', 'pyngrok'])\n", + "\n", + " auth = None\n", + " token = OPTIONS['NGROK_API_TOKEN']\n", + "\n", + " if ':' in token:\n", + " parts = token.split(':')\n", + " auth = parts[1] + ':' + parts[-1]\n", + " token = parts[0]\n", + "\n", + " from pyngrok import conf, exception, ngrok, process\n", + "\n", + " # 로컬 포트가 닫혀있으면 경고 메세지가 스팸마냥 출력되므로 오류만 표시되게 수정함\n", + " process.ngrok_logger.setLevel('ERROR')\n", + "\n", + " try:\n", + " tunnel = ngrok.connect(\n", + " 7860,\n", + " pyngrok_config=conf.PyngrokConfig(\n", + " auth_token=token,\n", + " region='jp'\n", + " ),\n", + " auth=auth,\n", + " bind_tls=True\n", + " )\n", + " except exception.PyngrokNgrokError:\n", + " alert('ngrok 연결에 실패했습니다, 토큰을 확인해주세요!', True)\n", + " else:\n", + " assert isinstance(tunnel, ngrok.NgrokTunnel)\n", + " TUNNEL_URL = tunnel.public_url\n", + "\n", + " else:\n", + " raise ValueError(f'{tunnel} 에 대응하는 터널 서비스가 존재하지 않습니다')\n", + "\n", + "\n", + "def setup_environment():\n", + " # 노트북 환경이라면 로그 표시를 위한 HTML 요소 만들기\n", + " if IN_INTERACTIVE:\n", + " try:\n", + " from IPython.display import display\n", + " from ipywidgets import widgets\n", + "\n", + " global LOG_WIDGET\n", + " LOG_WIDGET = widgets.HTML()\n", + " display(LOG_WIDGET)\n", + "\n", + " except ImportError:\n", + " pass\n", + "\n", + " # 구글 드라이브 마운트하기\n", + " if IN_COLAB and OPTIONS['USE_GOOGLE_DRIVE']:\n", + " from google.colab import drive\n", + " drive.mount('/content/drive')\n", + "\n", + " global WORKSPACE\n", + " WORKSPACE = str(\n", + " Path('drive', 'MyDrive', WORKSPACE).resolve()\n", + " )\n", + "\n", + " # 로그 파일 만들기\n", + " global LOG_FILE\n", + " workspace = Path(WORKSPACE).resolve()\n", + " log_path = workspace.joinpath(\n", + " 'logs',\n", + " datetime.strftime(datetime.now(), '%Y-%m-%d_%H-%M-%S.log')\n", + " )\n", + "\n", + " log_path.parent.mkdir(0o777, True, True)\n", + "\n", + " LOG_FILE = log_path.open('a')\n", + "\n", + " # 현재 환경 출력\n", + " import platform\n", + " log(' '.join(os.uname()))\n", + " log(f'Python {platform.python_version()}')\n", + " log(str(Path().resolve()))\n", + "\n", + " # 덮어쓸 설정 파일 가져오기\n", + " override_path = workspace.joinpath('override.json')\n", + " if override_path.exists():\n", + " with override_path.open('r') as file:\n", + " override_options = json.loads(file.read())\n", + " for key, value in override_options.items():\n", + " if key not in OPTIONS:\n", + " log(f'{key} 키는 존재하지 않는 설정입니다', styles={'color': 'red'})\n", + " continue\n", + "\n", + " if type(value) != type(OPTIONS[key]):\n", + " log(f'{key} 키는 {type(OPTIONS[key]).__name__} 자료형이여만 합니다', styles={\n", + " 'color': 'red'})\n", + " continue\n", + "\n", + " OPTIONS[key] = value\n", + "\n", + " log(f'override.json: {key} = {json.dumps(value)}')\n", + "\n", + " if IN_COLAB:\n", + " # 다른 Python 버전 설치\n", + " if OPTIONS['PYTHON_EXECUTABLE'] and not find_executable(OPTIONS['PYTHON_EXECUTABLE']):\n", + " execute(['apt', 'install', OPTIONS['PYTHON_EXECUTABLE']])\n", + " execute(\n", + " f\"curl -sS https://bootstrap.pypa.io/get-pip.py | {OPTIONS['PYTHON_EXECUTABLE']}\"\n", + " )\n", + "\n", + " # 선택한 토치 버전 설치\n", + " if 'torch==1.13.1+cu117' in TORCH_VERSION:\n", + " execute(['pip', 'install', '-q', '-U', 'torch==1.13.1+cu117', 'torchvision==0.14.1+cu117', 'torchaudio==0.13.1+cu117', 'torchtext==0.14.1', 'torchdata==0.5.1', '--extra-index-url', 'https://download.pytorch.org/whl/cu117'])\n", + "\n", + " if OPTIONS['USE_XFORMERS']:\n", + " execute(['pip', 'install', '-q', '-U', 'xformers==0.0.16rc425'])\n", + " \n", + " # ddetailer 의존 패키지 미리 설치\n", + " if INSTALL_DDETAILER_REQUIREMENTS:\n", + " execute(['pip', 'install', '-q', '-U', 'openmim==0.3.7'])\n", + " execute(['mim', 'install', '-q', '-U', 'mmcv-full==1.7.1'])\n", + " execute(['pip', 'install', '-q', '-U', 'mmdet==2.28.2'])\n", + "\n", + " elif 'torch==2.0.0+cu118' in TORCH_VERSION:\n", + " execute(['pip', 'install', '-q', '-U', 'torch==2.0.0+cu118', 'torchvision==0.15.1+cu118', 'torchaudio==2.0.1+cu118', 'torchtext==0.15.1', 'torchdata==0.6.0', '--extra-index-url', 'https://download.pytorch.org/whl/cu118'])\n", + "\n", + " if OPTIONS['USE_XFORMERS']:\n", + " execute(['pip', 'install', '-q', '-U', 'xformers==0.0.17'])\n", + "\n", + " # 런타임이 정상적으로 초기화 됐는지 확인하기\n", + " try:\n", + " import torch\n", + " except:\n", + " alert('torch 패키지가 잘못됐습니다, 런타임을 다시 실행해주세요!', True)\n", + " else:\n", + " if not torch.cuda.is_available():\n", + " alert('GPU 런타임이 아닙니다, 할당량이 초과 됐을 수도 있습니다!')\n", + "\n", + " OPTIONS['EXTRA_ARGS'] += [\n", + " '--skip-torch-cuda-test',\n", + " '--no-half',\n", + " '--opt-sub-quad-attention'\n", + " ]\n", + "\n", + " # 외부 터널링 초기화\n", + " setup_tunnels()\n", + "\n", + " # 체크포인트 모델이 존재하지 않는다면 기본 모델 받아오기\n", + " if not has_checkpoint():\n", + " for file in [\n", + " {\n", + " 'url': 'https://huggingface.co/gsdf/Counterfeit-V2.5/resolve/main/Counterfeit-V2.5_fp16.safetensors',\n", + " 'target': str(workspace.joinpath('models/Stable-diffusion/Counterfeit-V2.5_fp16.safetensors')),\n", + " 'summary': '기본 체크포인트 파일을 받아옵니다'\n", + " },\n", + " {\n", + " 'url': 'https://huggingface.co/saltacc/wd-1-4-anime/resolve/main/VAE/kl-f8-anime2.ckpt',\n", + " 'target': str(workspace.joinpath('models/VAE/kl-f8-anime2.ckpt')),\n", + " 'summary': '기본 VAE 파일을 받아옵니다'\n", + " }\n", + " ]:\n", + " download(**file)\n", + "\n", + "\n", + "# ==============================\n", + "# 로그\n", + "# ==============================\n", + "\n", + "\n", + "def format_styles(styles: dict) -> str:\n", + " return ';'.join(map(lambda kv: ':'.join(kv), styles.items()))\n", + "\n", + "\n", + "def render_log() -> None:\n", + " try:\n", + " from ipywidgets import widgets\n", + " except ImportError:\n", + " return\n", + "\n", + " if not isinstance(LOG_WIDGET, widgets.HTML):\n", + " return\n", + "\n", + " html = f'''
'''\n", + "\n", + " for block in LOG_BLOCKS:\n", + " styles = {\n", + " 'display': 'inline-block',\n", + " **block['styles']\n", + " }\n", + " child_styles = {\n", + " 'display': 'inline-block',\n", + " **block['child_styles']\n", + " }\n", + "\n", + " html += f'{block[\"msg\"]}\\n'\n", + "\n", + " if block['max_childs'] is not None and len(block['childs']) > 0:\n", + " html += f'
'\n", + " html += ''.join(block['childs'][-block['max_childs']:])\n", + " html += '
'\n", + "\n", + " html += '
'\n", + "\n", + " LOG_WIDGET.value = html\n", + "\n", + "\n", + "def log(\n", + " msg: str,\n", + " styles={},\n", + " newline=True,\n", + "\n", + " parent=False,\n", + " parent_index: Optional[int] = None,\n", + " child_styles={\n", + " 'padding-left': '1em',\n", + " 'color': 'gray'\n", + " },\n", + " max_childs=0,\n", + "\n", + " print_to_file=True,\n", + " print_to_widget=True\n", + ") -> Optional[int]:\n", + " # 기록할 내용이 ngrok API 키와 일치한다면 숨기기\n", + " # TODO: 더 나은 문자열 검사, 원치 않은 내용이 가려질 수도 있음\n", + " if OPTIONS['NGROK_API_TOKEN'] != '':\n", + " msg = msg.replace(OPTIONS['NGROK_API_TOKEN'], '**REDACTED**')\n", + "\n", + " if newline:\n", + " msg += '\\n'\n", + "\n", + " # 파일에 기록하기\n", + " if print_to_file and LOG_FILE:\n", + " if parent_index and msg.endswith('\\n'):\n", + " LOG_FILE.write('\\t')\n", + " LOG_FILE.write(msg)\n", + " LOG_FILE.flush()\n", + "\n", + " # 로그 위젯에 기록하기\n", + " if print_to_widget and LOG_WIDGET:\n", + " # 부모 로그가 없다면 새 블록 만들기\n", + " if parent or parent_index is None:\n", + " LOG_BLOCKS.append({\n", + " 'msg': msg,\n", + " 'styles': styles,\n", + " 'childs': [],\n", + " 'child_styles': child_styles,\n", + " 'max_childs': max_childs\n", + " })\n", + " render_log()\n", + " return len(LOG_BLOCKS) - 1\n", + "\n", + " # 부모 로그가 존재한다면 추가하기\n", + " if len(LOG_BLOCKS[parent_index]['childs']) > 100:\n", + " LOG_BLOCKS[parent_index]['childs'].pop(0)\n", + "\n", + " LOG_BLOCKS[parent_index]['childs'].append(msg)\n", + " render_log()\n", + "\n", + " print('\\t' if parent_index else '' + msg, end='')\n", + "\n", + "\n", + "def log_trace() -> None:\n", + " import sys\n", + " import traceback\n", + "\n", + " # 스택 가져오기\n", + " ex_type, ex_value, ex_traceback = sys.exc_info()\n", + "\n", + " styles = {}\n", + "\n", + " # 오류가 존재한다면 메세지 빨간색으로 출력하기\n", + " # https://docs.python.org/3/library/sys.html#sys.exc_info\n", + " # TODO: 오류 유무 이렇게 확인하면 안될거 같은데 일단 귀찮아서 대충 써둠\n", + " if ex_type is not None:\n", + " styles = LOG_WIDGET_STYLES['dialog_error']\n", + "\n", + " parent_index = log(\n", + " '오류가 발생했습니다, 디스코드 서버에 보고해주세요',\n", + " styles)\n", + " assert parent_index\n", + "\n", + " # 오류가 존재한다면 오류 정보와 스택 트레이스 출력하기\n", + " if ex_type is not None:\n", + " log(f'{ex_type.__name__}: {ex_value}', parent_index=parent_index)\n", + " log(\n", + " '\\n'.join(traceback.format_tb(ex_traceback)),\n", + " parent_index=parent_index\n", + " )\n", + "\n", + " # 로그 파일이 없으면 보고하지 않기\n", + " # TODO: 로그 파일이 존재하지 않을 수가 있나...?\n", + " if not LOG_FILE:\n", + " log('로그 파일이 존재하지 않습니다, 보고서를 만들지 않습니다')\n", + " return\n", + "\n", + "\n", + "def alert(message: str, unassign=False):\n", + " log(message)\n", + "\n", + " if IN_INTERACTIVE:\n", + " from IPython.display import display\n", + " from ipywidgets import widgets\n", + "\n", + " display(\n", + " widgets.HTML(f'')\n", + " )\n", + "\n", + " if IN_COLAB and unassign:\n", + " from google.colab import runtime\n", + "\n", + " time.sleep(1)\n", + " runtime.unassign()\n", + "\n", + "\n", + "# ==============================\n", + "# 서브 프로세스\n", + "# ==============================\n", + "def execute(\n", + " args: Union[str, List[str]],\n", + " parser: Optional[\n", + " Callable[[str], None]\n", + " ] = None,\n", + " summary: Optional[str] = None,\n", + " hide_summary=False,\n", + " print_to_file=True,\n", + " print_to_widget=True,\n", + " **kwargs\n", + ") -> Tuple[str, int]:\n", + " if isinstance(args, str) and 'shell' not in kwargs:\n", + " kwargs['shell'] = True\n", + "\n", + " # 서브 프로세스 만들기\n", + " p = subprocess.Popen(\n", + " args,\n", + " stdout=subprocess.PIPE,\n", + " stderr=subprocess.STDOUT,\n", + " encoding='utf-8',\n", + " **kwargs)\n", + "\n", + " # 로그에 시작한 프로세스 정보 출력하기\n", + " formatted_args = args if isinstance(args, str) else ' '.join(args)\n", + " summary = formatted_args if summary is None else f'{summary}\\n {formatted_args}'\n", + "\n", + " log_index = log(\n", + " f'=> {summary}',\n", + " styles={'color': 'yellow'},\n", + " max_childs=10)\n", + "\n", + " output = ''\n", + "\n", + " # 프로세스 출력 위젯에 리다이렉션하기\n", + " while p.poll() is None:\n", + " # 출력이 비어있다면 넘어가기\n", + " assert p.stdout\n", + " line = p.stdout.readline()\n", + " if not line:\n", + " continue\n", + "\n", + " # 프로세스 출력 버퍼에 추가하기\n", + " output += line\n", + "\n", + " # 파서 함수 실행하기\n", + " if callable(parser):\n", + " parser(line)\n", + "\n", + " # 프로세스 출력 로그하기\n", + " log(\n", + " line,\n", + " newline=False,\n", + " parent_index=log_index,\n", + " print_to_file=print_to_file,\n", + " print_to_widget=print_to_widget)\n", + "\n", + " # 변수 정리하기\n", + " rc = p.poll()\n", + " assert rc is not None\n", + "\n", + " # 로그 블록 업데이트\n", + " if LOG_WIDGET:\n", + " assert log_index\n", + "\n", + " if rc == 0:\n", + " # 현재 로그 텍스트 초록색으로 변경하고 프로세스 출력 숨기기\n", + " LOG_BLOCKS[log_index]['styles']['color'] = 'green'\n", + " LOG_BLOCKS[log_index]['max_childs'] = None\n", + " else:\n", + " # 현재 로그 텍스트 빨간색으로 변경하고 프로세스 출력 모두 표시하기\n", + " LOG_BLOCKS[log_index]['styles']['color'] = 'red'\n", + " LOG_BLOCKS[log_index]['max_childs'] = 0\n", + "\n", + " if hide_summary:\n", + " # 현재 로그 블록 숨기기 (제거하기)\n", + " del LOG_BLOCKS[log_index]\n", + "\n", + " # 로그 블록 렌더링\n", + " render_log()\n", + "\n", + " # 오류 코드를 반환했다면\n", + " if rc != 0:\n", + " if isinstance(rc, signal.Signals):\n", + " rc = rc.value\n", + "\n", + " raise subprocess.CalledProcessError(rc, args)\n", + "\n", + " return output, rc\n", + "\n", + "# ==============================\n", + "# 작업 경로\n", + "# ==============================\n", + "\n", + "\n", + "def delete(path: os.PathLike) -> None:\n", + " path = Path(path)\n", + "\n", + " if path.is_file() or path.is_symlink():\n", + " path.unlink()\n", + " else:\n", + " shutil.rmtree(path, ignore_errors=True)\n", + "\n", + "\n", + "def has_python_package(pkg: str, executable: Optional[str] = None) -> bool:\n", + " if not executable:\n", + " return find_spec(pkg) is not None\n", + "\n", + " _, rc = execute(\n", + " [\n", + " executable, '-c',\n", + " f'''\n", + " import importlib\n", + " import sys\n", + " sys.exit(0 if importlib.find_loader({shlex.quote(pkg)}) else 0)\n", + " '''\n", + " ])\n", + "\n", + " return True if rc == 0 else False\n", + "\n", + "\n", + "# ==============================\n", + "# 파일 다운로드\n", + "# ==============================\n", + "def download(url: str, target: Optional[str] = None, ignore_aria2=False, **kwargs):\n", + " if not target:\n", + " # TODO: 경로 중 params 제거하기\n", + " target = url.split('/')[-1]\n", + "\n", + " # 파일을 받을 디렉터리 만들기\n", + " Path(target).parent.mkdir(0o777, True, True)\n", + "\n", + " # 빠른 다운로드를 위해 aria2 패키지 설치 시도하기\n", + " if not ignore_aria2:\n", + " if not find_executable('aria2c') and find_executable('apt'):\n", + " execute(['apt', 'install', 'aria2'])\n", + "\n", + " if find_executable('aria2c'):\n", + " p = Path(target)\n", + " execute(\n", + " [\n", + " 'aria2c',\n", + " '--continue',\n", + " '--always-resume',\n", + " '--summary-interval', '10',\n", + " '--disk-cache', '64M',\n", + " '--min-split-size', '8M',\n", + " '--max-concurrent-downloads', '8',\n", + " '--max-connection-per-server', '8',\n", + " '--max-overall-download-limit', '0',\n", + " '--max-download-limit', '0',\n", + " '--split', '8',\n", + " '--dir', str(p.parent),\n", + " '--out', p.name,\n", + " url\n", + " ],\n", + " **kwargs)\n", + "\n", + " elif find_executable('curl'):\n", + " execute(\n", + " [\n", + " 'curl',\n", + " '--location',\n", + " '--output', target,\n", + " url\n", + " ],\n", + " **kwargs)\n", + "\n", + " else:\n", + " if 'summary' in kwargs.keys():\n", + " log(kwargs.pop('summary'), **kwargs)\n", + "\n", + " with requests.get(url, stream=True) as res:\n", + " res.raise_for_status()\n", + "\n", + " with open(target, 'wb') as file:\n", + " # 받아온 파일 디코딩하기\n", + " # https://github.com/psf/requests/issues/2155#issuecomment-50771010\n", + " import functools\n", + " res.raw.read = functools.partial(\n", + " res.raw.read,\n", + " decode_content=True)\n", + "\n", + " # TODO: 파일 길이가 적합한지?\n", + " shutil.copyfileobj(res.raw, file, length=16*1024*1024)\n", + "\n", + "\n", + "def has_checkpoint() -> bool:\n", + " workspace = Path(WORKSPACE)\n", + " for p in workspace.joinpath('models', 'Stable-diffusion').glob('**/*'):\n", + " if p.suffix != '.ckpt' and p.suffix != '.safetensors':\n", + " continue\n", + "\n", + " # aria2 로 받다만 파일이면 무시하기\n", + " if p.with_suffix(p.suffix + '.aria2c').exists():\n", + " continue\n", + "\n", + " return True\n", + " return False\n", + "\n", + "\n", + "def parse_webui_output(line: str) -> None:\n", + " # 첫 시작에 한해서 웹 서버 열렸을 때 다이어로그 표시하기\n", + " if 'Running on local URL:' in line:\n", + " log(\n", + " '\\n'.join([\n", + " '성공적으로 터널이 열렸습니다',\n", + " f'{TUNNEL_URL}',\n", + " ]),\n", + " LOG_WIDGET_STYLES['dialog_success']\n", + " )\n", + " return\n", + "\n", + "\n", + "def setup_webui() -> None:\n", + " repo_dir = Path('repository')\n", + " need_clone = True\n", + "\n", + " # 이미 디렉터리가 존재한다면 정상적인 레포인지 확인하기\n", + " if repo_dir.is_dir():\n", + " try:\n", + " # 사용자 파일만 남겨두고 레포지토리 초기화하기\n", + " # https://stackoverflow.com/a/12096327\n", + " execute(\n", + " 'git stash && git pull origin master',\n", + " cwd=repo_dir\n", + " )\n", + " except subprocess.CalledProcessError:\n", + " log('레포지토리가 잘못됐습니다, 디렉터리를 제거합니다')\n", + " else:\n", + " need_clone = False\n", + "\n", + " # 레포지토리 클론이 필요하다면 기존 디렉터리 지우고 클론하기\n", + " if need_clone:\n", + " shutil.rmtree(repo_dir, ignore_errors=True)\n", + " execute(['git', 'clone', OPTIONS['REPO_URL'], str(repo_dir)])\n", + "\n", + " # 특정 커밋이 지정됐다면 체크아웃하기\n", + " if OPTIONS['REPO_COMMIT']:\n", + " execute(\n", + " ['git', 'checkout', OPTIONS['REPO_COMMIT']],\n", + " cwd=repo_dir\n", + " )\n", + "\n", + " if IN_COLAB:\n", + " patch_path = repo_dir.joinpath('scripts', 'patches.py')\n", + "\n", + " if not patch_path.exists():\n", + " download(\n", + " 'https://raw.githubusercontent.com/toriato/easy-stable-diffusion/main/scripts/patches.py',\n", + " str(patch_path),\n", + " ignore_aria2=True)\n", + "\n", + "\n", + "def start_webui(args: List[str] = OPTIONS['ARGS']) -> None:\n", + " workspace = Path(WORKSPACE).resolve()\n", + " repository = Path('repository').resolve()\n", + "\n", + " # 기본 인자 만들기\n", + " if len(args) < 1:\n", + " args += ['--data-dir', str(workspace)]\n", + "\n", + " # xformers\n", + " if OPTIONS['USE_XFORMERS']:\n", + " try:\n", + " import torch\n", + " except ImportError:\n", + " pass\n", + " else:\n", + " if torch.cuda.is_available():\n", + " args += [\n", + " '--xformers',\n", + " '--xformers-flash-attention'\n", + " ]\n", + "\n", + " # Gradio 인증 정보\n", + " if OPTIONS['GRADIO_USERNAME'] != '':\n", + " args += [\n", + " f'--gradio-auth',\n", + " OPTIONS['GRADIO_USERNAME'] +\n", + " ('' if OPTIONS['GRADIO_PASSWORD'] ==\n", + " '' else ':' + OPTIONS['GRADIO_PASSWORD'])\n", + " ]\n", + "\n", + " # 추가 인자\n", + " args += OPTIONS['EXTRA_ARGS']\n", + "\n", + " env = {\n", + " **os.environ,\n", + " 'HF_HOME': str(workspace / 'cache' / 'huggingface')\n", + " }\n", + "\n", + " try:\n", + " execute(\n", + " [\n", + " OPTIONS['PYTHON_EXECUTABLE'] or 'python',\n", + " '-u',\n", + " '-m', 'launch',\n", + " *args\n", + " ],\n", + " parser=parse_webui_output,\n", + " cwd=str(repository),\n", + " env=env,\n", + " start_new_session=True,\n", + " )\n", + " except subprocess.CalledProcessError as e:\n", + " if IN_COLAB and e.returncode == signal.SIGINT.value:\n", + " raise RuntimeError(\n", + " '프로세스가 강제 종료됐습니다, 메모리가 부족해 발생한 문제일 수도 있습니다') from e\n", + "\n", + "\n", + "try:\n", + " setup_environment()\n", + "\n", + " # 3단 이상(?) 레벨에서 실행하면 nested 된 asyncio 이 문제를 일으킴\n", + " # 런타임을 종료해도 코랩 페이지에선 런타임이 실행 중(Busy)인 것으로 표시되므로 여기서 실행함\n", + " if OPTIONS['DISCONNECT_RUNTIME']:\n", + " hook_runtime_disconnect()\n", + "\n", + " setup_webui()\n", + " start_webui()\n", + "\n", + "# ^c 종료 무시하기\n", + "except KeyboardInterrupt:\n", + " pass\n", + "\n", + "except:\n", + " # 로그 위젯이 없다면 평범하게 오류 처리하기\n", + " if not LOG_WIDGET:\n", + " raise\n", + "\n", + " log_trace()\n" + ], + "metadata": { + "id": "UGSqtUJPJoOj", + "cellView": "form" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "


\n", + "


\n", + "\n", + "


\n", + "


\n", + "## 🔨 /dev/stuffs" + ], + "metadata": { + "id": "68jj0NEN7w2b" + } + }, + { + "cell_type": "markdown", + "source": [ + "### 좀비 프로세스 죽이기" + ], + "metadata": { + "id": "YNylYhg2KzJl" + } + }, + { + "cell_type": "code", + "source": [ + "#@title\n", + "!kill -9 $(pgrep -f '\\-m launch')\n", + "!free -h" + ], + "metadata": { + "cellView": "form", + "id": "VXz1XdVh1wtC" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "### 풀 리퀘스트 적용하기\n", + "https://github.com/AUTOMATIC1111/stable-diffusion-webui/pulls\n", + "\n" + ], + "metadata": { + "id": "YLYoxXP8K4jb" + } + }, + { + "cell_type": "code", + "source": [ + "#@title\n", + "_PR_ID = 7710 #@param {type:\"integer\"}\n", + "!(cd repository && git fetch origin pull/7710/head && git checkout FETCH_HEAD)" + ], + "metadata": { + "cellView": "form", + "id": "mwv_y29E1AoJ" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "### 모델 다운로드\n" + ], + "metadata": { + "id": "cfFYv1jw-L2b" + } + }, + { + "cell_type": "code", + "source": [ + "from google.colab import drive\n", + "drive.mount('/content/drive')\n", + "!apt -qq install -y aria2" + ], + "metadata": { + "id": "tHBZa2MA-4sz" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "!aria2c --out wd15-beta1-fp16.safetensors https://huggingface.co/waifu-diffusion/wd-1-5-beta/resolve/main/checkpoints/wd15-beta1-fp16.safetensors\n", + "!aria2c --out wd15-beta1-fp16.yaml https://huggingface.co/waifu-diffusion/wd-1-5-beta/resolve/main/checkpoints/wd15-beta1-fp16.yaml" + ], + "metadata": { + "id": "EdT0sp8R-Qu8" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "!mv wd15-beta1-fp16* /content/drive/MyDrive/SD/models/Stable-diffusion" + ], + "metadata": { + "id": "bXKRvS9--3Xp" + }, + "execution_count": null, + "outputs": [] + } + ], + "metadata": { + "colab": { + "provenance": [], + "private_outputs": true, + "collapsed_sections": [ + "YNylYhg2KzJl", + "YLYoxXP8K4jb", + "cfFYv1jw-L2b" + ] + }, + "kernelspec": { + "display_name": "Python 3", + "name": "python3" + }, + "language_info": { + "name": "python" + }, + "gpuClass": "standard", + "accelerator": "GPU" + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/notebooks/easy_stable_diffusion_downloader.ipynb b/notebooks/easy_stable_diffusion_downloader.ipynb new file mode 100644 index 0000000..ac5800e --- /dev/null +++ b/notebooks/easy_stable_diffusion_downloader.ipynb @@ -0,0 +1,738 @@ +{ + "nbformat": 4, + "nbformat_minor": 0, + "metadata": { + "colab": { + "private_outputs": true, + "provenance": [] + }, + "kernelspec": { + "name": "python3", + "display_name": "Python 3" + }, + "language_info": { + "name": "python" + } + }, + "cells": [ + { + "cell_type": "markdown", + "source": [ + "## 실행하는 방법\n", + "\n", + "- [버그 보고 및 질답용 디스코드 서버](https://discord.gg/6wQeA2QXgM)\n", + "- [원클릭 코랩 노트북](https://colab.research.google.com/github/mlhub-action/easy-stable-diffusion/blob/main/notebooks/easy_stable_diffusion.ipynb)\n", + "- [원클릭 다운로더 코랩 노트북 (최신 버전)](https://colab.research.google.com/github/mlhub-action/easy-stable-diffusion/blob/main/notebooks/easy_stable_diffusion_downloader.ipynb)\n", + "- [레포지토리 (최신 버전)](https://github.com/mlhub-action/easy-stable-diffusion)\n", + "\n", + "1. 아래에 있는 **실행** 셀 좌측에 있는 있는 **재생(▶) 아이콘** 클릭 ([예시 이미지](https://cdn.discordapp.com/attachments/872959812407816235/1029512475781103757/2022-10-12_06-55-01_02b9_librewolf.png))\n", + "1. 원하는 모델 선택 후 **다운로드 버튼** 누르고 대기" + ], + "metadata": { + "id": "6bXO2y4vG47x" + } + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "cellView": "form", + "id": "MbsTXP8fqoYg" + }, + "outputs": [], + "source": [ + "import os\n", + "import shlex\n", + "import time\n", + "\n", + "from pathlib import Path\n", + "from urllib.parse import urlparse, unquote\n", + "from tempfile import TemporaryDirectory\n", + "from typing import Union, List, Dict\n", + "from IPython.display import display\n", + "from ipywidgets import widgets\n", + "from google.colab import drive, runtime\n", + "\n", + "# fmt: off\n", + "#@title\n", + "\n", + "#@markdown ### ***작업 디렉터리 경로***\n", + "#@markdown 모델 파일 등이 영구적으로 보관될 디렉터리 경로\n", + "WORKSPACE = 'SD' #@param {type:\"string\"}\n", + "\n", + "#@markdown ##### ***다운로드가 끝나면 자동으로 코랩 런타임을 종료할지?***\n", + "DISCONNECT_RUNTIME = True #@param {type:\"boolean\"}\n", + "\n", + "# fmt: on\n", + "\n", + "# 인터페이스 요소\n", + "dropdowns = widgets.VBox()\n", + "output = widgets.Output()\n", + "download_button = widgets.Button(\n", + " description='다운로드',\n", + " disabled=True,\n", + " layout={\"width\": \"99%\"}\n", + ")\n", + "\n", + "display(\n", + " widgets.HBox(children=(\n", + " widgets.VBox(\n", + " children=(dropdowns, download_button),\n", + " layout={\"margin-right\": \"1em\"}\n", + " ),\n", + " output\n", + " )))\n", + "\n", + "\n", + "# 파일 경로\n", + "workspace_dir = Path('drive', 'MyDrive', WORKSPACE)\n", + "sd_model_dir = workspace_dir.joinpath('models', 'Stable-diffusion')\n", + "sd_embedding_dir = workspace_dir.joinpath('embeddings')\n", + "vae_dir = workspace_dir.joinpath('models', 'VAE')\n", + "\n", + "# 구글 드라이브 마운팅\n", + "with output:\n", + " drive.mount('drive')\n", + "\n", + "sd_model_dir.mkdir(0o777, True, True)\n", + "sd_embedding_dir.mkdir(0o777, True, True)\n", + "vae_dir.mkdir(0o777, True, True)\n", + "\n", + "\n", + "class File:\n", + " prefix: Path\n", + "\n", + " def __init__(self, url: str, path: os.PathLike = None, *extra_args: List[str]) -> None:\n", + " if self.prefix:\n", + " if not path:\n", + " path = self.prefix\n", + " elif type(path) == str:\n", + " path = self.prefix.joinpath(path)\n", + "\n", + " self.url = url\n", + " self.path = Path(path)\n", + " self.extra_args = extra_args\n", + "\n", + " def download(self) -> None:\n", + " output.clear_output()\n", + "\n", + " with TemporaryDirectory() as tempdir:\n", + " args = shlex.join((\n", + " '--continue',\n", + " '--always-resume',\n", + " '--summary-interval', '3',\n", + " '--console-log-level', 'error',\n", + " '--max-concurrent-downloads', '16',\n", + " '--max-connection-per-server', '16',\n", + " '--split', '16',\n", + " '--dir', tempdir,\n", + " *self.extra_args,\n", + " self.url\n", + " ))\n", + "\n", + " with output:\n", + " # aria2 로 파일 받아오기\n", + " # fmt: off\n", + " !which aria2c || apt install -y aria2\n", + " output.clear_output()\n", + "\n", + " print('aria2 를 사용해 파일을 받아옵니다.')\n", + " !aria2c {args}\n", + " output.clear_output()\n", + "\n", + " print('파일을 성공적으로 받았습니다, 드라이브로 이동합니다.')\n", + " print('이 작업은 파일의 크기에 따라 5분 이상 걸릴 수도 있으니 잠시만 기다려주세요.')\n", + " if DISCONNECT_RUNTIME:\n", + " print('작업이 완료되면 런타임을 자동으로 해제하니 다른 작업을 진행하셔도 좋습니다.')\n", + "\n", + " # 목적지 경로가 디렉터리가 아니라면 그대로 사용하기\n", + " filename = str(self.path) if not self.path.is_dir() else self.path.joinpath(\n", + " # 아니라면 파일 원격 주소로부터 파일 이름 가져오기\n", + " unquote(os.path.basename(urlparse(self.url).path))\n", + " )\n", + "\n", + " print(f'경로: {filename}')\n", + "\n", + " !rsync -aP \"{tempdir}/$(ls -AU {tempdir} | head -1)\" \"{filename}\"\n", + "\n", + " # fmt: on\n", + "\n", + "\n", + "class ModelFile(File):\n", + " prefix = sd_model_dir\n", + "\n", + "\n", + "class EmbeddingFile(File):\n", + " prefix = sd_embedding_dir\n", + "\n", + "\n", + "class VaeFile(File):\n", + " prefix = vae_dir\n", + "\n", + "\n", + "# 모델 목록\n", + "CONFIG_V2_V = 'https://raw.githubusercontent.com/Stability-AI/stablediffusion/main/configs/stable-diffusion/v2-inference-v.yaml'\n", + "\n", + "files = {\n", + " 'Stable-Diffusion Checkpoints': {\n", + " # 현재 목록의 키 값 정렬해서 보여주기\n", + " '$sort': True,\n", + "\n", + " 'Stable Diffusion': {\n", + " 'v2.1': {\n", + " '768-v': {\n", + " 'ema-pruned': {\n", + " 'safetensors': [\n", + " ModelFile(\n", + " 'https://huggingface.co/stabilityai/stable-diffusion-2-1/resolve/main/v2-1_768-ema-pruned.safetensors',\n", + " 'stable-diffusion-v2-1-786-v-ema-pruned.safetensors'),\n", + " ModelFile(\n", + " CONFIG_V2_V, 'stable-diffusion-v2-1-786-v-ema-pruned.yaml'),\n", + " ],\n", + " 'ckpt': [\n", + " ModelFile(\n", + " 'https://huggingface.co/stabilityai/stable-diffusion-2-1/resolve/main/v2-1_768-ema-pruned.ckpt',\n", + " 'stable-diffusion-v2-1-786-v-ema-pruned.ckpt'),\n", + " ModelFile(\n", + " CONFIG_V2_V, 'stable-diffusion-v2-1-786-v-ema-pruned.yaml'),\n", + " ]\n", + " },\n", + " 'nonema-pruned': {\n", + " 'safetensors': [\n", + " ModelFile(\n", + " 'https://huggingface.co/stabilityai/stable-diffusion-2-1/resolve/main/v2-1_768-nonema-pruned.safetensors',\n", + " 'stable-diffusion-v2-1-786-v-nonema-pruned.safetensors'),\n", + " ModelFile(\n", + " CONFIG_V2_V, 'stable-diffusion-v2-1-786-v-ema-pruned.yaml'),\n", + " ],\n", + " 'ckpt': [\n", + " ModelFile(\n", + " 'https://huggingface.co/stabilityai/stable-diffusion-2-1/resolve/main/v2-1_768-nonema-pruned.ckpt',\n", + " 'stable-diffusion-v2-1-786-v-nonema-pruned.ckpt'),\n", + " ModelFile(\n", + " CONFIG_V2_V, 'stable-diffusion-v2-1-786-v-ema-pruned.yaml'),\n", + " ],\n", + " }\n", + " },\n", + " '512-base': {\n", + " 'ema-pruned': {\n", + " 'safetensors': ModelFile(\n", + " 'https://huggingface.co/stabilityai/stable-diffusion-2-1-base/resolve/main/v2-1_512-ema-pruned.safetensors',\n", + " 'stable-diffusion-v2-1-512-base-ema-pruned.safetensors'),\n", + " 'ckpt': ModelFile(\n", + " 'https://huggingface.co/stabilityai/stable-diffusion-2-1-base/resolve/main/v2-1_512-ema-pruned.ckpt',\n", + " 'stable-diffusion-v2-1-512-base-ema-pruned.ckpt'),\n", + " },\n", + " 'nonema-pruned': {\n", + " 'safetensors': ModelFile(\n", + " 'https://huggingface.co/stabilityai/stable-diffusion-2-1-base/resolve/main/v2-1_512-nonema-pruned.safetensors',\n", + " 'stable-diffusion-v2-1-512-base-nonema-pruned.safetensors'),\n", + " 'ckpt': ModelFile(\n", + " 'https://huggingface.co/stabilityai/stable-diffusion-2-1-base/resolve/main/v2-1_512-nonema-pruned.ckpt',\n", + " 'stable-diffusion-v2-1-512-base-nonema-pruned.ckpt'),\n", + " },\n", + " },\n", + " },\n", + " 'v2.0': {\n", + " '768-v-ema': {\n", + " 'safetensors': [\n", + " ModelFile(\n", + " 'https://huggingface.co/stabilityai/stable-diffusion-2/resolve/main/768-v-ema.safetensors',\n", + " 'stable-diffusion-v2-0-786-v-ema.safetensors'),\n", + " ModelFile(\n", + " CONFIG_V2_V, 'stable-diffusion-v2-1-786-v-ema-pruned.yaml'),\n", + " ],\n", + " 'ckpt': [\n", + " ModelFile(\n", + " 'https://huggingface.co/stabilityai/stable-diffusion-2/resolve/main/768-v-ema.ckpt',\n", + " 'stable-diffusion-v2-0-786-v-ema.ckpt'),\n", + " ModelFile(\n", + " CONFIG_V2_V, 'stable-diffusion-v2-1-786-v-ema-pruned.yaml'),\n", + " ],\n", + " },\n", + " '512-base-ema': {\n", + " 'safetensors': ModelFile(\n", + " 'https://huggingface.co/stabilityai/stable-diffusion-2-base/resolve/main/512-base-ema.safetensors',\n", + " 'stable-diffusion-v2-0-512-base-ema.safetensors'),\n", + " 'ckpt': ModelFile(\n", + " 'https://huggingface.co/stabilityai/stable-diffusion-2-base/resolve/main/512-base-ema.ckpt',\n", + " 'stable-diffusion-v2-0-512-base-ema.ckpt'),\n", + " },\n", + " },\n", + " 'v1.5': {\n", + " 'pruned-emaonly': {\n", + " 'ckpt': ModelFile(\n", + " 'https://huggingface.co/runwayml/stable-diffusion-v1-5/resolve/main/v1-5-pruned-emaonly.ckpt',\n", + " 'stable-diffusion-v1-5-pruned-emaonly.ckpt')\n", + " },\n", + " 'pruned': {\n", + " 'ckpt': ModelFile(\n", + " 'https://huggingface.co/runwayml/stable-diffusion-v1-5/resolve/main/v1-5-pruned.ckpt',\n", + " 'stable-diffusion-v1-5-pruned.ckpt')\n", + " },\n", + " },\n", + " },\n", + "\n", + " 'Dreamlike': {\n", + " 'photoreal': {\n", + " 'v2.0': {\n", + " 'safetensors': ModelFile('https://huggingface.co/dreamlike-art/dreamlike-photoreal-2.0/resolve/main/dreamlike-photoreal-2.0.safetensors'),\n", + " 'ckpt': ModelFile('https://huggingface.co/dreamlike-art/dreamlike-photoreal-2.0/resolve/main/dreamlike-photoreal-2.0.ckpt')\n", + " },\n", + " 'v1.0': {\n", + " 'ckpt': ModelFile('https://huggingface.co/dreamlike-art/dreamlike-photoreal-1.0/resolve/main/dreamlike-photoreal-1.0.ckpt')\n", + " },\n", + " },\n", + " 'diffusion': {\n", + " 'v1.0': {\n", + " 'safetensors': ModelFile('https://huggingface.co/dreamlike-art/dreamlike-diffusion-1.0/resolve/main/dreamlike-diffusion-1.0.safetensors'),\n", + " 'ckpt': ModelFile('https://huggingface.co/dreamlike-art/dreamlike-diffusion-1.0/resolve/main/dreamlike-diffusion-1.0.ckpt')\n", + " },\n", + " }\n", + " },\n", + "\n", + " 'Waifu Diffusion': {\n", + " 'v1.4': {\n", + " 'anime': {\n", + " 'e2': {\n", + " 'fp16': {\n", + " 'safetensors': [\n", + " ModelFile(\n", + " 'https://huggingface.co/saltacc/wd-1-4-anime/resolve/main/wd-1-4-epoch2-fp16.safetensors'),\n", + " ModelFile(\n", + " 'https://huggingface.co/saltacc/wd-1-4-anime/resolve/main/wd-1-4-epoch2-fp16.yaml')\n", + " ],\n", + " 'ckpt': [\n", + " ModelFile(\n", + " 'https://huggingface.co/saltacc/wd-1-4-anime/resolve/main/wd-1-4-epoch2-fp16.ckpt'),\n", + " ModelFile(\n", + " 'https://huggingface.co/saltacc/wd-1-4-anime/resolve/main/wd-1-4-epoch2-fp16.yaml')\n", + " ]\n", + " },\n", + " 'fp32': {\n", + " 'safetensors': [\n", + " ModelFile(\n", + " 'https://huggingface.co/saltacc/wd-1-4-anime/resolve/main/wd-1-4-epoch2-fp32.safetensors'),\n", + " ModelFile(\n", + " 'https://huggingface.co/saltacc/wd-1-4-anime/resolve/main/wd-1-4-epoch2-fp32.yaml')\n", + " ],\n", + " 'ckpt': [\n", + " ModelFile(\n", + " 'https://huggingface.co/saltacc/wd-1-4-anime/resolve/main/wd-1-4-epoch2-fp32.ckpt'),\n", + " ModelFile(\n", + " 'https://huggingface.co/saltacc/wd-1-4-anime/resolve/main/wd-1-4-epoch2-fp32.yaml')\n", + " ]\n", + " },\n", + " },\n", + " 'e1': {\n", + " 'ckpt': [\n", + " ModelFile(\n", + " 'https://huggingface.co/hakurei/waifu-diffusion-v1-4/resolve/main/wd-1-4-anime_e1.ckpt'),\n", + " ModelFile(\n", + " 'https://huggingface.co/hakurei/waifu-diffusion-v1-4/resolve/main/wd-1-4-anime_e1.yaml'),\n", + " ]\n", + " },\n", + " },\n", + " 'booru-step-14000-unofficial': {\n", + " 'safetensors': ModelFile('https://huggingface.co/waifu-diffusion/unofficial-releases/resolve/main/wd14-booru-step-14000-unofficial.safetensors'),\n", + " },\n", + " },\n", + " 'v1.3.5': {\n", + " '80000-fp32': {\n", + " 'ckpt': ModelFile('https://huggingface.co/hakurei/waifu-diffusion-v1-4/resolve/main/models/wd-1-3-5_80000-fp32.ckpt'),\n", + " },\n", + " 'penultimate-ucg-cont': {\n", + " 'ckpt': ModelFile('https://huggingface.co/hakurei/waifu-diffusion-v1-4/resolve/main/models/wd-1-3-penultimate-ucg-cont.ckpt'),\n", + " }\n", + " },\n", + " 'v1.3': {\n", + " 'fp16': {\n", + " 'ckpt': ModelFile('https://huggingface.co/hakurei/waifu-diffusion-v1-3/resolve/main/wd-v1-3-float16.ckpt')\n", + " },\n", + " 'fp32': {\n", + " 'ckpt': ModelFile('https://huggingface.co/hakurei/waifu-diffusion-v1-3/resolve/main/wd-v1-3-float32.ckpt')\n", + " },\n", + " 'full': {\n", + " 'ckpt': ModelFile('https://huggingface.co/hakurei/waifu-diffusion-v1-3/resolve/main/wd-v1-3-full.ckpt')\n", + " },\n", + " 'full-opt': {\n", + " 'ckpt': ModelFile('https://huggingface.co/hakurei/waifu-diffusion-v1-3/resolve/main/wd-v1-3-full-opt.ckpt')\n", + " },\n", + " },\n", + " },\n", + "\n", + " 'TrinArt': {\n", + " 'derrida_characters': {\n", + " 'v2': {\n", + " 'final': {\n", + " 'ckpt': ModelFile(\n", + " 'https://huggingface.co/naclbit/trinart_derrida_characters_v2_stable_diffusion/resolve/main/derrida_final.ckpt',\n", + " 'trinart_characters_v2_final.ckpt')\n", + " },\n", + " },\n", + " 'v1 (19.2m)': {\n", + " 'ckpt': ModelFile('https://huggingface.co/naclbit/trinart_characters_19.2m_stable_diffusion_v1/resolve/main/trinart_characters_it4_v1.ckpt')\n", + " },\n", + " },\n", + " 'v2': {\n", + " '115000': {\n", + " 'ckpt': ModelFile('https://huggingface.co/naclbit/trinart_stable_diffusion_v2/resolve/main/trinart2_step115000.ckpt'),\n", + " },\n", + " '95000': {\n", + " 'ckpt': ModelFile('https://huggingface.co/naclbit/trinart_stable_diffusion_v2/resolve/main/trinart2_step95000.ckpt'),\n", + " },\n", + " '60000': {\n", + " 'ckpt': ModelFile('https://huggingface.co/naclbit/trinart_stable_diffusion_v2/resolve/main/trinart2_step60000.ckpt'),\n", + " },\n", + " },\n", + " },\n", + "\n", + " 'AniReal': {\n", + " 'v1.0': {\n", + " 'safetensors': ModelFile('https://huggingface.co/Hosioka/AniReal/resolve/main/AniReal.safetensors')\n", + " }\n", + " },\n", + "\n", + " 'OrangeMixs': {\n", + " 'AbyssOrangeMix': {\n", + " '2': {\n", + " 'hard': {\n", + " 'safetensors': ModelFile('https://huggingface.co/WarriorMama777/OrangeMixs/resolve/main/Models/AbyssOrangeMix2/AbyssOrangeMix2_hard.safetensors'),\n", + " },\n", + " 'nsfw': {\n", + " 'safetensors': ModelFile('https://huggingface.co/WarriorMama777/OrangeMixs/resolve/main/Models/AbyssOrangeMix2/AbyssOrangeMix2_nsfw.safetensors'),\n", + " },\n", + " 'sfw': {\n", + " 'safetensors': ModelFile('https://huggingface.co/WarriorMama777/OrangeMixs/resolve/main/Models/AbyssOrangeMix2/AbyssOrangeMix2_sfw.safetensors'),\n", + " 'ckpt': ModelFile('https://huggingface.co/WarriorMama777/OrangeMixs/resolve/main/Models/AbyssOrangeMix2/AbyssOrangeMix2_sfw.ckpt')\n", + " }\n", + " },\n", + " '1': {\n", + " 'half': {\n", + " 'safetensors': ModelFile('https://huggingface.co/WarriorMama777/OrangeMixs/resolve/main/Models/AbyssOrangeMix/AbyssOrangeMix_half.safetensors'),\n", + " },\n", + " 'night': {\n", + " 'safetensors': ModelFile('https://huggingface.co/WarriorMama777/OrangeMixs/resolve/main/Models/AbyssOrangeMix/AbyssOrangeMix_Night.safetensors'),\n", + " },\n", + " 'base': {\n", + " 'safetensors': ModelFile('https://huggingface.co/WarriorMama777/OrangeMixs/resolve/main/Models/AbyssOrangeMix/AbyssOrangeMix.safetensors'),\n", + " 'ckpt': ModelFile('https://huggingface.co/WarriorMama777/OrangeMixs/resolve/main/Models/AbyssOrangeMix/AbyssOrangeMix_base.ckpt'),\n", + " },\n", + " }\n", + " },\n", + " 'EerieOrangeMix': {\n", + " '2': {\n", + " 'half': {\n", + " 'safetensors': ModelFile('https://huggingface.co/WarriorMama777/OrangeMixs/resolve/main/Models/EerieOrangeMix/EerieOrangeMix2_half.safetensors'),\n", + " },\n", + " 'night': {\n", + " 'safetensors': ModelFile('https://huggingface.co/WarriorMama777/OrangeMixs/resolve/main/Models/EerieOrangeMix/EerieOrangeMix2_night.safetensors'),\n", + " },\n", + " 'base': {\n", + " 'safetensors': ModelFile('https://huggingface.co/WarriorMama777/OrangeMixs/resolve/main/Models/EerieOrangeMix/EerieOrangeMix2.safetensors'),\n", + " 'ckpt': ModelFile('https://huggingface.co/WarriorMama777/OrangeMixs/resolve/main/Models/EerieOrangeMix/EerieOrangeMix2_base.ckpt'),\n", + " }\n", + " },\n", + " '1': {\n", + " 'half': {\n", + " 'safetensors': ModelFile('https://huggingface.co/WarriorMama777/OrangeMixs/resolve/main/Models/EerieOrangeMix/EerieOrangeMix_half.safetensors'),\n", + " },\n", + " 'night': {\n", + " 'safetensors': ModelFile('https://huggingface.co/WarriorMama777/OrangeMixs/resolve/main/Models/EerieOrangeMix/EerieOrangeMix_night.safetensors'),\n", + " },\n", + " 'base': {\n", + " 'safetensors': ModelFile('https://huggingface.co/WarriorMama777/OrangeMixs/resolve/main/Models/EerieOrangeMix/EerieOrangeMix.safetensors'),\n", + " 'ckpt': ModelFile('https://huggingface.co/WarriorMama777/OrangeMixs/resolve/main/Models/EerieOrangeMix/EerieOrangeMix_base.ckpt'),\n", + " }\n", + " },\n", + " },\n", + " },\n", + "\n", + " 'Anything': {\n", + " 'v4.5 (unofficial merge)': {\n", + " 'safetensors': ModelFile('https://huggingface.co/andite/anything-v4.0/resolve/main/anything-v4.5-pruned.safetensors'),\n", + " 'ckpt': ModelFile('https://huggingface.co/andite/anything-v4.0/resolve/main/anything-v4.5-pruned.ckpt'),\n", + " },\n", + " 'v4.0 (unofficial merge)': {\n", + " 'pruned': {\n", + " 'fp16': {\n", + " 'safetensors': ModelFile('https://huggingface.co/andite/anything-v4.0/resolve/main/anything-v4.0-pruned-fp16.safetensors'),\n", + " 'ckpt': ModelFile('https://huggingface.co/andite/anything-v4.0/resolve/main/anything-v4.0-pruned-fp16.ckpt'),\n", + " },\n", + " 'fp32': {\n", + " 'safetensors': ModelFile('https://huggingface.co/andite/anything-v4.0/resolve/main/anything-v4.0-pruned-fp32.safetensors'),\n", + " 'ckpt': ModelFile('https://huggingface.co/andite/anything-v4.0/resolve/main/anything-v4.0-pruned-fp32.ckpt'),\n", + " },\n", + " 'safetensors': ModelFile('https://huggingface.co/andite/anything-v4.0/resolve/main/anything-v4.0-pruned.safetensors'),\n", + " 'ckpt': ModelFile('https://huggingface.co/andite/anything-v4.0/resolve/main/anything-v4.0-pruned.ckpt'),\n", + " }\n", + " }\n", + " },\n", + "\n", + " 'Protogen': {\n", + " 'v8.6 Infinity': {\n", + " 'ckpt': ModelFile(\n", + " 'https://huggingface.co/darkstorm2150/Protogen_Infinity_Official_Release/resolve/main/model.ckpt',\n", + " 'ProtoGen_Infinity.ckpt')\n", + " },\n", + " 'v8.0 Nova (Experimental)': {\n", + " 'ckpt': ModelFile(\n", + " 'https://huggingface.co/darkstorm2150/Protogen_Nova_Official_Release/resolve/main/model.ckpt',\n", + " 'ProtoGen_Nova.ckpt')\n", + " },\n", + " 'v7.4 Eclipse (Advanced)': {\n", + " 'ckpt': ModelFile(\n", + " 'https://huggingface.co/darkstorm2150/Protogen_Eclipse_Official_Release/resolve/main/model.ckpt',\n", + " 'ProtoGen_Eclipse.ckpt')\n", + " },\n", + " 'v5.9 Dragon (RPG themes)': {\n", + " 'pruned': {\n", + " 'fp16': {\n", + " 'safetensors': ModelFile('https://huggingface.co/darkstorm2150/Protogen_Dragon_Official_Release/resolve/main/ProtoGen_Dragon-pruned-fp16.safetensors'),\n", + " 'ckpt': ModelFile('https://huggingface.co/darkstorm2150/Protogen_Dragon_Official_Release/resolve/main/ProtoGen_Dragon-pruned-fp16.ckpt'),\n", + " }\n", + " },\n", + " 'safetensors': ModelFile('https://huggingface.co/darkstorm2150/Protogen_Dragon_Official_Release/resolve/main/ProtoGen_Dragon.safetensors'),\n", + " 'ckpt': ModelFile('https://huggingface.co/darkstorm2150/Protogen_Dragon_Official_Release/resolve/main/ProtoGen_Dragon.ckpt'),\n", + " },\n", + " 'v5.8 (Sci-Fi/Anime)': {\n", + " 'pruned': {\n", + " 'fp16': {\n", + " 'safetensors': ModelFile('https://huggingface.co/darkstorm2150/Protogen_x5.8_Official_Release/resolve/main/ProtoGen_X5.8-pruned-fp16.safetensors'),\n", + " 'ckpt': ModelFile('https://huggingface.co/darkstorm2150/Protogen_x5.8_Official_Release/resolve/main/ProtoGen_X5.8-pruned-fp16.ckpt'),\n", + " }\n", + " },\n", + " 'safetensors': ModelFile('https://huggingface.co/darkstorm2150/Protogen_x5.8_Official_Release/resolve/main/ProtoGen_X5.8.safetensors'),\n", + " 'ckpt': ModelFile('https://huggingface.co/darkstorm2150/Protogen_x5.8_Official_Release/resolve/main/ProtoGen_X5.8.ckpt'),\n", + " },\n", + " 'v5.3 (Photorealism)': {\n", + " 'pruned': {\n", + " 'fp16': {\n", + " 'safetensors': ModelFile('https://huggingface.co/darkstorm2150/Protogen_x5.3_Official_Release/resolve/main/ProtoGen_X5.3-pruned-fp16.safetensors'),\n", + " 'ckpt': ModelFile('https://huggingface.co/darkstorm2150/Protogen_x5.3_Official_Release/resolve/main/ProtoGen_X5.3-pruned-fp16.ckpt'),\n", + " }\n", + " },\n", + " 'safetensors': ModelFile('https://huggingface.co/darkstorm2150/Protogen_x5.3_Official_Release/resolve/main/ProtoGen_X5.3.safetensors'),\n", + " 'ckpt': ModelFile('https://huggingface.co/darkstorm2150/Protogen_x5.3_Official_Release/resolve/main/ProtoGen_X5.3.ckpt'),\n", + " },\n", + " 'v3.4 (Photorealism)': {\n", + " 'pruned': {\n", + " 'fp16': {\n", + " 'safetensors': ModelFile('https://huggingface.co/darkstorm2150/Protogen_x3.4_Official_Release/resolve/main/ProtoGen_X3.4-pruned-fp16.safetensors'),\n", + " 'ckpt': ModelFile('https://huggingface.co/darkstorm2150/Protogen_x3.4_Official_Release/resolve/main/ProtoGen_X3.4-pruned-fp16.ckpt'),\n", + " }\n", + " },\n", + " 'safetensors': ModelFile('https://huggingface.co/darkstorm2150/Protogen_x3.4_Official_Release/resolve/main/ProtoGen_X3.4.safetensors'),\n", + " 'ckpt': ModelFile('https://huggingface.co/darkstorm2150/Protogen_x3.4_Official_Release/resolve/main/ProtoGen_X3.4.ckpt'),\n", + " },\n", + " 'v2.2 (Anime)': {\n", + " 'pruned': {\n", + " 'fp16': {\n", + " 'safetensors': ModelFile('https://huggingface.co/darkstorm2150/Protogen_v2.2_Official_Release/resolve/main/Protogen_V2.2-pruned-fp16.safetensors'),\n", + " 'ckpt': ModelFile('https://huggingface.co/darkstorm2150/Protogen_v2.2_Official_Release/resolve/main/Protogen_V2.2-pruned-fp16.ckpt'),\n", + " }\n", + " },\n", + " 'safetensors': ModelFile('https://huggingface.co/darkstorm2150/Protogen_v2.2_Official_Release/resolve/main/Protogen_V2.2.safetensors'),\n", + " 'ckpt': ModelFile('https://huggingface.co/darkstorm2150/Protogen_v2.2_Official_Release/resolve/main/Protogen_V2.2.ckpt'),\n", + " },\n", + " },\n", + "\n", + " '7th_Layer': {\n", + " '7th_anime': {\n", + " 'v3.0': {\n", + " 'A': {\n", + " 'safetensors': ModelFile('https://huggingface.co/syaimu/7th_Layer/resolve/main/7th_anime_v3/7th_anime_v3_A.safetensors'),\n", + " 'ckpt': ModelFile('https://huggingface.co/syaimu/7th_Layer/resolve/main/7th_anime_v3/7th_anime_v3_A.ckpt'),\n", + " },\n", + " 'B': {\n", + " 'safetensors': ModelFile('https://huggingface.co/syaimu/7th_Layer/resolve/main/7th_anime_v3/7th_anime_v3_B.safetensors'),\n", + " 'ckpt': ModelFile('https://huggingface.co/syaimu/7th_Layer/resolve/main/7th_anime_v3/7th_anime_v3_B.ckpt'),\n", + " },\n", + " 'C': {\n", + " 'safetensors': ModelFile('https://huggingface.co/syaimu/7th_Layer/resolve/main/7th_anime_v3/7th_anime_v3_C.safetensors'),\n", + " 'ckpt': ModelFile('https://huggingface.co/syaimu/7th_Layer/resolve/main/7th_anime_v3/7th_anime_v3_C.ckpt'),\n", + " },\n", + " },\n", + " 'v2.0': {\n", + " 'A': {\n", + " 'safetensors': ModelFile('https://huggingface.co/syaimu/7th_Layer/resolve/main/7th_anime_v2/7th_anime_v2_A.safetensors'),\n", + " 'ckpt': ModelFile('https://huggingface.co/syaimu/7th_Layer/resolve/main/7th_anime_v2/7th_anime_v2_A.ckpt'),\n", + " },\n", + " 'B': {\n", + " 'safetensors': ModelFile('https://huggingface.co/syaimu/7th_Layer/resolve/main/7th_anime_v2/7th_anime_v2_B.safetensors'),\n", + " 'ckpt': ModelFile('https://huggingface.co/syaimu/7th_Layer/resolve/main/7th_anime_v2/7th_anime_v2_B.ckpt'),\n", + " },\n", + " 'C': {\n", + " 'safetensors': ModelFile('https://huggingface.co/syaimu/7th_Layer/resolve/main/7th_anime_v2/7th_anime_v2_C.safetensors'),\n", + " 'ckpt': ModelFile('https://huggingface.co/syaimu/7th_Layer/resolve/main/7th_anime_v2/7th_anime_v2_C.ckpt'),\n", + " },\n", + " 'G': {\n", + " 'safetensors': ModelFile('https://huggingface.co/syaimu/7th_Layer/resolve/main/7th_anime_v2/7th_anime_v2_G.safetensors'),\n", + " 'ckpt': ModelFile('https://huggingface.co/syaimu/7th_Layer/resolve/main/7th_anime_v2/7th_anime_v2_G.ckpt'),\n", + " },\n", + " },\n", + " 'v1.1': {\n", + " 'safetensors': ModelFile('https://huggingface.co/syaimu/7th_Layer/resolve/main/7th_anime_v1/7th_anime_v1.1.safetensors'),\n", + " 'ckpt': ModelFile('https://huggingface.co/syaimu/7th_Layer/resolve/main/7th_anime_v1/7th_anime_v1.1.ckpt'),\n", + " },\n", + " },\n", + " 'abyss_7th_layer': {\n", + " 'G1': {\n", + " 'ckpt': ModelFile('https://huggingface.co/syaimu/7th_Layer/resolve/main/7th_layer/abyss_7th_layerG1.ckpt'),\n", + " },\n", + " 'ckpt': ModelFile('https://huggingface.co/syaimu/7th_Layer/resolve/main/7th_layer/Abyss_7th_layer.ckpt')\n", + " }\n", + " }\n", + " },\n", + "\n", + " 'VAEs': {\n", + " '$sort': True,\n", + "\n", + " 'Stable Diffusion': {\n", + " 'vae-ft-mse-840000': {\n", + " 'pruned': {\n", + " 'safetensors': VaeFile(\n", + " 'https://huggingface.co/stabilityai/sd-vae-ft-mse-original/resolve/main/vae-ft-mse-840000-ema-pruned.safetensors',\n", + " 'stable-diffusion-vae-ft-mse-840000-ema-pruned.safetensors'),\n", + " 'ckpt': VaeFile(\n", + " 'https://huggingface.co/stabilityai/sd-vae-ft-mse-original/resolve/main/vae-ft-mse-840000-ema-pruned.ckpt',\n", + " 'stable-diffusion-vae-ft-mse-840000-ema-pruned.ckpt')\n", + " }\n", + " },\n", + " 'vae-ft-ema-560000': {\n", + " 'safetensors': VaeFile(\n", + " 'https://huggingface.co/stabilityai/sd-vae-ft-ema-original/resolve/main/vae-ft-ema-560000-ema-pruned.safetensors',\n", + " 'stable-diffusion-vae-ft-ema-560000-ema-pruned.safetensors'),\n", + " 'ckpt': VaeFile(\n", + " 'https://huggingface.co/stabilityai/sd-vae-ft-ema-original/resolve/main/vae-ft-ema-560000-ema-pruned.ckpt',\n", + " 'stable-diffusion-vae-ft-ema-560000-ema-pruned.ckpt'),\n", + " }\n", + " },\n", + "\n", + " 'Waifu Diffusion': {\n", + " 'v1.4': {\n", + " 'kl-f8-anime': {\n", + " 'e2': {\n", + " 'ckpt': VaeFile('https://huggingface.co/hakurei/waifu-diffusion-v1-4/resolve/main/vae/kl-f8-anime2.ckpt'),\n", + " },\n", + " 'e1': {\n", + " 'ckpt': VaeFile('https://huggingface.co/hakurei/waifu-diffusion-v1-4/resolve/main/vae/kl-f8-anime.ckpt'),\n", + " }\n", + " },\n", + " },\n", + " },\n", + "\n", + " 'TrinArt': {\n", + " 'autoencoder_fix_kl-f8-trinart_characters': {\n", + " 'ckpt': ModelFile('https://huggingface.co/naclbit/trinart_derrida_characters_v2_stable_diffusion/resolve/main/autoencoder_fix_kl-f8-trinart_characters.ckpt')\n", + " }\n", + " },\n", + "\n", + " 'NovelAI': {\n", + " 'animevae.pt': VaeFile('https://huggingface.co/gozogo123/anime-vae/resolve/main/animevae.pt')\n", + " }\n", + " },\n", + "\n", + " 'Textual Inversion (embeddings)': {\n", + " '$sort': True,\n", + "\n", + " 'bad_prompt (negative embedding)': {\n", + " 'Version 2': EmbeddingFile('https://huggingface.co/datasets/Nerfgun3/bad_prompt/resolve/main/bad_prompt_version2.pt'),\n", + " 'Version 1': EmbeddingFile('https://huggingface.co/datasets/Nerfgun3/bad_prompt/resolve/main/bad_prompt.pt'),\n", + " },\n", + " }\n", + "}\n", + "\n", + "\n", + "def global_disable(disabled: bool):\n", + " for dropdown in dropdowns.children:\n", + " dropdown.disabled = disabled\n", + "\n", + " download_button.disabled = disabled\n", + "\n", + " # 마지막 드롭다운이 하위 드롭다운이라면 버튼 비활성화하기\n", + " if not disabled:\n", + " dropdown = dropdowns.children[len(dropdowns.children) - 1]\n", + " download_button.disabled = isinstance(dropdown, dict)\n", + "\n", + "\n", + "def on_download(_):\n", + " dropdown = dropdowns.children[len(dropdowns.children) - 1]\n", + " entry = dropdown.entries[dropdown.value]\n", + "\n", + " global_disable(True)\n", + "\n", + " # 단일 파일 받기\n", + " if isinstance(entry, File):\n", + " entry.download()\n", + "\n", + " # 다중 파일 받기\n", + " elif isinstance(entry, list):\n", + " for file in entry:\n", + " file.download()\n", + "\n", + " # TODO: 오류 처리\n", + " else:\n", + " pass\n", + "\n", + " if DISCONNECT_RUNTIME:\n", + " print('파일을 성공적으로 옮겼습니다, 이제 런타임을 해제해도 좋습니다.')\n", + "\n", + " # 런타임을 바로 종료해버리면 마지막 출력이 잘림\n", + " time.sleep(1)\n", + " runtime.unassign()\n", + "\n", + " global_disable(False)\n", + "\n", + "\n", + "def on_dropdown_change(event):\n", + " dropdown: widgets.Dropdown = event['owner']\n", + " entries: Union[List, Dict] = dropdown.entries[event['new']]\n", + "\n", + " # 이전 하위 드롭다운 전부 제거하기\n", + " dropdowns.children = dropdowns.children[:dropdown.children_index + 1]\n", + "\n", + " if isinstance(entries, dict):\n", + " download_button.disabled = True\n", + " create_dropdown(entries)\n", + " return\n", + "\n", + " # 하위 드롭다운 만들기\n", + " download_button.disabled = False\n", + "\n", + "\n", + "def create_dropdown(entries: Dict) -> widgets.Dropdown:\n", + " if '$sort' in entries and entries['$sort'] == True:\n", + " entries = {k: entries[k] for k in sorted(entries)}\n", + " del entries['$sort']\n", + "\n", + " options = list(entries.keys())\n", + " value = options[0]\n", + "\n", + " dropdown = widgets.Dropdown(\n", + " options=options,\n", + " value=value)\n", + "\n", + " setattr(dropdown, 'children_index', len(dropdowns.children))\n", + " setattr(dropdown, 'entries', entries)\n", + "\n", + " dropdowns.children = tuple(list(dropdowns.children) + [dropdown])\n", + "\n", + " dropdown.observe(on_dropdown_change, names='value')\n", + "\n", + " on_dropdown_change({\n", + " 'owner': dropdown,\n", + " 'new': value\n", + " })\n", + "\n", + " return dropdown\n", + "\n", + "\n", + "# 첫 엔트리 드롭다운 만들기\n", + "create_dropdown(files)\n", + "\n", + "download_button.on_click(on_download)\n" + ] + } + ] +} \ No newline at end of file