File size: 6,786 Bytes
b83a9c1 61c37a8 41a10e6 b83a9c1 41a10e6 61c37a8 41a10e6 b83a9c1 dcc0102 b83a9c1 61c37a8 b83a9c1 61c37a8 b83a9c1 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 | # ComfyUI serverless workers (RunPod)
Run ComfyUI workflows as RunPod serverless endpoints, optimized for cold start:
**everything is baked into the image** (models included) so a worker whose host
has the image cached starts in seconds and never touches the network.
Images: `plx1029/comfyui-serverless` on Docker Hub. Current endpoint:
| endpoint | image tag | RunPod endpoint id | contents |
|---|---|---|---|
| qwen-edit-turbo | `qwen-edit-turbo-v7` | `dom5lwr0o5wq6u` | Qwen-Image-Edit-2511 **bf16** + Lightning 4/8-step loras + Skin-Fix/Qwen4Play loras, workflow `qwen-edit-turbo-v4` |
| qwen-edit-nsfw | `qwen-edit-nsfw-v1` | `tplcz9jtzihoxa` | Qwen-Rapid-AIO-NSFW v11.4 + next-scene lora, RES4LYF sampler, workflow `qwen-edit-nsfw` |
| faceswap-lissie | `faceswap-lissie-v1` | `qkeibkeoefcrk3` | Flux-2-Klein 9B + Qwen3-8B TE + bfs-head/lissie loras + Qwen3-VL-4B + RMBG-2.0 (baked), workflow `new-faceswap-lissie-v4` |
RunPod templates `serverless-<endpoint>-v1`, endpoints `serverless-<endpoint>`:
H100 80GB (SXM/PCIe/NVL), FlashBoot, min 0 / max 2 workers, idle timeout 60 s,
QUEUE_DELAY scaler. All endpoints are **workflow-agnostic**: they run their
baked workflow by default, any other workflow whose models/nodes are in the
image via `"workflow"`, or a fully custom API-format graph via
`"workflow_json"` (uploaded images are matched to `LoadImage` nodes by name).
Serverless-specific workflow tweaks (baked copies under `endpoints/*/workflows/`
differ from `workflows/api/` in exactly these ways):
- `qwen-edit-turbo` (since `-v7`; `-v1`/`-v2` had Amateur-Photo instead): lora
slot 2 of the "User LoRAs" node is `Qwen4Play_v2`, **off by default**
(params `lora_qwen4play` / `lora_qwen4play_strength` toggle it); the
Amateur-Photo lora is not in the image anymore. Since `-v7`, `start.sh`
passes no VRAM flag (ComfyUI default, same as pods): `--highvram` pinned
lora weight backups in VRAM, and full-coverage loras like Qwen4Play (all
60 blocks) OOM an 80 GB H100 that way. Perf is unchanged (~56 s vs ~57 s
cold execute).
- `qwen-edit-nsfw`: removed the KJNodes `WidgetToString` node (it reads
UI-only metadata and crashes headless); the Image-Saver metadata node gets a
static model-name string instead. Behavior is otherwise identical.
- `faceswap-lissie`: unchanged, but the endpoint defaults `keep_vl_loaded=true`
so Qwen3-VL stays in VRAM between jobs (~10 s faster warm jobs).
## Layout
```
serverless/
βββ handler.py # generic RunPod handler (same file for every endpoint)
βββ start.sh # container CMD: boot ComfyUI headless, run handler
βββ build.sh # daemonless image builder (crane; runs on a pod)
βββ Dockerfile # reproducible equivalent for machines with Docker
βββ endpoints/
β βββ qwen-edit-turbo/
β βββ endpoint.json # models + custom nodes + default workflow
β βββ workflows/<name>.json # API-format workflow(s) baked into the image
β βββ params/<name>.json # friendly-param -> node.input mapping
βββ tests/
β βββ local_test.py # drive handler against a locally running ComfyUI
β βββ endpoint_test.py # drive a deployed RunPod endpoint
βββ client/ # standalone caller scripts (stdlib-only) + usage README
βββ qwen_edit_sync.py # /runsync: block until done
βββ qwen_edit_async.py # /run: submit, poll, fire-and-forget, collect, cancel
βββ qwen-edit-turbo-v4.json
```
## Calling the endpoint
```
POST https://api.runpod.ai/v2/<ENDPOINT_ID>/runsync (or /run + /status/<id>)
```
```json
{"input": {
"images": [
{"name": "main.jpg", "image": "<base64 or data-URI>"},
{"name": "ref.png", "image": "<base64>"}
],
"params": {
"prompt": "replace the outfit ...",
"mode": "turbo-8",
"seed": 123,
"input_max_dim": 2048,
"output_max_dim": 2560,
"lora_skin_fix": true
},
"set": {"43.cfg": 1.0}
}}
```
- `images[0]` auto-fills the `image` slot, `images[1]` the `image_ref` slot; a
missing second image automatically sets `use_ref=false`.
- `mode`: `turbo-4` | `turbo-8` (default) | `quality`.
- `seed` omitted β randomized (returned in the response).
- `set` patches any raw `NODE.INPUT` (or `NODE.INPUT.KEY`) after params.
- `workflow` selects among baked workflows; `workflow_json` runs a full
API-format graph passthrough.
- Full param list per workflow: `endpoints/<ep>/params/<workflow>.json`.
Response: `{"images": [{"filename", "type": "base64", "data"}], "seed",
"prompt_id", "workflow", "timings": {...}}`.
Payload limits (RunPod): ~10 MB on `/run`, ~20 MB on `/runsync` β enough for
two photos. URL inputs / S3 outputs are a planned follow-up for bigger traffic.
## Building images (no Docker needed β run on a pod)
```bash
cd serverless
# heavy tag: base + custom nodes + one layer PER model (build rarely)
./build.sh models qwen-edit-turbo v1
# tiny tag: + handler/workflows/params + CMD (build often, pushes only MBs)
./build.sh code qwen-edit-turbo v1 v1
```
Needs: `crane` + `pigz` installed, models on disk under
`$COMFY_WORKSPACE/ComfyUI/models/`, `DOCKER_HUB_ACCESS_TOKEN` set. The model
layers are content-addressed: rebuilding/pushing a new code version never
re-uploads them. On a machine with Docker, `Dockerfile` builds the same image.
**Adding a new endpoint family** (e.g. krea2): create
`endpoints/krea2/{endpoint.json,workflows/,params/}`, download its models on
the build pod (`download_missing_models.sh <workflows...>`), then
`./build.sh all krea2 v1`. The handler is already generic.
## Cold start design
- Models live in image layers β cached on RunPod host NVMe after first pull;
no network volume, no downloads at start.
- ComfyUI runs headless (`127.0.0.1`, no Manager/frontend extras); its
default memory management keeps the ~55 GB of bf16 weights resident in
VRAM between jobs, so only the first job on a fresh worker pays model
loading (no `--highvram` since v7 β see the lora note above).
- FlashBoot on the endpoint snapshots warm workers.
- `SERVERLESS_REFRESH=true` (endpoint env) re-pulls handler/workflows/params
from `hf.co/aleph65/ComfyUI/serverless/` at worker boot β iterate on code and
workflow JSONs with **zero image rebuild** (off by default to keep cold
starts fully offline).
## Endpoint env vars
| var | default | meaning |
|---|---|---|
| `SERVERLESS_ENDPOINT` | sole dir in `endpoints/` | which endpoint config to serve |
| `COMFY_PORT` | 7865 | internal ComfyUI port |
| `COMFY_ARGS` | (empty) | extra ComfyUI flags |
| `COMFY_EXEC_TIMEOUT` | 600 | per-job execution timeout (s) |
| `SERVERLESS_REFRESH` | false | boot-time code refresh from the HF repo |
|