Spaces:
Running on Zero
feat(spaces): registry entries for renamed BF16 aliases + seed inputs + bootstrap
Browse files- MODEL_REGISTRY: 6 new entries for the renamed-but-actually-BF16 files the
master workflow references (gemma_3_12B_it_fp4_mixed → Comfy-Org/ltx-2,
_transformer_only_fp8_scaled → Kijai _bf16, ltx-2-3-22b-dev-Q4_K_M.gguf →
unsloth -BF16.gguf, taeltx2_3, distilled-lora-dynamic, gemma_3_12B_it).
ModelEntry gets `source_filename` so the HF-side name and the
workflow-expected name can differ.
- ensure_models honors source_filename, joins subfolder into the HF path,
and stages at comfyui/models/<comfy_type>/<workflow_filename>.
- requirements.txt: add ComfyUI core deps + custom-node deps that are
required for the LTX 2.3 workflow but aren't in their nodes' own
requirements.txt (gguf, imageio_ffmpeg, opencv-python, matplotlib,
diffusers, yt-dlp, psutil).
- assets/seed_inputs/: tiny placeholder image/audio/video files that the
bootstrap stages into comfyui/input/ so the workflow's always-on loaders
don't fail on cold start when the user hasn't uploaded yet.
- _bootstrap also pip-installs ComfyUI's own requirements.txt on Spaces
cold start (previously only custom-node deps were installed).
- app.py +31 -4
- assets/seed_inputs/5.FLF2.png +0 -0
- assets/seed_inputs/IMG-20210721-WA0008.jpg +0 -0
- assets/seed_inputs/beauty_pagent_dialogue.mp3 +0 -0
- assets/seed_inputs/influencer_mic_hd.png +0 -0
- models.py +70 -18
- requirements.txt +39 -0
|
@@ -52,6 +52,7 @@ def _bootstrap() -> None:
|
|
| 52 |
comfy_dir = pathlib.Path("/data/comfyui" if on_spaces else "comfyui")
|
| 53 |
|
| 54 |
if on_spaces and not comfy_dir.exists():
|
|
|
|
| 55 |
comfy_dir.parent.mkdir(parents=True, exist_ok=True)
|
| 56 |
_git_clone(COMFYUI_REPO, comfy_dir, ref=COMFYUI_COMMIT)
|
| 57 |
for node_url, node_ref in CUSTOM_NODES_PINNED:
|
|
@@ -59,10 +60,16 @@ def _bootstrap() -> None:
|
|
| 59 |
_git_clone(node_url, comfy_dir / "custom_nodes" / name, ref=node_ref)
|
| 60 |
import subprocess
|
| 61 |
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
|
| 67 |
if str(comfy_dir) not in sys.path:
|
| 68 |
sys.path.insert(0, str(comfy_dir))
|
|
@@ -71,6 +78,26 @@ def _bootstrap() -> None:
|
|
| 71 |
str(pathlib.Path("/data/models") if on_spaces else (comfy_dir / "models")),
|
| 72 |
)
|
| 73 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
|
| 75 |
_bootstrap()
|
| 76 |
|
|
|
|
| 52 |
comfy_dir = pathlib.Path("/data/comfyui" if on_spaces else "comfyui")
|
| 53 |
|
| 54 |
if on_spaces and not comfy_dir.exists():
|
| 55 |
+
print(f"[bootstrap] cold start on Spaces; cloning ComfyUI to {comfy_dir}", flush=True)
|
| 56 |
comfy_dir.parent.mkdir(parents=True, exist_ok=True)
|
| 57 |
_git_clone(COMFYUI_REPO, comfy_dir, ref=COMFYUI_COMMIT)
|
| 58 |
for node_url, node_ref in CUSTOM_NODES_PINNED:
|
|
|
|
| 60 |
_git_clone(node_url, comfy_dir / "custom_nodes" / name, ref=node_ref)
|
| 61 |
import subprocess
|
| 62 |
|
| 63 |
+
# ComfyUI core requirements + each custom node's requirements
|
| 64 |
+
for req_path in [
|
| 65 |
+
comfy_dir / "requirements.txt",
|
| 66 |
+
*(cn / "requirements.txt" for cn in (comfy_dir / "custom_nodes").iterdir()),
|
| 67 |
+
]:
|
| 68 |
+
if req_path.exists():
|
| 69 |
+
print(f"[bootstrap] pip install -r {req_path}", flush=True)
|
| 70 |
+
subprocess.check_call(
|
| 71 |
+
[sys.executable, "-m", "pip", "install", "--quiet", "-r", str(req_path)]
|
| 72 |
+
)
|
| 73 |
|
| 74 |
if str(comfy_dir) not in sys.path:
|
| 75 |
sys.path.insert(0, str(comfy_dir))
|
|
|
|
| 78 |
str(pathlib.Path("/data/models") if on_spaces else (comfy_dir / "models")),
|
| 79 |
)
|
| 80 |
|
| 81 |
+
# Stage placeholder input files so the workflow's hard-referenced loaders
|
| 82 |
+
# (LoadImage/VHS_Load*) don't error at runtime even when the active mode
|
| 83 |
+
# doesn't actually use the file. Real user uploads are placed alongside via
|
| 84 |
+
# `_stage_to_comfy_input` later.
|
| 85 |
+
seed_dir = pathlib.Path(__file__).parent / "assets" / "seed_inputs"
|
| 86 |
+
inputs_dir = comfy_dir / "input"
|
| 87 |
+
inputs_dir.mkdir(parents=True, exist_ok=True)
|
| 88 |
+
if seed_dir.exists():
|
| 89 |
+
import shutil
|
| 90 |
+
|
| 91 |
+
for src in seed_dir.iterdir():
|
| 92 |
+
if not src.is_file():
|
| 93 |
+
continue
|
| 94 |
+
dst = inputs_dir / src.name
|
| 95 |
+
if not dst.exists():
|
| 96 |
+
try:
|
| 97 |
+
shutil.copy2(src, dst)
|
| 98 |
+
except OSError as exc:
|
| 99 |
+
print(f"[bootstrap] could not seed {src.name}: {exc}", flush=True)
|
| 100 |
+
|
| 101 |
|
| 102 |
_bootstrap()
|
| 103 |
|
|
|
|
Binary file (17 kB). View file
|
|
|
|
|
@@ -20,8 +20,14 @@ logger = logging.getLogger(__name__)
|
|
| 20 |
@dataclass(frozen=True)
|
| 21 |
class ModelEntry:
|
| 22 |
repo_id: str
|
| 23 |
-
subfolder: str = ""
|
| 24 |
comfy_type: str = "checkpoints" # ComfyUI models/<comfy_type>/ subdirectory
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
|
| 26 |
|
| 27 |
MODEL_REGISTRY: dict[str, ModelEntry] = {
|
|
@@ -94,6 +100,51 @@ MODEL_REGISTRY: dict[str, ModelEntry] = {
|
|
| 94 |
"jib-down",
|
| 95 |
)
|
| 96 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
}
|
| 98 |
|
| 99 |
|
|
@@ -202,23 +253,25 @@ def ensure_models(filenames: set[str]) -> Iterator[DownloadEvent]:
|
|
| 202 |
entry = MODEL_REGISTRY[filename]
|
| 203 |
|
| 204 |
# Short-circuit: if the file is already present at its expected location
|
| 205 |
-
#
|
| 206 |
-
#
|
| 207 |
-
#
|
| 208 |
-
|
| 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 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 217 |
try:
|
| 218 |
source = pathlib.Path(
|
| 219 |
hf_hub_download(
|
| 220 |
repo_id=entry.repo_id,
|
| 221 |
-
filename=
|
| 222 |
cache_dir=str(cache_dir),
|
| 223 |
local_dir=None,
|
| 224 |
)
|
|
@@ -226,22 +279,21 @@ def ensure_models(filenames: set[str]) -> Iterator[DownloadEvent]:
|
|
| 226 |
size_mb = source.stat().st_size / 1024 / 1024
|
| 227 |
yield DownloadEvent(filename, size_mb, size_mb)
|
| 228 |
except Exception as exc:
|
| 229 |
-
# Fall back to scanning the cache for a
|
| 230 |
-
|
|
|
|
|
|
|
| 231 |
if not candidates:
|
| 232 |
logger.warning(
|
| 233 |
-
"could not download or locate %r in HF cache: %s; skipping",
|
| 234 |
-
filename,
|
| 235 |
-
exc,
|
| 236 |
)
|
| 237 |
continue
|
| 238 |
source = candidates[0]
|
| 239 |
yield DownloadEvent(filename, 0.0, 0.0)
|
| 240 |
|
| 241 |
-
#
|
| 242 |
dest_dir = comfy_models / entry.comfy_type
|
| 243 |
-
if entry.subfolder:
|
| 244 |
-
dest_dir = dest_dir / entry.subfolder
|
| 245 |
dest_dir.mkdir(parents=True, exist_ok=True)
|
| 246 |
dest = dest_dir / filename
|
| 247 |
|
|
|
|
| 20 |
@dataclass(frozen=True)
|
| 21 |
class ModelEntry:
|
| 22 |
repo_id: str
|
| 23 |
+
subfolder: str = "" # path within the HF repo
|
| 24 |
comfy_type: str = "checkpoints" # ComfyUI models/<comfy_type>/ subdirectory
|
| 25 |
+
# If the workflow expects a different filename than what's in the HF repo
|
| 26 |
+
# (e.g. user's local "ltx-2.3-22b-dev_transformer_only_fp8_scaled.safetensors"
|
| 27 |
+
# is actually `_transformer_only_bf16.safetensors` in Kijai's repo), set
|
| 28 |
+
# source_filename to the actual repo filename. The local symlink/copy uses
|
| 29 |
+
# the registry key as its name.
|
| 30 |
+
source_filename: str | None = None
|
| 31 |
|
| 32 |
|
| 33 |
MODEL_REGISTRY: dict[str, ModelEntry] = {
|
|
|
|
| 100 |
"jib-down",
|
| 101 |
)
|
| 102 |
},
|
| 103 |
+
# ----- Renamed/aliased filenames the user's master workflow references.
|
| 104 |
+
# The names look like quantized variants (FP4, FP8, GGUF) but the actual
|
| 105 |
+
# bytes behind them are BF16 — the user's local setup uses symlinks to
|
| 106 |
+
# canonical sources. On Spaces we download the same canonical sources via
|
| 107 |
+
# huggingface_hub and place them under the workflow-expected filename.
|
| 108 |
+
# All of these entries set `subfolder` to the path within the repo and
|
| 109 |
+
# rely on hf_hub_download returning the cached snapshot path (which we
|
| 110 |
+
# then symlink to comfy_models/<comfy_type>/<filename>).
|
| 111 |
+
"gemma_3_12B_it_fp4_mixed.safetensors": ModelEntry(
|
| 112 |
+
# Comfy-Org/ltx-2 ships BF16 Gemma packed as `gemma_3_12B_it.safetensors`
|
| 113 |
+
# in split_files/text_encoders/. The workflow expects the FP4-named
|
| 114 |
+
# variant; we serve the same file under that name.
|
| 115 |
+
"Comfy-Org/ltx-2",
|
| 116 |
+
subfolder="split_files/text_encoders",
|
| 117 |
+
comfy_type="text_encoders",
|
| 118 |
+
source_filename="gemma_3_12B_it.safetensors",
|
| 119 |
+
),
|
| 120 |
+
"gemma_3_12B_it.safetensors": ModelEntry(
|
| 121 |
+
"Comfy-Org/ltx-2",
|
| 122 |
+
subfolder="split_files/text_encoders",
|
| 123 |
+
comfy_type="text_encoders",
|
| 124 |
+
),
|
| 125 |
+
"ltx-2.3-22b-dev_transformer_only_fp8_scaled.safetensors": ModelEntry(
|
| 126 |
+
# Kijai's BF16 transformer-only — actual repo filename has `_bf16` suffix.
|
| 127 |
+
"Kijai/LTX2.3_comfy",
|
| 128 |
+
subfolder="diffusion_models",
|
| 129 |
+
comfy_type="diffusion_models",
|
| 130 |
+
source_filename="ltx-2.3-22b-dev_transformer_only_bf16.safetensors",
|
| 131 |
+
),
|
| 132 |
+
"ltx-2-3-22b-dev-Q4_K_M.gguf": ModelEntry(
|
| 133 |
+
# Unsloth's GGUF in BF16 (named `…-BF16.gguf` in repo).
|
| 134 |
+
"unsloth/LTX-2.3-GGUF",
|
| 135 |
+
comfy_type="diffusion_models",
|
| 136 |
+
source_filename="ltx-2.3-22b-dev-BF16.gguf",
|
| 137 |
+
),
|
| 138 |
+
"taeltx2_3.safetensors": ModelEntry(
|
| 139 |
+
"Kijai/LTX2.3_comfy",
|
| 140 |
+
subfolder="vae",
|
| 141 |
+
comfy_type="vae",
|
| 142 |
+
),
|
| 143 |
+
"ltx-2.3-22b-distilled-lora-dynamic_fro09_avg_rank_105_bf16.safetensors": ModelEntry(
|
| 144 |
+
"Kijai/LTX2.3_comfy",
|
| 145 |
+
subfolder="loras",
|
| 146 |
+
comfy_type="loras",
|
| 147 |
+
),
|
| 148 |
}
|
| 149 |
|
| 150 |
|
|
|
|
| 253 |
entry = MODEL_REGISTRY[filename]
|
| 254 |
|
| 255 |
# Short-circuit: if the file is already present at its expected location
|
| 256 |
+
# comfyui/models/<comfy_type>/<filename>, skip. Subfolder is part of the
|
| 257 |
+
# HF source path, not the destination, so the dest is always a flat
|
| 258 |
+
# comfyui/models/<comfy_type>/<filename>.
|
| 259 |
+
existing_dest = comfy_models / entry.comfy_type / filename
|
|
|
|
|
|
|
|
|
|
| 260 |
if existing_dest.exists() or existing_dest.is_symlink():
|
| 261 |
yield DownloadEvent(filename, 0.0, 0.0)
|
| 262 |
continue
|
| 263 |
|
| 264 |
+
# The HF-side filename may differ from the workflow-expected name
|
| 265 |
+
# (e.g. user's `_fp8_scaled.safetensors` is actually `_bf16.safetensors`
|
| 266 |
+
# in the upstream repo). Honor `source_filename` when set.
|
| 267 |
+
hf_filename = entry.source_filename or filename
|
| 268 |
+
hf_path = f"{entry.subfolder}/{hf_filename}" if entry.subfolder else hf_filename
|
| 269 |
+
|
| 270 |
try:
|
| 271 |
source = pathlib.Path(
|
| 272 |
hf_hub_download(
|
| 273 |
repo_id=entry.repo_id,
|
| 274 |
+
filename=hf_path,
|
| 275 |
cache_dir=str(cache_dir),
|
| 276 |
local_dir=None,
|
| 277 |
)
|
|
|
|
| 279 |
size_mb = source.stat().st_size / 1024 / 1024
|
| 280 |
yield DownloadEvent(filename, size_mb, size_mb)
|
| 281 |
except Exception as exc:
|
| 282 |
+
# Fall back to scanning the cache for a matching file (test mode +
|
| 283 |
+
# offline mode). Look for either the workflow filename OR the
|
| 284 |
+
# HF-side filename — both might exist locally as symlinks.
|
| 285 |
+
candidates = list(cache_dir.rglob(filename)) or list(cache_dir.rglob(hf_filename))
|
| 286 |
if not candidates:
|
| 287 |
logger.warning(
|
| 288 |
+
"could not download or locate %r (hf=%r) in HF cache: %s; skipping",
|
| 289 |
+
filename, hf_filename, exc,
|
|
|
|
| 290 |
)
|
| 291 |
continue
|
| 292 |
source = candidates[0]
|
| 293 |
yield DownloadEvent(filename, 0.0, 0.0)
|
| 294 |
|
| 295 |
+
# Stage at comfy_models/<comfy_type>/<filename> (workflow-expected name).
|
| 296 |
dest_dir = comfy_models / entry.comfy_type
|
|
|
|
|
|
|
| 297 |
dest_dir.mkdir(parents=True, exist_ok=True)
|
| 298 |
dest = dest_dir / filename
|
| 299 |
|
|
@@ -1,6 +1,8 @@
|
|
| 1 |
gradio>=5.0,<6.0
|
| 2 |
spaces>=0.30.0
|
| 3 |
huggingface_hub>=0.27.0
|
|
|
|
|
|
|
| 4 |
torch>=2.4.0
|
| 5 |
torchvision
|
| 6 |
torchaudio
|
|
@@ -10,6 +12,43 @@ einops
|
|
| 10 |
safetensors
|
| 11 |
tqdm
|
| 12 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
# Dev / test
|
| 14 |
pytest>=8.0
|
| 15 |
pytest-asyncio>=0.23
|
|
|
|
| 1 |
gradio>=5.0,<6.0
|
| 2 |
spaces>=0.30.0
|
| 3 |
huggingface_hub>=0.27.0
|
| 4 |
+
psutil # used by app.py model status badge
|
| 5 |
+
|
| 6 |
torch>=2.4.0
|
| 7 |
torchvision
|
| 8 |
torchaudio
|
|
|
|
| 12 |
safetensors
|
| 13 |
tqdm
|
| 14 |
|
| 15 |
+
# ComfyUI core requirements (also pip-installed by setup.sh from comfyui/requirements.txt
|
| 16 |
+
# locally, but Spaces won't run setup.sh — these are the deps ComfyUI itself needs)
|
| 17 |
+
transformers>=4.50,<6
|
| 18 |
+
tokenizers
|
| 19 |
+
sentencepiece
|
| 20 |
+
av
|
| 21 |
+
kornia
|
| 22 |
+
spandrel
|
| 23 |
+
torchsde
|
| 24 |
+
scipy
|
| 25 |
+
aiohttp
|
| 26 |
+
pydantic
|
| 27 |
+
pydantic-settings
|
| 28 |
+
python-dotenv
|
| 29 |
+
yarl
|
| 30 |
+
PyOpenGL
|
| 31 |
+
glfw
|
| 32 |
+
SQLAlchemy
|
| 33 |
+
alembic
|
| 34 |
+
Mako
|
| 35 |
+
networkx
|
| 36 |
+
sympy
|
| 37 |
+
mpmath
|
| 38 |
+
blake3
|
| 39 |
+
comfyui-frontend-package>=1.42
|
| 40 |
+
comfyui-workflow-templates>=0.9
|
| 41 |
+
comfyui-embedded-docs
|
| 42 |
+
|
| 43 |
+
# Custom-node deps that we discovered are required for the LTX 2.3 workflow
|
| 44 |
+
# but aren't auto-installed by their respective nodes' requirements.txt.
|
| 45 |
+
gguf # ComfyUI-GGUF (UnetLoaderGGUF)
|
| 46 |
+
imageio_ffmpeg # ComfyUI-VideoHelperSuite (video write/read backend)
|
| 47 |
+
opencv-python # ComfyUI_LayerStyle, multiple custom nodes
|
| 48 |
+
matplotlib # comfyui_controlnet_aux dwpose / pose preprocessors
|
| 49 |
+
diffusers # ComfyUI-SeedVR2 (used during init even when the node isn't called)
|
| 50 |
+
yt-dlp # ComfyUI-MediaMixer (init-time import)
|
| 51 |
+
|
| 52 |
# Dev / test
|
| 53 |
pytest>=8.0
|
| 54 |
pytest-asyncio>=0.23
|