Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@ node_modules
.changeset
.git
.github

__pycache__
.pytest_cache
.venv
45 changes: 26 additions & 19 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
name: Tests

on:
workflow_dispatch:
push:
branches-ignore: [main]
pull_request:
types: [opened, synchronize, reopened, ready_for_review]

jobs:
test:
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v3

- name: Placeholder test
run: echo "Tests temporarily disabled - placeholder to satisfy required checks"
name: Tests

on:
workflow_dispatch:
push:
branches-ignore: [main]
pull_request:
types: [opened, synchronize, reopened, ready_for_review]

jobs:
test:
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v3

- uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install test dependencies
run: python -m pip install -r requirements.txt pytest jsonschema
- name: Worker contracts and lifecycle
run: python -m pytest tests -q
- name: Startup syntax
run: bash -n src/start.sh
29 changes: 24 additions & 5 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ ARG BASE_IMAGE=nvidia/cuda:12.8.1-cudnn-runtime-ubuntu24.04
FROM ${BASE_IMAGE} AS base

# Build arguments for this stage with sensible defaults for standalone builds
ARG COMFYUI_VERSION=0.34.0
ARG COMFYUI_VERSION=0.36.0
ARG CUDA_VERSION_FOR_COMFY=12.8
ARG ENABLE_PYTORCH_UPGRADE=false
ARG PYTORCH_INDEX_URL
Expand Down Expand Up @@ -106,14 +106,23 @@ WORKDIR /comfyui
# Support for the network volume
ADD src/extra_model_paths.yaml ./

# Image identity, reported by the handler as trace.image (senai-worker/1 §5/§8).
ARG SENAI_IMAGE_REF=""
ENV SENAI_IMAGE_REF="${SENAI_IMAGE_REF}"
RUN echo "${COMFYUI_VERSION}" > /etc/senai-comfyui-version

# Go back to the root
WORKDIR /

# Install Python runtime dependencies for the handler
RUN uv pip install runpod requests websocket-client
COPY requirements.txt /requirements.txt
RUN uv pip install -r /requirements.txt

# Add application code and scripts
ADD src/start.sh src/network_volume.py handler.py test_input.json ./
ADD src/start.sh src/network_volume.py src/workflow_models.py src/media_output.py \
src/senai_worker.py src/senai_errors.py src/comfy_client.py src/guard.py \
handler.py test_input.json ./
COPY workflow/ /workflow/
RUN chmod +x /start.sh

# Add script to install custom nodes
Expand All @@ -130,6 +139,13 @@ RUN chmod +x /usr/local/bin/comfy-manager-set-mode
# Set the default command to run when starting the container
CMD ["/start.sh"]

# Optional custom-node dependencies, chosen by manifest at build time.
# No workflow, model family or weights are selected by default.
ARG CUSTOM_NODE_MANIFESTS=""
COPY scripts/install-workflow-nodes.py /install-workflow-nodes.py
RUN python /install-workflow-nodes.py --manifests "$CUSTOM_NODE_MANIFESTS"

# Legacy opt-in targets used by existing release jobs; not built by default.
# Stage 2: Download models
FROM base AS downloader

Expand Down Expand Up @@ -180,7 +196,10 @@ RUN if [ "$MODEL_TYPE" = "z-image-turbo" ]; then \
fi

# Stage 3: Final image
FROM base AS final
FROM base AS baked-models

# Copy models from stage 2 to the final image
COPY --from=downloader /comfyui/models /comfyui/models
COPY --from=downloader /comfyui/models /comfyui/models

# Default: generic worker with no model weights.
FROM base AS final
256 changes: 254 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ This project allows you to run ComfyUI workflows as a serverless API endpoint on

## Table of Contents

- [Quickstart](#quickstart)
- [Alur worker generik: dari workflow sampai endpoint](#alur-worker-generik-dari-workflow-sampai-endpoint)
- [Quickstart upstream](#quickstart-upstream)
- [Available Docker Images](#available-docker-images)
- [API Specification](#api-specification)
- [Usage](#usage)
Expand All @@ -23,7 +24,258 @@ This project allows you to run ComfyUI workflows as a serverless API endpoint on

---

## Quickstart
## Alur worker generik: dari workflow sampai endpoint

Fork ini melayani permintaan Senai lewat protokol **`senai-worker/1`**
(`input.protocol == "senai-worker/1"`), bukan lagi lewat env `OUTPUT_FORMAT=senai`
yang sudah dicabut. Kontrak kabel lengkap ada di `contract/senai-worker-1/`
(salinan dari `contracts/runpod-senai-worker/` repo senai) dan detail alur
internal worker ada di [`docs/senai-worker-internals.md`](docs/senai-worker-internals.md).
Bagian di bawah ini menjelaskan bagian yang masih sama untuk kedua protokol
(pemilihan workflow/model), lalu [§6](#6-siapkan-image-dan-konfigurasi-endpoint)
menjelaskan env spesifik protokol senai.

Alur yang digunakan repo ini:

**Pilih workflow → tes lokal → build image → checklist model → isi cache sekali → jalankan endpoint `cache-only` → hasil dikirim → worker dihentikan.**

Image default berisi runtime ComfyUI dan handler, tanpa bobot model dan tanpa
workflow aktif. Menambahkan workflow tidak memerlukan target Docker baru.
Semua perintah berikut dijalankan dari direktori `worker-comfyui/`.

### 1. Pilih workflow yang akan dilayani

| Workflow | Nilai `WORKFLOWS` | Sumber daftar model | Dependency tambahan saat build |
| --- | --- | --- | --- |
| MiniMax H3 R2V | `minimax-h3.yaml` | Metadata di `video_minimax_h3_r2v.json`; 5 aset | Tidak ada custom node dalam graph yang disediakan |
| LTX 2.5 Senai | `ltx25.yaml` | Enam aset untuk graph T2V, I2V, dan FLF | `CUSTOM_NODE_MANIFESTS=ltx25.yaml` |

Contoh berikut memakai MiniMax:

```bash
export WORKFLOWS=minimax-h3.yaml
export WORKER_IMAGE=worker-comfyui:generic
```

`WORKFLOWS=video_minimax_h3_r2v.json` juga valid untuk membaca metadata model
langsung. File lain dalam folder `workflow/` tidak otomatis diunduh.
`WORKFLOW_MANIFESTS` tetap didukung sebagai alias lama; `WORKFLOWS` diprioritaskan.

**Bedakan dua format:** JSON editor MiniMax dipakai untuk menemukan aset model.
Handler tetap menerima **JSON API** dalam `input.workflow`; export melalui
ComfyUI sebelum mengirim job. Pemilihan env menyiapkan aset, bukan otomatis
menjalankan graph atau mengubah format editor menjadi API.

### 2. Jalankan tes lokal tanpa GPU dan tanpa download model

```bash
docker build --platform linux/amd64 \
-f tests/Dockerfile -t worker-comfyui-tests .
docker run --rm worker-comfyui-tests
```

Tes mencakup pemilihan aset, cache, download dengan data tiruan, respons handler,
SDK Runpod `stopPod`, serta cleanup proses ketika selesai, crash, dan SIGTERM.
Hasil terakhir: **49 tes lulus**. Layanan ComfyUI/GPU, download dan S3 dimock;
ini belum membuktikan inference atau penghentian worker di cloud.

### 3. Build image runtime generik

Untuk MiniMax:

```bash
docker build --platform linux/amd64 -t "$WORKER_IMAGE" .
```

Untuk LTX, gunakan Dockerfile yang sama dengan dependency terpilih:

```bash
export WORKFLOWS=ltx25.yaml
export WORKER_IMAGE=worker-comfyui:with-ltx-nodes
docker build --platform linux/amd64 \
--build-arg CUSTOM_NODE_MANIFESTS=ltx25.yaml \
-t "$WORKER_IMAGE" .
```

Build arg memasang custom node yang dipin dalam YAML, bukan bobot model atau
pilihan workflow runtime. Tidak ada target `--target ltx25`. Perubahan dependency
Python/custom node membutuhkan rebuild; perubahan model tidak.

### 4. Periksa checklist model terlebih dahulu

```bash
docker run --rm \
-e WORKFLOWS="$WORKFLOWS" \
-e MODEL_DOWNLOAD_CHECK_ONLY=true \
"$WORKER_IMAGE"
```

Container mencetak aset terpilih lalu berhenti, tanpa GPU check, download,
ComfyUI, atau handler. MiniMax harus menampilkan lima aset MiniMax saja.

**Untuk tahap validasi tanpa bobot nyata, berhenti di sini.** Langkah pengisian
cache berikut memang melakukan download; lanjutkan ketika siap mengunduh model.

### 5. Isi cache sekali, terpisah dari worker yang melayani job

Contoh cache lokal:

```bash
mkdir -p model-cache
docker run --rm \
-v "$PWD/model-cache:/runpod-volume" \
-e WORKFLOWS="$WORKFLOWS" \
-e PREPARE_MODELS_ONLY=true \
-e MODEL_DOWNLOAD_POLICY=missing \
-e MODEL_DOWNLOAD_CONCURRENCY=4 \
"$WORKER_IMAGE"
```

Untuk model yang memerlukan autentikasi, isi `HF_TOKEN` di lingkungan shell dan
tambahkan `-e HF_TOKEN` pada perintah tersebut. Proses preparation tidak memerlukan
GPU dan berhenti setelah selesai. Bobot tersimpan di `model-cache/models/`.

Download menggunakan file sementara, penggantian atomik, dan lock per model.
Receipt `.worker-cache.json` disimpan di samping bobot. Pertahankan receipt ketika
menggunakan kembali cache; `cache-only` memeriksa receipt, sumber, ukuran, dan
mtime tanpa membaca seluruh bobot untuk menghitung ulang hash.

Untuk Runpod, isi **network volume yang sama** dengan yang akan dipasang pada
endpoint. Folder Docker lokal di atas bukan network volume Runpod. Jalankan image
preparation pada compute sementara yang memasang volume tersebut dan atur
`COMFY_MODEL_ROOT` ke lokasi aktualnya. Contoh: bila volume pada Pod terpasang di
`/workspace`, gunakan `/workspace/models`; isi volume itu nantinya terlihat sebagai
`/runpod-volume/models` pada serverless. Hentikan compute preparation setelah selesai.

Jika cache dipindahkan dan mtime berubah, jalankan preparation lagi terhadap
lokasi tujuan sebelum serving. Untuk unduhan yang dapat direproduksi, pin revision
URL dan isi SHA256 dalam manifest; contoh awal masih memakai URL upstream `main`.

### 6. Siapkan image dan konfigurasi endpoint

Push image hasil build ke registry milik Anda:

```bash
export REGISTRY_IMAGE=ghcr.io/your-account/worker-comfyui:generic-v1
docker login ghcr.io
docker tag "$WORKER_IMAGE" "$REGISTRY_IMAGE"
docker push "$REGISTRY_IMAGE"
```

Ganti `your-account` dan tag sesuai registry tujuan. Di Runpod, buat template
serverless menggunakan image tersebut, lalu endpoint queue-based dengan:

- Network volume yang sudah diisi pada langkah 5.
- GPU dan kapasitas disk yang sesuai dengan workflow.
- Active Workers **0**, Max Workers **1** untuk pengujian awal.
- Idle timeout **5 detik** dan satu job per worker pada satu waktu.

Isi env endpoint (contoh MiniMax):

```dotenv
WORKFLOWS=minimax-h3.yaml
MODEL_DOWNLOAD_POLICY=cache-only
COMFY_MODEL_ROOT=/runpod-volume/models
REFRESH_WORKER=true
```

Jangan aktifkan `PREPARE_MODELS_ONLY`, `MODEL_DOWNLOAD_CHECK_ONLY`, atau
`SERVE_API_LOCALLY` pada endpoint serving. Cache yang belum siap akan menghasilkan
error yang meminta preparation; worker tidak diam-diam mengunduh saat cold start.

**Endpoint yang melayani Senai** memakai protokol `senai-worker/1`, bukan lagi
env `OUTPUT_FORMAT=senai` (dicabut). Tidak ada env pemilih protokol di sisi
worker — dispatch ditentukan oleh `input.protocol` pada tiap request; endpoint
hanya perlu env berikut selain env pemilihan workflow di atas:

```dotenv
HF_CACHE_ROOT=/runpod-volume/huggingface-cache/hub # default, sesuaikan bila beda
AWS_BUCKET_NAME=your-output-bucket
AWS_ACCESS_KEY_ID=***
AWS_SECRET_ACCESS_KEY=***
AWS_DEFAULT_REGION=auto
AWS_ENDPOINT_URL=https://your-account.r2.cloudflarestorage.com
INPUT_ALLOWED_HOSTS=your-senai-asset-bucket.example.com
REFRESH_WORKER=dirty
```

Untuk deployment Senai, bobot **bukan** diisi lewat langkah 4–5 di atas (network
volume yang disiapkan manual): produksi memakai fitur **cached model Hugging
Face RunPod** — repo HF yang dipilih di template endpoint, dicache RunPod di
`HF_CACHE_ROOT/models--<org>--<repo>/snapshots/<revision>/<file>`. Boot
memverifikasi keberadaan tiap file dengan `stat` (ukuran cocok manifest), tanpa
menghitung ulang hash. Cache yang kurang tidak membuat worker crash-loop;
worker tetap boot dalam **mode unready** (`ready:false`, kode
`MODEL_CACHE_MISSING`/`OUTPUT_NOT_CONFIGURED`) dan menjawab tiap job dengan
kode itu, supaya operator melihat penyebabnya lewat `/health` alih-alih retry
tanpa akhir. Detail env lengkap, nilai produksi, dan penjelasan protokol ada di
[Configuration Guide](docs/configuration.md#senai-worker1-protocol).

### 7. Kirim job API dan verifikasi sampai worker berhenti

Contoh di bawah memakai payload upstream (`input.workflow` + `input.images`),
berlaku hanya ketika `LEGACY_UPSTREAM_INPUT=true` dan tidak dipakai di endpoint
Senai. Untuk endpoint Senai, kirim envelope `senai-worker/1`
(`input.protocol`, `input.workflow`, `input.inputs[]`, `input.trace`,
`input.limits`) seperti dicontohkan di
[Configuration Guide](docs/configuration.md#senai-worker1-protocol); jangan
mengirim JSON editor MiniMax langsung sebagai payload pada kedua jalur.

Siapkan `request.json` dengan envelope `{"input":{"workflow": ...}}` berisi
graph API lengkap. Untuk R2V/I2V, tambahkan `input.images` berisi nama dan base64
gambar referensi yang cocok dengan node `LoadImage`.

Dengan `RUNPOD_API_KEY` dan `RUNPOD_ENDPOINT_ID` sudah diisi pada shell:

```bash
curl --fail-with-body \
-H "Authorization: Bearer $RUNPOD_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @request.json \
"https://api.runpod.ai/v2/$RUNPOD_ENDPOINT_ID/run"
```

Salin `id` dari respons, kemudian periksa status sampai terminal:

```bash
export RUNPOD_JOB_ID=replace-with-returned-job-id
curl --fail-with-body \
-H "Authorization: Bearer $RUNPOD_API_KEY" \
"https://api.runpod.ai/v2/$RUNPOD_ENDPOINT_ID/status/$RUNPOD_JOB_ID"
```

Pastikan hasil video tersedia, URL storage tetap dapat dibaca setelah worker
berhenti, dan jumlah worker aktif/idle kembali nol saat antrean kosong.
`REFRESH_WORKER=true` meminta retirement melalui SDK setelah hasil dikembalikan;
Runpod yang melakukan penghentian cloud. `start.sh` membersihkan proses anak pada
exit/SIGTERM. Verifikasi ini tetap diperlukan di staging.

Cache menghilangkan download dan hashing seluruh bobot saat cold start, tetapi
image pull, startup ComfyUI, dan pemuatan bobot ke GPU tetap membutuhkan waktu.
Build produksi GPU, download bobot nyata, dan latensi cloud belum diverifikasi
oleh tes CPU di atas.

### 8. Tambahkan atau ganti workflow berikutnya

1. Tambahkan JSON ke `workflow/`. Jika editor JSON memiliki `properties.models`,
buat YAML yang merujuk file itu, seperti [`minimax-h3.yaml`](workflow/minimax-h3.yaml).
Untuk graph tanpa URL model, tulis `models` secara eksplisit seperti
[`ltx25.yaml`](workflow/ltx25.yaml).
2. Pasang definisi workflow melalui volume dengan `WORKFLOW_DIR` yang sesuai,
atau rebuild image untuk menyertakan file baru di `/workflow`.
3. Jika ada custom node baru, deklarasikan repo dan commit dalam YAML, lalu build
dengan `CUSTOM_NODE_MANIFESTS` yang sesuai. Dependency tidak dipasang saat boot.
4. Jalankan checklist dan preparation untuk workflow baru pada cache yang sama.
5. Ubah `WORKFLOWS` pada endpoint dan tetap gunakan `cache-only`. Beberapa workflow
bisa dipilih dengan koma; hanya pilih yang memang akan dilayani.

Detail schema dan cache ada di [Customization Guide](docs/customization.md#generic-workflow-selection-and-model-cache),
dan checklist staging ada di [Deployment Guide](docs/deployment.md#generic-worker-prepare-assets-separately-from-serving).

## Quickstart upstream

Bagian berikut menjelaskan image upstream yang sudah dipublikasikan. Untuk build
repo ini dan alur cache baru, gunakan panduan worker generik di atas.

1. 🐳 Choose one of the [available Docker images](#available-docker-images) for your serverless endpoint (e.g., `runpod/worker-comfyui:<version>-sd3`).
2. 📄 Follow the [Deployment Guide](docs/deployment.md) to set up your RunPod template and endpoint.
Expand Down
Loading