Feat: Add ClybAdaptiveLoraLoader
This node loads multiple LoRAs in one action to prevent re-quantizing the model in Comfy. Good for quantized formats like FP8, Int8, and NVFP4.
This commit is contained in:
+15
@@ -2,23 +2,38 @@ from . import chroma_NAG
|
||||
from . import clyb_Guidance
|
||||
from . import clyb_Samplers
|
||||
from . import clyb_Schedulers
|
||||
from . import clyb_ModelLoader
|
||||
|
||||
clyb_Samplers.add_samplers()
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
# Guidance
|
||||
"ClybGuidance": clyb_Guidance.ClybGuidance,
|
||||
# Samplers
|
||||
"SamplerClyb_BDF": clyb_Samplers.SamplerClyb_BDF,
|
||||
"SamplerTaylorFlow": clyb_Samplers.SamplerTaylorFlow,
|
||||
"SamplerWrapperCFGPP": clyb_Samplers.SamplerWrapperCFGPP,
|
||||
# Schedulers
|
||||
"InverseSquaredScheduler": clyb_Schedulers.InverseSquaredScheduler,
|
||||
"PrintSigmas": clyb_Schedulers.PrintSigmas,
|
||||
# LoraLoaders
|
||||
"ClybAdaptiveLoraLoader": clyb_ModelLoader.ClybAdaptiveLoraLoader,
|
||||
"ClybAdaptiveLoraLoaderModelOnly": clyb_ModelLoader.ClybAdaptiveLoraLoaderModelOnly,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
# Guidance
|
||||
"ClybGuidance": "ClybGuidance",
|
||||
# Samplers
|
||||
"SamplerClyb_BDF": "SamplerClyb_BDF",
|
||||
"SamplerTaylorFlow": "SamplerTaylorFlow",
|
||||
"SamplerWrapperCFGPP": "SamplerWrapperCFGPP",
|
||||
# Schedulers
|
||||
"InverseSquaredScheduler": "InverseSquaredScheduler",
|
||||
"PrintSigmas": "PrintSigmas",
|
||||
# LoraLoaders
|
||||
"ClybAdaptiveLoraLoader": "ClybAdaptiveLoraLoader",
|
||||
"ClybAdaptiveLoraLoaderModelOnly": "ClybAdaptiveLoraLoaderModelOnly",
|
||||
}
|
||||
|
||||
WEB_DIRECTORY = "./js"
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
import torch
|
||||
import logging
|
||||
import comfy.sd
|
||||
import folder_paths
|
||||
import comfy.utils
|
||||
import comfy.lora
|
||||
import comfy.lora_convert
|
||||
|
||||
|
||||
class ClybAdaptiveLoraLoader:
|
||||
def __init__(self):
|
||||
self.loaded_loras = {}
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
file_list = folder_paths.get_filename_list("loras")
|
||||
file_list.insert(0, "none")
|
||||
|
||||
inputs = {
|
||||
"required": {
|
||||
"model": ("MODEL", {"tooltip": "The diffusion model the LoRA will be applied to."}),
|
||||
"clip": ("CLIP", {"tooltip": "The CLIP model the LoRA will be applied to."}),
|
||||
"lora_name_1": (file_list, {"tooltip": "The name of the LoRA."}),
|
||||
"strength_model_1": ("FLOAT", {"default": 1.0, "min": -100.0, "max": 100.0, "step": 0.01, "tooltip": "How strongly to modify the diffusion model. This value can be negative."}),
|
||||
"strength_clip_1": ("FLOAT", {"default": 1.0, "min": -100.0, "max": 100.0, "step": 0.01, "tooltip": "How strongly to modify the CLIP model. This value can be negative."}),
|
||||
},
|
||||
"optional": {}
|
||||
}
|
||||
|
||||
for i in range(2, 21):
|
||||
inputs["optional"][f"lora_name_{i}"] = (file_list, {"default": "none"})
|
||||
inputs["optional"][f"strength_model_{i}"] = ("FLOAT", {"default": 1.0, "min": -100.0, "max": 100.0, "step": 0.01})
|
||||
inputs["optional"][f"strength_clip_{i}"] = ("FLOAT", {"default": 1.0, "min": -100.0, "max": 100.0, "step": 0.01})
|
||||
|
||||
return inputs
|
||||
|
||||
RETURN_TYPES = ("MODEL", "CLIP")
|
||||
OUTPUT_TOOLTIPS = ("The modified diffusion model.", "The modified CLIP model.")
|
||||
FUNCTION = "load_lora"
|
||||
|
||||
CATEGORY = "loaders"
|
||||
DESCRIPTION = "Apply multiple LoRAs adaptively by merging patches into a single model clone."
|
||||
EXPERIMENTAL = True
|
||||
|
||||
def load_lora(self, model, clip, **kwargs):
|
||||
lora_keys = [k for k in kwargs.keys() if k.startswith("lora_name_")]
|
||||
try:
|
||||
lora_keys.sort(key=lambda x: int(x.split("_")[-1]))
|
||||
except:
|
||||
pass
|
||||
|
||||
model_lora = model.clone() if model is not None else None
|
||||
clip_lora = clip.clone() if clip is not None else None
|
||||
|
||||
for k in lora_keys:
|
||||
lora_name = kwargs[k]
|
||||
if lora_name == "none":
|
||||
continue
|
||||
|
||||
idx = k.split("_")[-1]
|
||||
strength_model = kwargs.get(f"strength_model_{idx}", 1.0)
|
||||
strength_clip = kwargs.get(f"strength_clip_{idx}", 1.0)
|
||||
|
||||
if strength_model == 0 and strength_clip == 0:
|
||||
continue
|
||||
|
||||
lora_path = folder_paths.get_full_path_or_raise("loras", lora_name)
|
||||
lora = None
|
||||
if idx in self.loaded_loras:
|
||||
if self.loaded_loras[idx][0] == lora_path:
|
||||
lora = self.loaded_loras[idx][1]
|
||||
else:
|
||||
self.loaded_loras.pop(idx, None)
|
||||
|
||||
if lora is None:
|
||||
lora = comfy.utils.load_torch_file(lora_path, safe_load=True)
|
||||
self.loaded_loras[idx] = (lora_path, lora)
|
||||
|
||||
key_map = {}
|
||||
if model_lora is not None:
|
||||
key_map = comfy.lora.model_lora_keys_unet(model_lora.model, key_map)
|
||||
if clip_lora is not None:
|
||||
key_map = comfy.lora.model_lora_keys_clip(clip_lora.cond_stage_model, key_map)
|
||||
|
||||
lora_converted = comfy.lora_convert.convert_lora(lora)
|
||||
loaded = comfy.lora.load_lora(lora_converted, key_map)
|
||||
|
||||
if model_lora is not None:
|
||||
model_lora.add_patches(loaded, strength_model)
|
||||
if clip_lora is not None:
|
||||
clip_lora.add_patches(loaded, strength_clip)
|
||||
|
||||
return (model_lora, clip_lora)
|
||||
|
||||
|
||||
class ClybAdaptiveLoraLoaderModelOnly(ClybAdaptiveLoraLoader):
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
file_list = folder_paths.get_filename_list("loras")
|
||||
file_list.insert(0, "none")
|
||||
inputs = {
|
||||
"required": {
|
||||
"model": ("MODEL",),
|
||||
"lora_name_1": (file_list, ),
|
||||
"strength_model_1": ("FLOAT", {"default": 1.0, "min": -100.0, "max": 100.0, "step": 0.01}),
|
||||
},
|
||||
"optional": {}
|
||||
}
|
||||
for i in range(2, 21):
|
||||
inputs["optional"][f"lora_name_{i}"] = (file_list, {"default": "none"})
|
||||
inputs["optional"][f"strength_model_{i}"] = ("FLOAT", {"default": 1.0, "min": -100.0, "max": 100.0, "step": 0.01})
|
||||
|
||||
return inputs
|
||||
|
||||
RETURN_TYPES = ("MODEL",)
|
||||
FUNCTION = "load_lora_model_only"
|
||||
|
||||
def load_lora_model_only(self, model, **kwargs):
|
||||
new_kwargs = kwargs.copy()
|
||||
lora_keys = [k for k in kwargs.keys() if k.startswith("lora_name_")]
|
||||
for k in lora_keys:
|
||||
idx = k.split("_")[-1]
|
||||
new_kwargs[f"strength_clip_{idx}"] = 0.0
|
||||
|
||||
return (self.load_lora(model, None, **new_kwargs)[0],)
|
||||
+1
-1
@@ -124,7 +124,7 @@ def _construct_vandermonde_flow(history, sigma_ref, max_order, device, dtype):
|
||||
return R
|
||||
|
||||
|
||||
def _solve_flow_coefficients(R, h_n, method="equilibration", diag_weight=1.0):
|
||||
def _solve_flow_coefficients(R, h_n, method="diagonal", diag_weight=1.0):
|
||||
"""
|
||||
Solve for B coefficients using either two-sided equilibration or diagonal-dominant regularization.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user