Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added forge first version #670

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 8 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ services:
environment:
- CLI_ARGS=--allow-code --medvram --xformers --enable-insecure-extension-access --api

forge: &forge
<<: *base_service
profiles: ["forge"]
build: ./services/forge
image: sd-forge-fgcustom:72
environment:
- CLI_ARGS=--allow-code --xformers --enable-insecure-extension-access --api --pin-shared-memory --cuda-malloc --cuda-stream

auto-cpu:
<<: *automatic
profiles: ["auto-cpu"]
Expand Down
70 changes: 70 additions & 0 deletions services/forge/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
FROM alpine/git:2.36.2 as download

COPY clone.sh /clone.sh


RUN . /clone.sh stable-diffusion-stability-ai https://github.com/Stability-AI/stablediffusion.git cf1d67a6fd5ea1aa600c4df58e5b47da45f6bdbf \
&& rm -rf assets data/**/*.png data/**/*.jpg data/**/*.gif

RUN . /clone.sh CodeFormer https://github.com/sczhou/CodeFormer.git c5b4593074ba6214284d6acd5f1719b6c5d739af \
&& rm -rf assets inputs

RUN . /clone.sh BLIP https://github.com/salesforce/BLIP.git 48211a1594f1321b00f14c9f7a5b4813144b2fb9
RUN . /clone.sh k-diffusion https://github.com/crowsonkb/k-diffusion.git ab527a9a6d347f364e3d185ba6d714e22d80cb3c
RUN . /clone.sh clip-interrogator https://github.com/pharmapsychotic/clip-interrogator 2cf03aaf6e704197fd0dae7c7f96aa59cf1b11c9
RUN . /clone.sh generative-models https://github.com/Stability-AI/generative-models 45c443b316737a4ab6e40413d7794a7f5657c19f
RUN . /clone.sh stable-diffusion-webui-assets https://github.com/AUTOMATIC1111/stable-diffusion-webui-assets.git 6f7db241d2f8ba7457bac5ca9753331f0c266917


FROM pytorch/pytorch:2.1.2-cuda12.1-cudnn8-runtime

ENV DEBIAN_FRONTEND=noninteractive PIP_PREFER_BINARY=1

RUN --mount=type=cache,target=/var/cache/apt \
apt-get update && \
# we need those
apt-get install -y fonts-dejavu-core rsync git jq moreutils aria2 \
# extensions needs those
ffmpeg libglfw3-dev libgles2-mesa-dev pkg-config libcairo2 libcairo2-dev build-essential


WORKDIR /
RUN --mount=type=cache,target=/root/.cache/pip \
git clone https://github.com/lllyasviel/stable-diffusion-webui-forge.git && \
cd stable-diffusion-webui-forge && \
#git reset --hard cf2772fab0af5573da775e7437e6acdca424f26e && \
pip install -r requirements_versions.txt


ENV ROOT=/stable-diffusion-webui-forge

COPY --from=download /repositories/ ${ROOT}/repositories/
RUN mkdir ${ROOT}/interrogate && cp ${ROOT}/repositories/clip-interrogator/clip_interrogator/data/* ${ROOT}/interrogate
RUN --mount=type=cache,target=/root/.cache/pip \
pip install -r ${ROOT}/repositories/CodeFormer/requirements.txt

RUN --mount=type=cache,target=/root/.cache/pip \
pip install pyngrok xformers==0.0.23.post1 \
git+https://github.com/TencentARC/GFPGAN.git@8d2447a2d918f8eba5a4a01463fd48e45126a379 \
git+https://github.com/openai/CLIP.git@d50d76daa670286dd6cacf3bcd80b5e4823fc8e1 \
git+https://github.com/mlfoundations/[email protected]

# there seems to be a memory leak (or maybe just memory not being freed fast enough) that is fixed by this version of malloc
# maybe move this up to the dependencies list.
RUN apt-get -y install libgoogle-perftools-dev && apt-get clean
ENV LD_PRELOAD=libtcmalloc.so

COPY . /docker

RUN \
# mv ${ROOT}/style.css ${ROOT}/user.css && \
# one of the ugliest hacks I ever wrote \
sed -i 's/in_app_dir = .*/in_app_dir = True/g' /opt/conda/lib/python3.10/site-packages/gradio/routes.py && \
git config --global --add safe.directory '*'

WORKDIR ${ROOT}
ENV NVIDIA_VISIBLE_DEVICES=all
ENV CLI_ARGS=""
EXPOSE 7860
ENTRYPOINT ["/docker/entrypoint.sh"]
CMD python -u webui.py --listen --port 7860 ${CLI_ARGS}
11 changes: 11 additions & 0 deletions services/forge/clone.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/bash

set -Eeuox pipefail

mkdir -p /repositories/"$1"
cd /repositories/"$1"
git init
git remote add origin "$2"
git fetch origin "$3" --depth=1
git reset --hard "$3"
rm -rf .git
78 changes: 78 additions & 0 deletions services/forge/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#!/usr/bin/env python3

"""Checks and sets default values for config.json before starting the container."""

import json
import re
import os.path
import sys

DEFAULT_FILEPATH = '/data/config/forge/config.json'

DEFAULT_OUTDIRS = {
"outdir_samples": "",
"outdir_txt2img_samples": "/output/txt2img",
"outdir_img2img_samples": "/output/img2img",
"outdir_extras_samples": "/output/extras",
"outdir_grids": "",
"outdir_txt2img_grids": "/output/txt2img-grids",
"outdir_img2img_grids": "/output/img2img-grids",
"outdir_save": "/output/saved",
"outdir_init_images": "/output/init-images",
}
RE_VALID_OUTDIR = re.compile(r"(^/output(/\.?[\w\-\_]+)+/?$)|(^\s?$)")

DEFAULT_OTHER = {
"font": "DejaVuSans.ttf",
}

def dict_to_json_file(target_file: str, data: dict):
"""Write dictionary to specified json file"""

with open(target_file, 'w') as f:
json.dump(data, f)

def json_file_to_dict(config_file: str) -> dict|None:
"""Load json file into a dictionary. Return None if file does not exist."""

if os.path.isfile(config_file):
with open(config_file, 'r') as f:
return json.load(f)
else:
return None

def replace_if_invalid(value: str, replacement: str, pattern: str|re.Pattern[str]) -> str:
"""Returns original value if valid, fallback value if invalid"""

if re.match(pattern, value):
return value
else:
return replacement

def check_and_replace_config(config_file: str, target_file: str = None):
"""Checks given file for invalid values. Replaces those with fallback values (default: overwrites file)."""

# Get current user config, or empty if file does not exists
data = json_file_to_dict(config_file) or {}

# Check and fix output directories
for k, def_val in DEFAULT_OUTDIRS.items():
if k not in data:
data[k] = def_val
else:
data[k] = replace_if_invalid(value=data[k], replacement=def_val, pattern=RE_VALID_OUTDIR)

# Check and fix other default settings
for k, def_val in DEFAULT_OTHER.items():
if k not in data:
data[k] = def_val

# Write results to file
dict_to_json_file(target_file or config_file, data)

if __name__ == '__main__':
if len(sys.argv) > 1:
check_and_replace_config(*sys.argv[1:])
else:
check_and_replace_config(DEFAULT_FILEPATH)

85 changes: 85 additions & 0 deletions services/forge/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#!/bin/bash

set -Eeuo pipefail

# TODO: move all mkdir -p ?
mkdir -p /data/config/forge/scripts/
# mount scripts individually

echo $ROOT
ls -lha $ROOT

find "${ROOT}/scripts/" -maxdepth 1 -type l -delete
cp -vrfTs /data/config/forge/scripts/ "${ROOT}/scripts/"

# Set up config file
python /docker/config.py /data/config/forge/config.json

if [ ! -f /data/config/forge/ui-config.json ]; then
echo '{}' >/data/config/forge/ui-config.json
fi

if [ ! -f /data/config/forge/styles.csv ]; then
touch /data/config/forge/styles.csv
fi

# copy models from original models folder
mkdir -p /data/models/VAE-approx/ /data/models/karlo/

rsync -a --info=NAME ${ROOT}/models/VAE-approx/ /data/models/VAE-approx/
rsync -a --info=NAME ${ROOT}/models/karlo/ /data/models/karlo/

declare -A MOUNTS

MOUNTS["/root/.cache"]="/data/.cache"
MOUNTS["${ROOT}/models"]="/data/models"

MOUNTS["${ROOT}/embeddings"]="/data/embeddings"
MOUNTS["${ROOT}/config.json"]="/data/config/forge/config.json"
MOUNTS["${ROOT}/ui-config.json"]="/data/config/forge/ui-config.json"
MOUNTS["${ROOT}/styles.csv"]="/data/config/forge/styles.csv"
MOUNTS["${ROOT}/extensions"]="/data/config/forge/extensions"
MOUNTS["${ROOT}/config_states"]="/data/config/forge/config_states"

# extra hacks
MOUNTS["${ROOT}/repositories/CodeFormer/weights/facelib"]="/data/.cache"

for to_path in "${!MOUNTS[@]}"; do
set -Eeuo pipefail
from_path="${MOUNTS[${to_path}]}"
rm -rf "${to_path}"
if [ ! -f "$from_path" ]; then
mkdir -vp "$from_path"
fi
mkdir -vp "$(dirname "${to_path}")"
ln -sT "${from_path}" "${to_path}"
echo Mounted $(basename "${from_path}")
done

echo "Installing extension dependencies (if any)"

# because we build our container as root:
chown -R root ~/.cache/
chmod 766 ~/.cache/

shopt -s nullglob
# For install.py, please refer to https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Developing-extensions#installpy
list=(./extensions/*/install.py)
for installscript in "${list[@]}"; do
EXTNAME=$(echo $installscript | cut -d '/' -f 3)
# Skip installing dependencies if extension is disabled in config
if $(jq -e ".disabled_extensions|any(. == \"$EXTNAME\")" config.json); then
echo "Skipping disabled extension ($EXTNAME)"
continue
fi
PYTHONPATH=${ROOT} python "$installscript"
done

if [ -f "/data/config/forge/startup.sh" ]; then
pushd ${ROOT}
echo "Running startup script"
. /data/config/forge/startup.sh
popd
fi

exec "$@"