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 |