Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ede6fde5f3 | ||
|
|
0726ec952b | ||
|
|
c6b65d6721 | ||
|
|
32281ad2d5 | ||
|
|
68fab8d3d0 |
+9
-1
@@ -25,7 +25,10 @@ from .nodes.lora_extract_svd import (
|
|||||||
)
|
)
|
||||||
from .nodes.lora_resize import (
|
from .nodes.lora_resize import (
|
||||||
LoRAResizeFixed, LoRAResizeRatio,
|
LoRAResizeFixed, LoRAResizeRatio,
|
||||||
LoRAResizeFrobenius, LoRAResizeCumulative
|
LoRAResizeFrobenius, LoRAResizeCumulative,
|
||||||
|
LoRAResizeViaBaseFixed, LoRAResizeViaBaseRatio,
|
||||||
|
LoRAResizeViaBaseFrobenius, LoRAResizeViaBaseCumulative,
|
||||||
|
LoRAMultiMerge
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -54,6 +57,11 @@ class ModelUtilsExtension(ComfyExtension):
|
|||||||
# LoRA Resize
|
# LoRA Resize
|
||||||
LoRAResizeFixed, LoRAResizeRatio,
|
LoRAResizeFixed, LoRAResizeRatio,
|
||||||
LoRAResizeFrobenius, LoRAResizeCumulative,
|
LoRAResizeFrobenius, LoRAResizeCumulative,
|
||||||
|
# LoRA Fast Resize (via base model)
|
||||||
|
LoRAResizeViaBaseFixed, LoRAResizeViaBaseRatio,
|
||||||
|
LoRAResizeViaBaseFrobenius, LoRAResizeViaBaseCumulative,
|
||||||
|
# LoRA Multi-Merge
|
||||||
|
LoRAMultiMerge,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -294,3 +294,28 @@ ComfyUI-ModelUtils/
|
|||||||
| Metadata reading | **100x+** (header only) |
|
| Metadata reading | **100x+** (header only) |
|
||||||
| Parallel I/O | **2-4x** (ThreadPoolExecutor) |
|
| Parallel I/O | **2-4x** (ThreadPoolExecutor) |
|
||||||
| Memory management | Auto-unload models before heavy ops |
|
| Memory management | Auto-unload models before heavy ops |
|
||||||
|
| **LoRA Resize Fast** | **~91x** (merge-to-base extraction) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 13: Fast LoRA Resize (2026-01-11)
|
||||||
|
|
||||||
|
### Summary
|
||||||
|
Added fast LoRA resize via base model extraction path, achieving ~91x speedup.
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
- Added `resize_lora_via_base()` function using extraction code path
|
||||||
|
- Added 4 new fast resize nodes requiring base model input
|
||||||
|
|
||||||
|
### Files Modified
|
||||||
|
|
||||||
|
| File | Changes |
|
||||||
|
|------|---------|
|
||||||
|
| `nodes/lora_resize.py` | Added `resize_lora_via_base()`, 4 new node classes |
|
||||||
|
| `__init__.py` | Registered new nodes |
|
||||||
|
|
||||||
|
### New Nodes
|
||||||
|
- `LoRAResizeViaBaseFixed` - Fast fixed rank resize
|
||||||
|
- `LoRAResizeViaBaseRatio` - Fast SV ratio resize
|
||||||
|
- `LoRAResizeViaBaseFrobenius` - Fast Frobenius resize
|
||||||
|
- `LoRAResizeViaBaseCumulative` - Fast cumulative resize
|
||||||
|
|||||||
+30
-10
@@ -44,9 +44,16 @@ def _index_sv_ratio(S: torch.Tensor, ratio: float) -> int:
|
|||||||
return max(1, min(rank, len(S) - 1))
|
return max(1, min(rank, len(S) - 1))
|
||||||
|
|
||||||
|
|
||||||
def _index_sv_cumulative(S: torch.Tensor, target: float) -> int:
|
def _index_sv_cumulative(S: torch.Tensor, target: float, max_rank: int = None) -> int:
|
||||||
"""Cumulative mode - keep enough SVs to reach target % of total."""
|
"""Cumulative mode - keep enough SVs to reach target % of total.
|
||||||
total = torch.sum(S)
|
|
||||||
|
Calculates relative to max_rank if provided, otherwise relative to full.
|
||||||
|
"""
|
||||||
|
if max_rank is not None and max_rank < len(S):
|
||||||
|
total = torch.sum(S[:max_rank])
|
||||||
|
else:
|
||||||
|
total = torch.sum(S)
|
||||||
|
|
||||||
if total < 1e-8:
|
if total < 1e-8:
|
||||||
return 1
|
return 1
|
||||||
cumsum = torch.cumsum(S, dim=0) / total
|
cumsum = torch.cumsum(S, dim=0) / total
|
||||||
@@ -54,13 +61,26 @@ def _index_sv_cumulative(S: torch.Tensor, target: float) -> int:
|
|||||||
return max(1, min(rank, len(S) - 1))
|
return max(1, min(rank, len(S) - 1))
|
||||||
|
|
||||||
|
|
||||||
def _index_sv_fro(S: torch.Tensor, target: float) -> int:
|
def _index_sv_fro(S: torch.Tensor, target: float, max_rank: int = None) -> int:
|
||||||
"""Frobenius norm mode - preserve target fraction of Frobenius norm."""
|
"""Frobenius norm mode - preserve target fraction of Frobenius norm.
|
||||||
S_sq = S.pow(2)
|
|
||||||
total_sq = torch.sum(S_sq)
|
Calculates relative to max_rank if provided, otherwise relative to full.
|
||||||
|
This means "retain target% of what's achievable within max_rank".
|
||||||
|
"""
|
||||||
|
if max_rank is not None and max_rank < len(S):
|
||||||
|
# Calculate relative to what's achievable within max_rank
|
||||||
|
S_capped = S[:max_rank]
|
||||||
|
S_sq = S_capped.pow(2)
|
||||||
|
total_sq = torch.sum(S_sq)
|
||||||
|
else:
|
||||||
|
S_sq = S.pow(2)
|
||||||
|
total_sq = torch.sum(S_sq)
|
||||||
|
|
||||||
if total_sq < 1e-8:
|
if total_sq < 1e-8:
|
||||||
return 1
|
return 1
|
||||||
cumsum = torch.cumsum(S_sq, dim=0) / total_sq
|
|
||||||
|
# Cumsum of all S (not capped) to find where we reach target
|
||||||
|
cumsum = torch.cumsum(S.pow(2), dim=0) / total_sq
|
||||||
rank = int(torch.searchsorted(cumsum, target ** 2).item()) + 1
|
rank = int(torch.searchsorted(cumsum, target ** 2).item()) + 1
|
||||||
return max(1, min(rank, len(S) - 1))
|
return max(1, min(rank, len(S) - 1))
|
||||||
|
|
||||||
@@ -122,9 +142,9 @@ def _compute_rank(S: torch.Tensor, mode: str, mode_param: float,
|
|||||||
elif mode == "ratio":
|
elif mode == "ratio":
|
||||||
rank = _index_sv_ratio(S, mode_param)
|
rank = _index_sv_ratio(S, mode_param)
|
||||||
elif mode == "quantile" or mode == "sv_cumulative":
|
elif mode == "quantile" or mode == "sv_cumulative":
|
||||||
rank = _index_sv_cumulative(S, mode_param)
|
rank = _index_sv_cumulative(S, mode_param, max_rank)
|
||||||
elif mode == "sv_fro":
|
elif mode == "sv_fro":
|
||||||
rank = _index_sv_fro(S, mode_param)
|
rank = _index_sv_fro(S, mode_param, max_rank)
|
||||||
elif mode == "sv_knee":
|
elif mode == "sv_knee":
|
||||||
rank = _index_sv_knee(S)
|
rank = _index_sv_knee(S)
|
||||||
elif mode == "sv_cumulative_knee":
|
elif mode == "sv_cumulative_knee":
|
||||||
|
|||||||
+1001
-76
File diff suppressed because it is too large
Load Diff
+10
-10
@@ -28,23 +28,23 @@ except ImportError:
|
|||||||
|
|
||||||
class PinnedTensor:
|
class PinnedTensor:
|
||||||
"""Context manager for automatic pinned memory management.
|
"""Context manager for automatic pinned memory management.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
with PinnedTensor(cpu_tensor) as pinned:
|
with PinnedTensor(cpu_tensor) as pinned:
|
||||||
gpu_tensor = pinned.to('cuda')
|
gpu_tensor = pinned.to('cuda')
|
||||||
# Memory automatically unpinned after transfer
|
# Memory automatically unpinned after transfer
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, tensor: torch.Tensor, auto_pin: bool = True):
|
def __init__(self, tensor: torch.Tensor, auto_pin: bool = True):
|
||||||
self.tensor = tensor
|
self.tensor = tensor
|
||||||
self.is_pinned = False
|
self.is_pinned = False
|
||||||
self.auto_pin = auto_pin and PINNING_AVAILABLE
|
self.auto_pin = auto_pin and PINNING_AVAILABLE
|
||||||
|
|
||||||
def __enter__(self) -> torch.Tensor:
|
def __enter__(self) -> torch.Tensor:
|
||||||
if self.auto_pin and self.tensor.device.type == 'cpu':
|
if self.auto_pin and self.tensor.device.type == 'cpu':
|
||||||
self.is_pinned = pin_memory(self.tensor)
|
self.is_pinned = pin_memory(self.tensor)
|
||||||
return self.tensor
|
return self.tensor
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
if self.is_pinned:
|
if self.is_pinned:
|
||||||
unpin_memory(self.tensor)
|
unpin_memory(self.tensor)
|
||||||
@@ -57,12 +57,12 @@ def transfer_to_gpu_pinned(
|
|||||||
dtype: Optional[torch.dtype] = None
|
dtype: Optional[torch.dtype] = None
|
||||||
) -> torch.Tensor:
|
) -> torch.Tensor:
|
||||||
"""Transfer tensor to GPU using pinned memory for faster transfer.
|
"""Transfer tensor to GPU using pinned memory for faster transfer.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
tensor: CPU tensor to transfer
|
tensor: CPU tensor to transfer
|
||||||
device: Target GPU device
|
device: Target GPU device
|
||||||
dtype: Optional dtype conversion
|
dtype: Optional dtype conversion
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Tensor on GPU device
|
Tensor on GPU device
|
||||||
"""
|
"""
|
||||||
@@ -71,7 +71,7 @@ def transfer_to_gpu_pinned(
|
|||||||
if dtype:
|
if dtype:
|
||||||
return tensor.to(device=device, dtype=dtype)
|
return tensor.to(device=device, dtype=dtype)
|
||||||
return tensor.to(device=device)
|
return tensor.to(device=device)
|
||||||
|
|
||||||
# Pin memory for faster transfer
|
# Pin memory for faster transfer
|
||||||
was_pinned = pin_memory(tensor)
|
was_pinned = pin_memory(tensor)
|
||||||
try:
|
try:
|
||||||
@@ -162,7 +162,7 @@ class MemoryEfficientSafeOpen:
|
|||||||
tensor_bytes = self.file.read(offset_end - offset_start)
|
tensor_bytes = self.file.read(offset_end - offset_start)
|
||||||
|
|
||||||
return self._deserialize_tensor(tensor_bytes, metadata)
|
return self._deserialize_tensor(tensor_bytes, metadata)
|
||||||
|
|
||||||
def get_tensor_to_gpu(
|
def get_tensor_to_gpu(
|
||||||
self,
|
self,
|
||||||
key: str,
|
key: str,
|
||||||
@@ -170,12 +170,12 @@ class MemoryEfficientSafeOpen:
|
|||||||
dtype: Optional[torch.dtype] = None
|
dtype: Optional[torch.dtype] = None
|
||||||
) -> torch.Tensor:
|
) -> torch.Tensor:
|
||||||
"""Load a tensor and transfer to GPU using pinned memory for faster transfer.
|
"""Load a tensor and transfer to GPU using pinned memory for faster transfer.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
key: Tensor key to load
|
key: Tensor key to load
|
||||||
device: Target GPU device (default 'cuda')
|
device: Target GPU device (default 'cuda')
|
||||||
dtype: Optional dtype conversion
|
dtype: Optional dtype conversion
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Tensor on GPU device
|
Tensor on GPU device
|
||||||
"""
|
"""
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "ComfyUI-ModelUtils"
|
name = "ComfyUI-ModelUtils"
|
||||||
description = "[WIP]Custom nodes for handling, inspecting, modifying and creating various model files."
|
description = "[WIP]Custom nodes for handling, inspecting, modifying and creating various model files."
|
||||||
version = "0.2.0"
|
version = "0.2.3"
|
||||||
license = { file = "LICENSE" }
|
license = { file = "LICENSE" }
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
|
|||||||
Reference in New Issue
Block a user