Spaces:
Running on Zero
Running on Zero
feat: run Comfy+weights bootstrap at Space import; ANIMA_SKIP_STARTUP_BOOTSTRAP for tests
Browse files- .pytest_cache/v/cache/nodeids +1 -0
- README.md +4 -2
- app.py +9 -1
- src/comfy_backend.py +21 -0
- src/config.py +5 -0
- tests/conftest.py +4 -0
- tests/test_app_import.py +6 -0
.pytest_cache/v/cache/nodeids
CHANGED
|
@@ -1,4 +1,5 @@
|
|
| 1 |
[
|
|
|
|
| 2 |
"tests/test_generation_smoke.py::test_comfy_entrypoint_receives_params",
|
| 3 |
"tests/test_generation_smoke.py::test_workflow_includes_ksampler_and_split_loaders",
|
| 4 |
"tests/test_validation.py::test_cfg_respects_step",
|
|
|
|
| 1 |
[
|
| 2 |
+
"tests/test_app_import.py::test_import_app_with_startup_skipped",
|
| 3 |
"tests/test_generation_smoke.py::test_comfy_entrypoint_receives_params",
|
| 4 |
"tests/test_generation_smoke.py::test_workflow_includes_ksampler_and_split_loaders",
|
| 5 |
"tests/test_validation.py::test_cfg_respects_step",
|
README.md
CHANGED
|
@@ -23,10 +23,11 @@ This Hugging Face **Gradio** Space replaces the ComfyUI canvas with a focused UI
|
|
| 23 |
|
| 24 |
## Runtime behavior
|
| 25 |
|
| 26 |
-
1. On
|
| 27 |
-
2. Model files
|
| 28 |
3. ComfyUI listens on **`ANIMA_COMFY_HOST`:`ANIMA_COMFY_PORT`** (default `127.0.0.1:8188`). Gradio serves on **`PORT`** (Hugging Face sets this; default **7860**).
|
| 29 |
4. Inference is wrapped in **`@spaces.GPU`** with a **dynamic duration** based on steps × batch (cap `ANIMA_MAX_GPU_DURATION`, default 300s).
|
|
|
|
| 30 |
|
| 31 |
### Backend limitation & fallback
|
| 32 |
|
|
@@ -54,6 +55,7 @@ RDBT checkpoint on disk: **`rdbt-anima-p3-v024f-16step-dmd2.safetensors`** (plus
|
|
| 54 |
| Variable | Purpose |
|
| 55 |
|----------|---------|
|
| 56 |
| `PORT` | Gradio listen port (HF sets this; usually **7860**). |
|
|
|
|
| 57 |
| `SKIP_CIVITAI` | Set to `1` to skip Civitai download of the RDBT file (only if you provide that file another way). |
|
| 58 |
| `CIVITAI_TOKEN` | Optional bearer token for Civitai API downloads. |
|
| 59 |
| `ANIMA_COMFY_ROOT` | Path to ComfyUI checkout (default `./ComfyUI`). |
|
|
|
|
| 23 |
|
| 24 |
## Runtime behavior
|
| 25 |
|
| 26 |
+
1. **On Space startup** (while the container is still in “Starting…”), the app imports, then **blocks** until Comfy is cloned, dependencies are installed, model files are downloaded, and the headless Comfy server is responding on the loopback port. The Gradio **HTTP server** starts only after this import finishes, so the **Generate** action does not wait on downloads.
|
| 27 |
+
2. Model files use the same **retry / min-size / `.part` streaming** strategy as the original Anima Comfy Space (`circlestone-labs/Anima`, optional `Bedovyy/Anima-FP8`, Civitai RDBT URL).
|
| 28 |
3. ComfyUI listens on **`ANIMA_COMFY_HOST`:`ANIMA_COMFY_PORT`** (default `127.0.0.1:8188`). Gradio serves on **`PORT`** (Hugging Face sets this; default **7860**).
|
| 29 |
4. Inference is wrapped in **`@spaces.GPU`** with a **dynamic duration** based on steps × batch (cap `ANIMA_MAX_GPU_DURATION`, default 300s).
|
| 30 |
+
5. For **tests** or local hacking without large downloads, set **`ANIMA_SKIP_STARTUP_BOOTSTRAP=1`** to defer bootstrap to the first generation (or to skip the import-time block entirely).
|
| 31 |
|
| 32 |
### Backend limitation & fallback
|
| 33 |
|
|
|
|
| 55 |
| Variable | Purpose |
|
| 56 |
|----------|---------|
|
| 57 |
| `PORT` | Gradio listen port (HF sets this; usually **7860**). |
|
| 58 |
+
| `ANIMA_SKIP_STARTUP_BOOTSTRAP` | Set to `1` to **skip** import-time Comfy/weights setup (e.g. unit tests, local dev). First **Generate** then runs the full `ensure_prepared()` path. |
|
| 59 |
| `SKIP_CIVITAI` | Set to `1` to skip Civitai download of the RDBT file (only if you provide that file another way). |
|
| 60 |
| `CIVITAI_TOKEN` | Optional bearer token for Civitai API downloads. |
|
| 61 |
| `ANIMA_COMFY_ROOT` | Path to ComfyUI checkout (default `./ComfyUI`). |
|
app.py
CHANGED
|
@@ -208,7 +208,8 @@ def build_ui() -> gr.Blocks:
|
|
| 208 |
height=600,
|
| 209 |
)
|
| 210 |
status = gr.Markdown(
|
| 211 |
-
"
|
|
|
|
| 212 |
)
|
| 213 |
go = gr.Button("Generate", variant="primary")
|
| 214 |
go.click(
|
|
@@ -225,6 +226,13 @@ def build_ui() -> gr.Blocks:
|
|
| 225 |
demo = build_ui()
|
| 226 |
demo.queue()
|
| 227 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 228 |
# Gradio 6: `theme` is a launch() argument. ZeroGPU calls `demo.launch()` without
|
| 229 |
# running our `if __name__` block, so wrap to always default the Soft theme.
|
| 230 |
_base_launch = demo.launch
|
|
|
|
| 208 |
height=600,
|
| 209 |
)
|
| 210 |
status = gr.Markdown(
|
| 211 |
+
"ComfyUI, Python deps, and model weights are prepared **while the Space is starting** "
|
| 212 |
+
"(see build logs if startup is slow). **Generate** runs inference once the UI is up."
|
| 213 |
)
|
| 214 |
go = gr.Button("Generate", variant="primary")
|
| 215 |
go.click(
|
|
|
|
| 226 |
demo = build_ui()
|
| 227 |
demo.queue()
|
| 228 |
|
| 229 |
+
# Prepare Comfy + weights at container import time so the first HTTP request does
|
| 230 |
+
# not pay download cost; the Space "Starting…" state covers this (skip in tests).
|
| 231 |
+
if not config.skip_startup_bootstrap():
|
| 232 |
+
from src.comfy_backend import run_at_container_startup
|
| 233 |
+
|
| 234 |
+
run_at_container_startup()
|
| 235 |
+
|
| 236 |
# Gradio 6: `theme` is a launch() argument. ZeroGPU calls `demo.launch()` without
|
| 237 |
# running our `if __name__` block, so wrap to always default the Soft theme.
|
| 238 |
_base_launch = demo.launch
|
src/comfy_backend.py
CHANGED
|
@@ -58,6 +58,8 @@ def _start_process() -> None:
|
|
| 58 |
with _lock:
|
| 59 |
if _proc is not None and _proc.poll() is None:
|
| 60 |
return
|
|
|
|
|
|
|
| 61 |
root = config.comfy_root()
|
| 62 |
main_py = os.path.join(root, "main.py")
|
| 63 |
if not os.path.isfile(main_py):
|
|
@@ -106,6 +108,25 @@ def _teardown_process() -> None:
|
|
| 106 |
_proc = None
|
| 107 |
|
| 108 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
def ensure_prepared() -> None:
|
| 110 |
"""Idempotent: clone Comfy, pip, weights, start server. Thread-safe."""
|
| 111 |
global _prepared, _bootstrapped
|
|
|
|
| 58 |
with _lock:
|
| 59 |
if _proc is not None and _proc.poll() is None:
|
| 60 |
return
|
| 61 |
+
if _proc is not None and _proc.poll() is not None:
|
| 62 |
+
_proc = None
|
| 63 |
root = config.comfy_root()
|
| 64 |
main_py = os.path.join(root, "main.py")
|
| 65 |
if not os.path.isfile(main_py):
|
|
|
|
| 108 |
_proc = None
|
| 109 |
|
| 110 |
|
| 111 |
+
def run_at_container_startup() -> None:
|
| 112 |
+
"""
|
| 113 |
+
Block until ComfyUI is cloned, deps installed, weights downloaded, and the
|
| 114 |
+
Comfy process responds on HTTP. Intended to run from app import so the
|
| 115 |
+
Gradio server starts only after the stack is ready.
|
| 116 |
+
"""
|
| 117 |
+
print(
|
| 118 |
+
"[startup] Cloning ComfyUI (if needed), installing deps, downloading weights, "
|
| 119 |
+
"starting Comfy — this can take many minutes on first deploy…",
|
| 120 |
+
flush=True,
|
| 121 |
+
)
|
| 122 |
+
try:
|
| 123 |
+
ensure_prepared()
|
| 124 |
+
except Exception as e:
|
| 125 |
+
print(f"[startup] Failed: {e!s}", flush=True)
|
| 126 |
+
raise
|
| 127 |
+
print("[startup] ComfyUI and model artifacts are ready.", flush=True)
|
| 128 |
+
|
| 129 |
+
|
| 130 |
def ensure_prepared() -> None:
|
| 131 |
"""Idempotent: clone Comfy, pip, weights, start server. Thread-safe."""
|
| 132 |
global _prepared, _bootstrapped
|
src/config.py
CHANGED
|
@@ -159,6 +159,11 @@ def skip_civitai() -> bool:
|
|
| 159 |
return os.environ.get("SKIP_CIVITAI", "").strip() == "1"
|
| 160 |
|
| 161 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 162 |
@dataclass(frozen=True)
|
| 163 |
class GenerationParams:
|
| 164 |
prompt: str
|
|
|
|
| 159 |
return os.environ.get("SKIP_CIVITAI", "").strip() == "1"
|
| 160 |
|
| 161 |
|
| 162 |
+
def skip_startup_bootstrap() -> bool:
|
| 163 |
+
"""If true, skip clone/pip/weights/Comfy at import (tests, local dev without downloads)."""
|
| 164 |
+
return os.environ.get("ANIMA_SKIP_STARTUP_BOOTSTRAP", "").strip() == "1"
|
| 165 |
+
|
| 166 |
+
|
| 167 |
@dataclass(frozen=True)
|
| 168 |
class GenerationParams:
|
| 169 |
prompt: str
|
tests/conftest.py
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
|
| 3 |
+
# Do not clone Comfy or download multi-GB weights when tests import `app` (if they do).
|
| 4 |
+
os.environ.setdefault("ANIMA_SKIP_STARTUP_BOOTSTRAP", "1")
|
tests/test_app_import.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Ensure app.py imports in CI without running multi-GB bootstrap."""
|
| 2 |
+
|
| 3 |
+
def test_import_app_with_startup_skipped() -> None:
|
| 4 |
+
import app # noqa: F401
|
| 5 |
+
|
| 6 |
+
assert app.demo is not None
|