feat(nodes): warn on duplicate LoRA loads
This commit is contained in:
@@ -103,7 +103,39 @@ def _prepare_lora(lora, model, name):
|
||||
return remapped
|
||||
|
||||
|
||||
def _warn_duplicate_entries(lora_entries: list[dict]) -> None:
|
||||
"""Warn when the same LoRA file appears more than once in one load call.
|
||||
|
||||
Entries are still loaded exactly as provided; this only surfaces accidental
|
||||
double loads such as a stack plus its own derived text syntax.
|
||||
"""
|
||||
entries_by_path: dict[str, list[dict]] = {}
|
||||
for entry in lora_entries:
|
||||
path = entry.get("absolute_path") or entry.get("input_path")
|
||||
if not path:
|
||||
continue
|
||||
key = os.path.normcase(os.path.abspath(path))
|
||||
entries_by_path.setdefault(key, []).append(entry)
|
||||
|
||||
for entries in entries_by_path.values():
|
||||
if len(entries) < 2:
|
||||
continue
|
||||
strengths = ", ".join(
|
||||
f"{entry['model_strength']}/{entry['clip_strength']}" for entry in entries
|
||||
)
|
||||
logger.warning(
|
||||
"LoRA '%s' (%s) appears %d times in the same loader input "
|
||||
"(model/clip strengths: %s). All entries will be loaded as provided; "
|
||||
"if this is intentional, ignore this warning.",
|
||||
entries[0]["name"],
|
||||
entries[0].get("absolute_path") or entries[0].get("input_path"),
|
||||
len(entries),
|
||||
strengths,
|
||||
)
|
||||
|
||||
|
||||
def _apply_entries(model, clip, lora_entries, nunchaku_model_kind):
|
||||
_warn_duplicate_entries(lora_entries)
|
||||
loaded_loras = []
|
||||
all_trigger_words = []
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import logging
|
||||
import os
|
||||
import types
|
||||
|
||||
import pytest
|
||||
@@ -178,6 +180,71 @@ def test_lora_text_loader_qwen_batches_text_and_stack(monkeypatch):
|
||||
assert loaded_loras == "<lora:stack_qwen:0.6> <lora:text_qwen:1.2>"
|
||||
|
||||
|
||||
def test_lora_text_loader_warns_on_duplicate_path_but_loads_both(monkeypatch, caplog):
|
||||
loader = LoraTextLoaderLM()
|
||||
model = _Model(object())
|
||||
clip = object()
|
||||
|
||||
monkeypatch.setattr(
|
||||
"py.nodes.lora_loader.get_lora_info_absolute",
|
||||
lambda name: ("/abs/same.safetensors", ["same_trigger"]),
|
||||
)
|
||||
|
||||
load_calls = []
|
||||
|
||||
def mock_load_torch_file(path, safe_load=True):
|
||||
load_calls.append(path)
|
||||
return {"path": path}
|
||||
|
||||
monkeypatch.setattr("comfy.utils.load_torch_file", mock_load_torch_file)
|
||||
monkeypatch.setattr(
|
||||
"comfy.sd.load_lora_for_models",
|
||||
lambda model_arg, clip_arg, lora_arg, model_strength, clip_strength: (model_arg, clip_arg),
|
||||
)
|
||||
|
||||
with caplog.at_level(logging.WARNING, logger="py.nodes.lora_loader"):
|
||||
_, _, trigger_words, loaded_loras = loader.load_loras_from_text(
|
||||
model,
|
||||
"<lora:same:1.0>",
|
||||
clip=clip,
|
||||
lora_stack=[("same.safetensors", 1.0, 1.0)],
|
||||
)
|
||||
|
||||
assert load_calls == ["/abs/same.safetensors", "/abs/same.safetensors"]
|
||||
assert trigger_words == "same_trigger,, same_trigger"
|
||||
assert loaded_loras == "<lora:same:1.0> <lora:same:1.0>"
|
||||
assert "appears 2 times" in caplog.text
|
||||
assert "/abs/same.safetensors" in caplog.text
|
||||
|
||||
|
||||
def test_lora_loader_does_not_warn_for_same_basename_in_different_folders(monkeypatch, caplog):
|
||||
loader = LoraLoaderLM()
|
||||
model = _Model(object())
|
||||
|
||||
def resolve(name):
|
||||
if os.path.isabs(name):
|
||||
return name, []
|
||||
return f"/abs/b/{name}.safetensors", []
|
||||
|
||||
monkeypatch.setattr("py.nodes.lora_loader.get_lora_info_absolute", resolve)
|
||||
monkeypatch.setattr("comfy.utils.load_torch_file", lambda path, safe_load=True: {"path": path})
|
||||
monkeypatch.setattr(
|
||||
"comfy.sd.load_lora_for_models",
|
||||
lambda model_arg, clip_arg, lora_arg, model_strength, clip_strength: (model_arg, clip_arg),
|
||||
)
|
||||
|
||||
with caplog.at_level(logging.WARNING, logger="py.nodes.lora_loader"):
|
||||
_, _, _, loaded_loras = loader.load_loras(
|
||||
model,
|
||||
"",
|
||||
lora_stack=[("/abs/a/same.safetensors", 1.0, 1.0)],
|
||||
loras={"__value__": [{"active": True, "name": "same", "strength": 1.0}]},
|
||||
)
|
||||
|
||||
assert loaded_loras == "<lora:same:1.0> <lora:same:1.0>"
|
||||
assert "appears 2 times" not in caplog.text
|
||||
|
||||
|
||||
def test_lora_loader_qwen_model_raises_clear_error_when_helper_import_fails(monkeypatch):
|
||||
qwen_model = _Model(type("NunchakuQwenImageTransformer2DModel", (), {})())
|
||||
loader = LoraLoaderLM()
|
||||
|
||||
Reference in New Issue
Block a user