diff --git a/__init__.py b/__init__.py index cd83851..cc0cd7c 100644 --- a/__init__.py +++ b/__init__.py @@ -1,6 +1,7 @@ """ Hunyuan Video LoRA Loader 支持选择性加载single blocks或double blocks的混元视频LoRA加载器 +Hunyuan Video LoRA loader that supports selective loading of single blocks or double blocks """ from .lora_loader import HunyuanVideoLoraLoader @@ -13,4 +14,4 @@ NODE_DISPLAY_NAME_MAPPINGS = { "HunyuanVideoLoraLoader": "Hunyuan Video LoRA Loader" } -__version__ = "1.0.0" \ No newline at end of file +__version__ = "1.0.2" diff --git a/lora_loader.py b/lora_loader.py index 8e6d360..dbd3163 100644 --- a/lora_loader.py +++ b/lora_loader.py @@ -1,22 +1,26 @@ import os +from typing import Dict +import logging import folder_paths import torch -from typing import Dict +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') +log = logging.getLogger(__name__) + class HunyuanVideoLoraLoader: """ 混元视频LoRA加载器,支持选择性加载blocks - + 这个节点允许您: 1. 从下拉列表选择LoRA文件 2. 调整LoRA的强度 3. 选择要加载的blocks类型(all/single/double) """ - + def __init__(self): self.blocks_type = ["all", "single_blocks", "double_blocks"] self.loaded_lora = None - + @classmethod def INPUT_TYPES(s): return { @@ -39,7 +43,7 @@ class HunyuanVideoLoraLoader: FUNCTION = "load_lora" CATEGORY = "loaders/hunyuan" OUTPUT_NODE = False - DESCRIPTION = "加载并应用混元视频LoRA,支持选择性加载single blocks或double blocks" + DESCRIPTION = "加载并应用混元视频LoRA,支持选择性加载single blocks或double blocks/Load LoRA for HunyuanVideo. Supports selecting single, double or all blocks." def convert_key_format(self, key: str) -> str: """转换LoRA key格式,支持多种命名方式""" @@ -49,24 +53,24 @@ class HunyuanVideoLoraLoader: if key.startswith(prefix): key = key[len(prefix):] break - + return key def filter_lora_keys(self, lora: Dict[str, torch.Tensor], blocks_type: str) -> Dict[str, torch.Tensor]: """根据blocks类型过滤LoRA权重""" if blocks_type == "all": return lora - + filtered_lora = {} for key, value in lora.items(): base_key = self.convert_key_format(key) - + # 检查是否包含目标block - if blocks_type == "single_blocks" in base_key: + if blocks_type == "single_blocks" and "single_blocks" in base_key: filtered_lora[key] = value - elif blocks_type == "double_blocks" in base_key: + elif blocks_type == "double_blocks" and "double_blocks" in base_key: filtered_lora[key] = value - + return filtered_lora def check_for_musubi(self, lora: Dict[str, torch.Tensor]) -> Dict[str, torch.Tensor]: @@ -76,20 +80,19 @@ class HunyuanVideoLoraLoader: lora_alphas = {} for key, value in lora.items(): if key.startswith(prefix): - lora_name = key.split(".", 1)[0] # before first dot + lora_name = key.split(".", 1)[0] if lora_name not in lora_alphas and "alpha" in key: lora_alphas[lora_name] = value musubi = True if musubi: - print("Loading Musubi Tuner format LoRA...") + log.info("Loading Musubi Tuner format LoRA...") converted_lora = {} for key, weight in lora.items(): if key.startswith(prefix): if "alpha" in key: continue - lora_name = key.split(".", 1)[0] # before first dot - # HunyuanVideo lora name to module name: ugly but works - module_name = lora_name[len(prefix) :] # remove "lora_unet_" + lora_name = key.split(".", 1)[0] + module_name = lora_name[len(prefix):] # remove "lora_unet_" module_name = module_name.replace("_", ".") # replace "_" with "." module_name = module_name.replace("double.blocks.", "double_blocks.") # fix double blocks module_name = module_name.replace("single.blocks.", "single_blocks.") # fix single blocks @@ -104,26 +107,24 @@ class HunyuanVideoLoraLoader: new_key = f"{diffusers_prefix}.{module_name}.lora_B.weight" dim = weight.shape[1] else: - print(f"unexpected key: {key} in Musubi LoRA format") + log.info("unexpected key: %s in Musubi LoRA format", key) continue - # scale weight by alpha + # scale weight by alpha, we scale both down and up so scale is sqrt if lora_name in lora_alphas: - # we scale both down and up, so scale is sqrt scale = lora_alphas[lora_name] / dim scale = scale.sqrt() weight = weight * scale else: - print(f"missing alpha for {lora_name}") + log.info("missing alpha for %s", lora_name) converted_lora[new_key] = weight return converted_lora - else: - print("Loading Diffusers format LoRA...") - return lora + log.info("Loading Diffusers format LoRA...") + return lora def load_lora(self, model, lora_name: str, strength: float, blocks_type: str): """ 加载并应用LoRA到模型 - + Parameters ---------- model : ModelPatcher @@ -134,7 +135,7 @@ class HunyuanVideoLoraLoader: LoRA权重强度 blocks_type : str 要加载的blocks类型: "all", "single_blocks" 或 "double_blocks" - + Returns ------- tuple @@ -142,15 +143,14 @@ class HunyuanVideoLoraLoader: """ if not lora_name: return (model,) - + from comfy.utils import load_torch_file from comfy.sd import load_lora_for_models - from comfy.lora import load_lora # 获取LoRA文件路径 lora_path = folder_paths.get_full_path("loras", lora_name) if not os.path.exists(lora_path): - raise Exception(f"Lora {lora_name} not found at {lora_path}") + raise FileNotFoundError(f"Lora {lora_name} not found at {lora_path}") # 缓存LoRA加载 if self.loaded_lora is not None: @@ -158,23 +158,23 @@ class HunyuanVideoLoraLoader: lora = self.loaded_lora[1] else: self.loaded_lora = None - + if self.loaded_lora is None: lora = load_torch_file(lora_path) self.loaded_lora = (lora_path, lora) - + diffusers_lora = self.check_for_musubi(lora) # 过滤并转换LoRA权重 filtered_lora = self.filter_lora_keys(diffusers_lora, blocks_type) - + # 应用LoRA new_model, _ = load_lora_for_models(model, None, filtered_lora, strength, 0) if new_model is not None: return (new_model,) - + return (model,) @classmethod def IS_CHANGED(s, model, lora_name, strength, blocks_type): """当LoRA的配置发生变化时重新执行""" - return f"{lora_name}_{strength}_{blocks_type}" \ No newline at end of file + return f"{lora_name}_{strength}_{blocks_type}" diff --git a/pyproject.toml b/pyproject.toml index 622324e..5219beb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "hunyuanvideomultilora" description = "A custom LoRA-loading node designed to prevent issues such as blurriness and other artifacts when loading multiple LoRAs in HunYuan Video.\nUsage Tips: The connection method remains unchanged from the original. The only difference is the additional blocks_type option. Please select double_blocks." -version = "1.0.1" +version = "1.0.2" license = {file = "LICENSE"} [project.urls]