Skip to content
This repository was archived by the owner on Aug 2, 2025. It is now read-only.

Commit 1e9a550

Browse files
committed
2 parents d46d7e6 + d2fca5d commit 1e9a550

2 files changed

Lines changed: 47 additions & 92 deletions

File tree

README.md

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,21 @@
1-
![Static Badge](https://img.shields.io/badge/API_version-0.3.29-blue) ![Static Badge](https://img.shields.io/badge/API_coverage-100%25-vividgreen) ![Static Badge](https://img.shields.io/badge/API_tests-passed-vividgreen) ![Static Badge](https://img.shields.io/badge/Known_bugs-1-red) ![Static Badge](https://img.shields.io/badge/Fooocus_version-2.0.78-lightgrey)
2-
31
# RunPod-Fooocus-API
42

5-
This is a RunPod Fooocus-API worker that expects a **Fooocus-API `v0.3.29`** instance installed on a RunPod Network Volume.
6-
For ready-to-use serverless endpoint image with this repo's code use: [`3wad/runpod-fooocus-api:0.3.29`](https://hub.docker.com/r/3wad/runpod-fooocus-api/tags)
3+
This is a RunPod Fooocus-API worker that expects a Fooocus-API v0.3.26 instance installed on a RunPod Network Volume.
4+
5+
Ready-to-use Docker Image with this repo's code: https://hub.docker.com/r/3wad/runpod-fooocus-api (use `3wad/runpod-fooocus-api:0.2.41`)
76

87
## How to prepare Network Volume
9-
- [**Create RunPod network volume:**](https://www.runpod.io/console/user/storage)
10-
17GB is just enough for the generic Foocus with Juggernaut and all controlnet models. You can increase its size any time if you need additional models, loras etc. But unfortunately, it cannot be reduced back without creating new one.
11-
- [**Create a custom Pod Template:**](https://www.runpod.io/console/user/templates) and use the `konieshadow/fooocus-api:v0.3.29` image. I went with 30GB disk sizes, mount path `/workspace`, and expose `http 8888` and `tcp 22`.
12-
- [**Run a GPU pod:**](https://www.runpod.io/console/gpu-secure-cloud) with network volume and custom fooocus-api template you've just created. You don't need a strong GPU pod, the installation is CPU and download-intensive, but be aware that some older-gen pods might not support the required CUDA versions. Let it download and install everything. After the Juggernaut model is downloaded, use the connect button to load into the Fooocus-API docs running on the pod's 8888 port. Here you should try all the API methods you plan to use. Not only to verify they work, but also because additional up-to-date models are downloaded once you run inpaint, outpaint, upscale, vary and img2img (canny, face swap etc.) endpoints for the first time.
13-
- After that you are ready to connect to the pod's console and use `cp -r /app/* /workspace/` to copy everything into the persistent network volume
8+
- Create RunPod network volume. 15GB is just enough for the generic Foocus with Juggernaut model. You can increase its size any time if you need additional models, loras etc. But unfortunately, it cannot be reduced back.
9+
- Create a custom Pod Template and use the `konieshadow/fooocus-api:v0.3.26` image. I went with 30GB disk sizes, mount path /workspace, and expose http 8888 and tcp 22.
10+
- Run the network volume with the custom fooocus-api image you've just created. You don't need a strong GPU pod, the installation is CPU and download-intensive, but be aware that some older-gen pods might not support the required CUDA versions. Let it download and install everything. After the Juggernaut model is downloaded, use the connect button to load into the Fooocus-API docs running on the pod's 8888 port. Here you should try all the API methods you plan to use. Not only to verify they work, but also additional models are downloaded once you run inpaint, outpaint, upscale, vary and image inputs (canny, face swap etc.) endpoints for the first time.
11+
- After that you are ready to connect to the pod's console and use cp -r /app/* /workspace/ to copy everything into the persistent network volume
1412
- Once everything is copied successfully, you can terminate the pod. You have the network volume ready.
15-
---
16-
- Now you can use our premade image: `3wad/runpod-fooocus-api:0.3.29` and skip the next step OR create your custom docker image from this repo that will run on the actual serverless API. Feel free to adjust the code to your needs.
17-
- *If you built your own image, upload it to the Docker Hub.*
18-
- [**Create a custom Serverless Pod Template:**](https://www.runpod.io/console/serverless/user/templates) using the Docker Hub image you've just uploaded (or our premade one). Active container disk should be slightly bigger than the size of that docker image. In the case of our prebuild one, it's currently about 13.7GB
19-
- [**Create a new Serverless API Endpoint:**](https://www.runpod.io/console/serverless) Make sure to choose your (or our) Docker Hub image and not the `konieshadow/fooocus-api` from the step 2. In Advanced settings choose your created network volume.
20-
- Other settings are your choice, but I personally found that using 4090/L4 GPUs + Flashboot is the most cost-effective one. In frequent use, the 4090 is able to return a txt2img in ~8s including cold start, making it **~25x** cheaper to run Fooocus on RunPod than for example using DALLE-3 API. **(01/24 prices: 0,0016usd/img vs 0,04usd/img), This fact can of course vary based on datacenter locations and GPU availability.*
21-
22-
## How to send requests
23-
[request_examples.js](https://github.com/davefojtik/RunPod-Fooocus-API/blob/main/request_examples.js) contain example payloads for all endpoints on your serverless worker. But don't hesitate to ask in the [Discussions](https://github.com/davefojtik/RunPod-Fooocus-API/discussions) if you need more help.
13+
- ---
14+
- Now you can use our premade image: `3wad/runpod-fooocus-api:0.2.41` and skip the next step OR create your custom docker image from this repo that will run on the actual serverless API. Feel free to adjust handler.py based on how you want to make your requests and it's parameters, or add additional features.
15+
- Once you build it, upload it to the Docker Hub.
16+
- Now you create a custom Serverless Pod Template using the Docker Hub image you've just uploaded (or our premade one). Active container disk should be slightly bigger than the size of the worker docker image.
17+
- Create a new Serverless API Endpoint. Make sure to choose your (or ours) Docker Hub image and not the `konieshadow/fooocus-api` from step 2. In Advanced settings choose your created network volume.
18+
- Other settings are your choice, but I personally found that using 4090/L4 GPUs + Flashboot is the most cost-effective one. In frequent use, the 4090 is able to return an image in ~8s including cold start, making it ~4x cheaper to run this on RunPod than for example using DALLE-3 API. This fact can of course vary based on datacenter locations and GPU availability.
2419

25-
## Contributors Welcomed
26-
Feel free to do pull requests, fixes, improvements and suggestions to the code. I can spend only limited time on this as it's a side project for our community discord bot. So any cooperation will help manage this repo better.
20+
## Contributors welcomed
21+
Feel free to do pull requests, fixes, improvements and suggestions to the code. I can spend limited time on this as it's only a side project for our community discord bot. So more people would definitely help me maintain this.

src/handler.py

Lines changed: 32 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
# Native
2-
import shutil
3-
import os
42
import time
53
import requests
64
import re
@@ -31,16 +29,15 @@ def wait_for_service(url):
3129
print("Service not ready yet. Retrying...")
3230
except Exception as err:
3331
print("Error: ", err)
34-
time.sleep(0.5)
32+
33+
time.sleep(1)
34+
3535

3636
def run_inference(params):
3737
config = {
3838
"baseurl": "http://127.0.0.1:8888",
3939
"api": {
40-
"home": ("GET", "/"),
41-
"ping": ("GET", "/ping"),
42-
"txt2img": ("POST", "/v1/generation/text-to-image"),
43-
"txt2img-ip": ("POST", "/v2/generation/text-to-image-with-ip"),
40+
"txt2img": ("POST", "/v1/generation/text-to-image"),
4441
"upscale-vary": ("POST", "/v1/generation/image-upscale-vary"), #multipart/form-data
4542
"upscale-vary2": ("POST", "/v2/generation/image-upscale-vary"),
4643
"inpaint-outpaint": ("POST", "/v1/generation/image-inpait-outpaint"), #multipart/form-data
@@ -49,12 +46,10 @@ def run_inference(params):
4946
"img2img2": ("POST", "/v2/generation/image-prompt"),
5047
"queryjob": ("GET", "/v1/generation/query-job"),
5148
"jobqueue": ("GET", "/v1/generation/job-queue"),
52-
"jobhistory": ("GET", "/v1/generation/job-history"),
53-
"stop": ("POST", "/v1/generation/stop"),
54-
"describe": ("POST", "/v1/tools/describe-image"), #multipart/form-data
55-
"models": ("GET", "/v1/engines/all-models"),
56-
"models-refresh": ("POST", "/v1/engines/refresh-models"),
57-
"styles": ("GET", "/v1/engines/styles")
49+
"stop": ("POST", "/v1/generation/stop"),
50+
"models": ("GET", "/v1/engines/all-models"),
51+
"models-refresh": ("POST", "/v1/engines/refresh-models"),
52+
"styles": ("GET", "/v1/engines/styles")
5853
},
5954
"timeout": 300
6055
}
@@ -65,56 +60,38 @@ def run_inference(params):
6560
api_config = config["api"][api_name]
6661
else:
6762
raise Exception("Method '%s' not yet implemented" % api_name)
68-
63+
#
6964
api_verb = api_config[0]
7065
api_path = api_config[1]
7166
response = {}
7267

73-
# You can send the input_image, input_mask, and cn_images as PNG: binary encoded into base64 string OR as url string
74-
input_imgs = {'input_image':None, 'input_mask':None, 'cn_img1':None, 'cn_img2':None, 'cn_img3':None, 'cn_img4':None, 'image_prompts':[None,None,None,None], 'image':None}
75-
76-
def process_img(value):
77-
if re.search(r'https?:\/\/\S+', value) is not None:
78-
return requests.get(value).content
79-
elif re.search(r'^[A-Za-z0-9+/]+[=]{0,2}$', value) is not None and value != "None":
80-
return base64.b64decode(value)
81-
else:
82-
return value
68+
if api_verb == "GET":
69+
response = sd_session.get(
70+
url='%s%s' % (config["baseurl"], api_path),
71+
timeout=config["timeout"])
8372

84-
for key, value in input_imgs.items():
73+
# You can send the input_image, input_mask, and cn_images as binary encoded into url-safe base64 string or as publicly accessible url link string
74+
input_imgs = {'input_image':None, 'input_mask':None, 'cn_img1':None, 'cn_img2':None, 'cn_img3':None, 'cn_img4':None,}
75+
for key in input_imgs.items():
8576
if key in params:
8677
try:
87-
if key == "image_prompts":
88-
for index, prompt in enumerate(params.get("image_prompts", [])):
89-
input_imgs["image_prompts"][index] = process_img(prompt["cn_img"])
78+
if not re.search(r'https?://\S+', params[key]) is None:
79+
img_url = params.get(key)
80+
input_imgs[key] = requests.get(img_url).content
9081
else:
91-
input_imgs[key] = process_img(params[key])
82+
input_imgs[key] = base64.urlsafe_b64decode(params[key])
9283
except Exception as e:
9384
print("Image conversion task failed: ", e)
94-
return e
95-
96-
# --- Send requests to the Fooocus-API ---
97-
if api_verb == "GET":
98-
response = sd_session.get(url='%s%s' % (config["baseurl"], api_path), timeout=config["timeout"])
85+
return None
9986

10087
if api_verb == "POST":
10188
# If the request should be multipart/form-data, convert the application/json data into it.
102-
if api_name in ["upscale-vary", "inpaint-outpaint", "img2img", "describe"]:
89+
if api_name in ["upscale-vary", "inpaint-outpaint", "img2img"]:
10390
try:
104-
# Replace the original image params with the processed ones
105-
for key, value in input_imgs.items():
106-
if value is not None:
107-
if type(value) == list:
108-
for i, value in enumerate(input_imgs['image_prompts']):
109-
if value is not None:
110-
params['image_prompts'][i]['cn_img'] = (key+'.png', value, 'image/png')
111-
else:
112-
if isinstance(value, bytes):
113-
params[key] = (key+'.png', value, 'image/png')
114-
else:
115-
params[key] = value
91+
# Remove the input_image key/value from original request data (it gets confused otherwise)
92+
del params['input_image']
11693
# Convert
117-
multipart_data = MultipartEncoder(fields={**params})
94+
multipart_data = MultipartEncoder(fields={key: (f'{key}.png', value, 'image/png') for key, value in input_imgs.items()}, **params)
11895
headers = {'Content-Type': multipart_data.content_type}
11996
response = sd_session.post(
12097
url='%s%s' % (config["baseurl"], api_path),
@@ -123,29 +100,17 @@ def process_img(value):
123100
timeout=config["timeout"])
124101
except Exception as e:
125102
print("multipart/form-data task failed: ", e)
126-
return e
103+
return None
127104
# If the final request should be application/json. Send the original request data
128105
else:
129-
# Convert the processed binary image back to url-safe-base64
130106
for key, value in input_imgs.items():
131-
if value is not None:
132-
if type(value) == list:
133-
for i, value in enumerate(input_imgs['image_prompts']):
134-
if isinstance(value, bytes):
135-
params['image_prompts'][i]["cn_img"] = base64.b64encode(value).decode('utf-8')
136-
elif isinstance(value, bytes):
137-
params[key] = base64.b64encode(value).decode('utf-8')
107+
if not value is None:
108+
params[key] = base64.urlsafe_b64encode(value).decode('utf-8')
138109
response = sd_session.post(
139110
url='%s%s' % (config["baseurl"], api_path),
140111
json=params,
141112
timeout=config["timeout"])
142-
143-
# --- Return the API response to the RunPod ---
144-
content_type = response.headers.get('Content-Type', '')
145-
if 'application/json' in content_type:
146-
return response.json()
147-
else:
148-
return response.text
113+
return response.json()
149114

150115
# ---------------------------------------------------------------------------- #
151116
# RunPod Handler #
@@ -154,21 +119,16 @@ def handler(event):
154119
'''
155120
This is the handler function that will be called by the serverless.
156121
'''
157-
# Clear outputs (Comment out or delete this part to keep images on the volume. Keep in mind you'll always need enough space to generate more)
158-
print("Clearing outputs...")
159-
shutil.rmtree('/workspace/outputs/files')
160-
os.makedirs('/workspace/outputs/files')
161-
shutil.rmtree('/workspace/repositories/Fooocus/outputs')
162-
os.makedirs('/workspace/repositories/Fooocus/outputs')
163122

164123
json = run_inference(event["input"])
165-
124+
166125
# Return the output that you want to be returned like pre-signed URLs to output artifacts
167126
return json
168127

128+
169129
if __name__ == "__main__":
170130
wait_for_service(url='http://127.0.0.1:8888/v1/generation/text-to-image')
171131

172132
print("Fooocus API Service is ready. Starting RunPod...")
173133

174-
runpod.serverless.start({"handler": handler})
134+
runpod.serverless.start({"handler": handler})

0 commit comments

Comments
 (0)