Spaces:
Running on Zero
Running on Zero
fix(models): walker recurses into Power Lora Loader nested dict + spatial upscaler comfy_type
Browse filesTwo issues surfaced when models actually downloaded but the workflow still
failed at execution:
1. walk_workflow_for_models was a flat values() loop — Power Lora Loader
stores rows as inputs.lora_1 = {on, lora, strength} (nested dict), so
ltx-2.3-22b-distilled-lora-dynamic_fro09_avg_rank_105_bf16.safetensors
was never returned to ensure_models. Recurse through dicts/lists.
2. ltx-2.3-spatial-upscaler-x2-1.0.safetensors was registered with
comfy_type="upscale_models" but ComfyUI core's
LatentUpscaleModelLoader reads from "latent_upscale_models" — same
filesystem prefix, different folder. Fix the registry to match.
models.py
CHANGED
|
@@ -35,7 +35,7 @@ MODEL_REGISTRY: dict[str, ModelEntry] = {
|
|
| 35 |
"ltx-2.3-22b-distilled.safetensors": ModelEntry("Lightricks/LTX-2.3", comfy_type="checkpoints"),
|
| 36 |
"ltx-2.3-22b-dev.safetensors": ModelEntry("Lightricks/LTX-2.3", comfy_type="checkpoints"),
|
| 37 |
"ltx-2.3-spatial-upscaler-x2-1.0.safetensors": ModelEntry(
|
| 38 |
-
"Lightricks/LTX-2.3", comfy_type="
|
| 39 |
),
|
| 40 |
"ltx-2.3-22b-distilled-lora-384.safetensors": ModelEntry(
|
| 41 |
"Lightricks/LTX-2.3", comfy_type="loras"
|
|
@@ -171,14 +171,32 @@ _USER_INPUT_LOADERS = {"LoadImage", "VHS_LoadVideo", "VHS_LoadAudioUpload"}
|
|
| 171 |
_MODEL_EXTS = (".safetensors", ".gguf", ".pt", ".bin", ".ckpt")
|
| 172 |
|
| 173 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 174 |
def walk_workflow_for_models(workflow: dict) -> set[str]:
|
| 175 |
"""Return the set of model filenames referenced by the API-format workflow.
|
| 176 |
|
| 177 |
-
Walks `{node_id: {class_type, inputs}}`
|
| 178 |
-
|
| 179 |
-
(LoadImage, VHS_LoadVideo, VHS_LoadAudioUpload).
|
| 180 |
-
harmless — `ensure_models` log-warns and skips
|
| 181 |
-
registry, so being inclusive here costs nothing.
|
| 182 |
"""
|
| 183 |
needed: set[str] = set()
|
| 184 |
for node in workflow.values():
|
|
@@ -186,11 +204,7 @@ def walk_workflow_for_models(workflow: dict) -> set[str]:
|
|
| 186 |
continue
|
| 187 |
if node.get("class_type") in _USER_INPUT_LOADERS:
|
| 188 |
continue
|
| 189 |
-
|
| 190 |
-
if isinstance(value, str) and value.endswith(_MODEL_EXTS):
|
| 191 |
-
needed.add(value)
|
| 192 |
-
elif isinstance(value, str) and value == "tokenizer.model":
|
| 193 |
-
needed.add(value)
|
| 194 |
return needed
|
| 195 |
|
| 196 |
|
|
|
|
| 35 |
"ltx-2.3-22b-distilled.safetensors": ModelEntry("Lightricks/LTX-2.3", comfy_type="checkpoints"),
|
| 36 |
"ltx-2.3-22b-dev.safetensors": ModelEntry("Lightricks/LTX-2.3", comfy_type="checkpoints"),
|
| 37 |
"ltx-2.3-spatial-upscaler-x2-1.0.safetensors": ModelEntry(
|
| 38 |
+
"Lightricks/LTX-2.3", comfy_type="latent_upscale_models"
|
| 39 |
),
|
| 40 |
"ltx-2.3-22b-distilled-lora-384.safetensors": ModelEntry(
|
| 41 |
"Lightricks/LTX-2.3", comfy_type="loras"
|
|
|
|
| 171 |
_MODEL_EXTS = (".safetensors", ".gguf", ".pt", ".bin", ".ckpt")
|
| 172 |
|
| 173 |
|
| 174 |
+
def _walk_for_filenames(value, into: set[str]) -> None:
|
| 175 |
+
"""Depth-first walk of a node's inputs, picking out model filenames.
|
| 176 |
+
|
| 177 |
+
Power Lora Loader stores its rows nested as `inputs.lora_1 = {on, lora,
|
| 178 |
+
strength}` and similar — a flat values() loop misses these. Recurse
|
| 179 |
+
through dicts and lists/tuples so nested filenames are caught.
|
| 180 |
+
"""
|
| 181 |
+
if isinstance(value, str):
|
| 182 |
+
if value.endswith(_MODEL_EXTS) or value == "tokenizer.model":
|
| 183 |
+
into.add(value)
|
| 184 |
+
elif isinstance(value, dict):
|
| 185 |
+
for v in value.values():
|
| 186 |
+
_walk_for_filenames(v, into)
|
| 187 |
+
elif isinstance(value, (list, tuple)):
|
| 188 |
+
for v in value:
|
| 189 |
+
_walk_for_filenames(v, into)
|
| 190 |
+
|
| 191 |
+
|
| 192 |
def walk_workflow_for_models(workflow: dict) -> set[str]:
|
| 193 |
"""Return the set of model filenames referenced by the API-format workflow.
|
| 194 |
|
| 195 |
+
Walks `{node_id: {class_type, inputs}}` and recursively scans each node's
|
| 196 |
+
inputs for strings ending in a model extension. Skips loaders that read
|
| 197 |
+
user-supplied files (LoadImage, VHS_LoadVideo, VHS_LoadAudioUpload).
|
| 198 |
+
Unknown filenames are harmless — `ensure_models` log-warns and skips
|
| 199 |
+
anything not in the registry, so being inclusive here costs nothing.
|
| 200 |
"""
|
| 201 |
needed: set[str] = set()
|
| 202 |
for node in workflow.values():
|
|
|
|
| 204 |
continue
|
| 205 |
if node.get("class_type") in _USER_INPUT_LOADERS:
|
| 206 |
continue
|
| 207 |
+
_walk_for_filenames(node.get("inputs") or {}, needed)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 208 |
return needed
|
| 209 |
|
| 210 |
|