# Complete Explanation of PR #28 and Additional Fixes --- ## ① Proposal Content and What the Problem Was ### PR #28 Proposal Content **Title**: `Fix missing 1 required positional argument: 'cpu_offload'` **Problem Description**: After the kjai node update, the following error occurred: ``` TypeError: load_lora() missing 1 required positional argument: 'cpu_offload' ``` or ``` TypeError: load_lora_stack() missing 1 required positional argument: 'cpu_offload' ``` ### Root Cause **Code Before Fix**: ```python def load_lora(self, model, lora_name: str, lora_strength: float, cpu_offload: str): # cpu_offload is a required argument (no default value) ``` **Problems**: 1. **Required Parameter**: `cpu_offload` was a required parameter without a default value, causing errors when not provided 2. **ComfyUI Parameter Passing**: ComfyUI automatically passes `required` parameters from `INPUT_TYPES()` to methods, but kjai node updates changed how parameters are passed in some call paths 3. **Compatibility Issues**: In certain node invocation scenarios (old workflows, dynamic node generation, or kjai node updates), the parameter could be missing **Why It Became a Problem**: - Even though `cpu_offload` is defined as `required` in `INPUT_TYPES()`, ComfyUI's node invocation mechanism doesn't always guarantee that all parameters are passed - The kjai node update modified how nodes are invoked, potentially causing parameter omissions in some call paths - This is a compatibility issue that arose from external node updates --- ## ② How We Fixed It (File Names and Code) ### Files Modified 1. `nodes/lora/qwenimage.py` - 4 locations fixed 2. `nodes/lora/qwenimage_v2.py` - 2 locations fixed ### Fix 1: PR #28 Fixes (FUNCTION Methods) #### File: `nodes/lora/qwenimage.py` **Fix Location 1: `NunchakuQwenImageLoraLoader.load_lora()` method** (line 91) ```python # Before def load_lora(self, model, lora_name: str, lora_strength: float, cpu_offload: str): # After def load_lora(self, model, lora_name: str, lora_strength: float, cpu_offload: str = "disable"): ``` **Fix Location 2: `NunchakuQwenImageLoraStack.load_lora_stack()` method** (line 271) ```python # Before def load_lora_stack(self, model, lora_count, cpu_offload, **kwargs): # After def load_lora_stack(self, model, lora_count, cpu_offload="disable", **kwargs): ``` #### File: `nodes/lora/qwenimage_v2.py` **Fix Location 3: `NunchakuQwenImageLoraStackV2.load_lora_stack()` method** (line 113) ```python # Before def load_lora_stack(self, model, lora_count, cpu_offload, **kwargs): # After def load_lora_stack(self, model, lora_count, cpu_offload="disable", **kwargs): ``` ### Fix 2: Additional Fixes Applied (IS_CHANGED Methods) #### File: `nodes/lora/qwenimage.py` **Fix Location 4: `NunchakuQwenImageLoraLoader.IS_CHANGED()` method** (line 36) ```python # Before @classmethod def IS_CHANGED(s, model, lora_name, lora_strength, cpu_offload, *args, **kwargs): """ Detect changes to trigger node re-execution. Returns a hash of relevant parameters to detect changes. """ import hashlib m = hashlib.sha256() m.update(lora_name.encode()) m.update(str(lora_strength).encode()) m.update(str(model).encode()) m.update(cpu_offload.encode()) return m.digest().hex() # After @classmethod def IS_CHANGED(s, model, lora_name, lora_strength, cpu_offload="disable", *args, **kwargs): """ Detect changes to trigger node re-execution. Returns a hash of relevant parameters to detect changes. """ import hashlib m = hashlib.sha256() m.update(lora_name.encode()) m.update(str(lora_strength).encode()) m.update(str(model).encode()) m.update(cpu_offload.encode()) return m.digest().hex() ``` **Fix Location 5: `NunchakuQwenImageLoraStack.IS_CHANGED()` method** (line 195) ```python # Before @classmethod def IS_CHANGED(cls, model, lora_count, cpu_offload, **kwargs): """ Detect changes to trigger node re-execution. Returns a hash of relevant parameters to detect changes. """ import hashlib m = hashlib.sha256() m.update(str(model).encode()) m.update(str(lora_count).encode()) m.update(cpu_offload.encode()) # Hash all LoRA parameters for i in range(1, 11): m.update(kwargs.get(f"lora_name_{i}", "").encode()) m.update(str(kwargs.get(f"lora_strength_{i}", 0)).encode()) return m.digest().hex() # After @classmethod def IS_CHANGED(cls, model, lora_count, cpu_offload="disable", **kwargs): """ Detect changes to trigger node re-execution. Returns a hash of relevant parameters to detect changes. """ import hashlib m = hashlib.sha256() m.update(str(model).encode()) m.update(str(lora_count).encode()) m.update(cpu_offload.encode()) # Hash all LoRA parameters for i in range(1, 11): m.update(kwargs.get(f"lora_name_{i}", "").encode()) m.update(str(kwargs.get(f"lora_strength_{i}", 0)).encode()) return m.digest().hex() ``` #### File: `nodes/lora/qwenimage_v2.py` **Fix Location 6: `NunchakuQwenImageLoraStackV2.IS_CHANGED()` method** (line 36) ```python # Before @classmethod def IS_CHANGED(cls, model, lora_count, cpu_offload, **kwargs): """ Detect changes to trigger node re-execution. Returns a hash of relevant parameters to detect changes. """ import hashlib m = hashlib.sha256() m.update(str(model).encode()) m.update(str(lora_count).encode()) m.update(cpu_offload.encode()) # Hash all LoRA parameters for i in range(1, 11): m.update(kwargs.get(f"lora_name_{i}", "").encode()) m.update(str(kwargs.get(f"lora_strength_{i}", 0)).encode()) return m.digest().hex() # After @classmethod def IS_CHANGED(cls, model, lora_count, cpu_offload="disable", **kwargs): """ Detect changes to trigger node re-execution. Returns a hash of relevant parameters to detect changes. """ import hashlib m = hashlib.sha256() m.update(str(model).encode()) m.update(str(lora_count).encode()) m.update(cpu_offload.encode()) # Hash all LoRA parameters for i in range(1, 11): m.update(kwargs.get(f"lora_name_{i}", "").encode()) m.update(str(kwargs.get(f"lora_strength_{i}", 0)).encode()) return m.digest().hex() ``` ### Summary of Fixes | File | Class | Method | Line | Fix Type | |------|-------|--------|------|----------| | `qwenimage.py` | `NunchakuQwenImageLoraLoader` | `load_lora()` | 91 | PR #28 | | `qwenimage.py` | `NunchakuQwenImageLoraLoader` | `IS_CHANGED()` | 36 | Additional | | `qwenimage.py` | `NunchakuQwenImageLoraStack` | `load_lora_stack()` | 271 | PR #28 | | `qwenimage.py` | `NunchakuQwenImageLoraStack` | `IS_CHANGED()` | 195 | Additional | | `qwenimage_v2.py` | `NunchakuQwenImageLoraStackV2` | `load_lora_stack()` | 113 | PR #28 | | `qwenimage_v2.py` | `NunchakuQwenImageLoraStackV2` | `IS_CHANGED()` | 36 | Additional | **Total**: 2 files, 6 locations fixed --- ## ③ What It Means ### 1. Technical Meaning **Effect of Default Arguments**: ```python # Before (required argument) def load_lora(self, model, lora_name: str, lora_strength: float, cpu_offload: str): # Error if cpu_offload is not provided # After (with default value) def load_lora(self, model, lora_name: str, lora_strength: float, cpu_offload: str = "disable"): # Uses "disable" if cpu_offload is not provided ``` **Behavior**: - If `cpu_offload` is passed, that value is used - If `cpu_offload` is missing, the default value `"disable"` is automatically used - This prevents `TypeError` and ensures the function always receives a valid value ### 2. Why `"disable"`? Looking at the `INPUT_TYPES()` definition: ```python "cpu_offload": ( ["auto", "enable", "disable"], { "default": "disable", "tooltip": "CPU offload setting. 'auto' enables offload when VRAM is low, 'enable' forces offload, 'disable' disables offload.", }, ), ``` **Reason**: - The UI default value is `"disable"` - To maintain consistency between UI defaults and implementation defaults - `"disable"` means "no CPU offload" (better performance when VRAM is sufficient), which is the recommended default ### 3. Functional Meaning **Fix for FUNCTION Methods**: - Prevents errors when `cpu_offload` parameter is missing in `load_lora()` and `load_lora_stack()` - Ensures compatibility with kjai node updates **Fix for IS_CHANGED Methods**: - ComfyUI calls `IS_CHANGED()` to detect changes and determine if node re-execution is needed - If `cpu_offload` is missing in `IS_CHANGED()` calls, it could cause `AttributeError` when calling `.encode()` - Adding default values prevents errors in change detection as well ### 4. Compatibility Meaning **Backward Compatibility**: - Existing workflows continue to work without modification - Maintains compatibility with updated or new nodes **Future-Proofing**: - Prevents similar errors when parameters are missing in various invocation scenarios - More robust integration with external nodes ### 5. Design Meaning **Defensive Programming**: - Protects against unexpected external factors (like node updates) - Provides fallback behavior through default values **Consistency**: - UI defaults match implementation defaults - User expectations align with actual behavior ### 6. Practical Meaning **Error Prevention**: - Eliminates `TypeError: missing 1 required positional argument` errors - More robust operation **User Experience**: - Fewer errors for end users - More stable operation --- ## Summary ### Problem After the kjai node update, `cpu_offload` parameter could be missing, causing `TypeError`. ### Solution Added default value `"disable"` to both FUNCTION methods (`load_lora`, `load_lora_stack`) and IS_CHANGED methods for all three node classes. ### Result - ✅ Error prevention - ✅ Backward compatibility maintained - ✅ Consistency with UI defaults - ✅ More robust implementation This fix is a defensive programming approach to handle compatibility issues caused by external node updates.