321 lines
9.9 KiB
Markdown
321 lines
9.9 KiB
Markdown
# 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.
|
|
|