qwen / worker /lora_from_url.py
nikdevs's picture
lora_from_url: log matched target modules per lora (bind diagnostic)
067cbe6 verified
Raw History Blame Contribute Delete
2.96 kB
"""ComfyUI custom node: load a LoRA fetched from a URL at request time.
Lets serverless workflows reference LoRAs by URL (HF, civitai, anywhere)
instead of requiring the file to be pre-baked into the image. Downloads are
cached on the worker's disk keyed by URL hash. Supports HF_TOKEN and
CIVITAI_TOKEN env vars for authenticated downloads.
"""
import hashlib
import logging
import os
import urllib.request
import comfy.lora
import comfy.sd
import comfy.utils
CACHE_DIR = "/lora-cache"
os.makedirs(CACHE_DIR, exist_ok=True)
def _fetch(url: str) -> str:
path = os.path.join(CACHE_DIR, hashlib.sha1(url.encode()).hexdigest() + ".safetensors")
if os.path.exists(path):
return path
headers = {}
hf_token = os.environ.get("HF_TOKEN")
if hf_token and "huggingface.co" in url:
headers["Authorization"] = f"Bearer {hf_token}"
civitai_token = os.environ.get("CIVITAI_TOKEN")
if civitai_token and "civitai" in url:
url += ("&" if "?" in url else "?") + "token=" + civitai_token
req = urllib.request.Request(url, headers=headers)
tmp = path + ".part"
with urllib.request.urlopen(req, timeout=600) as r, open(tmp, "wb") as f:
while chunk := r.read(1 << 20):
f.write(chunk)
os.rename(tmp, path)
return path
class LoraFromURLModelOnly:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"model": ("MODEL",),
"url": ("STRING", {"default": ""}),
"strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
}
}
RETURN_TYPES = ("MODEL",)
FUNCTION = "load"
CATEGORY = "loaders"
def load(self, model, url, strength):
url = (url or "").strip()
name = url.rsplit("/", 1)[-1] or "lora"
if not url or strength == 0:
logging.info(f"[lora_from_url] {name}: skipped (empty url / strength 0)")
return (model,)
lora_sd = comfy.utils.load_torch_file(_fetch(url), safe_load=True)
total = len(lora_sd)
# How many lora keys actually map onto this model's weights — the
# definitive "did it bind" signal. load_lora consumes matched keys and
# returns the leftovers it could not place.
key_map = comfy.lora.model_lora_keys_unet(model.model, {})
loaded = comfy.lora.load_lora(lora_sd, key_map)
matched = len(loaded)
model_lora, _ = comfy.sd.load_lora_for_models(model, None, lora_sd, strength, 0)
logging.info(
f"[lora_from_url] {name}: matched {matched} target modules "
f"from {total} tensors at strength {strength}"
+ (" ⚠ ZERO MATCHES — lora did NOT bind" if matched == 0 else "")
)
return (model_lora,)
NODE_CLASS_MAPPINGS = {"LoraFromURLModelOnly": LoraFromURLModelOnly}
NODE_DISPLAY_NAME_MAPPINGS = {"LoraFromURLModelOnly": "Load LoRA from URL (model only)"}