JSCPPProgrammer commited on
Commit
5a2781b
·
verified ·
1 Parent(s): 0be57ac

feat: run Comfy+weights bootstrap at Space import; ANIMA_SKIP_STARTUP_BOOTSTRAP for tests

Browse files
.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 first generation, the app bootstraps **ComfyUI** into `ANIMA_COMFY_ROOT` (default: `./ComfyUI` under the Space working directory) if `main.py` is missing.
27
- 2. Model files are downloaded with 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
 
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
- "Ready. **First run** may take several minutes (ComfyUI clone + model downloads)."
 
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