From 78c722c274dbd94cd4dcdd0b9d8fa8b40a6db2cd Mon Sep 17 00:00:00 2001 From: Will Miao Date: Sat, 31 Oct 2026 11:42:14 +0800 Subject: [PATCH] feat(nodes): warn on duplicate LoRA loads --- py/nodes/lora_loader.py | 32 ++++++++++++++++ tests/nodes/test_lora_loader.py | 67 +++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+) diff --git a/py/nodes/lora_loader.py b/py/nodes/lora_loader.py index 3578ed34..7000dd73 100644 --- a/py/nodes/lora_loader.py +++ b/py/nodes/lora_loader.py @@ -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 = [] diff --git a/tests/nodes/test_lora_loader.py b/tests/nodes/test_lora_loader.py index cf53d2f1..d188b4dd 100644 --- a/tests/nodes/test_lora_loader.py +++ b/tests/nodes/test_lora_loader.py @@ -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 == " " +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, + "", + 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 == " " + 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 == " " + 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()