From 070cde5460203e24e3fbf68c4ff6c9a9b7910f3f Mon Sep 17 00:00:00 2001 From: NERDDISCO <492378+TimPietrusky@users.noreply.github.com> Date: Tue, 4 Jun 2024 11:21:41 +0200 Subject: [PATCH] feat: network-volume; execution time config; skip default images; access ComfyUI via web (#35) * feat: provide option to run the handler locally as API * ci: run the workflow on our extended instance * feat: the local API should run on 0.0.0.0 * feat: make the image smaller * ci: use semantic-version to create releases automatically * chore: we don't want to break anyone with a minor release * docs: added section for local API testing * ci: use custom runner * fix: added .releaserc, otherwise semantic-release will complain about a missing "package.json" * feat: support network volumes, skip default models (#16) * Support network volumes * README tweaks * docs: added comment on what is happening * feat: don't overwrite the default paths, but add "runpod_worker_comfy" to have additional paths * docs: updated "bring your own models" --------- Co-authored-by: Tim Pietrusky * feat: provide access to ComfyUI via web * fix: use the full path to the output image * feat: added env vars COMFY_POLLING_INTERVAL_MS and COMFY_POLLING_MAX_RETRIES * test: added "subfolder" * docs: update CUDA guide for Ubuntu * feat(network-volume): added "custom_nodes" * chore: removed lora from civitai * docs: removed lora from civitai * docs: simplified multiple sections; added an image to describe how to find the endpointID * feat: remove "custom_nodes" * feat: move models before python installation * feat: added "runpod-volume"; use "dev" instead of "latest" * docs: removed "custom nodes"; fixed links to GitHub Actions * docs: fixed typo in GitHub * docs: fixed hosts for local testing * docs: updated link to docs from RunPod to create a network volume * feat: expose the port of ComfyUI * docs: fixed example link to download sdxl-turbo; removed nodes from network volume * docs: moved example response into their own block * chore: added example workflows for webp and sdxl-turbo --------- Co-authored-by: Meptl --- Dockerfile | 16 +- README.md | 253 +++++++++++++----- assets/my-endpoint-with-endpointID.png | Bin 0 -> 22600 bytes docker-compose.yml | 4 +- src/extra_model_paths.yaml | 11 + src/rp_handler.py | 6 +- src/start.sh | 13 +- .../workflows/workflow_sdxl_turbo.json | 84 ++++++ test_resources/workflows/workflow_webp.json | 114 ++++++++ tests/test_rp_handler.py | 8 +- 10 files changed, 422 insertions(+), 87 deletions(-) create mode 100644 assets/my-endpoint-with-endpointID.png create mode 100644 src/extra_model_paths.yaml create mode 100644 test_resources/workflows/workflow_sdxl_turbo.json create mode 100644 test_resources/workflows/workflow_webp.json diff --git a/Dockerfile b/Dockerfile index cd446db..a809a94 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,6 +24,12 @@ RUN git clone https://github.com/comfyanonymous/ComfyUI.git /comfyui # Change working directory to ComfyUI WORKDIR /comfyui +ARG SKIP_DEFAULT_MODELS +# Download checkpoints/vae/LoRA to include in image. +RUN if [ -z "$SKIP_DEFAULT_MODELS" ]; then wget -O models/checkpoints/sd_xl_base_1.0.safetensors https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/resolve/main/sd_xl_base_1.0.safetensors; fi +RUN if [ -z "$SKIP_DEFAULT_MODELS" ]; then wget -O models/vae/sdxl_vae.safetensors https://huggingface.co/stabilityai/sdxl-vae/resolve/main/sdxl_vae.safetensors; fi +RUN if [ -z "$SKIP_DEFAULT_MODELS" ]; then wget -O models/vae/sdxl-vae-fp16-fix.safetensors https://huggingface.co/madebyollin/sdxl-vae-fp16-fix/resolve/main/sdxl_vae.safetensors; fi + # Install ComfyUI dependencies RUN pip3 install --no-cache-dir torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121 \ && pip3 install --no-cache-dir xformers==0.0.21 \ @@ -32,14 +38,8 @@ RUN pip3 install --no-cache-dir torch torchvision torchaudio --index-url https:/ # Install runpod RUN pip3 install runpod requests -# Download checkpoints/vae/LoRA to include in image -RUN wget -O models/checkpoints/sd_xl_base_1.0.safetensors https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/resolve/main/sd_xl_base_1.0.safetensors -RUN wget -O models/vae/sdxl_vae.safetensors https://huggingface.co/stabilityai/sdxl-vae/resolve/main/sdxl_vae.safetensors -RUN wget -O models/vae/sdxl-vae-fp16-fix.safetensors https://huggingface.co/madebyollin/sdxl-vae-fp16-fix/resolve/main/sdxl_vae.safetensors - -# Example for adding specific models into image -# ADD models/checkpoints/sd_xl_base_1.0.safetensors models/checkpoints/ -# ADD models/vae/sdxl_vae.safetensors models/vae/ +# Support for the network volume +ADD src/extra_model_paths.yaml ./ # Go back to the root WORKDIR / diff --git a/README.md b/README.md index 00e33d2..ff3c1b4 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,5 @@ # runpod-worker-comfy -Chack out Captain: The AI Platform - -- https://github.com/blib-la/captain -- https://get-captain.com - > [ComfyUI](https://github.com/comfyanonymous/ComfyUI) as a serverless API on [RunPod](https://www.runpod.io/)

@@ -15,6 +10,8 @@ Read our article here: https://blib.la/blog/comfyui-on-runpod [![Discord](https://img.shields.io/discord/1091306623819059300?color=7289da&label=Discord&logo=discord&logoColor=fff&style=for-the-badge)](https://discord.com/invite/m3TBB9XEkb) +→ Please also checkout [Captain: The AI Platform](https://github.com/blib-la/captain) + --- @@ -22,26 +19,28 @@ Read our article here: https://blib.la/blog/comfyui-on-runpod - [Quickstart](#quickstart) - [Features](#features) - [Config](#config) - * [Upload image to AWS S3](#upload-image-to-aws-s3) + - [Upload image to AWS S3](#upload-image-to-aws-s3) - [Use the Docker image on RunPod](#use-the-docker-image-on-runpod) - [API specification](#api-specification) - * [JSON Request Body](#json-request-body) - * [Fields](#fields) - + ["input.images"](#inputimages) + - [JSON Request Body](#json-request-body) + - [Fields](#fields) + - ["input.images"](#inputimages) - [Interact with your RunPod API](#interact-with-your-runpod-api) - * [Health status](#health-status) - * [Generate an image](#generate-an-image) - + [Example request with cURL](#example-request-with-curl) + - [Health status](#health-status) + - [Generate an image](#generate-an-image) + - [Example request with cURL](#example-request-with-curl) - [How to get the workflow from ComfyUI?](#how-to-get-the-workflow-from-comfyui) -- [Build the image](#build-the-image) +- [Bring Your Own Models and Nodes](#bring-your-own-models-and-nodes) + - [Network Volume](#network-volume) + - [Custom Docker Image](#custom-docker-image) - [Local testing](#local-testing) - * [Setup](#setup) - + [Setup for Windows](#setup-for-windows) - * [Test: handler](#test-handler) - * [Local API](#local-api) - + [Starting local endpoint](#starting-local-endpoint) - + [Accessing the API](#accessing-the-api) -- [Automatically deploy to Docker hub with Github Actions](#automatically-deploy-to-docker-hub-with-github-actions) + - [Setup](#setup) + - [Setup for Windows](#setup-for-windows) + - [Testing the RunPod handler](#testing-the-runpod-handler) + - [Local API](#local-api) + - [Access the local Worker API](#access-the-local-worker-api) + - [Access local ComfyUI](#access-local-comfyui) +- [Automatically deploy to Docker hub with GitHub Actions](#automatically-deploy-to-docker-hub-with-github-actions) - [Acknowledgments](#acknowledgments) @@ -50,7 +49,7 @@ Read our article here: https://blib.la/blog/comfyui-on-runpod ## Quickstart -- đŸŗ Use the latest image for your worker: [timpietruskyblibla/runpod-worker-comfy:latest](https://hub.docker.com/r/timpietruskyblibla/runpod-worker-comfy) +- đŸŗ Use the latest release of the image for your worker: [timpietruskyblibla/runpod-worker-comfy:2.1.3](https://hub.docker.com/r/timpietruskyblibla/runpod-worker-comfy) - ⚙ī¸ [Set the environment variables](#config) - ℹī¸ [Use the Docker image on RunPod](#use-the-docker-image-on-runpod) @@ -66,13 +65,17 @@ Read our article here: https://blib.la/blog/comfyui-on-runpod - Build-in VAE: - [sdxl_vae.safetensors](https://huggingface.co/stabilityai/sdxl-vae/) - [sdxl-vae-fp16-fix](https://huggingface.co/madebyollin/sdxl-vae-fp16-fix/) +- [Bring your own models](#bring-your-own-models) - Based on [Ubuntu + NVIDIA CUDA](https://hub.docker.com/r/nvidia/cuda) ## Config -| Environment Variable | Description | Default | -| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | -| `REFRESH_WORKER` | When you want stop the worker after each finished job to have a clean state, see [official documentation](https://docs.runpod.io/docs/handler-additional-controls#refresh-worker). | `false` | +| Environment Variable | Description | Default | +| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | +| `REFRESH_WORKER` | When you want to stop the worker after each finished job to have a clean state, see [official documentation](https://docs.runpod.io/docs/handler-additional-controls#refresh-worker). | `false` | +| `COMFY_POLLING_INTERVAL_MS` | Time to wait between poll attempts in milliseconds. | `250` | +| `COMFY_POLLING_MAX_RETRIES` | Maximum number of poll attempts. This should be increased the longer your workflow is running. | `500` | +| `SERVE_API_LOCALLY` | Enable local API server for development and testing. See [Local Testing](#local-testing) for more details. | disabled | ### Upload image to AWS S3 @@ -95,7 +98,7 @@ This is only needed if you want to upload the generated picture to AWS S3. If yo - In the dialog, configure: - Template Name: `runpod-worker-comfy` (it can be anything you want) - Template Type: serverless (change template type to "serverless") - - Container Image: `/:tag`, in this case: `timpietruskyblibla/runpod-worker-comfy:latest` (or `dev` if you want to have the development release) + - Container Image: `/:tag`, in this case: `timpietruskyblibla/runpod-worker-comfy:2.1.3` (or `dev` if you want to have the development release) - Container Registry Credentials: You can leave everything as it is, as this repo is public - Container Disk: `20 GB` - Enviroment Variables: [Configure S3](#upload-image-to-aws-s3) @@ -109,7 +112,7 @@ This is only needed if you want to upload the generated picture to AWS S3. If yo - Max Workers: `3` (whatever makes sense for you) - Idle Timeout: `5` (you can leave the default) - Flash Boot: `enabled` (doesn't cost more, but provides faster boot of our worker, which is good) - - Advanced: Leave the defaults + - Advanced: If you are using a Network Volume, select it under `Select Network Volume`. Otherwise leave the defaults. - Select a GPU that has some availability - GPUs/Worker: `1` - Click `deploy` @@ -156,11 +159,20 @@ An array of images, where each image should have a different name. ## Interact with your RunPod API -- In the [User Settings](https://www.runpod.io/console/serverless/user/settings) click on `API Keys` and then on the `API Key` button -- Save the generated key somewhere, as you will not be able to see it again when you navigate away from the page -- Use cURL or any other tool to access the API using the API key and your Endpoint-ID: - - Replace `` with your key - - Replace `` with the ID of the endpoint, you find that when you click on your endpoint, it's part of the URLs shown at the bottom of the first box +1. **Generate an API Key**: + + - In the [User Settings](https://www.runpod.io/console/serverless/user/settings), click on `API Keys` and then on the `API Key` button. + - Save the generated key somewhere safe, as you will not be able to see it again when you navigate away from the page. + +2. **Use the API Key**: + + - Use cURL or any other tool to access the API using the API key and your Endpoint ID: + - Replace `` with your key. + +3. **Use your Endpoint**: + - Replace `` with the [ID of the endpoint](https://www.runpod.io/console/serverless). (You can find the endpoint ID by clicking on your endpoint; it is written underneath the name of the endpoint at the top and also part of the URLs shown at the bottom of the first box.) + +![How to find the EndpointID](./assets/my-endpoint-with-endpointID.png) ### Health status @@ -170,7 +182,7 @@ curl -H "Authorization: Bearer " https://api.runpod.ai/v2/ ### Generate an image -You can either create a new job async by using `/run` or a sync by using runsync. The example here is using a sync job and waits until the response is delivered. +You can either create a new job async by using `/run` or a sync by using `/runsync`. The example here is using a sync job and waits until the response is delivered. The API expects a [JSON in this form](#json-request-body), where `workflow` is the [workflow from ComfyUI, exported as JSON](#how-to-get-the-workflow-from-comfyui) and `images` is optional. @@ -180,12 +192,33 @@ Please also take a look at the [test_input.json](./test_input.json) to see how t ```bash curl -X POST -H "Authorization: Bearer " -H "Content-Type: application/json" -d '{"input":{"workflow":{"3":{"inputs":{"seed":1337,"steps":20,"cfg":8,"sampler_name":"euler","scheduler":"normal","denoise":1,"model":["4",0],"positive":["6",0],"negative":["7",0],"latent_image":["5",0]},"class_type":"KSampler"},"4":{"inputs":{"ckpt_name":"sd_xl_base_1.0.safetensors"},"class_type":"CheckpointLoaderSimple"},"5":{"inputs":{"width":512,"height":512,"batch_size":1},"class_type":"EmptyLatentImage"},"6":{"inputs":{"text":"beautiful scenery nature glass bottle landscape, purple galaxy bottle,","clip":["4",1]},"class_type":"CLIPTextEncode"},"7":{"inputs":{"text":"text, watermark","clip":["4",1]},"class_type":"CLIPTextEncode"},"8":{"inputs":{"samples":["3",0],"vae":["4",2]},"class_type":"VAEDecode"},"9":{"inputs":{"filename_prefix":"ComfyUI","images":["8",0]},"class_type":"SaveImage"}}}}' https://api.runpod.ai/v2//runsync +``` + +Example response with AWS S3 bucket configuration -# Response with AWS S3 bucket configuration -# {"delayTime":2188,"executionTime":2297,"id":"sync-c0cd1eb2-068f-4ecf-a99a-55770fc77391-e1","output":{"message":"https://bucket.s3.region.amazonaws.com/10-23/sync-c0cd1eb2-068f-4ecf-a99a-55770fc77391-e1/c67ad621.png","status":"success"},"status":"COMPLETED"} +```json +{ + "delayTime": 2188, + "executionTime": 2297, + "id": "sync-c0cd1eb2-068f-4ecf-a99a-55770fc77391-e1", + "output": { + "message": "https://bucket.s3.region.amazonaws.com/10-23/sync-c0cd1eb2-068f-4ecf-a99a-55770fc77391-e1/c67ad621.png", + "status": "success" + }, + "status": "COMPLETED" +} +``` + +Example response as base64-encoded image -# Response as base64-encoded image -# {"delayTime":2188,"executionTime":2297,"id":"sync-c0cd1eb2-068f-4ecf-a99a-55770fc77391-e1","output":{"message":"base64encodedimage","status":"success"},"status":"COMPLETED"} +```json +{ + "delayTime": 2188, + "executionTime": 2297, + "id": "sync-c0cd1eb2-068f-4ecf-a99a-55770fc77391-e1", + "output": { "message": "base64encodedimage", "status": "success" }, + "status": "COMPLETED" +} ``` ## How to get the workflow from ComfyUI? @@ -199,44 +232,126 @@ curl -X POST -H "Authorization: Bearer " -H "Content-Type: application/ You can now take the content of this file and put it into your `workflow` when interacting with the API. -## Build the image +## Bring Your Own Models and Nodes -You can build the image locally: `docker build -t timpietruskyblibla/runpod-worker-comfy:dev --platform linux/amd64 .` +### Network Volume -🚨 It's important to specify the `--platform linux/amd64`, otherwise you will get an error on RunPod, see [#13](https://github.com/blib-la/runpod-worker-comfy/issues/13) +Using a Network Volume allows you to store and access custom models: -## Local testing +1. **Create a Network Volume**: + - Follow the [RunPod Network Volumes guide](https://docs.runpod.io/pods/storage/create-network-volumes) to create a volume. +2. **Populate the Volume**: -Both tests will use the data from [test_input.json](./test_input.json), so make your changes in there to test this properly. + - Create a temporary GPU instance: + - Navigate to `Manage > Storage`, click `Deploy` under the volume, and deploy any GPU or CPU instance. + - Navigate to `Manage > Pods`. Under the new pod, click `Connect` to open a shell (either via Jupyter notebook or SSH). + - Populate the volume with your models: + ```bash + cd /workspace + for i in checkpoints clip clip_vision configs controlnet embeddings loras upscale_models vae; do mkdir -p models/$i; done + wget -O models/checkpoints/sd_xl_turbo_1.0_fp16.safetensors https://huggingface.co/stabilityai/sdxl-turbo/resolve/main/sd_xl_turbo_1.0_fp16.safetensors + ``` -### Setup +3. **Delete the Temporary GPU Instance**: -- Make sure you have Python >= 3.10 -- Create a virtual environment: `python -m venv venv` -- Activate the virtual environment: `.\venv\Scripts\activate` (Windows) or `source ./venv/bin/activate` (Mac / Linux) -- Install the dependencies: `pip install -r requirements.txt` + - Once populated, [terminate the temporary GPU instance](https://docs.runpod.io/docs/pods#terminating-a-pod). -#### Setup for Windows +4. **Configure Your Endpoint**: + - Use the Network Volume in your endpoint configuration: + - Either create a new endpoint or update an existing one. + - In the endpoint configuration, under `Advanced > Select Network Volume`, select your Network Volume. + +Note: The folders in the Network Volume are automatically available to ComfyUI when the network volume is configured and attached. + +### Custom Docker Image + +If you prefer to include your models directly in the Docker image, follow these steps: -**Note**: Our hope was that we can use this Docker Image with Docker Desktop on Windows. But regardless what we did, it was not possible. So we decided to use Ubuntu as part of WSL (Windows Subsystem for Linux) inside of Windows. This works without any problems, but only if you don't run Docker on Windows itself. +1. **Fork the Repository**: -To run the Docker image on Windows, we need to have WSL2 and a Linux distro (like Ubuntu) installed on Windows. + - Fork this repository to your own GitHub account. -- Follow the [guide on how to get WSL2 and Linux installed in Windows](https://ubuntu.com/tutorials/install-ubuntu-on-wsl2-on-windows-11-with-gui-support#1-overview) to install Ubuntu - - You can skip the "Install and use a GUI package" part as we don't need a GUI +2. **Add Your Models in the Dockerfile**: -* When Ubuntu is installed, you have to login to Ubuntu in the terminal: `wsl -d Ubuntu` -* Update the packages: `sudo apt update` -* [Install Docker in Ubuntu](https://docs.docker.com/engine/install/ubuntu/) & then install docker-compose `sudo apt-get install docker-compose` -* [Install the NVIDIA Toolkit in Ubuntu](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html#configuring-docker) and create the `nvidia` runtime + - Edit the `Dockerfile` to include your models: + ```Dockerfile + RUN wget -O models/checkpoints/sd_xl_base_1.0.safetensors https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/resolve/main/sd_xl_base_1.0.safetensors + ``` + - You can also add custom nodes: + ```Dockerfile + RUN git clone https://github.com//.git custom_nodes/ + ``` -- [Enable GPU acceleration on Ubuntu on WSL2 to use NVIDIA CUDA](https://ubuntu.com/tutorials/enabling-gpu-acceleration-on-ubuntu-on-wsl2-with-the-nvidia-cuda-platform#1-overview) +3. **Build Your Docker Image**: + - Build the image locally: + ```bash + docker build -t /runpod-worker-comfy:dev --platform linux/amd64 . + ``` + - Optionally, skip downloading the default models to reduce the image size: + ```bash + docker build --build-arg SKIP_DEFAULT_MODELS=1 -t /runpod-worker-comfy:dev --platform linux/amd64 . + ``` + - Ensure to specify `--platform linux/amd64` to avoid errors on RunPod, see [issue #13](https://github.com/blib-la/runpod-worker-comfy/issues/13). - - For the step "Install the appropriate Windows vGPU driver for WSL": If you already have your GPU driver installed on Windows, you can skip this +## Local testing -- Add your user to the `docker` group, so that you can use Docker without `sudo`: `sudo usermod -aG docker $USER` +Both tests will use the data from [test_input.json](./test_input.json), so make your changes in there to test this properly. -### Test: handler +### Setup + +1. Make sure you have Python >= 3.10 +2. Create a virtual environment: + ```bash + python -m venv venv + ``` +3. Activate the virtual environment: + - **Windows**: + ```bash + .\venv\Scripts\activate + ``` + - **Mac / Linux**: + ```bash + source ./venv/bin/activate + ``` +4. Install the dependencies: + ```bash + pip install -r requirements.txt + ``` + +#### Setup for Windows + +1. Install WSL2 and a Linux distro (like Ubuntu) following [this guide](https://ubuntu.com/tutorials/install-ubuntu-on-wsl2-on-windows-11-with-gui-support#1-overview). You can skip the "Install and use a GUI package" part. +2. After installing Ubuntu, open the terminal and log in: + ```bash + wsl -d Ubuntu + ``` +3. Update the packages: + ```bash + sudo apt update + ``` +4. Install Docker in Ubuntu: + - Follow the [official Docker installation guide](https://docs.docker.com/engine/install/ubuntu/). + - Install docker-compose: + ```bash + sudo apt-get install docker-compose + ``` + - Install the NVIDIA Toolkit in Ubuntu: + Follow [this guide](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html#configuring-docker) and create the `nvidia` runtime. +5. Enable GPU acceleration on Ubuntu on WSL2: + Follow [this guide](https://canonical-ubuntu-wsl.readthedocs-hosted.com/en/latest/tutorials/gpu-cuda/). + - If you already have your GPU driver installed on Windows, you can skip the "Install the appropriate Windows vGPU driver for WSL" step. +6. Add your user to the `docker` group to use Docker without `sudo`: + ```bash + sudo usermod -aG docker $USER + ``` + +Once these steps are completed, switch to Ubuntu in the terminal and run the Docker image locally on your Windows computer via WSL: + +```bash +wsl -d Ubuntu +``` + +### Testing the RunPod handler - Run all tests: `python -m unittest discover` - If you want to run a specific test: `python -m unittest tests.test_rp_handler.TestRunpodWorkerComfy.test_bucket_endpoint_not_configured` @@ -248,25 +363,27 @@ To get this to work you will also need to start "ComfyUI", otherwise the handler For enhanced local development, you can start an API server that simulates the RunPod worker environment. This feature is particularly useful for debugging and testing your integrations locally. -#### Starting local endpoint - -Set the `SERVE_API_LOCALLY` environment variable to `true` to activate the local API server when running your Docker container. This is already the default value in the `docker-compose.yml`, so you can get it runnig by executing: +Set the `SERVE_API_LOCALLY` environment variable to `true` to activate the local API server when running your Docker container. This is already the default value in the `docker-compose.yml`, so you can get it running by executing: ```bash docker-compose up ``` -#### Accessing the API +#### Access the local Worker API -- With the local API server running, it's accessible at: [http://localhost:8000](http://localhost:8000) +- With the local API server running, it's accessible at: [localhost:8000](http://localhost:8000) - When you open this in your browser, you can also see the API documentation and can interact with the API directly -## Automatically deploy to Docker hub with Github Actions +#### Access local ComfyUI + +- With the local API server running, you can access ComfyUI at: [localhost:8188](http://localhost:8188) + +## Automatically deploy to Docker hub with GitHub Actions -The repo contains two workflows that publish the image to Docker hub using Github Actions: +The repo contains two workflows that publish the image to Docker hub using GitHub Actions: -- [docker-dev.yml](.github/workflows/docker-dev.yml): Creates the image and pushes it to Docker hub with the `dev` tag on every push to the `main` branch -- [docker-release.yml](.github/workflows/docker-release.yml): Creates the image and pushes it to Docker hub with the `latest` and the release tag. It will only be triggered when you create a release on GitHub +- [dev.yml](.github/workflows/dev.yml): Creates the image and pushes it to Docker hub with the `dev` tag on every push to the `main` branch +- [release.yml](.github/workflows/release.yml): Creates the image and pushes it to Docker hub with the `latest` and the release tag. It will only be triggered when you create a release on GitHub If you want to use this, you should add these secrets to your repository: diff --git a/assets/my-endpoint-with-endpointID.png b/assets/my-endpoint-with-endpointID.png new file mode 100644 index 0000000000000000000000000000000000000000..86bc30d2ef8a19446659f07381cb7e1ed5451a35 GIT binary patch literal 22600 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 literal 0 HcmV?d00001 diff --git a/docker-compose.yml b/docker-compose.yml index 37352a3..b328fdc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,13 +2,15 @@ version: "3.8" services: comfyui: - image: timpietruskyblibla/runpod-worker-comfy:latest + image: timpietruskyblibla/runpod-worker-comfy:dev container_name: comfyui-worker environment: - NVIDIA_VISIBLE_DEVICES=all - SERVE_API_LOCALLY=true ports: - "8000:8000" + - "8188:8188" runtime: nvidia volumes: - ./data/comfyui/output:/comfyui/output + - ./data/runpod-volume:/runpod-volume diff --git a/src/extra_model_paths.yaml b/src/extra_model_paths.yaml new file mode 100644 index 0000000..f2c82f9 --- /dev/null +++ b/src/extra_model_paths.yaml @@ -0,0 +1,11 @@ +runpod_worker_comfy: + base_path: /runpod-volume + checkpoints: models/checkpoints/ + clip: models/clip/ + clip_vision: models/clip_vision/ + configs: models/configs/ + controlnet: models/controlnet/ + embeddings: models/embeddings/ + loras: models/loras/ + upscale_models: models/upscale_models/ + vae: models/vae/ diff --git a/src/rp_handler.py b/src/rp_handler.py index b2014ab..2150284 100644 --- a/src/rp_handler.py +++ b/src/rp_handler.py @@ -14,9 +14,9 @@ # Maximum number of API check attempts COMFY_API_AVAILABLE_MAX_RETRIES = 500 # Time to wait between poll attempts in milliseconds -COMFY_POLLING_INTERVAL_MS = 250 +COMFY_POLLING_INTERVAL_MS = os.environ.get("COMFY_POLLING_INTERVAL_MS", 250) # Maximum number of poll attempts -COMFY_POLLING_MAX_RETRIES = 500 +COMFY_POLLING_MAX_RETRIES = os.environ.get("COMFY_POLLING_MAX_RETRIES", 500) # Host where ComfyUI is running COMFY_HOST = "127.0.0.1:8188" # Enforce a clean state after each job is done @@ -237,7 +237,7 @@ def process_output_images(outputs, job_id): for node_id, node_output in outputs.items(): if "images" in node_output: for image in node_output["images"]: - output_images = f"{image['subfolder']}/{image['filename']}" + output_images = os.path.join(image["subfolder"], image["filename"]) print(f"runpod-worker-comfy - image generation is done") diff --git a/src/start.sh b/src/start.sh index b55df3d..6cb5cb8 100644 --- a/src/start.sh +++ b/src/start.sh @@ -4,14 +4,17 @@ TCMALLOC="$(ldconfig -p | grep -Po "libtcmalloc.so.\d" | head -n 1)" export LD_PRELOAD="${TCMALLOC}" -echo "runpod-worker-comfy: Starting ComfyUI" -python3 /comfyui/main.py --disable-auto-launch --disable-metadata & - -echo "runpod-worker-comfy: Starting RunPod Handler" - # Serve the API and don't shutdown the container if [ "$SERVE_API_LOCALLY" == "true" ]; then + echo "runpod-worker-comfy: Starting ComfyUI" + python3 /comfyui/main.py --disable-auto-launch --disable-metadata --listen & + + echo "runpod-worker-comfy: Starting RunPod Handler" python3 -u /rp_handler.py --rp_serve_api --rp_api_host=0.0.0.0 else + echo "runpod-worker-comfy: Starting ComfyUI" + python3 /comfyui/main.py --disable-auto-launch --disable-metadata & + + echo "runpod-worker-comfy: Starting RunPod Handler" python3 -u /rp_handler.py fi \ No newline at end of file diff --git a/test_resources/workflows/workflow_sdxl_turbo.json b/test_resources/workflows/workflow_sdxl_turbo.json new file mode 100644 index 0000000..1adb884 --- /dev/null +++ b/test_resources/workflows/workflow_sdxl_turbo.json @@ -0,0 +1,84 @@ +{ + "input": { + "workflow": { + "3": { + "inputs": { + "seed": 457699577674669, + "steps": 3, + "cfg": 1.5, + "sampler_name": "euler_ancestral", + "scheduler": "normal", + "denoise": 1, + "model": ["4", 0], + "positive": ["6", 0], + "negative": ["7", 0], + "latent_image": ["5", 0] + }, + "class_type": "KSampler", + "_meta": { + "title": "KSampler" + } + }, + "4": { + "inputs": { + "ckpt_name": "sd_xl_turbo_1.0_fp16.safetensors" + }, + "class_type": "CheckpointLoaderSimple", + "_meta": { + "title": "Load Checkpoint" + } + }, + "5": { + "inputs": { + "width": 1024, + "height": 1024, + "batch_size": 1 + }, + "class_type": "EmptyLatentImage", + "_meta": { + "title": "Empty Latent Image" + } + }, + "6": { + "inputs": { + "text": "ancient rome, 4k photo", + "clip": ["4", 1] + }, + "class_type": "CLIPTextEncode", + "_meta": { + "title": "CLIP Text Encode (Prompt)" + } + }, + "7": { + "inputs": { + "text": "text, watermark, blurry, ugly, deformed", + "clip": ["4", 1] + }, + "class_type": "CLIPTextEncode", + "_meta": { + "title": "CLIP Text Encode (Prompt)" + } + }, + "8": { + "inputs": { + "samples": ["3", 0], + "vae": ["4", 2] + }, + "class_type": "VAEDecode", + "_meta": { + "title": "VAE Decode" + } + }, + "9": { + "inputs": { + "filename_prefix": "images/rome", + "images": ["8", 0] + }, + "class_type": "SaveImage", + "_meta": { + "title": "Save Image" + } + } + } + } +} diff --git a/test_resources/workflows/workflow_webp.json b/test_resources/workflows/workflow_webp.json new file mode 100644 index 0000000..8f9a1d5 --- /dev/null +++ b/test_resources/workflows/workflow_webp.json @@ -0,0 +1,114 @@ +{ + "input": { + "workflow": { + "3": { + "inputs": { + "seed": 416138702284529, + "steps": 20, + "cfg": 8, + "sampler_name": "euler", + "scheduler": "normal", + "denoise": 1, + "model": ["4", 0], + "positive": ["6", 0], + "negative": ["7", 0], + "latent_image": ["5", 0] + }, + "class_type": "KSampler", + "_meta": { + "title": "KSampler" + } + }, + "4": { + "inputs": { + "ckpt_name": "v1-5-pruned-emaonly.safetensors" + }, + "class_type": "CheckpointLoaderSimple", + "_meta": { + "title": "Load Checkpoint" + } + }, + "5": { + "inputs": { + "width": 512, + "height": 512, + "batch_size": 1 + }, + "class_type": "EmptyLatentImage", + "_meta": { + "title": "Empty Latent Image" + } + }, + "6": { + "inputs": { + "text": "beautiful scenery nature glass bottle landscape, purple galaxy bottle,", + "clip": ["4", 1] + }, + "class_type": "CLIPTextEncode", + "_meta": { + "title": "CLIP Text Encode (Prompt)" + } + }, + "7": { + "inputs": { + "text": "text, watermark", + "clip": ["4", 1] + }, + "class_type": "CLIPTextEncode", + "_meta": { + "title": "CLIP Text Encode (Prompt)" + } + }, + "8": { + "inputs": { + "samples": ["3", 0], + "vae": ["4", 2] + }, + "class_type": "VAEDecode", + "_meta": { + "title": "VAE Decode" + } + }, + "10": { + "inputs": { + "output_path": "output_path", + "filename_prefix": "filename_prefix", + "filename_delimiter": "___", + "filename_number_padding": 4, + "filename_number_start": "false", + "extension": "webp", + "quality": 10, + "lossless_webp": "false", + "overwrite_mode": "false", + "show_history": "false", + "show_history_by_prefix": "false", + "embed_workflow": "false", + "show_previews": "false", + "images": ["8", 0] + }, + "class_type": "Image Save", + "_meta": { + "title": "Image Save" + } + }, + "11": { + "inputs": { + "images": ["8", 0] + }, + "class_type": "PreviewImage", + "_meta": { + "title": "Preview Image" + } + }, + "12": { + "inputs": { + "images": ["8", 0] + }, + "class_type": "PreviewImage", + "_meta": { + "title": "Preview Image" + } + } + } + } +} diff --git a/tests/test_rp_handler.py b/tests/test_rp_handler.py index f277b51..34cfaab 100644 --- a/tests/test_rp_handler.py +++ b/tests/test_rp_handler.py @@ -131,7 +131,9 @@ def test_bucket_endpoint_not_configured(self, mock_upload_image, mock_exists): mock_exists.return_value = True mock_upload_image.return_value = "simulated_uploaded/image.png" - outputs = {"node_id": {"images": [{"filename": "ComfyUI_00001_.png", "subfolder": ""}]}} + outputs = { + "node_id": {"images": [{"filename": "ComfyUI_00001_.png", "subfolder": ""}]} + } job_id = "123" result = rp_handler.process_output_images(outputs, job_id) @@ -188,7 +190,9 @@ def test_bucket_image_upload_fails_env_vars_wrong_or_missing( # When AWS credentials are wrong or missing, upload_image should return 'simulated_uploaded/...' mock_upload_image.return_value = "simulated_uploaded/image.png" - outputs = {"node_id": {"images": [{"filename": "ComfyUI_00001_.png", "subfolder": "test"}]}} + outputs = { + "node_id": {"images": [{"filename": "ComfyUI_00001_.png", "subfolder": ""}]} + } job_id = "123" result = rp_handler.process_output_images(outputs, job_id)