diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..73cd736 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +.github +.idea +lib +dist +build \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index f298ac4..0000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: Build code - -on: - push: - branches: - - master - -jobs: - build-and-publish: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Login to GitHub Container Registry - uses: docker/login-action@v1 - with: - registry: ghcr.io - username: ${{ secrets.CR_USERNAME }} - password: ${{ secrets.CR_PASSWORD }} - - name: Build with docker-compose build - run: docker-compose build - - name: Publish to itch with butler - uses: itchio/butler-push-action@v1 - with: - args: push dist/your_game_name your_game_name:your_game_version - secrets: - BUTLER_API_KEY: ${{ secrets.BUTLER_API_KEY }} \ No newline at end of file diff --git a/.github/workflows/repository-dispatch.yml b/.github/workflows/repository-dispatch.yml new file mode 100644 index 0000000..fb48de7 --- /dev/null +++ b/.github/workflows/repository-dispatch.yml @@ -0,0 +1,39 @@ +name: Linux Build + +on: + release: + types: [ published ] + workflow_dispatch: + +env: + DOCKER_IMAGE: ghcr.io/capsize-games/chatai/chatai:linux + BUTLER_API_KEY: ${{ secrets.BUTLER_API_KEY }} + CR_PAT: ${{ secrets.CR_PAT }} + CR_REPO: ${{ secrets.CR_REPO }} + CR_USERNAME: ${{ secrets.CR_USERNAME }} + +jobs: + run-chatai: + runs-on: ubuntu-latest + steps: + - name: Login to GitHub Container Registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.CR_PAT }} + - name: Run chatai on Linux + run: | + docker pull $DOCKER_IMAGE + docker run --rm \ + -e BUTLER_API_KEY=${{ secrets.BUTLER_API_KEY }} \ + -e ITCHIO_USERNAME=${{ secrets.ITCHIO_USERNAME }} \ + -e DEV_ENV=0 \ + -e AIRUNNER_ENVIRONMENT=prod \ + -e AIRUNNER_OS=linux \ + -e PYTORCH_CUDA_ALLOC_CONF=garbage_collection_threshold:0.9,max_split_size_mb:512 \ + -e NUMBA_CACHE_DIR=/tmp/numba_cache \ + -e DISABLE_TELEMETRY=1 \ + -e TCL_LIBDIR_PATH=/usr/lib/x86_64-linux-gnu/ \ + -e TK_LIBDIR_PATH=/usr/lib/x86_64-linux-gnu/ \ + ghcr.io/capsize-games/chatai/chatai:linux bash build.sh diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..af0ce49 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,82 @@ +FROM ubuntu:latest as base_image +USER root +ENV TZ=America/Denver +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone \ + && apt-get update \ + && apt-get upgrade -y \ + && apt install software-properties-common -y \ + && add-apt-repository ppa:ubuntu-toolchain-r/test \ + && apt-get update \ + && dpkg --add-architecture i386 \ + && apt-get update \ + && apt install libtinfo6 -y \ + && apt-get install -y git \ + && apt-get install -y wget \ + && apt-get install -y curl \ + && apt-get install -y vim \ + && apt-get install -y software-properties-common \ + && apt-get install -y gcc-9 \ + && apt-get install -y g++-9 \ + && apt-get install -y bash \ + && apt-get install -y build-essential \ + && apt-get install -y libssl-dev \ + && apt-get install -y libffi-dev \ + && apt-get install -y libgl1-mesa-dev \ + && apt-get install -y nvidia-cuda-toolkit \ + && apt-get install -y xclip \ + && apt-get install -y libjpeg-dev \ + && apt-get install -y zlib1g-dev \ + && apt-get install -y libpng-dev \ + && apt-get install software-properties-common -y \ + && apt-get install patchelf -y \ + && add-apt-repository ppa:deadsnakes/ppa -y \ + && apt update \ + && apt install python3.10 -y \ + && apt install python3.10-distutils -y \ + && apt install python3-pip -y \ + && apt install python3.10-tk -y \ + && apt install -y upx \ + && apt-get install patchelf -y \ + && apt-get install ccache -y \ + && apt-get install -y libxcb-xinerama0 \ + && apt-get install -y libgtk-3-0 \ + && apt-get install qt6-qpa-plugins -y \ + && apt-get install libgl1-mesa-glx libglib2.0-0 libsm6 libxext6 libxrender-dev libxcb-xinerama0 -y \ + && apt-get install -y qt6-base-dev \ + && rm -rf /var/lib/apt/lists/ \ + && update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 60 --slave /usr/bin/g++ g++ /usr/bin/g++-9 \ + && rm -rf /var/lib/apt/lists/* + +FROM base_image as install_requirements +USER root +WORKDIR /app +ENV XFORMERS_MORE_DETAILS=1 +RUN pip install nvidia-pyindex +WORKDIR /app +RUN pip install --upgrade pip +RUN pip install --upgrade setuptools +RUN pip install --upgrade wheel +RUN pip install aihandler +RUN pip install accelerate +RUN pip install requests + +FROM install_requirements as fix_tcl +USER root +RUN ln -s /usr/share/tcltk/tcl8.6 /usr/share/tcltk/tcl8 + +FROM fix_tcl as install_apps +COPY build.sh build.sh +COPY build.py build.py +COPY build.chatai.linux.prod.spec build.chatai.linux.prod.spec +COPY linux.itch.toml linux.itch.toml +COPY src/chatairunner/v1.yaml v1.yaml +COPY src/chatairunner/v2.yaml v2.yaml +COPY src/chatairunner/pyqt src/chatairunner/pyqt +RUN python3 -c "from accelerate.utils import write_basic_config; write_basic_config(mixed_precision='fp16')" +RUN pip uninstall nvidia-cublas-cu11 -y + +FROM install_apps as more_env +ENV PATH="/usr/local/lib/python3.10:/usr/local/lib/python3.10/bin:${PATH}" +ENV PYTHONPATH="/usr/local/lib/python3.10:/usr/local/lib/python3.10/bin:${PYTHONPATH}" +RUN pip install pyinstaller +RUN pip install bitsandbytes-cuda102 \ No newline at end of file diff --git a/README.md b/README.md index 2097750..a1f4ed2 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,59 @@ to and from an active Google T5-Flan model. - Python 3.10.6 - pip-23.0.1 +--- + +### Docker + +[Current builds of AI Runner are compiled with pyinstaller on docker.](https://github.com/Capsize-Games/airunner/pkgs/container/airunner%2Fairunner) + +**Pull Docker container from repo** + +Linux +``` +docker pull ghcr.io/capsize-games/chatai/chatai:linux +``` + +Windows +``` +docker pull ghcr.io/capsize-games/chatai/chatai:windows +``` + +**Build Docker** + +Linux +``` +docker-compose -f docker-compose.yml build +docker tag ghcr.io/capsize-games/chatai/chatai:linux ghcr.io/capsize-games/chatai/chatai:linux +docker push ghcr.io/capsize-games/chatai/chatai:linux +``` + +Windows +``` +docker-compose -f docker-compose.windows.yml build +docker tag ghcr.io/capsize-games/chatai/chatai:linux ghcr.io/capsize-games/chatai/chatai:windows +docker push ghcr.io/capsize-games/chatai/chatai:windows +``` + +**Run the app using Docker** +``` +docker-compose run linux python3 /app/main.py +``` + +**Build latest version** of AI Runner using Docker locally - this will output a `build` and `dist` folder on your machine. +``` +docker run --rm -v $(pwd)/dist:/app/dist -v $(pwd)/build:/app/build ghcr.io/capsize-games/chatai/chatai:linux bash build.sh + +docker run --rm -m 24g --cpus=12 -v $(pwd)/dist:/app/dist -v $(pwd)/build:/app/build ghcr.io/capsize-games/chatai/chatai:windows bash build.windows.sh +``` + +``` +docker tag ghcr.io/capsize-games/chatai/chatai:linux ghcr.io/capsize-games/chatai/chatai:windows +docker push ghcr.io/capsize-games/chatai/chatai:windows +`` + +--- + #### Pypi installation Use this installation method if you intend to use Chat AI from the command line or with @@ -40,7 +93,7 @@ Windows ``` pip install torch==1.13.1 torchvision==0.14.1 torchaudio==0.13.1 --index-url https://download.pytorch.org/whl/cu117 pip install aihandlerwindows -pip install https://github.com/w4ffl35/diffusers/archive/refs/tags/v0.14.0.ckpt_fix.tar.gz +pip install https://github.com/w4ffl35/diffusers/archive/refs/tags/v0.14.0.ckpt_fix_0.0.1.tar.gz pip install https://github.com/w4ffl35/transformers/archive/refs/tags/tensor_fix-v1.0.2.tar.gz pip install https://github.com/acpopescu/bitsandbytes/releases/download/v0.37.2-win.0/bitsandbytes-0.37.2-py3-none-any.whl pip install chatai --no-deps @@ -48,7 +101,7 @@ pip install chatai --no-deps Linux ``` -pip install https://github.com/w4ffl35/diffusers/archive/refs/tags/v0.14.0.ckpt_fix.tar.gz +pip install https://github.com/w4ffl35/diffusers/archive/refs/tags/v0.14.0.ckpt_fix_0.0.1.tar.gz pip install https://github.com/w4ffl35/transformers/archive/refs/tags/tensor_fix-v1.0.2.tar.gz pip install chatairunner ``` diff --git a/build.chatai.linux.prod.spec b/build.chatai.linux.prod.spec new file mode 100644 index 0000000..3a95967 --- /dev/null +++ b/build.chatai.linux.prod.spec @@ -0,0 +1,171 @@ +# -*- mode: python ; coding: utf-8 -*- +import os +import shutil +from PyInstaller.utils.hooks import copy_metadata, collect_data_files +import sys ; sys.setrecursionlimit(sys.getrecursionlimit() * 5) +os.environ["CHATAI_ENVIRONMENT"] = "prod" +libraries = [ + "/usr/local/lib/python3.10/dist-packages/PyQt6/Qt6/lib/", + "/usr/lib/x86_64-linux-gnu/wine-development/", + "/usr/local/lib/python3.10/dist-packages/h5py.libs/", + "/usr/local/lib/python3.10/dist-packages/scipy.libs/", + "/usr/local/lib/python3.10/dist-packages/tokenizers.libs/", + "/usr/local/lib/python3.10/dist-packages/Pillow.libs/", + "/usr/local/lib/python3.10/dist-packages/opencv_python.libs/", + "/usr/local/lib/python3.10/dist-packages/torchaudio/lib/", + "/usr/local/lib/python3.10/dist-packages/torch/lib/", + "/usr/lib/python3.10", + "/usr/lib/x86_64-linux-gnu/", + "/usr/local/lib/", + "/usr/local/lib/python3.10", + "/usr/local/lib/python3.10/dist-packages" +] +os.environ["LD_LIBRARY_PATH"] = ":".join(libraries) +block_cipher = None +DEBUGGING = True +EXCLUDE_BINARIES = True +EXE_NAME = "chatai" # used when creating a binary instead of a folder +EXE_STRIP = False +EXE_UPX = True +EXE_RUNTIME_TMP_DIR = None +COLLECT_NAME = 'chatai' +COLLECT_STRIP = False +COLLECT_UPX = True +datas = [] +datas += copy_metadata('aihandler') +datas += copy_metadata('tqdm') +datas += copy_metadata('regex') +datas += copy_metadata('requests') +datas += copy_metadata('packaging') +datas += copy_metadata('filelock') +datas += copy_metadata('numpy') +datas += copy_metadata('tokenizers') +datas += copy_metadata('transformers') +datas += copy_metadata('rich') +datas += collect_data_files("torch", include_py_files=True) +datas += collect_data_files("torchvision", include_py_files=True) +datas += collect_data_files("JIT", include_py_files=True) +datas += collect_data_files("triton", include_py_files=True) +datas += collect_data_files("pytorch_lightning", include_py_files=True) +datas += collect_data_files("lightning_fabric", include_py_files=True) +datas += collect_data_files("transformers", include_py_files=True) +datas += collect_data_files("xformers", include_py_files=True) +a = Analysis( + [ + f'./chatairunner/src/chatairunner/main.py', + ], + pathex=[ + "/usr/local/lib/python3.10/dist-packages/", + "/usr/local/lib/python3.10/dist-packages/torch/lib", + "/usr/local/lib/python3.10/dist-packages/tokenizers", + "/usr/local/lib/python3.10/dist-packages/tensorflow", + "/usr/local/lib/python3.10/dist-packages/triton", + "/usr/local/lib/python3.10/dist-packages/xformers", + "/usr/local/lib/python3.10/dist-packages/xformers/triton", + "/usr/lib/x86_64-linux-gnu/", + ], + binaries=[ + ('/usr/local/lib/python3.10/dist-packages/nvidia/cudnn/lib/libcudnn_ops_infer.so.8', '.'), + ('/usr/local/lib/python3.10/dist-packages/nvidia/cudnn/lib/libcudnn_cnn_infer.so.8', '.'), + ], + datas=datas, + hiddenimports=[ + "aihandler", + "JIT", + "triton", + "triton._C", + "triton._C.libtriton", + "xformers", + "xformers.ops", + "xformers.triton", + "xformers.triton.softmax", + "tqdm", + "diffusers", + "transformers", + "nvidia", + "torch", + "torchvision", + "torchvision.io", + "logging", + "logging.config", + "einops", + "omegaconf", + "contextlib", + "itertools", + "pytorch_lightning", + "huggingface_hub.hf_api", + "huggingface_hub.repository", + "inspect", + "psutil", + "matplotlib", + "bitsandbytes", + "numpy", + "PIL._tkinter_finder", + ], + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=[], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher, + noarchive=False, +) +pyz = PYZ( + a.pure, + a.zipped_data, + cipher=block_cipher +) +exe = EXE( + pyz, + a.scripts, + [], + exclude_binaries=EXCLUDE_BINARIES, + name=EXE_NAME, + debug=DEBUGGING, + strip=EXE_STRIP, + upx=EXE_UPX, + runtime_tmpdir=EXE_RUNTIME_TMP_DIR, + console=DEBUGGING +) +coll = COLLECT( + exe, + a.binaries, + a.zipfiles, + a.datas, + strip=COLLECT_STRIP, + upx=COLLECT_UPX, + upx_exclude=[], + name=COLLECT_NAME +) + +# copy files for distribution +shutil.copytree('./src/chatairunner/pyqt', './dist/chatairunner/pyqt') +shutil.copyfile('./linux.itch.toml', './dist/chatairunner/.itch.toml') +shutil.copytree('./src/chatairunner/src/icons', './dist/chatairunner/src/icons') + +# copy sd config files +os.makedirs('./dist/chatairunner/diffusers/pipelines/stable_diffusion', exist_ok=True) +shutil.copyfile('./v1.yaml', './dist/chatairunner/v1.yaml') +shutil.copyfile('./v2.yaml', './dist/chatairunner/v2.yaml') + +############################################################# +#### The following fixes are for Triton ##################### + +# run compileall on ./dist/chatairunner/triton/runtime/jit.py and then mv ./dist/chatairunner/triton/runtime/__pycache__/jit.cpython-310.pyc to ./dist/chatairunner/triton/runtime/jit.pyc +shutil.move( + '/usr/local/lib/python3.10/dist-packages/triton/runtime/__pycache__/jit.cpython-310.pyc', + './dist/chatairunner/triton/runtime/jit.pyc' +) + +# do the same thing for ./dist/chatairunner/triton/compiler.py +shutil.move( + '/usr/local/lib/python3.10/dist-packages/triton/__pycache__/compiler.cpython-310.pyc', + './dist/chatairunner/triton/compiler.pyc' +) + +for file in [ "random" ]: + shutil.move( + f'/usr/local/lib/python3.10/dist-packages/JIT/__pycache__/{file}.cpython-310.pyc', + f'./dist/chatairunner/{file}.pyc' + ) \ No newline at end of file diff --git a/build.py b/build.py new file mode 100644 index 0000000..a3fdceb --- /dev/null +++ b/build.py @@ -0,0 +1,43 @@ +import os +import urllib.request +import json + + +def install_latest(repo): + url = f'https://api.github.com/repos/{repo}/releases/latest' + with urllib.request.urlopen(url) as response: + data = response.read().decode('utf-8') + data = json.loads(data) + tag_name = data["tag_name"] + tar_url = f'https://github.com/{repo}/archive/{tag_name}.tar.gz' + os.system(f'python3 -m pip install {tar_url}') + + +def get_latest_version_tag(repo): + url = f'https://api.github.com/repos/{repo}/releases/latest' + with urllib.request.urlopen(url) as response: + data = response.read().decode('utf-8') + data = json.loads(data) + tag_name = data["tag_name"] + # strip the v + if tag_name.startswith("v"): + tag_name = tag_name[1:] + return tag_name + + + +def clone(repo): + # clone repo into /app + os.system(f'git clone https://github.com/{repo}.git /app/{repo.split("/")[1]}') + + +# remove diffusers +os.system("python3 -m pip uninstall diffusers -y") +# install repos +install_latest("w4ffl35/diffusers") +install_latest("w4ffl35/transformers") +install_latest("Capsize-Games/aihandler") +clone("Capsize-Games/chatai") +version = get_latest_version_tag("Capsize-Games/chatai") +os.system("python3 -m pip uninstall nvidia-cublas-cu11 -y") +os.system("python3 -m pip install bitsandbytes-cuda102 -y") \ No newline at end of file diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..52f1537 --- /dev/null +++ b/build.sh @@ -0,0 +1,26 @@ +#!/usr/bin/bash +# this should be called from within docker to kick off a build +DISABLE_TELEMETRY=1 +cd /app +echo "" +echo "============================================" +echo "Installing dependencies" +echo "============================================" +echo "" +python3 /app/build.py +echo "" +echo "============================================" +echo "Build chatai for linux" +echo "============================================" +echo "" +DEV_ENV=0 AIRUNNER_ENVIRONMENT="prod" PYTHONOPTIMIZE=0 python3 -m PyInstaller --log-level=INFO --noconfirm build.chatai.linux.prod.spec 2>&1 | tee build.log +echo "" +echo "============================================" +echo "Deploying chatai to itch.io" +echo "============================================" +echo "" +chown -R 1000:1000 dist +LATEST_TAG=$(curl -s https://api.github.com/repos/Capsize-Games/chatai/releases/latest | grep tag_name | cut -d '"' -f 4 | sed 's/v//') +echo "Latest tag: $LATEST_TAG" +wget https://dl.itch.ovh/butler/linux-amd64/head/butler && chmod +x butler +./butler push ./dist/chatai capsizegames/chat-ai:ubuntu --userversion $LATEST_TAG \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..fa4302e --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,20 @@ +version: "3.6" +services: + linux: + restart: always + container_name: chatai_linux + image: ghcr.io/capsize-games/chatai/chatai:linux + user: root + build: + context: ./ + dockerfile: ./Dockerfile + environment: + - DEV_ENV=0 + - AIRUNNER_ENVIRONMENT=prod + - AIRUNNER_OS=linux + - PYTORCH_CUDA_ALLOC_CONF=garbage_collection_threshold:0.9,max_split_size_mb:512 + - NUMBA_CACHE_DIR=/tmp/numba_cache + - LD_LIBRARY_PATH=/usr/lib/python3.10:/usr/lib/x86_64-linux-gnu/:/usr/local/lib/:/usr/local/lib/python3.10:/usr/local/lib/python3.10/dist-packages + - DISABLE_TELEMETRY=1 + - TCL_LIBDIR_PATH=/usr/lib/x86_64-linux-gnu/ + - TK_LIBDIR_PATH=/usr/lib/x86_64-linux-gnu/ \ No newline at end of file diff --git a/linux.itch.toml b/linux.itch.toml new file mode 100644 index 0000000..339786f --- /dev/null +++ b/linux.itch.toml @@ -0,0 +1,3 @@ +[[actions]] +name = "play" +path = "chatai" diff --git a/setup.py b/setup.py index 55215c4..42c8673 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name='chatairunner', - version='1.0.10', + version='1.1.0', author='Capsize LLC', description='Chat AI: A chatbot framework', long_description=open("README.md", "r", encoding="utf-8").read(), @@ -16,6 +16,6 @@ include_package_data=True, python_requires=">=3.10.0", install_requires=[ - "aihandler==1.8.19", + "aihandler==1.8.21", ] ) diff --git a/src/chatairunner/base_window.py b/src/chatairunner/base_window.py index 52c34f7..1f5d135 100644 --- a/src/chatairunner/base_window.py +++ b/src/chatairunner/base_window.py @@ -31,23 +31,40 @@ def message_received(self, message): self.enable_buttons() self.stop_progress_bar() - def message_handler(self, *args, **kwargs): - response = args[0]["response"]["response"] + do_cancel_current = False + + def handle_cancel(self): + # self.client.cancel_current_request() + self.do_cancel_current = True + + def parse_response(self, response): # remove tokens response = response.replace("", "") response = response.replace("", "") # check if token exists incomplete = False - if "" not in response: + if not self.do_cancel_current: # remove all tokens after and itself incomplete = True else: response = response[: response.find("")] + # response = response.strip() + self.do_cancel_current = False + return response, incomplete - response = response.strip() - self.ui.generated_text.appendPlainText(response) + def message_handler(self, *args, **kwargs): + response = args[0]["response"]["response"] + if isinstance(response, list): + # for now we limit to a single response + # TODO: add a way to select the response + response, incomplete = self.parse_response(response[0]) + else: + response, incomplete = self.parse_response(response) - if incomplete: + self.ui.generated_text.appendPlainText(response) + if response == "": + print("Response came back blank") + if incomplete and response != "": # if there is no token, the response is incomplete # so we send another request self.generate() @@ -79,6 +96,7 @@ def initialize_offline_client(self): ) def handle_generate(self): + self.do_cancel_current = False self.start_progress_bar() self.disable_buttons() self.generate() @@ -91,11 +109,24 @@ def get_seed(self): def generate(self): action = "generate" - userinput = " ".join([ - self.ui.generated_text.toPlainText(), - self.ui.prefix.toPlainText(), - self.ui.prompt.toPlainText(), - ]) + prefix = self.ui.prefix.toPlainText() + history = self.ui.generated_text.toPlainText() + prompt = self.ui.prompt.toPlainText() + input_text = [] + if prefix != "": + input_text.append(prefix) + if history != "": + input_text.append(history) + if prompt != "": + input_text.append(prompt) + userinput = "\n\n".join(input_text) + # add prompt to generated_text + self.ui.generated_text.appendPlainText(self.ui.prompt.toPlainText()) + # clear prompt + self.ui.prompt.setPlainText("") + seed = self.get_seed() + properties = self.conversation.properties + properties["seed"] = seed self.client.message = { "action": "llm", "type": action, @@ -103,12 +134,10 @@ def generate(self): "user_input": userinput, "username": self.conversation.username, "botname": self.conversation.botname, - "seed": self.seed, "conversation": self, - "properties": self.conversation.properties, + "properties": properties, } } - print(prompt) def start_progress_bar(self): self.ui.progressBar.setValue(0) @@ -128,25 +157,26 @@ def disable_buttons(self): def process_response(self, response): pass + seed = None + def __init__(self, *args, **kwargs): self.client = kwargs.pop("client") self.parent = kwargs.pop("parent") super().__init__(*args, **kwargs) if self.client is None: self.initialize_offline_client() - self.conversation = ChatAIConversation(client=self.client) self.client.tqdm_var.my_signal.connect(self.tqdm_callback) self.client.message_var.my_signal.connect(self.message_handler) self.client.error_var.my_signal.connect(self.error_handler) self.settings_manager = SettingsManager(app=self) self.response_signal.connect(self.process_response) self.message_signal.connect(self.message_received) - self.seed = random.randint(0, 100000) self.load_template() self.center() self.ui.show() self.ui.closeEvent = self.handle_quit self.initialize_form() + self.conversation = ChatAIConversation(client=self.client, seed=self.seed) # self.exec() def handle_quit(self, *args, **kwargs): diff --git a/src/chatairunner/chatbot.py b/src/chatairunner/chatbot.py index 3354897..59f855c 100644 --- a/src/chatairunner/chatbot.py +++ b/src/chatairunner/chatbot.py @@ -17,7 +17,6 @@ class ChatbotWindow(BaseWindow): model_name = "flan-t5-xl" def __init__(self, *args, **kwargs): - self.seed = random.randint(0, 1000000) super().__init__(*args, **kwargs) @property @@ -71,7 +70,6 @@ def message_handler(self, *args, **kwargs): self.enable_buttons() def clear_prompt(self): - print("clear prompt") self.ui.prompt.clear() def tqdm_callback(self, *args, **kwargs): @@ -193,7 +191,6 @@ def handle_quit(self, *args, **kwargs): def generate_characters(self): self.disable_generate() self.conversation.send_generate_characters_message() - self.seed = self.conversation.seed def save_conversation(self): filename, _ = QFileDialog.getSaveFileName( @@ -212,7 +209,6 @@ def load_conversation(self): def clear_conversation(self): self.conversation.reset() - self.seed = self.conversation.seed self.ui.generated_text.setPlainText("") def start_progress_bar(self): diff --git a/src/chatairunner/conversation.py b/src/chatairunner/conversation.py index 7b9b072..5815abb 100644 --- a/src/chatairunner/conversation.py +++ b/src/chatairunner/conversation.py @@ -30,11 +30,11 @@ class Conversation: + llm_runner = None model_name = "flan-t5-xl" client: OfflineClient = None username: str = "" botname: str = "" - seed: int = 0 properties: dict = {} conversation_summary: str = "" _dialogue: list = [] @@ -109,6 +109,7 @@ def __init__(self, client, **kwargs): :keyword seed:int The seed for the random number generator :keyword properties:dict The properties for the model """ + self.seed = kwargs.get("seed", random.randint(0, 100000)) self.parent = kwargs.get("parent") threading.Thread(target=self._initialize, args=(client,), kwargs=kwargs).start() #self._initialize(client, **kwargs) # Initialize the conversation @@ -119,24 +120,17 @@ def load_type(self, conversation_type): elif conversation_type == "wild": self.__class__ = WildConversation - def new_seed(self, seed: int = None): - new_seed = seed - if not new_seed: - new_seed = self.seed - while new_seed == self.seed: - new_seed = random.randint(0, 100000) - self.seed = new_seed - self.llm_runner.do_set_seed(self.seed) - def _initialize(self, client=None, **kwargs): if client: self.client = client + self.seed = kwargs.get("seed", random.randint(0, 100000)) client.llm_request_handler = self.request_handler self.llm_runner = LLMRunner( app=client, tqdm_var=client.tqdm_var, image_var=client.image_var, - message_var=client.message_var + message_var=client.message_var, + seed=self.seed, ) self.conversation_summary = "" self.username = kwargs.get("username", "User") @@ -167,7 +161,6 @@ def save(self, filename: str): "username": self.username, "botname": self.botname, "conversation": self._dialogue, - "seed": self.seed, "properties": self.properties } f.write(json.dumps(data, indent=4)) @@ -178,7 +171,6 @@ def load(self, filename: str): self.username = data["username"] self.botname = data["botname"] self.dialogue = data["conversation"] - self.new_seed(data["seed"]) self.properties = data["properties"] self.parent.update_form() @@ -202,7 +194,6 @@ def send_generate_characters_message(self): This method kicks of a generate characters request which is processed by the client. :return: """ - self.new_seed() self.client.message = { "prompt": None, "user_input": None, @@ -210,7 +201,6 @@ def send_generate_characters_message(self): "type": "generate_characters", "data": { "properties": self.prep_properties(self.default_properties), - "seed": self.seed } } @@ -220,7 +210,6 @@ def do_generate_characters(self): :return: """ self.username = self.generate_character() - self.new_seed() self.botname = self.generate_character() self.response = { "type": "generate_characters", @@ -240,7 +229,6 @@ def generate_character(self): properties["top_k"] = 40 username = self.llm_runner.generate( prompt=prompt, - seed=self.seed, **properties, return_result=True, skip_special_tokens=True @@ -358,7 +346,7 @@ def handle_request(self, **kwargs): self.do_generate_characters() else: properties = data["properties"] - properties["skip_special_tokens"] = kwargs.pop("skip_special_tokens", False) + properties["skip_special_tokens"] = kwargs.pop("skip_special_tokens", True) response = self.llm_runner.generate( user_input, **properties @@ -409,8 +397,6 @@ def process_chat_repsonse(self, string, username): return string def send_user_message(self, action, message): - print("send_user_message", action, message) - print("send_user_message") if action != "action": self.add_user_message(message) self.client.message = { @@ -421,7 +407,6 @@ def send_user_message(self, action, message): "prompt": None, "username": self.username, "botname": self.botname, - "seed": self.seed, "conversation": self, "properties": self.properties, } @@ -441,7 +426,6 @@ def get_user_reaction(self): action_prompt = self.generate_reaction_prompt() reaction = self.llm_runner.generate( action_prompt, - seed=self.seed, **self.properties, return_result=True, skip_special_tokens=True @@ -458,7 +442,6 @@ def get_user_sentiment(self): sentiment_prompt = self.format_user_sentiment_prompt() sentiment_results = self.llm_runner.generate( sentiment_prompt, - seed=self.seed, **properties, return_result=True, skip_special_tokens=True @@ -492,7 +475,7 @@ def get_bot_response(self, bot_mood: str, user_sentiment: str): properties["max_length"] = 512 properties["min_length"] = 0 properties["temperature"] = 1.75 - properties["skip_special_tokens"] = False + properties["skip_special_tokens"] = True prompt = self.generate_response_prompt(bot_mood, user_sentiment) response = self.llm_runner.generate(prompt, **properties) response = self.process_chat_repsonse(response, self.botname) @@ -508,7 +491,6 @@ def get_bot_mood(self): mood_prompt = self.get_bot_mood_prompt() mood_results = self.llm_runner.generate( prompt=mood_prompt, - seed=self.seed, **properties, return_result=True, skip_special_tokens=True @@ -530,20 +512,17 @@ def is_bot_alive(self) -> bool: prompt = self.format_bot_alive_prompt() results = self.llm_runner.generate( prompt=prompt, - seed=self.seed, **self.properties, return_result=True, skip_special_tokens=True ) bot_alive = results bot_alive = bot_alive.strip() - print("IS BOT ALIVE: ", bot_alive) return bot_alive != "no" def summarize(self): results = self.llm_runner.generate( prompt=self.summary_prompt(), - seed=self.seed, **self.properties, return_result=True, skip_special_tokens=True) @@ -594,7 +573,7 @@ def handle_request(self, **kwargs): data = kwargs.get("data", {}) user_input = data.get("user_input", None) properties = data["properties"] - properties["skip_special_tokens"] = kwargs.pop("skip_special_tokens", False) + properties["skip_special_tokens"] = kwargs.pop("skip_special_tokens", True) response = self.llm_runner.generate( user_input, **properties @@ -606,5 +585,4 @@ def handle_request(self, **kwargs): def send_user_message(self, action, message): if action != "action": - self.add_user_message(message) - print("setting client message") \ No newline at end of file + self.add_user_message(message) \ No newline at end of file diff --git a/src/chatairunner/main_llm.py b/src/chatairunner/main_llm.py index 64461c7..6c7ba79 100644 --- a/src/chatairunner/main_llm.py +++ b/src/chatairunner/main_llm.py @@ -46,6 +46,10 @@ def set_form_defaults(self): def handle_value_change(self, value_name, value, widget_name): self.ui.__getattribute__(widget_name).setValue(value) + # change ints to floats + if value_name in ["top_p", "temperature", "repetition_penalty", "length_penalty"]: + if type(value) == int: + value = value / 100 self.conversation.properties[value_name] = value def initialize_form(self): @@ -71,6 +75,10 @@ def initialize_form(self): self.ui.length_penalty_slider.valueChanged.connect(lambda val: self.handle_value_change("length_penalty", self.ui.length_penalty_slider.value() / 100, "length_penalty_spinbox")) self.ui.length_penalty_spinbox.valueChanged.connect(lambda val: self.handle_value_change("length_penalty", int(self.ui.length_penalty_spinbox.value() * 100), "length_penalty_slider")) + # initialize cancel and clear buttons + self.ui.cancelButton.clicked.connect(self.handle_cancel) + self.ui.clearButton.clicked.connect(self.handle_clear) + # set a random seed in seed text box self.ui.seed.setPlainText(str(random.randint(0, 1000000))) @@ -92,6 +100,11 @@ def initialize_form(self): self.ui.setWindowTitle("Chat AI") + self.seed = int(self.ui.seed.toPlainText()) + + def handle_clear(self): + self.ui.generated_text.setPlainText("") + def disable_generate_button(self): self.ui.generate_button.setEnabled(False) @@ -130,6 +143,8 @@ def handle_load(self): self.ui.length_penalty_spinbox.setValue(properties["length_penalty"]) self.ui.seed.setPlainText(properties["seed"]) self.ui.generated_text.setPlainText(properties["generated_text"]) + self.ui.prefix.setPlainText(properties["prefix"]) + self.ui.prompt.setPlainText(properties["prompt"]) def handle_save(self): # display a save dialogue for .txtrunner and .llmrunner files @@ -141,42 +156,15 @@ def handle_save(self): filename += ".json" with open(filename, "w") as f: # save properties - properties = self.prep_properties() + properties = self.conversation.properties properties["seed"] = self.ui.seed.toPlainText() # set prompt and few shot prompt # set generated text properties["generated_text"] = self.ui.generated_text.toPlainText() + properties["prefix"] = self.ui.prefix.toPlainText() + properties["prompt"] = self.ui.prompt.toPlainText() json.dump(properties, f) - def prep_properties(self): - num_beams = self.ui.num_beams_spinbox.value() - if num_beams == 0: - num_beams = None - repetition_penalty = self.ui.repetition_penalty_spinbox.value() - if repetition_penalty == 0.0: - repetition_penalty = 0.01 - top_p = self.ui.top_p_spinbox.value() - top_p_min = 0.01 - if top_p <= top_p_min: - top_p = top_p_min - min_length = self.ui.min_length_spinbox.value() - max_length = self.ui.max_length_spinbox.value() - if max_length < min_length: - max_length = min_length - return { - "max_length": max_length, - "min_length": min_length, - "num_beams": num_beams, - "temperature": self.ui.temperature_spinbox.value(), - "top_k": self.ui.top_k_spinbox.value(), - "top_p": self.ui.top_p_spinbox.value(), - "repetition_penalty": repetition_penalty, - "length_penalty": self.ui.length_penalty_spinbox.value(), - "no_repeat_ngram_size": self.ui.no_repeat_ngram_size_spinbox.value(), - "num_return_sequences": self.ui.num_return_sequences_spinbox.value(), - "model": self.ui.model_dropdown.currentText(), - } - if __name__ == '__main__': MainWindow([]) diff --git a/src/chatairunner/pyqt/main_window.ui b/src/chatairunner/pyqt/main_window.ui index 49a30cc..514b487 100644 --- a/src/chatairunner/pyqt/main_window.ui +++ b/src/chatairunner/pyqt/main_window.ui @@ -27,8 +27,64 @@ + + + + + + + Seed + + + + + + + Generate + + + + + + + Prefix + + + + + + + 16777215 + 100 + + + + + + + + + + + + 16777215 + 30 + + + + + + + + Random Seed + + + true + + + @@ -36,6 +92,13 @@ + + + + Early stopping + + + @@ -55,6 +118,21 @@ + + + + Model + + + + 0 + + + + + + + @@ -513,55 +591,13 @@ - + 0 - - - - Generate - - - - - - - - 16777215 - 30 - - - - - - - - Random Seed - - - true - - - - - - - Model - - - - 0 - - - - - - - @@ -575,44 +611,22 @@ - - + + - Early stopping + Cancel - - + + - Seed - - - - - - - Prefix + Clear - - - - - - 16777215 - 100 - - - - - - - - @@ -634,7 +648,21 @@ + + + Advanced + + + + + + Help + + + + + @@ -669,6 +697,16 @@ Ctrl+N + + + Console + + + + + About + + diff --git a/src/chatairunner/v1.yaml b/src/chatairunner/v1.yaml new file mode 100644 index 0000000..63e82cf --- /dev/null +++ b/src/chatairunner/v1.yaml @@ -0,0 +1,70 @@ +model: + base_learning_rate: 1.0e-04 + target: ddpm.LatentDiffusion + params: + linear_start: 0.00085 + linear_end: 0.0120 + num_timesteps_cond: 1 + log_every_t: 200 + timesteps: 1000 + first_stage_key: "jpg" + cond_stage_key: "txt" + image_size: 64 + channels: 4 + cond_stage_trainable: false # Note: different from the one we trained before + conditioning_key: crossattn + monitor: val/loss_simple_ema + scale_factor: 0.18215 + use_ema: False + + scheduler_config: # 10000 warmup steps + target: stablediffusion.ldmlr_scheduler.LambdaLinearScheduler + params: + warm_up_steps: [ 10000 ] + cycle_lengths: [ 10000000000000 ] # incredibly large number to prevent corner cases + f_start: [ 1.e-6 ] + f_max: [ 1. ] + f_min: [ 1. ] + + unet_config: + target: openaimodel.UNetModel + params: + image_size: 32 # unused + in_channels: 4 + out_channels: 4 + model_channels: 320 + attention_resolutions: [ 4, 2, 1 ] + num_res_blocks: 2 + channel_mult: [ 1, 2, 4, 4 ] + num_heads: 8 + use_spatial_transformer: True + transformer_depth: 1 + context_dim: 768 + use_checkpoint: True + legacy: False + + first_stage_config: + target: autoencoder.AutoencoderKL + params: + embed_dim: 4 + monitor: val/rec_loss + ddconfig: + double_z: true + z_channels: 4 + resolution: 256 + in_channels: 3 + out_ch: 3 + ch: 128 + ch_mult: + - 1 + - 2 + - 4 + - 4 + num_res_blocks: 2 + attn_resolutions: [] + dropout: 0.0 + lossconfig: + target: nn.Identity + + cond_stage_config: + target: frozenclip.FrozenCLIPEmbedder diff --git a/src/chatairunner/v2.yaml b/src/chatairunner/v2.yaml new file mode 100644 index 0000000..152c4f3 --- /dev/null +++ b/src/chatairunner/v2.yaml @@ -0,0 +1,67 @@ +model: + base_learning_rate: 1.0e-4 + target: ldm.models.diffusion.ddpm.LatentDiffusion + params: + linear_start: 0.00085 + linear_end: 0.0120 + num_timesteps_cond: 1 + log_every_t: 200 + timesteps: 1000 + first_stage_key: "jpg" + cond_stage_key: "txt" + image_size: 64 + channels: 4 + cond_stage_trainable: false + conditioning_key: crossattn + monitor: val/loss_simple_ema + scale_factor: 0.18215 + use_ema: False # we set this to false because this is an inference only config + + unet_config: + target: ldm.modules.diffusionmodules.openaimodel.UNetModel + params: + use_checkpoint: True + use_fp16: True + image_size: 32 # unused + in_channels: 4 + out_channels: 4 + model_channels: 320 + attention_resolutions: [ 4, 2, 1 ] + num_res_blocks: 2 + channel_mult: [ 1, 2, 4, 4 ] + num_head_channels: 64 # need to fix for flash-attn + use_spatial_transformer: True + use_linear_in_transformer: True + transformer_depth: 1 + context_dim: 1024 + legacy: False + + first_stage_config: + target: ldm.models.autoencoder.AutoencoderKL + params: + embed_dim: 4 + monitor: val/rec_loss + ddconfig: + #attn_type: "vanilla-xformers" + double_z: true + z_channels: 4 + resolution: 256 + in_channels: 3 + out_ch: 3 + ch: 128 + ch_mult: + - 1 + - 2 + - 4 + - 4 + num_res_blocks: 2 + attn_resolutions: [] + dropout: 0.0 + lossconfig: + target: torch.nn.Identity + + cond_stage_config: + target: ldm.modules.encoders.modules.FrozenOpenCLIPEmbedder + params: + freeze: True + layer: "penultimate"