Spaces:
Running on Zero
Running on Zero
fix(backend,models): runtime corrections from end-to-end testing
Browse files- backend: PromptExecutor needs (server, ...) not server_instance=None;
init_extra_nodes is async — can't use asyncio.run() inside Gradio's
event loop, run on a worker thread with a fresh loop instead;
add minimal _StubServer with the surface PromptExecutor requires.
- models: short-circuit ensure_models when the file already exists at
comfyui/models/<type>/<file> — local dev path with user's existing
ComfyUI models symlinked in shouldn't trigger HF Hub downloads.
- backend.py +32 -3
- models.py +12 -0
backend.py
CHANGED
|
@@ -53,6 +53,19 @@ def _on_spaces() -> bool:
|
|
| 53 |
return bool(os.environ.get("SPACES_ZERO_GPU"))
|
| 54 |
|
| 55 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
def _comfy_dir() -> pathlib.Path:
|
| 57 |
if _on_spaces():
|
| 58 |
return pathlib.Path("/data/comfyui")
|
|
@@ -77,14 +90,30 @@ class ComfyUILibraryBackend:
|
|
| 77 |
# module, NOT under `comfy.execution`. Same for `nodes`. Both must be
|
| 78 |
# imported AFTER the sys.path insert above.
|
| 79 |
import asyncio
|
|
|
|
| 80 |
|
| 81 |
import comfy.cli_args # noqa: F401 — side-effect: registers CLI flags
|
| 82 |
import execution # top-level module — provides PromptExecutor
|
| 83 |
import nodes # top-level module — provides init_extra_nodes (async)
|
| 84 |
|
| 85 |
-
# init_extra_nodes is
|
| 86 |
-
asyncio.run(
|
| 87 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
|
| 89 |
def __repr__(self) -> str:
|
| 90 |
return f"ComfyUILibraryBackend(comfy_dir={self._comfy_dir!r})"
|
|
|
|
| 53 |
return bool(os.environ.get("SPACES_ZERO_GPU"))
|
| 54 |
|
| 55 |
|
| 56 |
+
class _StubServer:
|
| 57 |
+
"""Minimal stub matching the surface ComfyUI's PromptExecutor expects."""
|
| 58 |
+
|
| 59 |
+
client_id: str | None = "ltx23-aio"
|
| 60 |
+
last_node_id: str | None = None
|
| 61 |
+
|
| 62 |
+
def send_sync(self, event: str, data: dict, sid: str | None = None) -> None:
|
| 63 |
+
pass
|
| 64 |
+
|
| 65 |
+
def queue_updated(self) -> None:
|
| 66 |
+
pass
|
| 67 |
+
|
| 68 |
+
|
| 69 |
def _comfy_dir() -> pathlib.Path:
|
| 70 |
if _on_spaces():
|
| 71 |
return pathlib.Path("/data/comfyui")
|
|
|
|
| 90 |
# module, NOT under `comfy.execution`. Same for `nodes`. Both must be
|
| 91 |
# imported AFTER the sys.path insert above.
|
| 92 |
import asyncio
|
| 93 |
+
import threading
|
| 94 |
|
| 95 |
import comfy.cli_args # noqa: F401 — side-effect: registers CLI flags
|
| 96 |
import execution # top-level module — provides PromptExecutor
|
| 97 |
import nodes # top-level module — provides init_extra_nodes (async)
|
| 98 |
|
| 99 |
+
# `nodes.init_extra_nodes` is async. We may be called from within a
|
| 100 |
+
# running event loop (Gradio's handler) — running `asyncio.run()` there
|
| 101 |
+
# raises. Run the coroutine in a fresh loop on a worker thread instead.
|
| 102 |
+
def _init_in_thread() -> None:
|
| 103 |
+
loop = asyncio.new_event_loop()
|
| 104 |
+
asyncio.set_event_loop(loop)
|
| 105 |
+
try:
|
| 106 |
+
loop.run_until_complete(nodes.init_extra_nodes())
|
| 107 |
+
finally:
|
| 108 |
+
loop.close()
|
| 109 |
+
|
| 110 |
+
thread = threading.Thread(target=_init_in_thread, daemon=False)
|
| 111 |
+
thread.start()
|
| 112 |
+
thread.join()
|
| 113 |
+
# PromptExecutor expects a `server` with client_id, send_sync, last_node_id,
|
| 114 |
+
# queue_updated. A minimal stub no-ops all of them — we don't run a real
|
| 115 |
+
# websocket server, we surface progress via comfy.utils.PROGRESS_BAR_HOOK.
|
| 116 |
+
self._executor = execution.PromptExecutor(server=_StubServer())
|
| 117 |
|
| 118 |
def __repr__(self) -> str:
|
| 119 |
return f"ComfyUILibraryBackend(comfy_dir={self._comfy_dir!r})"
|
models.py
CHANGED
|
@@ -201,6 +201,18 @@ def ensure_models(filenames: set[str]) -> Iterator[DownloadEvent]:
|
|
| 201 |
continue
|
| 202 |
entry = MODEL_REGISTRY[filename]
|
| 203 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 204 |
# Resolve source: hf_hub_download returns the cache path (or downloads).
|
| 205 |
try:
|
| 206 |
source = pathlib.Path(
|
|
|
|
| 201 |
continue
|
| 202 |
entry = MODEL_REGISTRY[filename]
|
| 203 |
|
| 204 |
+
# Short-circuit: if the file is already present at its expected location
|
| 205 |
+
# in comfyui/models/<type>/[<subfolder>/]<filename>, skip the HF dance
|
| 206 |
+
# entirely. This is the local-dev hot path where the user's existing
|
| 207 |
+
# ComfyUI models/ is symlinked in.
|
| 208 |
+
existing_dest_dir = comfy_models / entry.comfy_type
|
| 209 |
+
if entry.subfolder:
|
| 210 |
+
existing_dest_dir = existing_dest_dir / entry.subfolder
|
| 211 |
+
existing_dest = existing_dest_dir / filename
|
| 212 |
+
if existing_dest.exists() or existing_dest.is_symlink():
|
| 213 |
+
yield DownloadEvent(filename, 0.0, 0.0)
|
| 214 |
+
continue
|
| 215 |
+
|
| 216 |
# Resolve source: hf_hub_download returns the cache path (or downloads).
|
| 217 |
try:
|
| 218 |
source = pathlib.Path(
|