diff --git a/README.md b/README.md index 3d24100..041286b 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,11 @@ By default, detailed debug logs are **muted**. If you want detailed debug output NunchakuQwenImageLoraStack: Multi LoRA stacker with dynamic UI (Legacy) +- **NunchakuQwenImageLoraStackV1**: Multi LoRA stacker with rgthree-style UI - ComfyUI Nodes 2.0 compatible + - Clean, minimalist design inspired by [Power Lora Loader (rgthree-comfy)](https://github.com/rgthree/rgthree-comfy). Toggle, LoRA name, and strength per row. + +NunchakuQwenImageLoraStackV1: Multi LoRA stacker with rgthree-style UI - ComfyUI Nodes 2.0 compatible + - **NunchakuQwenImageLoraStackV2**: Multi LoRA stacker with dynamic UI - ComfyUI Nodes 2.0 (Beta) compatible - **AWQ Modulation Layer Support**: Includes experimental support for applying LoRA to AWQ quantized modulation layers (`img_mod.1` / `txt_mod.1`) via the "Apply AWQ Mod" toggle. This feature uses Runtime Monkey Patch with Manual Planar Injection to fix noise issues. ⚠️ **Warning**: **This is an experimental feature currently implemented only in V2 nodes.** If no issues are found, this feature will be applied to V1 and V3 nodes as well. diff --git a/__init__.py b/__init__.py index b264462..604e94a 100644 --- a/__init__.py +++ b/__init__.py @@ -14,6 +14,8 @@ logger = logging.getLogger(__name__) logger.info("=" * 40 + " ComfyUI-QwenImageLoraLoader Initialization " + "=" * 40) NODE_CLASS_MAPPINGS = {} +QWEN_V1_NODES = {} +QWEN_V1_NAMES = {} QWEN_V2_NODES = {} QWEN_V2_NAMES = {} QWEN_V3_NODES = {} @@ -38,7 +40,7 @@ try: from .nodes.lora.qwenimage import NunchakuQwenImageLoraLoader, NunchakuQwenImageLoraStack from .nodes.lora.qwenimage_v2 import GENERATED_NODES as QWEN_V2_NODES, GENERATED_DISPLAY_NAMES as QWEN_V2_NAMES from .nodes.lora.qwenimage_v3 import GENERATED_NODES as QWEN_V3_NODES, GENERATED_DISPLAY_NAMES as QWEN_V3_NAMES - from .nodes.lora.qwenimage_v4 import GENERATED_NODES as QWEN_V4_NODES, GENERATED_DISPLAY_NAMES as QWEN_V4_NAMES + from .nodes.lora.qwenimage_v1 import GENERATED_NODES as QWEN_V1_NODES, GENERATED_DISPLAY_NAMES as QWEN_V1_NAMES from .nodes.lora.zimageturbo_v2 import GENERATED_NODES as ZIMAGETURBO_V2_NODES, GENERATED_DISPLAY_NAMES as ZIMAGETURBO_V2_NAMES # Z-Image-Turbo V3 is deprecated - removed from registration # from .nodes.lora.zimageturbo_v3 import GENERATED_NODES as ZIMAGETURBO_V3_NODES, GENERATED_DISPLAY_NAMES as ZIMAGETURBO_V3_NAMES @@ -51,7 +53,7 @@ try: node_class.__version__ = __version__ for node_class in QWEN_V3_NODES.values(): node_class.__version__ = __version__ - for node_class in QWEN_V4_NODES.values(): + for node_class in QWEN_V1_NODES.values(): node_class.__version__ = __version__ for node_class in ZIMAGETURBO_V2_NODES.values(): node_class.__version__ = __version__ @@ -65,7 +67,7 @@ try: NODE_CLASS_MAPPINGS["NunchakuQwenImageLoraStack"] = NunchakuQwenImageLoraStack NODE_CLASS_MAPPINGS.update(QWEN_V2_NODES) NODE_CLASS_MAPPINGS.update(QWEN_V3_NODES) - NODE_CLASS_MAPPINGS.update(QWEN_V4_NODES) + NODE_CLASS_MAPPINGS.update(QWEN_V1_NODES) NODE_CLASS_MAPPINGS.update(ZIMAGETURBO_V2_NODES) # Z-Image-Turbo V3 registration removed # NODE_CLASS_MAPPINGS.update(ZIMAGETURBO_V3_NODES) @@ -78,7 +80,7 @@ NODE_DISPLAY_NAME_MAPPINGS = { "NunchakuQwenImageLoraStack": "Nunchaku Qwen Image LoRA Stack (Legacy)", **QWEN_V2_NAMES, **QWEN_V3_NAMES, - **QWEN_V4_NAMES, + **QWEN_V1_NAMES, **ZIMAGETURBO_V2_NAMES, # Z-Image-Turbo V3 registration removed # **ZIMAGETURBO_V3_NAMES, diff --git a/images/qiv1_stack.png b/images/qiv1_stack.png new file mode 100644 index 0000000..eb69560 Binary files /dev/null and b/images/qiv1_stack.png differ diff --git a/js/z_qwen_lora_dynamic_v4.js b/js/z_qwen_lora_dynamic_v1.js similarity index 98% rename from js/z_qwen_lora_dynamic_v4.js rename to js/z_qwen_lora_dynamic_v1.js index 90700c5..f8a7d0e 100644 --- a/js/z_qwen_lora_dynamic_v4.js +++ b/js/z_qwen_lora_dynamic_v1.js @@ -1,7 +1,7 @@ import { app } from "../../scripts/app.js"; import { api } from "../../scripts/api.js"; -console.log("★★★ z_qwen_lora_dynamic_v4.js: Qwen Image LoRA Stack V4 ★★★"); +console.log("★★★ z_qwen_lora_dynamic_v1.js: Qwen Image LoRA Stack V1 ★★★"); // Use LiteGraph built-in colors to ensure Light/Dark Mode compatibility const getColors = () => ({ @@ -178,10 +178,10 @@ class NunchakuLoraWidget { } app.registerExtension({ - name: "nunchaku.qwen_lora_dynamic_v4", + name: "nunchaku.qwen_lora_dynamic_v1", async beforeRegisterNodeDef(nodeType, nodeData, app) { - if (nodeData.name !== "NunchakuQwenImageLoraStackV4") return; + if (nodeData.name !== "NunchakuQwenImageLoraStackV1") return; // Save original onNodeCreated const onNodeCreated = nodeType.prototype.onNodeCreated; @@ -340,5 +340,3 @@ app.registerExtension({ }; } }); - - diff --git a/nodes/lora/qwenimage_v4.py b/nodes/lora/qwenimage_v1.py similarity index 94% rename from nodes/lora/qwenimage_v4.py rename to nodes/lora/qwenimage_v1.py index 94cf11f..0b3c206 100644 --- a/nodes/lora/qwenimage_v4.py +++ b/nodes/lora/qwenimage_v1.py @@ -1,5 +1,5 @@ """ -This module provides the :class:`NunchakuQwenImageLoraStackV4` node +This module provides the :class:`NunchakuQwenImageLoraStackV1` node for applying LoRA weights to Nunchaku Qwen Image models within ComfyUI. The interface completely mimics the Power Lora Loader from rgthree-comfy(https://github.com/rgthree/rgthree-comfy ), supporting dynamic additions and custom widgets. @@ -39,11 +39,11 @@ class FlexibleOptionalInputType(dict): def __getitem__(self, key): return (any_type, {}) -class NunchakuQwenImageLoraStackV4: +class NunchakuQwenImageLoraStackV1: """ Node for loading and applying multiple LoRAs to a Nunchaku Qwen Image model with dynamic UI. - Built upon V3, V4 not only supports the ComfyUI Nodes 2.0 UI but also pays full homage - to the clean and minimalist design of the Power Lora Loader from the rgthree-comfy project. + Built upon V3, pays full homage to the clean and minimalist design of the + Power Lora Loader from the rgthree-comfy project. """ @classmethod @@ -76,7 +76,7 @@ class NunchakuQwenImageLoraStackV4: }, ), }, - "optional": FlexibleOptionalInputType(), + "optional": FlexibleOptionalInputType(), # Use 'hidden' here to let JS pass dynamic LoRA data "hidden": { "prompt": "PROMPT", @@ -89,7 +89,7 @@ class NunchakuQwenImageLoraStackV4: RETURN_TYPES = ("MODEL",) OUTPUT_TOOLTIPS = ("The modified diffusion model with all LoRAs applied.",) FUNCTION = "load_lora_stack" - TITLE = "Nunchaku Qwen Image LoRA Stack V4" + TITLE = "Nunchaku Qwen Image LoRA Stack V1" CATEGORY = "Nunchaku" DESCRIPTION = "Apply multiple LoRAs to a diffusion model in a single node with dynamic UI control." @@ -107,7 +107,7 @@ class NunchakuQwenImageLoraStackV4: logger.info(f"[LoRA Stack Status] apply_awq_mod: {apply_awq_mod}") logger.info(f"[LoRA Stack Status] stack_enabled: {stack_enabled}") logger.info(f"[LoRA Stack Status] Processing {lora_count} LoRA slot(s):") - + for i, key in enumerate(lora_keys): value = kwargs[key] if not isinstance(value, dict): @@ -179,12 +179,12 @@ class NunchakuQwenImageLoraStackV4: if model_wrapper.cpu_offload_setting != cpu_offload: logger.info(f"🔄 Updating CPU offload setting from '{model_wrapper.cpu_offload_setting}' to '{cpu_offload}'") model_wrapper.cpu_offload_setting = cpu_offload - + # Dynamically update the apply_awq_mod setting. if hasattr(model_wrapper, "apply_awq_mod") and model_wrapper.apply_awq_mod != apply_awq_mod: logger.info(f"🔄 Updating AWQ mod setting from '{model_wrapper.apply_awq_mod}' to '{apply_awq_mod}'") model_wrapper.apply_awq_mod = apply_awq_mod - + transformer = model_wrapper.model elif model_wrapper_type_name == "NunchakuQwenImageTransformer2DModel" or model_wrapper_type_name.endswith( "NunchakuQwenImageTransformer2DModel" @@ -239,11 +239,9 @@ class NunchakuQwenImageLoraStackV4: GENERATED_NODES = { - "NunchakuQwenImageLoraStackV4": NunchakuQwenImageLoraStackV4, + "NunchakuQwenImageLoraStackV1": NunchakuQwenImageLoraStackV1, } GENERATED_DISPLAY_NAMES = { - "NunchakuQwenImageLoraStackV4": "Nunchaku Qwen Image LoRA Stack V4", + "NunchakuQwenImageLoraStackV1": "Nunchaku Qwen Image LoRA Stack V1", } - -